From 2abe3703b65caffd71068aa252b15b3d6a3c5da5 Mon Sep 17 00:00:00 2001 From: amit 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 +#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