diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 012c14b..0964d82 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1976,6 +1976,17 @@ _outEquivalenceMember(StringInfo str, const EquivalenceMember *node) } 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) { WRITE_NODE_TYPE("PATHKEY"); @@ -3339,6 +3350,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 48be45d..16193d9 100644 --- a/src/backend/optimizer/path/equivclass.c +++ b/src/backend/optimizer/path/equivclass.c @@ -17,6 +17,7 @@ #include "postgres.h" #include "access/stratnum.h" +#include "catalog/pg_am.h" #include "catalog/pg_type.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -840,6 +841,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 */ static void @@ -848,6 +880,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 @@ -893,6 +926,56 @@ generate_base_implied_equalities_no_const(PlannerInfo *root, ec->ec_below_outer_join, false); } + + /* + * 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_below_outer_join, + false); + } + prev_ems[relid] = cur_em; } @@ -1384,6 +1467,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((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 2f4e818..93f340b 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -51,7 +51,7 @@ static void add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs); 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 SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, Relids left_rels, Relids right_rels, Relids inner_join_rels, @@ -65,7 +65,9 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, Relids ojscope, Relids outerjoin_nonnullable, Relids deduced_nullable_relids, - List **postponed_qual_list); + List **postponed_qual_list, + List **filter_qual_list); +static bool is_simple_filter_qual(OpExpr *expr); 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, @@ -648,6 +650,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 && @@ -658,11 +661,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; } @@ -683,6 +689,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. @@ -690,7 +698,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; @@ -737,7 +745,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--; @@ -770,7 +779,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, distribute_qual_to_rels(root, pq->qual, false, below_outer_join, JOIN_INNER, *qualscope, NULL, NULL, NULL, - NULL); + NULL, filter_qual_list); else *postponed_qual_list = lappend(*postponed_qual_list, pq); } @@ -785,7 +794,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, distribute_qual_to_rels(root, qual, false, below_outer_join, JOIN_INNER, *qualscope, NULL, NULL, NULL, - postponed_qual_list); + postponed_qual_list, filter_qual_list); } } else if (IsA(jtnode, JoinExpr)) @@ -823,11 +832,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 */ @@ -840,11 +851,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; @@ -854,11 +867,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 */ @@ -875,11 +890,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 */ @@ -963,7 +980,8 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join, false, below_outer_join, j->jointype, *qualscope, ojscope, nonnullable_rels, NULL, - postponed_qual_list); + postponed_qual_list, + filter_qual_list); } /* Now we can add the SpecialJoinInfo to join_info_list */ @@ -1485,7 +1503,8 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, Relids ojscope, Relids outerjoin_nonnullable, Relids deduced_nullable_relids, - List **postponed_qual_list) + List **postponed_qual_list, + List **filter_qual_list) { Relids relids; bool is_pushed_down; @@ -1848,6 +1867,48 @@ 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((OpExpr *) clause)) + *filter_qual_list = lappend(*filter_qual_list, clause); +} + +/* + * 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(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((Node *) rightexpr)) == BMS_SINGLETON && + !contain_volatile_functions((Node *) rightexpr)) + return true; + } + else if (IsA(rightexpr, Const)) + { + if (bms_membership(pull_varnos((Node *) leftexpr)) == BMS_SINGLETON && + !contain_volatile_functions((Node *) leftexpr)) + return true; + } + + return false; } /* @@ -2183,7 +2244,7 @@ process_implied_equality(PlannerInfo *root, distribute_qual_to_rels(root, (Node *) clause, true, below_outer_join, JOIN_INNER, qualscope, NULL, NULL, nullable_relids, - NULL); + NULL, NULL); } /* diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 093da76..7e05e72 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -340,6 +340,34 @@ get_ordering_op_for_equality_op(Oid opno, bool use_lhs_type) } /* + * 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 * of the btree opfamilies in which it represents equality. diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 94bdb7c..c463ebf 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -243,6 +243,7 @@ typedef enum NodeTag T_GatherPath, T_EquivalenceClass, T_EquivalenceMember, + T_EquivalenceFilter, T_PathKey, T_RestrictInfo, T_PlaceHolderVar, diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index 9a0dd28..07119a5 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -619,6 +619,8 @@ 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; /* list of OpExprs may be pushed into other + * relations as 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? */ @@ -671,6 +673,42 @@ typedef struct EquivalenceMember } EquivalenceMember; /* + * 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; + +/* * PathKeys * * The sort ordering of a path is represented by a list of PathKey nodes. diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h index 87123a5..6eaf587 100644 --- a/src/include/optimizer/paths.h +++ b/src/include/optimizer/paths.h @@ -113,6 +113,8 @@ extern bool process_equivalence(PlannerInfo *root, RestrictInfo *restrictinfo, bool below_outer_join); extern Expr *canonicalize_ec_expression(Expr *expr, Oid req_type, Oid req_collation); +extern void distribute_filter_quals_to_eclass(PlannerInfo *root, + List *quallist); extern void reconsider_outer_join_clauses(PlannerInfo *root); extern EquivalenceClass *get_eclass_for_sort_expr(PlannerInfo *root, Expr *expr, diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h index dcc421f..41bbbd7 100644 --- a/src/include/utils/lsyscache.h +++ b/src/include/utils/lsyscache.h @@ -52,6 +52,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 0391b8e..7608912 100644 --- a/src/test/regress/expected/equivclass.out +++ b/src/test/regress/expected/equivclass.out @@ -381,3 +381,48 @@ explain (costs off) Index Cond: (ff = '42'::bigint) (14 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) + diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql index 17fad67..bd5c5b3 100644 --- a/src/test/regress/sql/equivclass.sql +++ b/src/test/regress/sql/equivclass.sql @@ -222,3 +222,14 @@ explain (costs off) union all select ff + 4 as x from ec1) as ss1 where ss1.x = ec1.f1 and ec1.ff = 42::int8; + +-- test equivalence filters +explain (costs off) + select * from ec0 + inner join ec1 on ec0.ff = ec1.ff + where ec0.ff between 1 and 10; + +explain (costs off) + select * from ec0 + inner join ec1 on ec0.ff = ec1.ff + where ec1.ff between 1 and 10;