From 56f5ad9438aa6fe8a94d9b1033b8d99b9f440ba3 Mon Sep 17 00:00:00 2001 From: Ashutosh Bapat Date: Thu, 22 Jun 2023 14:27:14 +0530 Subject: [PATCH 4/4] Avoid translating RestrictInfo repeatedly RestrictInfo to be applied to the child rels (including the child join rels) are obtained by translating RestrictInfo applicable to the parent rel. Since these translations are not tracked, the same RestrictInfo may get translated multiple times for the same parent and child pair. When using partitionwise join this can happen as many times as the number of possible join orders between the partitioned tables or as many times a parent path is reparameterized for a child if a clause is used in such a path. Repeated translations are avoided by saving the translations in a hash table. PlannerInfo maintains a hash table of hash tables to store and retrieve translated RestrictInfos. The first level hash table is referenced by RestrictInfo::rinfo_serial and the second level hash table contains translated RestrictInfos referenced by RestrictInfo::required_relids. Two function find_child_rinfo() and add_child_rinfo() are used to search a RestrictInfo for a given tanslation and add a child RestrictInfo respectively. Translations of commuted clauses need further tracking. When creating a join clause using an equivalence class, if the child clause to be created is commuted version of the parent clause mark the child clause so and also add it to the hash table containing translations. When building a child clause from parent clause, commute the child clause if the parent clause is commuted version. Ashutosh Bapat --- src/backend/optimizer/path/equivclass.c | 26 +++-- src/backend/optimizer/path/joinrels.c | 76 ++++++++++++- src/backend/optimizer/util/pathnode.c | 123 ++++++++++++++++++++-- src/backend/optimizer/util/relnode.c | 7 +- src/backend/optimizer/util/restrictinfo.c | 123 ++++++++++++++++++++++ src/include/nodes/pathnodes.h | 4 +- src/include/optimizer/paths.h | 2 + src/include/optimizer/restrictinfo.h | 5 + src/tools/pgindent/typedefs.list | 2 + 9 files changed, 347 insertions(+), 21 deletions(-) diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 7fa502d6e2..4eb35088b8 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -1816,7 +1816,7 @@ create_join_clause(PlannerInfo *root, EquivalenceMember *rightem, EquivalenceClass *parent_ec) { - RestrictInfo *rinfo; + RestrictInfo *rinfo = NULL; RestrictInfo *parent_rinfo = NULL; ListCell *lc; MemoryContext oldcontext; @@ -1884,11 +1884,6 @@ create_join_clause(PlannerInfo *root, bms_union(leftem->em_relids, rightem->em_relids), ec->ec_min_security); - - /* If it's a child clause, copy the parent's rinfo_serial */ - if (parent_rinfo) - rinfo->rinfo_serial = parent_rinfo->rinfo_serial; - /* Mark the clause as redundant, or not */ rinfo->parent_ec = parent_ec; @@ -1907,6 +1902,25 @@ create_join_clause(PlannerInfo *root, MemoryContextSwitchTo(oldcontext); + /* We have obtained a translation through EC machinery, save it. */ + if (parent_rinfo) + { + rinfo->rinfo_serial = parent_rinfo->rinfo_serial; + + /* + * If the child clause is commuted translation of the parent clause, + * mark it accordingly. + */ + if ((leftem->em_parent && + leftem->em_parent == parent_rinfo->right_em) || + (rightem->em_parent && + rightem->em_parent == parent_rinfo->left_em)) + rinfo->is_commuted = !parent_rinfo->is_commuted; + else + rinfo->is_commuted = parent_rinfo->is_commuted; + add_child_rinfo(root, parent_rinfo, rinfo); + } + return rinfo; } diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index d03ace50a1..f991d38b77 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -19,6 +19,7 @@ #include "optimizer/joininfo.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/restrictinfo.h" #include "partitioning/partbounds.h" #include "utils/memutils.h" @@ -1436,6 +1437,76 @@ restriction_is_constant_false(List *restrictlist, return false; } +/* + * Build the child RestrictInfo by translating the given parent RestrictInfo. If + * the translation is already available use it. Otherwise translate the parent + * clause and add to the available translations. + */ +static RestrictInfo * +build_child_clause(PlannerInfo *root, + RestrictInfo *parent_rinfo, + int nappinfos, AppendRelInfo **appinfos) +{ + Relids child_req_relids = + adjust_child_relids(parent_rinfo->required_relids, + nappinfos, appinfos); + RestrictInfo *child_rinfo; + + /* If no child relids are involved no translation is required. */ + if (bms_equal(child_req_relids, parent_rinfo->required_relids)) + return parent_rinfo; + + child_rinfo = find_child_rinfo(root, parent_rinfo, child_req_relids); + if (!child_rinfo) + { + child_rinfo = castNode(RestrictInfo, + adjust_appendrel_attrs(root, + (Node *) parent_rinfo, + nappinfos, + appinfos)); + add_child_rinfo(root, parent_rinfo, child_rinfo); + } + bms_free(child_req_relids); + + /* If the parent clause is commuted, return commuted child clause. */ + if (parent_rinfo->is_commuted != child_rinfo->is_commuted) + { + if (!child_rinfo->comm_rinfo) + { + OpExpr *parent_clause = castNode(OpExpr, parent_rinfo->clause); + + commute_restrictinfo(child_rinfo, parent_clause->opno); + } + child_rinfo = child_rinfo->comm_rinfo; + } + + Assert(parent_rinfo->is_commuted == child_rinfo->is_commuted); + return child_rinfo; +} + +/* + * Build the child RestrictInfo list by translating the given parent + * RestrictInfo list. + */ +List * +build_child_clauses(PlannerInfo *root, List *parent_clauselist, + int nappinfos, AppendRelInfo **appinfos) +{ + ListCell *lc; + List *child_clauselist = NIL; + + foreach(lc, parent_clauselist) + { + RestrictInfo *parent_rinfo = lfirst_node(RestrictInfo, lc); + RestrictInfo *child_rinfo = build_child_clause(root, parent_rinfo, + nappinfos, appinfos); + + child_clauselist = lappend(child_clauselist, child_rinfo); + } + + return child_clauselist; +} + /* * Assess whether join between given two partitioned relations can be broken * down into joins between matching partitions; a technique called @@ -1631,9 +1702,8 @@ try_partitionwise_join(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, * applicable to the parent join. */ child_restrictlist = - (List *) adjust_appendrel_attrs(root, - (Node *) parent_restrictlist, - nappinfos, appinfos); + build_child_clauses(root, parent_restrictlist, nappinfos, + appinfos); /* Find or construct the child join's RelOptInfo */ child_joinrel = joinrel->part_rels[cnt_parts]; diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c index 211ba65389..7bddd36977 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -4024,6 +4024,89 @@ reparameterize_path(PlannerInfo *root, Path *path, return NULL; } +/* + * build_child_clauses_multilevel + * Similar to build_child_clauses() but for translations through multiple levels of inheritance (actually partitioning) hierarchy. + * + * In a way this is similar to adjust_appendrel_attrs_multilevel() except that this function uses a translated RestrictInfo if it's available. + */ +static List * +build_child_clauses_multilevel(PlannerInfo *root, List *parent_clauselist, + RelOptInfo *child_rel, + RelOptInfo *top_parent) +{ + AppendRelInfo **appinfos; + int nappinfos; + List *tmp_clauselist = parent_clauselist; + List *child_clauselist; + + if (child_rel->parent != top_parent) + { + if (child_rel->parent) + { + tmp_clauselist = build_child_clauses_multilevel(root, + parent_clauselist, + child_rel->parent, + top_parent); + } + else + elog(ERROR, "child_rel is not a child of top_parent"); + } + + appinfos = find_appinfos_by_relids(root, child_rel->relids, &nappinfos); + child_clauselist = build_child_clauses(root, tmp_clauselist, + nappinfos, appinfos); + pfree(appinfos); + /* Build any intermediate clauselist this function built. */ + if (tmp_clauselist != parent_clauselist) + list_free(tmp_clauselist); + + return child_clauselist; +} + +/* + * build_child_iclauses_multilevel + * Translate IndexClause list to be used for the + * given top parent relation for given child relation through the hierarchy of inheritance (actually partitioning). + * + * This is similar to adjust_appendrel_attrs_multilevel() for IndexClause except that it uses translated RestrictInfos if already available. + */ +static List * +build_child_iclauses_multilevel(PlannerInfo *root, + List *parent_iclauselist, + RelOptInfo *childrel, + RelOptInfo *top_parent) +{ + ListCell *lc; + List *child_iclauses = NIL; + + foreach(lc, parent_iclauselist) + { + IndexClause *iclause = lfirst_node(IndexClause, lc); + List *rinfo_list = NIL; + List *child_rinfo_list; + IndexClause *child_iclause = makeNode(IndexClause); + + /* + * Collect the RestrictInfos to be translated. That's all what needs + * to be translated right now. + */ + rinfo_list = lappend(rinfo_list, iclause->rinfo); + rinfo_list = list_concat(rinfo_list, iclause->indexquals); + + child_rinfo_list = build_child_clauses_multilevel(root, rinfo_list, + childrel, top_parent); + memcpy(child_iclause, iclause, sizeof(IndexClause)); + child_iclause->rinfo = linitial(child_rinfo_list); + child_iclause->indexquals = list_delete_first(child_rinfo_list); + + list_free(rinfo_list); + child_iclauses = lappend(child_iclauses, child_iclause); + } + + return child_iclauses; +} + /* * reparameterize_path_by_child * Given a path parameterized by the parent of the given child relation, @@ -4112,7 +4195,10 @@ do { \ IndexPath *ipath; FLAT_COPY_PATH(ipath, path, IndexPath); - ADJUST_CHILD_ATTRS(ipath->indexclauses); + ipath->indexclauses = + build_child_iclauses_multilevel(root, + ipath->indexclauses, + child_rel, child_rel->top_parent); new_path = (Path *) ipath; } break; @@ -4196,7 +4282,11 @@ do { \ jpath = (JoinPath *) npath; REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath); REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath); - ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo); + jpath->joinrestrictinfo = + build_child_clauses_multilevel(root, + jpath->joinrestrictinfo, + child_rel, + child_rel->top_parent); new_path = (Path *) npath; } break; @@ -4211,8 +4301,16 @@ do { \ jpath = (JoinPath *) mpath; REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath); REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath); - ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo); - ADJUST_CHILD_ATTRS(mpath->path_mergeclauses); + jpath->joinrestrictinfo = + build_child_clauses_multilevel(root, + jpath->joinrestrictinfo, + child_rel, + child_rel->top_parent); + mpath->path_mergeclauses = + build_child_clauses_multilevel(root, + mpath->path_mergeclauses, + child_rel, + child_rel->top_parent); new_path = (Path *) mpath; } break; @@ -4227,8 +4325,16 @@ do { \ jpath = (JoinPath *) hpath; REPARAMETERIZE_CHILD_PATH(jpath->outerjoinpath); REPARAMETERIZE_CHILD_PATH(jpath->innerjoinpath); - ADJUST_CHILD_ATTRS(jpath->joinrestrictinfo); - ADJUST_CHILD_ATTRS(hpath->path_hashclauses); + jpath->joinrestrictinfo = + build_child_clauses_multilevel(root, + jpath->joinrestrictinfo, + child_rel, + child_rel->top_parent); + hpath->path_hashclauses = + build_child_clauses_multilevel(root, + hpath->path_hashclauses, + child_rel, + child_rel->top_parent); new_path = (Path *) hpath; } break; @@ -4310,7 +4416,10 @@ do { \ new_ppi->ppi_req_outer = bms_copy(required_outer); new_ppi->ppi_rows = old_ppi->ppi_rows; new_ppi->ppi_clauses = old_ppi->ppi_clauses; - ADJUST_CHILD_ATTRS(new_ppi->ppi_clauses); + new_ppi->ppi_clauses = + build_child_clauses_multilevel(root, + new_ppi->ppi_clauses, child_rel, + child_rel->top_parent); new_ppi->ppi_serials = bms_copy(old_ppi->ppi_serials); rel->ppilist = lappend(rel->ppilist, new_ppi); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 76dad17e33..e69151d16f 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -949,10 +949,9 @@ build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel, nappinfos, appinfos); /* Construct joininfo list. */ - joinrel->joininfo = (List *) adjust_appendrel_attrs(root, - (Node *) parent_joinrel->joininfo, - nappinfos, - appinfos); + joinrel->joininfo = build_child_clauses(root, + parent_joinrel->joininfo, + nappinfos, appinfos); /* * Lateral relids referred in child join will be same as that referred in diff --git a/src/backend/optimizer/util/restrictinfo.c b/src/backend/optimizer/util/restrictinfo.c index 9d67c623d0..ffb1692bb9 100644 --- a/src/backend/optimizer/util/restrictinfo.c +++ b/src/backend/optimizer/util/restrictinfo.c @@ -20,6 +20,19 @@ #include "optimizer/optimizer.h" #include "optimizer/restrictinfo.h" +typedef struct RinfoHashTabEntry +{ + int rinfo_serial; /* Key, must be the first one. */ + HTAB *hash_tab; /* Hash table containing child RestrictInfo + * for RestrictInfo indicated by given + * rinfo_serial. */ +} RinfoHashTabEntry; + +typedef struct ChildRinfoTabEntry +{ + Relids required_relids; /* Key, must be the first one */ + RestrictInfo *child_rinfo; /* Child RestrictInfo for required_relids. */ +} ChildRinfoTabEntry; static RestrictInfo *make_restrictinfo_internal(PlannerInfo *root, Expr *clause, @@ -42,6 +55,8 @@ static Expr *make_sub_restrictinfos(PlannerInfo *root, Relids required_relids, Relids incompatible_relids, Relids outer_relids); +static HTAB *get_child_rinfo_htab(PlannerInfo *root, + RestrictInfo *parent_rinfo); /* @@ -705,3 +720,111 @@ join_clause_is_movable_into(RestrictInfo *rinfo, return true; } + +/* + * find_child_rinfo + * Find the translation of the given parent RestrictInfo for the given + * child relids. + * + * The given parent RestrictInfo need not be the top parent relations's + * RestrictInfo. We use RestrictInfo::rinfo_serial to access all the + * translations of the top parent relation's RestrictInfo. + * + * PlannerInfo maintains a hash table of hash tables to store and search + * translated RestrictInfos. The first level hash table is referenced by + * RestrictInfo::rinfo_serial and the second level hash table contains + * translated RestrictInfos referenced by RestrictInfo::required_relids. + */ +RestrictInfo * +find_child_rinfo(PlannerInfo *root, RestrictInfo *parent_rinfo, + Relids child_required_relids) +{ + HTAB *rinfo_hash = get_child_rinfo_htab(root, parent_rinfo); + ChildRinfoTabEntry *child_rinfo_entry; + + child_rinfo_entry = hash_search(rinfo_hash, &child_required_relids, + HASH_FIND, + NULL); + return (child_rinfo_entry ? child_rinfo_entry->child_rinfo : NULL); +} + +/* + * add_child_rinfo + * Save the given child RestrictInfo in the hash table containing translations of the given parent RestrictInfo. + * + * The parent RestrictInfo need not be the top parent RestrictInfo. We use + * RestrictInfo::rinfo_serial to identify the set of RestrictInfos. + * + * The function assumes that the given child RestrictInfo is not already + * available in the hash table. + */ +extern void +add_child_rinfo(PlannerInfo *root, RestrictInfo *parent_rinfo, + RestrictInfo *child_rinfo) +{ + HTAB *rinfo_hash = get_child_rinfo_htab(root, parent_rinfo); + ChildRinfoTabEntry *child_rinfo_entry; + bool found; + + Assert(child_rinfo->rinfo_serial == parent_rinfo->rinfo_serial); + + child_rinfo_entry = hash_search(rinfo_hash, &(child_rinfo->required_relids), + HASH_ENTER, &found); + + /* + * A child restrictinfo should be translated only once and thus is + * required to be added only once. + */ + Assert(!found); + child_rinfo_entry->child_rinfo = child_rinfo; +} + +/* + * get_child_rinfo_htab + * Given a parent RestrictInfo, return the hash table containing all its child RestrictInfos. + * + * A helper function to access the RestrictInfo translation hash tables. The function creates the required hash table if they are not already available. + */ +static HTAB * +get_child_rinfo_htab(PlannerInfo *root, RestrictInfo *parent_rinfo) +{ + RinfoHashTabEntry *rinfo_tab_entry; + bool found_htab; + + if (!root->child_rinfo_hash) + { + HASHCTL hash_ctl = {0}; + + Assert(sizeof(int) == sizeof(parent_rinfo->rinfo_serial)); + hash_ctl.keysize = sizeof(int); + hash_ctl.entrysize = sizeof(RinfoHashTabEntry); + hash_ctl.hcxt = root->planner_cxt; + + root->child_rinfo_hash = hash_create("hash of child rinfo hash", + root->last_rinfo_serial, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | + HASH_CONTEXT); + } + + rinfo_tab_entry = hash_search(root->child_rinfo_hash, + &(parent_rinfo->rinfo_serial), HASH_ENTER, + &found_htab); + if (!found_htab) + { + HASHCTL hash_ctl = {0}; + + hash_ctl.keysize = sizeof(Relids); + hash_ctl.entrysize = sizeof(ChildRinfoTabEntry); + hash_ctl.hash = bitmap_hash; + hash_ctl.match = bitmap_match; + hash_ctl.hcxt = root->planner_cxt; + + rinfo_tab_entry->hash_tab = hash_create("child rinfo hash", 100, + &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | + HASH_CONTEXT); + } + + return rinfo_tab_entry->hash_tab; +} diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index c575fd11ec..ffe4fc8ddc 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -338,6 +338,7 @@ struct PlannerInfo /* counter for assigning RestrictInfo serial numbers */ int last_rinfo_serial; + struct HTAB *child_rinfo_hash pg_node_attr(read_write_ignore); /* * all_result_relids is empty for SELECT, otherwise it contains at least @@ -2523,7 +2524,8 @@ typedef struct LimitPath * comm_rinfo and vice versa. Both the RestrictInfos are commuted versions of * each other. is_commuted flag is false for the first one to appear and is * true in the other. The order doesn't matter. The flag just differentiate - * between the commuted version. + * between the commuted version. The child RestrictInfos inherit this flag from + * their respective parent RestrictInfo. */ typedef struct RestrictInfo diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 50bc3b503a..02ab078188 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -112,6 +112,8 @@ extern bool have_join_order_restriction(PlannerInfo *root, extern bool have_dangerous_phv(PlannerInfo *root, Relids outer_relids, Relids inner_params); extern void mark_dummy_rel(RelOptInfo *rel); +extern List *build_child_clauses(PlannerInfo *root, List *parent_clauselist, + int nappinfos, AppendRelInfo **appinfos); /* * equivclass.c diff --git a/src/include/optimizer/restrictinfo.h b/src/include/optimizer/restrictinfo.h index e140e619ac..507fa21e55 100644 --- a/src/include/optimizer/restrictinfo.h +++ b/src/include/optimizer/restrictinfo.h @@ -47,5 +47,10 @@ extern bool join_clause_is_movable_to(RestrictInfo *rinfo, RelOptInfo *baserel); extern bool join_clause_is_movable_into(RestrictInfo *rinfo, Relids currentrelids, Relids current_and_outer); +extern RestrictInfo *find_child_rinfo(PlannerInfo *root, + RestrictInfo *parent_rinfo, + Bitmapset *child_required_relids); +extern void add_child_rinfo(PlannerInfo *root, RestrictInfo *parent_rinfo, + RestrictInfo *child_rinfo); #endif /* RESTRICTINFO_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index f2af84d7ca..ab713d5063 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -372,6 +372,7 @@ CheckPointStmt CheckpointStatsData CheckpointerRequest CheckpointerShmemStruct +ChildRinfoTabEntry Chromosome CkptSortItem CkptTsStatus @@ -2373,6 +2374,7 @@ RewriteMappingDataEntry RewriteMappingFile RewriteRule RewriteState +RinfoHashTabEntry RmgrData RmgrDescData RmgrId -- 2.25.1