From dee63e6e198ecfe66638910b8764dc5ba6e96e7b Mon Sep 17 00:00:00 2001 From: =?utf-8?q?=E4=B8=80=E6=8C=83?= Date: Tue, 1 Feb 2022 20:56:40 +0800 Subject: [PATCH v1 1/6] Rebaee David's patch against the latest code. --- src/backend/nodes/outfuncs.c | 14 ++ src/backend/optimizer/path/equivclass.c | 182 +++++++++++++++++++++++ src/backend/optimizer/plan/initsplan.c | 96 +++++++++--- src/backend/utils/cache/lsyscache.c | 28 ++++ src/include/nodes/nodes.h | 1 + src/include/nodes/pathnodes.h | 37 +++++ src/include/optimizer/paths.h | 1 + src/include/utils/lsyscache.h | 1 + src/test/regress/expected/equivclass.out | 45 ++++++ 9 files changed, 388 insertions(+), 17 deletions(-) diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 2b0236937aa..504b805326f 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -2504,6 +2504,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node) WRITE_OID_FIELD(em_datatype); } +static void +_outEquivalenceFilter(StringInfo str, const EquivalenceFilter *node) +{ + WRITE_NODE_TYPE("EQUIVALENCEFILTER"); + + WRITE_NODE_FIELD(ef_const); + WRITE_OID_FIELD(ef_opno); + WRITE_BOOL_FIELD(ef_const_is_left); + WRITE_UINT_FIELD(ef_source_rel); +} + static void _outPathKey(StringInfo str, const PathKey *node) { @@ -4304,6 +4315,9 @@ outNode(StringInfo str, const void *obj) case T_EquivalenceMember: _outEquivalenceMember(str, obj); break; + case T_EquivalenceFilter: + _outEquivalenceFilter(str, obj); + break; case T_PathKey: _outPathKey(str, obj); break; diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c index 8c6770de972..f9ae2785d60 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -19,6 +19,7 @@ #include #include "access/stratnum.h" +#include "catalog/pg_am.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -1250,6 +1251,37 @@ generate_base_implied_equalities_const(PlannerInfo *root, } } +/* + * finds the opfamily and strategy number for the specified 'opno' and 'method' + * access method. Returns True if one is found and sets 'family' and + * 'amstrategy', or returns False if none are found. + */ +static bool +find_am_family_and_stategy(Oid opno, Oid method, Oid *family, int *amstrategy) +{ + List *opfamilies; + ListCell *l; + int strategy; + + opfamilies = get_opfamilies(opno, method); + + foreach(l, opfamilies) + { + Oid opfamily = lfirst_oid(l); + + strategy = get_op_opfamily_strategy(opno, opfamily); + + if (strategy) + { + *amstrategy = strategy; + *family = opfamily; + return true; + } + } + + return false; +} + /* * generate_base_implied_equalities when EC contains no pseudoconstants */ @@ -1259,6 +1291,7 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, { EquivalenceMember **prev_ems; ListCell *lc; + ListCell *lc2; /* * We scan the EC members once and track the last-seen member for each @@ -1320,6 +1353,57 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, rinfo->right_em = cur_em; } } + + /* + * Also push any EquivalenceFilter clauses down into all relations + * other than the one which the filter actually originated from. + */ + foreach(lc2, ec->ec_filters) + { + EquivalenceFilter *ef = (EquivalenceFilter *) lfirst(lc2); + Expr *leftexpr; + Expr *rightexpr; + int strategy; + Oid opno; + Oid family; + + if (ef->ef_source_rel == relid) + continue; + + if (!find_am_family_and_stategy(ef->ef_opno, BTREE_AM_OID, + &family, &strategy)) + continue; + + if (ef->ef_const_is_left) + { + leftexpr = (Expr *) ef->ef_const; + rightexpr = cur_em->em_expr; + } + else + { + leftexpr = cur_em->em_expr; + rightexpr = (Expr *) ef->ef_const; + } + + opno = get_opfamily_member(family, + exprType((Node *) leftexpr), + exprType((Node *) rightexpr), + strategy); + + if (opno == InvalidOid) + continue; + + process_implied_equality(root, opno, + ec->ec_collation, + leftexpr, + rightexpr, + bms_copy(ec->ec_relids), + bms_copy(cur_em->em_nullable_relids), + ec->ec_min_security, + ec->ec_below_outer_join, + false); + } + prev_ems[relid] = cur_em; } @@ -1901,6 +1985,104 @@ create_join_clause(PlannerInfo *root, return rinfo; } +/* + * distribute_filter_quals_to_eclass + * For each OpExpr in quallist look for an eclass which has an Expr + * matching the Expr in the OpExpr. If a match is found we add a new + * EquivalenceFilter to the eclass containing the filter details. + */ +void +distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist) +{ + ListCell *l; + + /* fast path for when no eclasses have been generated */ + if (root->eq_classes == NIL) + return; + + /* + * For each qual in quallist try and find an eclass which contains the + * non-Const part of the OpExpr. We'll tag any matches that we find onto + * the correct eclass. + */ + foreach(l, quallist) + { + OpExpr *opexpr = (OpExpr *) lfirst(l); + Expr *leftexpr = (Expr *) linitial(opexpr->args); + Expr *rightexpr = (Expr *) lsecond(opexpr->args); + Const *constexpr; + Expr *varexpr; + Relids exprrels; + int relid; + bool const_isleft; + ListCell *l2; + + /* + * Determine if the the OpExpr is in the form "expr op const" or + * "const op expr". + */ + if (IsA(leftexpr, Const)) + { + constexpr = (Const *) leftexpr; + varexpr = rightexpr; + const_isleft = true; + } + else + { + constexpr = (Const *) rightexpr; + varexpr = leftexpr; + const_isleft = false; + } + + exprrels = pull_varnos(root, (Node *) varexpr); + + /* should be filtered out, but we need to determine relid anyway */ + if (!bms_get_singleton_member(exprrels, &relid)) + continue; + + /* search for a matching eclass member in all eclasses */ + foreach(l2, root->eq_classes) + { + EquivalenceClass *ec = (EquivalenceClass *) lfirst(l2); + ListCell *l3; + + if (ec->ec_broken || ec->ec_has_volatile) + continue; + + /* + * if the eclass has a const then that const will serve as the + * filter, we needn't add any others. + */ + if (ec->ec_has_const) + continue; + + /* skip this eclass no members exist which belong to this relid */ + if (!bms_is_member(relid, ec->ec_relids)) + continue; + + foreach(l3, ec->ec_members) + { + EquivalenceMember *em = (EquivalenceMember *) lfirst(l3); + + if (!bms_is_member(relid, em->em_relids)) + continue; + + if (equal(em->em_expr, varexpr)) + { + EquivalenceFilter *efilter; + efilter = makeNode(EquivalenceFilter); + efilter->ef_const = (Const *) copyObject(constexpr); + efilter->ef_const_is_left = const_isleft; + efilter->ef_opno = opexpr->opno; + efilter->ef_source_rel = relid; + + ec->ec_filters = lappend(ec->ec_filters, efilter); + break; /* Onto the next eclass */ + } + } + } + } +} /* * reconsider_outer_join_clauses diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 023efbaf092..b219bee8567 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -53,7 +53,7 @@ static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, Relids *qualscope, Relids *inner_join_rels, - List **postponed_qual_list); + List **postponed_qual_list, List **filter_qual_list); static void process_security_barrier_quals(PlannerInfo *root, int rti, Relids qualscope, bool below_outer_join); @@ -70,7 +70,8 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, Relids qualscope, Relids ojscope, Relids outerjoin_nonnullable, - List **postponed_qual_list); + List **postponed_qual_list, + List **filter_qual_list); static bool check_outerjoin_delay(PlannerInfo *root, Relids *relids_p, Relids *nullable_relids_p, bool is_pushed_down); static bool check_equivalence_delay(PlannerInfo *root, @@ -650,6 +651,43 @@ create_lateral_join_info(PlannerInfo *root) } } +/* + * is_simple_filter_qual + * Analyzes an OpExpr to determine if it may be useful as an + * EquivalenceFilter. Returns true if the OpExpr may be of some use, or + * false if it should not be used. + */ +static bool +is_simple_filter_qual(PlannerInfo *root, OpExpr *expr) +{ + Expr *leftexpr; + Expr *rightexpr; + + if (!IsA(expr, OpExpr)) + return false; + + if (list_length(expr->args) != 2) + return false; + + leftexpr = (Expr *) linitial(expr->args); + rightexpr = (Expr *) lsecond(expr->args); + + /* XXX should we restrict these to simple Var op Const expressions? */ + if (IsA(leftexpr, Const)) + { + if (bms_membership(pull_varnos(root, (Node *) rightexpr)) == BMS_SINGLETON && + !contain_volatile_functions((Node *) rightexpr)) + return true; + } + else if (IsA(rightexpr, Const)) + { + if (bms_membership(pull_varnos(root, (Node *) leftexpr)) == BMS_SINGLETON && + !contain_volatile_functions((Node *) leftexpr)) + return true; + } + + return false; +} /***************************************************************************** * @@ -690,6 +728,7 @@ deconstruct_jointree(PlannerInfo *root) Relids qualscope; Relids inner_join_rels; List *postponed_qual_list = NIL; + List *filter_qual_list = NIL; /* Start recursion at top of jointree */ Assert(root->parse->jointree != NULL && @@ -700,11 +739,14 @@ deconstruct_jointree(PlannerInfo *root) result = deconstruct_recurse(root, (Node *) root->parse->jointree, false, &qualscope, &inner_join_rels, - &postponed_qual_list); + &postponed_qual_list, &filter_qual_list); /* Shouldn't be any leftover quals */ Assert(postponed_qual_list == NIL); + /* try and match each filter_qual_list item up with an eclass. */ + distribute_filter_quals_to_eclass(root, filter_qual_list); + return result; } @@ -725,6 +767,8 @@ deconstruct_jointree(PlannerInfo *root) * or free this, either) * *postponed_qual_list is a list of PostponedQual structs, which we can * add quals to if they turn out to belong to a higher join level + * *filter_qual_list is appended to with a list of quals which may be useful + * include as EquivalenceFilters. * Return value is the appropriate joinlist for this jointree node * * In addition, entries will be added to root->join_info_list for outer joins. @@ -732,7 +776,7 @@ deconstruct_jointree(PlannerInfo *root) static List * deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, Relids *qualscope, Relids *inner_join_rels, - List **postponed_qual_list) + List **postponed_qual_list, List **filter_qual_list) { List *joinlist; @@ -785,7 +829,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, below_outer_join, &sub_qualscope, inner_join_rels, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); *qualscope = bms_add_members(*qualscope, sub_qualscope); sub_members = list_length(sub_joinlist); remaining--; @@ -819,7 +864,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, below_outer_join, JOIN_INNER, root->qual_security_level, *qualscope, NULL, NULL, - NULL); + NULL, + filter_qual_list); else *postponed_qual_list = lappend(*postponed_qual_list, pq); } @@ -835,7 +881,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, below_outer_join, JOIN_INNER, root->qual_security_level, *qualscope, NULL, NULL, - postponed_qual_list); + postponed_qual_list, + filter_qual_list); } } else if (IsA(jtnode, JoinExpr)) @@ -873,11 +920,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, leftjoinlist = deconstruct_recurse(root, j->larg, below_outer_join, &leftids, &left_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); rightjoinlist = deconstruct_recurse(root, j->rarg, below_outer_join, &rightids, &right_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); *qualscope = bms_union(leftids, rightids); *inner_join_rels = *qualscope; /* Inner join adds no restrictions for quals */ @@ -890,11 +939,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, leftjoinlist = deconstruct_recurse(root, j->larg, below_outer_join, &leftids, &left_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); rightjoinlist = deconstruct_recurse(root, j->rarg, true, &rightids, &right_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); *qualscope = bms_union(leftids, rightids); *inner_join_rels = bms_union(left_inners, right_inners); nonnullable_rels = leftids; @@ -904,11 +955,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, leftjoinlist = deconstruct_recurse(root, j->larg, below_outer_join, &leftids, &left_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); rightjoinlist = deconstruct_recurse(root, j->rarg, below_outer_join, &rightids, &right_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); *qualscope = bms_union(leftids, rightids); *inner_join_rels = bms_union(left_inners, right_inners); /* Semi join adds no restrictions for quals */ @@ -925,11 +978,13 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, leftjoinlist = deconstruct_recurse(root, j->larg, true, &leftids, &left_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); rightjoinlist = deconstruct_recurse(root, j->rarg, true, &rightids, &right_inners, - &child_postponed_quals); + &child_postponed_quals, + filter_qual_list); *qualscope = bms_union(leftids, rightids); *inner_join_rels = bms_union(left_inners, right_inners); /* each side is both outer and inner */ @@ -1013,7 +1068,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, root->qual_security_level, *qualscope, ojscope, nonnullable_rels, - postponed_qual_list); + postponed_qual_list, + filter_qual_list); } /* Now we can add the SpecialJoinInfo to join_info_list */ @@ -1117,6 +1173,7 @@ process_security_barrier_quals(PlannerInfo *root, qualscope, qualscope, NULL, + NULL, NULL); } security_level++; @@ -1610,7 +1667,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, Relids qualscope, Relids ojscope, Relids outerjoin_nonnullable, - List **postponed_qual_list) + List **postponed_qual_list, + List **filter_qual_list) { Relids relids; bool is_pushed_down; @@ -1964,6 +2022,10 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, /* No EC special case applies, so push it into the clause lists */ distribute_restrictinfo_to_rels(root, restrictinfo); + + /* Check if the qual looks useful to harvest as an EquivalenceFilter */ + if (filter_qual_list != NULL && is_simple_filter_qual(root, (OpExpr *) clause)) + *filter_qual_list = lappend(*filter_qual_list, clause); } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index feef9998634..add2e7176ae 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -341,6 +341,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type) return result; } +/* + * get_opfamilies + * Returns a list of Oids of each opfamily which 'opno' belonging to + * 'method' access method. + */ +List * +get_opfamilies(Oid opno, Oid method) +{ + List *result = NIL; + CatCList *catlist; + int i; + + catlist = SearchSysCacheList1(AMOPOPID, ObjectIdGetDatum(opno)); + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple tuple = &catlist->members[i]->tuple; + Form_pg_amop aform = (Form_pg_amop) GETSTRUCT(tuple); + + if (aform->amopmethod == method) + result = lappend_oid(result, aform->amopfamily); + } + + ReleaseSysCacheList(catlist); + + return result; +} + /* * get_mergejoin_opfamilies * Given a putatively mergejoinable operator, return a list of the OIDs diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index da35f2c2722..3a9a235cd0e 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -265,6 +265,7 @@ typedef enum NodeTag /* these aren't subclasses of Path: */ T_EquivalenceClass, T_EquivalenceMember, + T_EquivalenceFilter, T_PathKey, T_PathTarget, T_RestrictInfo, diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 1f3845b3fec..e73fef057a4 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -990,6 +990,7 @@ typedef struct EquivalenceClass List *ec_members; /* list of EquivalenceMembers */ List *ec_sources; /* list of generating RestrictInfos */ List *ec_derives; /* list of derived RestrictInfos */ + List *ec_filters; Relids ec_relids; /* all relids appearing in ec_members, except * for child members (see below) */ bool ec_has_const; /* any pseudoconstants in ec_members? */ @@ -1002,6 +1003,42 @@ typedef struct EquivalenceClass struct EquivalenceClass *ec_merged; /* set if merged into another EC */ } EquivalenceClass; +/* + * EquivalenceFilter - List of filters on Consts which belong to the + * EquivalenceClass. + * + * When building the equivalence classes we also collected a list of quals in + * the form of; "Expr op Const" and "Const op Expr". These are collected in the + * hope that we'll later generate an equivalence class which contains the + * "Expr" part. For example, if we parse a query such as; + * + * SELECT * FROM t1 INNER JOIN t2 ON t1.id = t2.id WHERE t1.id < 10; + * + * then since we'll end up with an equivalence class containing {t1.id,t2.id}, + * we'll tag the "< 10" filter onto the eclass. We are able to do this because + * the eclass proves equality between each class member, therefore all members + * must be below 10. + * + * EquivalenceFilters store the details required to allow us to push these + * filter clauses down into other relations which share an equivalence class + * containing a member which matches the expression of this EquivalenceFilter. + * + * ef_const is the Const value which this filter should filter against. + * ef_opno is the operator to filter on. + * ef_const_is_left marks if the OpExpr was in the form "Const op Expr" or + * "Expr op Const". + * ef_source_rel is the relation id of where this qual originated from. + */ +typedef struct EquivalenceFilter +{ + NodeTag type; + + Const *ef_const; /* the constant expression to filter on */ + Oid ef_opno; /* Operator Oid of filter operator */ + bool ef_const_is_left; /* Is the Const on the left of the OpExrp? */ + Index ef_source_rel; /* relid of originating relation. */ +} EquivalenceFilter; + /* * If an EC contains a const and isn't below-outer-join, any PathKey depending * on it must be redundant, since there's only one possible value of the key. diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 0c3a0b90c85..ce2aac7d3aa 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -126,6 +126,7 @@ extern bool process_equivalence(PlannerInfo *root, extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); extern void reconsider_outer_join_clauses(PlannerInfo *root); +extern void distribute_filter_quals_to_eclass(PlannerInfo *root, List *quallist); extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, Relids nullable_relids, diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index b8dd27d4a96..188d65faa0d 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -78,6 +78,7 @@ extern bool get_ordering_op_properties(Oid opno, Oid *opfamily, Oid *opcintype, int16 *strategy); extern Oid get_equality_op_for_ordering_op(Oid opno, bool *reverse); extern Oid get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type); +extern List *get_opfamilies(Oid opno, Oid method); extern List *get_mergejoin_opfamilies(Oid opno); extern bool get_compatible_hash_operators(Oid opno, Oid *lhs_opno, Oid *rhs_opno); diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out index 126f7047fed..ce4c9b11748 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -451,3 +451,48 @@ explain (costs off) -- this should not require a sort Filter: (f1 = 'foo'::name) (2 rows) +-- test equivalence filters +explain (costs off) + select * from ec0 + inner join ec1 on ec0.ff = ec1.ff + where ec0.ff between 1 and 10; + QUERY PLAN +------------------------------------------------------------ + Merge Join + Merge Cond: (ec0.ff = ec1.ff) + -> Sort + Sort Key: ec0.ff + -> Bitmap Heap Scan on ec0 + Recheck Cond: ((ff >= 1) AND (ff <= 10)) + -> Bitmap Index Scan on ec0_pkey + Index Cond: ((ff >= 1) AND (ff <= 10)) + -> Sort + Sort Key: ec1.ff + -> Bitmap Heap Scan on ec1 + Recheck Cond: ((ff >= 1) AND (ff <= 10)) + -> Bitmap Index Scan on ec1_pkey + Index Cond: ((ff >= 1) AND (ff <= 10)) +(14 rows) + +explain (costs off) + select * from ec0 + inner join ec1 on ec0.ff = ec1.ff + where ec1.ff between 1 and 10; + QUERY PLAN +------------------------------------------------------------ + Merge Join + Merge Cond: (ec0.ff = ec1.ff) + -> Sort + Sort Key: ec0.ff + -> Bitmap Heap Scan on ec0 + Recheck Cond: ((ff >= 1) AND (ff <= 10)) + -> Bitmap Index Scan on ec0_pkey + Index Cond: ((ff >= 1) AND (ff <= 10)) + -> Sort + Sort Key: ec1.ff + -> Bitmap Heap Scan on ec1 + Recheck Cond: ((ff >= 1) AND (ff <= 10)) + -> Bitmap Index Scan on ec1_pkey + Index Cond: ((ff >= 1) AND (ff <= 10)) +(14 rows) + -- 2.21.0