diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 26264cb..27f7348 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1964,12 +1964,78 @@ _copyOnConflictExpr(const OnConflictExpr *from) /* **************************************************************** * relation.h copy functions * - * We don't support copying RelOptInfo, IndexOptInfo, or Path nodes. + * We don't support copying RelOptInfo, IndexOptInfo or Path node. * There are some subsidiary structs that are useful to copy, though. * **************************************************************** */ /* + * CopyPathFields + */ +static void +CopyPathFields(const Path *from, Path *newnode) +{ + COPY_SCALAR_FIELD(pathtype); + + /* + * We use COPY_SCALAR_FIELDS() for parent instead of COPY_NODE_FIELDS() + * because RelOptInfo contains Path which is made from, so + * jump into the infinite loop. + */ + COPY_SCALAR_FIELD(parent); + + COPY_SCALAR_FIELD(param_info); + + COPY_SCALAR_FIELD(rows); + COPY_SCALAR_FIELD(startup_cost); + COPY_SCALAR_FIELD(total_cost); + + COPY_NODE_FIELD(pathkeys); +} + +/* + * _copyPath + */ +static Path * +_copyPath(const Path *from) +{ + Path *newnode = makeNode(Path); + + CopyPathFields(from, newnode); + + return newnode; +} + +/* + * _copyIndexPath + * XXX Need to make copy function for IndexOptInfo, etc. + */ +static IndexPath * +_copyIndexPath(const IndexPath *from) +{ + IndexPath *newnode = makeNode(IndexPath); + + CopyPathFields(&from->path, &newnode->path); + + COPY_NODE_FIELD(indexinfo); + COPY_NODE_FIELD(indexclauses); + COPY_NODE_FIELD(indexquals); + COPY_NODE_FIELD(indexqualcols); + COPY_NODE_FIELD(indexorderbys); + COPY_NODE_FIELD(indexorderbycols); + COPY_SCALAR_FIELD(indexscandir); + COPY_SCALAR_FIELD(indextotalcost); + COPY_SCALAR_FIELD(indexselectivity); + + return newnode; +} + +/* + * XXX Need to make copy function for BitmapHeapPath, TidPath + * and GatherPath. + */ + +/* * _copyPathKey */ static PathKey * @@ -4507,6 +4573,12 @@ copyObject(const void *from) /* * RELATION NODES */ + case T_Path: + retval = _copyPath(from); + break; + case T_IndexPath: + retval = _copyIndexPath(from); + break; case T_PathKey: retval = _copyPathKey(from); break; diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c index a35c881..6dec33c 100644 --- a/src/backend/optimizer/path/joinpath.c +++ b/src/backend/optimizer/path/joinpath.c @@ -18,9 +18,22 @@ #include "executor/executor.h" #include "foreign/fdwapi.h" +#include "nodes/nodeFuncs.h" +#include "nodes/nodes.h" +#include "optimizer/clauses.h" #include "optimizer/cost.h" #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "optimizer/plancat.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "utils/lsyscache.h" + +typedef struct +{ + List *joininfo; + bool is_substituted; +} substitution_node_context; /* Hook for plugins to get control in add_paths_to_joinrel() */ set_join_pathlist_hook_type set_join_pathlist_hook = NULL; @@ -45,6 +58,11 @@ static List *select_mergejoin_clauses(PlannerInfo *root, JoinType jointype, bool *mergejoin_allowed); +static void try_append_pullup_across_join(PlannerInfo *root, + RelOptInfo *joinrel, RelOptInfo *outer_rel, + RelOptInfo *inner_rel, + List *restrictlist); + /* * add_paths_to_joinrel @@ -82,6 +100,18 @@ add_paths_to_joinrel(PlannerInfo *root, bool mergejoin_allowed = true; ListCell *lc; + /* + * Try to pull-up Append across Join + */ + if (!IS_OUTER_JOIN(jointype)) + { + try_append_pullup_across_join(root, + joinrel, + outerrel, + innerrel, + restrictlist); + } + extra.restrictlist = restrictlist; extra.mergeclause_list = NIL; extra.sjinfo = sjinfo; @@ -1474,3 +1504,616 @@ select_mergejoin_clauses(PlannerInfo *root, return result_list; } + +/* + * Try to substitute Var node according to join conditions. + * This process is from following steps. + * + * 1. Try to find whether Var node matches to left/right Var node of + * one join condition. + * 2. If found, replace Var node with the opposite expression node of + * the join condition. + * + * For example, let's assume that we have following expression and + * join condition. + * Expression : A.num % 4 = 1 + * Join condition : A.num = B.data + 2 + * In this case, we can get following expression. + * (B.data + 2) % 4 = 1 + */ +static Node * +substitute_node_with_join_cond(Node *node, substitution_node_context *context) +{ + /* Failed to substitute. Abort. */ + if (!context->is_substituted) + return (Node *) copyObject(node); + + if (node == NULL) + return NULL; + + if (IsA(node, Var)) + { + List *join_cond = context->joininfo; + ListCell *lc; + + Assert(list_length(join_cond) > 0); + + foreach (lc, join_cond) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + Expr *expr = rinfo->clause; + + /* + * Make sure whether OpExpr of Join clause means "=". + */ + if (!rinfo->can_join || + !IsA(expr, OpExpr) || + !op_hashjoinable(((OpExpr *) expr)->opno, + exprType(get_leftop(expr)))) + continue; + + if (equal(get_leftop(expr), node)) + { + /* + * This node is equal to LEFT node of join condition, + * thus will be replaced with RIGHT clause. + */ + return (Node *) copyObject(get_rightop(expr)); + } + else + if (equal(get_rightop(expr), node)) + { + /* + * This node is equal to RIGHT node of join condition, + * thus will be replaced with LEFT clause. + */ + return (Node *) copyObject(get_leftop(expr)); + } + } + + /* Unfortunately, substituting is failed. */ + context->is_substituted = false; + return (Node *) copyObject(node); + } + + return expression_tree_mutator(node, substitute_node_with_join_cond, context); +} + +/* + * Create RestrictInfo_List from CHECK() constraints. + * + * This function creates list of RestrictInfo from CHECK() constraints + * according to expression of join clause. + * + * For example, let's assume that we have following CHECK() constraints + * for table A and join clause between table A and B. + * CHECK of table A : 0 <= num AND num <= 100 + * JOIN CLAUSE : A.num = B.data + * In this conditions, we can get below by mathematical substituting. + * 0 <= B.data AND B.data <= 100 + * + * We can use this restrictions to reduce result rows. + * This means that we can make Sort faster by reducing rows in MergeJoin, + * and also means that we can make HashTable smaller in HashJoin to fit + * to smaller work_mem environments. + */ +static List * +create_rinfo_from_check_constr(PlannerInfo *root, List *joininfo, + RelOptInfo *outer_rel, bool *succeed) +{ + List *result = NIL; + RangeTblEntry *childRTE = root->simple_rte_array[outer_rel->relid]; + List *check_constr = + get_relation_constraints(root, childRTE->relid, + outer_rel, false); + ListCell *lc; + substitution_node_context context; + + if (list_length(check_constr) <= 0) + { + *succeed = true; + return NIL; + } + + context.joininfo = joininfo; + context.is_substituted = true; + + /* + * Try to convert CHECK() constraints to filter expressions. + */ + foreach(lc, check_constr) + { + Node *substituted = + expression_tree_mutator((Node *) lfirst(lc), + substitute_node_with_join_cond, + (void *) &context); + + if (!context.is_substituted) + { + *succeed = false; + list_free_deep(check_constr); + return NIL; + } + result = lappend(result, substituted); + } + + Assert(list_length(check_constr) == list_length(result)); + list_free_deep(check_constr); + + return make_restrictinfos_from_actual_clauses(root, result); +} + +/* + * Convert parent's join clauses to child's. + */ +static List * +convert_parent_joinclauses_to_child(PlannerInfo *root, List *join_clauses, + RelOptInfo *outer_rel) +{ + AppendRelInfo *appinfo = find_childrel_appendrelinfo(root, outer_rel); + List *clauses_parent = get_actual_clauses(join_clauses); + List *clauses_child = NIL; + ListCell *lc; + + foreach(lc, clauses_parent) + { + Node *one_clause_child = + adjust_appendrel_attrs(root, lfirst(lc), appinfo); + clauses_child = lappend(clauses_child, one_clause_child); + } + + return make_restrictinfos_from_actual_clauses(root, clauses_child); +} + +static inline List * +extract_join_clauses(List *restrictlist, RelOptInfo *outer_prel, + RelOptInfo *inner_rel) +{ + List *result = NIL; + ListCell *lc; + + foreach (lc, restrictlist) + { + RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + + if (clause_sides_match_join(rinfo, outer_prel, inner_rel)) + result = lappend(result, rinfo); + } + + return result; +} + +/* + * Copy path node for try_append_pullup_across_join() + * + * This includes following steps. + * (a) Prepare ParamPathInfo for RestrictInfos by CHECK constraints. + * (See comment below.) + * (b) Copy path node and specify ParamPathInfo node made at (a) to it. + * (c) Re-calculate costs for path node copied at (b). + * + * NOTE : "nworkers" argument is used for the (Parallel)SeqScan node under + * the Gather node, which calls this function recursively. Therefore, + * "nworkers" argument should be 0, except for recursive call for + * the Gather node. + */ +static Path * +copy_inner_path_for_append_pullup(PlannerInfo *root, + Path *orig_inner_path, RelOptInfo *inner_rel, + List *restrictlist_by_check_constr, int nworkers) +{ + /* + * Prepare ParamPathInfo for RestrictInfos by CHECK constraints. + * + * For specifying additional restrictions to inner path, + * we attach ParamPathInfo not to RelOptInfo but only to Path. + * + * PPI is generally used for parameterizing Scan node under Join node + * for example, the purpose of this usage is to extract rows which + * satisfies join conditions fixed one side. + * + * In this function, PPI is used for specifying additional restrictions + * to inner path, and using join conditions and using converted CHECK() + * constraints differ, however these don't differ from a point that + * it extracts rows. Therefore it looks good to use PPI for this purpose. + * + */ + ParamPathInfo *newppi = makeNode(ParamPathInfo); + Path *alter_inner_path; + + newppi->ppi_req_outer = NULL; + newppi->ppi_rows = + get_parameterized_baserel_size(root, + inner_rel, + restrictlist_by_check_constr); + newppi->ppi_clauses = restrictlist_by_check_constr; + + /* Copy Path of inner relation, and specify newppi to it. */ + alter_inner_path = copyObject(orig_inner_path); + alter_inner_path->param_info = newppi; + + /* Re-calculate costs of alter_inner_path */ + switch (orig_inner_path->pathtype) + { + case T_SeqScan : + cost_seqscan(alter_inner_path, root, inner_rel, newppi, nworkers); + break; + case T_SampleScan : + cost_samplescan(alter_inner_path, root, inner_rel, newppi); + break; + case T_IndexScan : + case T_IndexOnlyScan : + { + IndexPath *ipath = (IndexPath *) alter_inner_path; + + cost_index(ipath, root, 1.0); + } + break; + case T_BitmapHeapScan : + { + BitmapHeapPath *bpath = + (BitmapHeapPath *) alter_inner_path; + + cost_bitmap_heap_scan(&bpath->path, root, inner_rel, + newppi, bpath->bitmapqual, 1.0); + } + break; + case T_TidScan : + { + TidPath *tpath = (TidPath *) alter_inner_path; + + cost_tidscan(&tpath->path, root, inner_rel, + tpath->tidquals, newppi); + } + break; + case T_Gather : + { + GatherPath *orig_gpath = (GatherPath *) orig_inner_path; + GatherPath *alter_gpath = (GatherPath *) alter_inner_path; + + Path *alter_sub_path = + copy_inner_path_for_append_pullup(root, + orig_gpath->subpath, + inner_rel, + restrictlist_by_check_constr, + orig_gpath->num_workers); + + alter_gpath->subpath = alter_sub_path; + + cost_gather(alter_gpath, root, inner_rel, newppi); + } + break; + default: + Assert(false); + break; + } + + return alter_inner_path; +} + +/* + * try_append_pullup_across_join + * + * When outer-path of JOIN is AppendPath, we can rewrite path-tree with + * relocation of JoinPath across AppendPath, to generate equivalent + * results, like a diagram below. + * This adjustment gives us a few performance benefits when the relations + * scaned by sub-plan of Append-node have CHECK() constraints - typically, + * configured as partitioned table. + * + * In case of INNER JOIN with equivalent join condition, like A = B, we + * can exclude a part of inner rows that are obviously unreferenced, if + * outer side has CHECK() constraints that contains join keys. + * The CHECK() constraints ensures all the rows within outer relation + * satisfies the condition, in other words, any inner rows that does not + * satisfies the condition (with adjustment using equivalence of join keys) + * never match any outer rows. + * + * Once we can reduce number of inner rows, here are two beneficial scenario. + * 1. HashJoin may avoid split of hash-table even if preload of entire + * inner relation exceeds work_mem. + * 2. MergeJoin may be able to take smaller scale of Sort, because quick-sort + * is O(NlogN) scale problem. Reduction of rows to be sorted on both side + * reduces CPU cost more than liner. + * + * [BEFORE] + * JoinPath ... (parent.X = inner.Y) + * -> AppendPath on parent + * -> ScanPath on child_1 ... CHECK(hash(X) % 3 = 0) + * -> ScanPath on child_2 ... CHECK(hash(X) % 3 = 1) + * -> ScanPath on child_3 ... CHECK(hash(X) % 3 = 2) + * -> ScanPath on inner + * + * [AFTER] + * AppendPath + * -> JoinPath ... (child_1.X = inner.Y) + * -> ScanPath on child_1 ... CHECK(hash(X) % 3 = 0) + * -> ScanPath on inner ... filter (hash(Y) % 3 = 0) + * -> JoinPath ... (child_2.X = inner.Y) + * -> ScanPath on child_2 ... CHECK(hash(X) % 3 = 1) + * -> ScanPath on inner ... filter (hash(Y) % 3 = 1) + * -> JoinPath ... (child_3.X = inner.Y) + * -> ScanPath on child_3 ... CHECK(hash(X) % 3 = 2) + * -> ScanPath on inner ... filter (hash(Y) % 3 = 2) + * + * Point to be focused on is filter condition attached on child relation's + * scan. It is clause of CHECK() constraint, but X is replaced by Y using + * equivalence join condition. + */ +static void +try_append_pullup_across_join(PlannerInfo *root, + RelOptInfo *joinrel, RelOptInfo *outer_rel, + RelOptInfo *inner_rel, + List *restrictlist) +{ + AppendPath *outer_path; + ListCell *lc_subpath; + ListCell *lc_outer_path, *lc_inner_path; + List *joinclauses_parent; + List *alter_append_subpaths = NIL; + int num_pathlist_join = list_length(joinrel->pathlist); + + if (outer_rel->rtekind != RTE_RELATION) + { + elog(DEBUG1, "Outer Relation is not for table scan. Give up."); + return; + } + + /* + * Extract join clauses to convert CHECK() constraints. + * We don't have to clobber this list to convert CHECK() constraints, + * so we need to do only once. + */ + joinclauses_parent = extract_join_clauses(restrictlist, outer_rel, inner_rel); + if (list_length(joinclauses_parent) <= 0) + { + elog(DEBUG1, "No join clauses specified. Give up."); + return; + } + + /* + * We use ParamPathInfo for specifying additional RestrictInfos + * created from CHECK constraints to inner relation. Therefore, + * we can NOT perform append pull-up when PPI has already specified + * to inner relation. + */ + if (list_length(inner_rel->ppilist) > 0) + { + elog(DEBUG1, "ParamPathInfo is already set in inner_rel. Can't pull-up."); + return; + } + + foreach(lc_outer_path, outer_rel->pathlist) + { + /* When specified outer path is not an AppendPath, nothing to do here. */ + if (!IsA(lfirst(lc_outer_path), AppendPath)) + { + elog(DEBUG1, "Outer path is not an AppendPath. Do nothing."); + continue; + } + + outer_path = (AppendPath *) lfirst(lc_outer_path); + + foreach(lc_inner_path, inner_rel->pathlist) + { + switch (((Path *) lfirst(lc_inner_path))->pathtype) + { + case T_SeqScan : + case T_SampleScan : + case T_IndexScan : + case T_IndexOnlyScan : + case T_BitmapHeapScan : + case T_TidScan : + case T_Gather : + /* These types are supported. Pass through. */ + break; + default : + { + elog(DEBUG1, "Type of Inner path is not supported yet." + " Give up."); + continue; + } + } + + /* + * Make new joinrel between each of outer path's sub-paths and + * inner path. + */ + foreach(lc_subpath, outer_path->subpaths) + { + RelOptInfo *orig_outer_sub_rel = + ((Path *) lfirst(lc_subpath))->parent; + RelOptInfo *alter_outer_sub_rel; + Path *alter_inner_path = NULL; + List *joinclauses_child; + List *restrictlist_by_check_constr; + bool is_valid; + List **join_rel_level; + + ListCell *parentvars, *childvars; + + Assert(!IS_DUMMY_REL(orig_outer_sub_rel)); + + /* + * Join clause points parent's relid, + * so we must change it to child's one. + */ + joinclauses_child = + convert_parent_joinclauses_to_child(root, + joinclauses_parent, + orig_outer_sub_rel); + + /* + * Make RestrictInfo list from CHECK() constraints of outer table. + * "is_valid" indicates whether making RestrictInfo list succeeded + * or not. + */ + restrictlist_by_check_constr = + create_rinfo_from_check_constr(root, joinclauses_child, + orig_outer_sub_rel, &is_valid); + + if (!is_valid) + { + elog(DEBUG1, "Join clause doesn't match with CHECK() constraint. " + "Can't pull-up."); + list_free_deep(alter_append_subpaths); + list_free(joinclauses_parent); + return; + } + + if (list_length(restrictlist_by_check_constr) > 0) + { + /* Copy Path of inner relation, and specify newppi to it. */ + alter_inner_path = copy_inner_path_for_append_pullup(root, + (Path *) lfirst(lc_inner_path), + inner_rel, + restrictlist_by_check_constr, + 0); + + /* + * Append this path to pathlist temporary. + * This path will be removed after returning from make_join_rel(). + */ + inner_rel->pathlist = + lappend(inner_rel->pathlist, alter_inner_path); + set_cheapest(inner_rel); + } + + /* + * Add relids, which are marked as needed not in child's attribute + * but in parent's one, to child's attribute. + * + * attr_needed[] fields of all RelOptInfo under the Append node + * are originally empty sets, therefore unintentional target list + * is made by build_rel_tlist() for new joinrel; because + * bms_noempty_difference() always returns false for outer + * relation, no target is enumerated for it. + * + * We make really needed relids from parent RelOptInfo, and add + * these relids to child's attr_needed[] to get intended target + * list for new joinrel. + * + * This behavior may be harmless for considering other paths, + * so we don't remove these relids from child after processing + * append pulling-up. + */ + forboth(parentvars, outer_rel->reltargetlist, + childvars, orig_outer_sub_rel->reltargetlist) + { + Var *parentvar = (Var *) lfirst(parentvars); + Var *childvar = (Var *) lfirst(childvars); + int p_ndx; + Relids required_relids; + + if (!IsA(parentvar, Var) || !IsA(childvar, Var)) + continue; + + Assert(find_base_rel(root, parentvar->varno) == outer_rel); + p_ndx = parentvar->varattno - outer_rel->min_attr; + + required_relids = bms_del_members( + bms_copy(outer_rel->attr_needed[p_ndx]), + joinrel->relids); + + if (!bms_is_empty(required_relids)) + { + RelOptInfo *baserel = + find_base_rel(root, childvar->varno); + int c_ndx = + childvar->varattno - baserel->min_attr; + + baserel->attr_needed[c_ndx] = bms_add_members( + baserel->attr_needed[c_ndx], + required_relids); + } + } + + /* + * NOTE: root->join_rel_level is used to track candidate of join + * relations for each level, then these relations are consolidated + * to one relation. + * (See the comment in standard_join_search) + * + * Even though we construct RelOptInfo of child relations of the + * Append node, these relations should not appear as candidate of + * relations join in the later stage. So, we once save the list + * during make_join_rel() for the child relations. + */ + join_rel_level = root->join_rel_level; + root->join_rel_level = NULL; + + /* + * Create new joinrel (as a sub-path of Append). + */ + alter_outer_sub_rel = + make_join_rel(root, orig_outer_sub_rel, inner_rel); + + /* restore the join_rel_level */ + root->join_rel_level = join_rel_level; + + Assert(alter_outer_sub_rel != NULL); + + if (alter_inner_path) + { + /* + * Remove (temporary added) alter_inner_path from pathlist. + * + * The alter_inner_path may be inner/outer path of JoinPath + * made by make_join_rel() above, thus we MUST NOT free + * alter_inner_path itself. + */ + inner_rel->pathlist = + list_delete_ptr(inner_rel->pathlist, alter_inner_path); + set_cheapest(inner_rel); + } + + if (IS_DUMMY_REL(alter_outer_sub_rel)) + { + pfree(alter_outer_sub_rel); + continue; + } + + /* + * We must check if alter_outer_sub_rel has one or more paths. + * add_path() sometime rejects to add new path to parent RelOptInfo. + */ + if (list_length(alter_outer_sub_rel->pathlist) <= 0) + { + /* + * Sadly, No paths added. This means that pull-up is failed, + * thus clean up here. + */ + list_free_deep(alter_append_subpaths); + pfree(alter_outer_sub_rel); + list_free(joinclauses_parent); + elog(DEBUG1, "Append pull-up failed."); + return; + } + + set_cheapest(alter_outer_sub_rel); + Assert(alter_outer_sub_rel->cheapest_total_path != NULL); + alter_append_subpaths = lappend(alter_append_subpaths, + alter_outer_sub_rel->cheapest_total_path); + } /* End of foreach(outer_path->subpaths) */ + + /* Append pull-up is succeeded. Add path to original joinrel. */ + add_path(joinrel, + (Path *) create_append_path(joinrel, alter_append_subpaths, NULL)); + + list_free(joinclauses_parent); + elog(DEBUG1, "Append pull-up succeeded."); + } /* End of foreach(inner_path->pathlist) */ + + /* + * We check length of joinrel's pathlist here. + * If it is equal to or lesser than before trying above, + * all inner_paths are not suitable for append pulling-up, + * thus we decide to abort trying anymore. + */ + if (list_length(joinrel->pathlist) > num_pathlist_join) + { + elog(DEBUG1, "No paths are added. Abort now."); + return; + } + } /* End of foreach(outer_path->pathlist) */ +} diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 411b36c..b088ba9 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -4235,8 +4235,18 @@ prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys, /* * Ignore child members unless they match the rel being * sorted. + * + * For append pull-up, we must not ignore child members + * when this is called from make_sort_from_pathkeys(). + * Because "em_is_child" fields of all "ec_members" are true + * in this case, thus it may fail to find pathkey + * (and raise an error). + * + * In this condition, "relids" field may be NULL. So we don't + * ignore child members when "relids" field is NULL. */ - if (em->em_is_child && + if (relids != NULL && + em->em_is_child && !bms_equal(em->em_relids, relids)) continue; @@ -4349,8 +4359,17 @@ find_ec_member_for_tle(EquivalenceClass *ec, /* * Ignore child members unless they match the rel being sorted. + * + * For append pull-up, we must not ignore child members when this is + * called from make_sort_from_pathkeys(). Because "em_is_child" fields + * of all "ec_members" are true in this case, thus it will fail to + * find pathkey (and raise an error). + * + * In this condition, "relids" field may be NULL. So we don't ignore + * child members when "relids" field is NULL. */ - if (em->em_is_child && + if (relids != NULL && + em->em_is_child && !bms_equal(em->em_relids, relids)) continue; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 9442e5f..c137b09 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -54,9 +54,6 @@ get_relation_info_hook_type get_relation_info_hook = NULL; 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, - bool include_notnull); static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index, Relation heapRelation); @@ -1022,7 +1019,7 @@ get_relation_data_width(Oid relid, int32 *attr_widths) * run, and in many cases it won't be invoked at all, so there seems no * point in caching the data in RelOptInfo. */ -static List * +List * get_relation_constraints(PlannerInfo *root, Oid relationObjectId, RelOptInfo *rel, bool include_notnull)