X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Fbackend%2Foptimizer%2Futil%2Fpredtest.c;h=eadd2d5104a5abd80838799dfaab069d8db2a8a6;hb=7e04792a1cbd1763edf72474f6b1fbad2cd0ad31;hp=53f8db6d224de7ff8002bc1b0a51d0efd1d76ee3;hpb=fdf5a5efb7b28c13085fe7313658de8d7b9914f6;p=postgresql diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c index 53f8db6d22..eadd2d5104 100644 --- a/src/backend/optimizer/util/predtest.c +++ b/src/backend/optimizer/util/predtest.c @@ -4,29 +4,39 @@ * Routines to attempt to prove logical implications between predicate * expressions. * - * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.17 2007/11/15 21:14:36 momjian Exp $ + * src/backend/optimizer/util/predtest.c * *------------------------------------------------------------------------- */ #include "postgres.h" -#include "catalog/pg_amop.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "executor/executor.h" +#include "miscadmin.h" #include "optimizer/clauses.h" +#include "optimizer/planmain.h" #include "optimizer/predtest.h" -#include "parser/parse_expr.h" #include "utils/array.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/syscache.h" +/* + * Proof attempts involving large arrays in ScalarArrayOpExpr nodes are + * likely to require O(N^2) time, and more often than not fail anyway. + * So we set an arbitrary limit on the number of array elements that + * we will allow to be treated as an AND or OR clause. + * XXX is it worth exposing this as a GUC knob? + */ +#define MAX_SAOP_ARRAY_SIZE 100 + /* * To avoid redundant coding in predicate_implied_by_recurse and * predicate_refuted_by_recurse, we need to abstract out the notion of @@ -82,11 +92,13 @@ static Node *arrayexpr_next_fn(PredIterInfo info); static void arrayexpr_cleanup_fn(PredIterInfo info); static bool predicate_implied_by_simple_clause(Expr *predicate, Node *clause); static bool predicate_refuted_by_simple_clause(Expr *predicate, Node *clause); -static bool is_null_contradicts(NullTest *ntest, Node *clause); static Node *extract_not_arg(Node *clause); +static Node *extract_strong_not_arg(Node *clause); static bool list_member_strip(List *list, Expr *datum); static bool btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it); +static Oid get_btree_test_op(Oid pred_op, Oid clause_op, bool refute_it); +static void InvalidateOprProofCacheCallBack(Datum arg, int cacheid, uint32 hashvalue); /* @@ -111,14 +123,31 @@ static bool btree_predicate_proof(Expr *predicate, Node *clause, bool predicate_implied_by(List *predicate_list, List *restrictinfo_list) { + Node *p, + *r; + if (predicate_list == NIL) return true; /* no predicate: implication is vacuous */ if (restrictinfo_list == NIL) return false; /* no restriction: implication must fail */ - /* Otherwise, away we go ... */ - return predicate_implied_by_recurse((Node *) restrictinfo_list, - (Node *) predicate_list); + /* + * If either input is a single-element list, replace it with its lone + * member; this avoids one useless level of AND-recursion. We only need + * to worry about this at top level, since eval_const_expressions should + * have gotten rid of any trivial ANDs or ORs below that. + */ + if (list_length(predicate_list) == 1) + p = (Node *) linitial(predicate_list); + else + p = (Node *) predicate_list; + if (list_length(restrictinfo_list) == 1) + r = (Node *) linitial(restrictinfo_list); + else + r = (Node *) restrictinfo_list; + + /* And away we go ... */ + return predicate_implied_by_recurse(r, p); } /* @@ -129,6 +158,14 @@ predicate_implied_by(List *predicate_list, List *restrictinfo_list) * This is NOT the same as !(predicate_implied_by), though it is similar * in the technique and structure of the code. * + * An important fine point is that truth of the clauses must imply that + * the predicate returns FALSE, not that it does not return TRUE. This + * is normally used to try to refute CHECK constraints, and the only + * thing we can assume about a CHECK constraint is that it didn't return + * FALSE --- a NULL result isn't a violation per the SQL spec. (Someday + * perhaps this code should be extended to support both "strong" and + * "weak" refutation, but for now we only need "strong".) + * * The top-level List structure of each list corresponds to an AND list. * We assume that eval_const_expressions() has been applied and so there * are no un-flattened ANDs or ORs (e.g., no AND immediately within an AND, @@ -144,14 +181,31 @@ predicate_implied_by(List *predicate_list, List *restrictinfo_list) bool predicate_refuted_by(List *predicate_list, List *restrictinfo_list) { + Node *p, + *r; + if (predicate_list == NIL) return false; /* no predicate: no refutation is possible */ if (restrictinfo_list == NIL) return false; /* no restriction: refutation must fail */ - /* Otherwise, away we go ... */ - return predicate_refuted_by_recurse((Node *) restrictinfo_list, - (Node *) predicate_list); + /* + * If either input is a single-element list, replace it with its lone + * member; this avoids one useless level of AND-recursion. We only need + * to worry about this at top level, since eval_const_expressions should + * have gotten rid of any trivial ANDs or ORs below that. + */ + if (list_length(predicate_list) == 1) + p = (Node *) linitial(predicate_list); + else + p = (Node *) predicate_list; + if (list_length(restrictinfo_list) == 1) + r = (Node *) linitial(restrictinfo_list); + else + r = (Node *) restrictinfo_list; + + /* And away we go ... */ + return predicate_refuted_by_recurse(r, p); } /*---------- @@ -408,10 +462,13 @@ predicate_implied_by_recurse(Node *clause, Node *predicate) * * In addition, if the predicate is a NOT-clause then we can use * A R=> NOT B if: A => B - * while if the restriction clause is a NOT-clause then we can use - * NOT A R=> B if: B => A * This works for several different SQL constructs that assert the non-truth * of their argument, ie NOT, IS FALSE, IS NOT TRUE, IS UNKNOWN. + * Unfortunately we *cannot* use + * NOT A R=> B if: B => A + * because this type of reasoning fails to prove that B doesn't yield NULL. + * We can however make the more limited deduction that + * NOT A R=> A * * Other comments are as for predicate_implied_by_recurse(). *---------- @@ -596,12 +653,18 @@ predicate_refuted_by_recurse(Node *clause, Node *predicate) case CLASS_ATOM: /* - * If A is a NOT-clause, A R=> B if B => A's arg + * If A is a strong NOT-clause, A R=> B if B equals A's arg + * + * We cannot make the stronger conclusion that B is refuted if B + * implies A's arg; that would only prove that B is not-TRUE, not + * that it's not NULL either. Hence use equal() rather than + * predicate_implied_by_recurse(). We could do the latter if we + * ever had a need for the weak form of refutation. */ - not_arg = extract_not_arg(clause); - if (not_arg && - predicate_implied_by_recurse(predicate, not_arg)) + not_arg = extract_strong_not_arg(clause); + if (not_arg && equal(predicate, not_arg)) return true; + switch (pclass) { case CLASS_AND: @@ -670,6 +733,13 @@ predicate_refuted_by_recurse(Node *clause, Node *predicate) * * If the expression is classified as AND- or OR-type, then *info is filled * in with the functions needed to iterate over its components. + * + * This function also implements enforcement of MAX_SAOP_ARRAY_SIZE: if a + * ScalarArrayOpExpr's array has too many elements, we just classify it as an + * atom. (This will result in its being passed as-is to the simple_clause + * functions, which will fail to prove anything about it.) Note that we + * cannot just stop after considering MAX_SAOP_ARRAY_SIZE elements; in general + * that would result in wrong proofs, rather than failing to prove anything. */ static PredClass predicate_classify(Node *clause, PredIterInfo info) @@ -721,13 +791,22 @@ predicate_classify(Node *clause, PredIterInfo info) if (arraynode && IsA(arraynode, Const) && !((Const *) arraynode)->constisnull) { - info->startup_fn = arrayconst_startup_fn; - info->next_fn = arrayconst_next_fn; - info->cleanup_fn = arrayconst_cleanup_fn; - return saop->useOr ? CLASS_OR : CLASS_AND; + ArrayType *arrayval; + int nelems; + + arrayval = DatumGetArrayTypeP(((Const *) arraynode)->constvalue); + nelems = ArrayGetNItems(ARR_NDIM(arrayval), ARR_DIMS(arrayval)); + if (nelems <= MAX_SAOP_ARRAY_SIZE) + { + info->startup_fn = arrayconst_startup_fn; + info->next_fn = arrayconst_next_fn; + info->cleanup_fn = arrayconst_cleanup_fn; + return saop->useOr ? CLASS_OR : CLASS_AND; + } } - if (arraynode && IsA(arraynode, ArrayExpr) && - !((ArrayExpr *) arraynode)->multidims) + else if (arraynode && IsA(arraynode, ArrayExpr) && + !((ArrayExpr *) arraynode)->multidims && + list_length(((ArrayExpr *) arraynode)->elements) <= MAX_SAOP_ARRAY_SIZE) { info->startup_fn = arrayexpr_startup_fn; info->next_fn = arrayexpr_next_fn; @@ -825,12 +904,15 @@ arrayconst_startup_fn(Node *clause, PredIterInfo info) state->opexpr.opfuncid = saop->opfuncid; state->opexpr.opresulttype = BOOLOID; state->opexpr.opretset = false; + state->opexpr.opcollid = InvalidOid; + state->opexpr.inputcollid = saop->inputcollid; state->opexpr.args = list_copy(saop->args); /* Set up a dummy Const node to hold the per-element values */ state->constexpr.xpr.type = T_Const; state->constexpr.consttype = ARR_ELEMTYPE(arrayval); state->constexpr.consttypmod = -1; + state->constexpr.constcollid = arrayconst->constcollid; state->constexpr.constlen = elmlen; state->constexpr.constbyval = elmbyval; lsecond(state->opexpr.args) = &state->constexpr; @@ -890,6 +972,8 @@ arrayexpr_startup_fn(Node *clause, PredIterInfo info) state->opexpr.opfuncid = saop->opfuncid; state->opexpr.opresulttype = BOOLOID; state->opexpr.opretset = false; + state->opexpr.opcollid = InvalidOid; + state->opexpr.inputcollid = saop->inputcollid; state->opexpr.args = list_copy(saop->args); /* Initialize iteration variable to first member of ArrayExpr */ @@ -948,6 +1032,9 @@ arrayexpr_cleanup_fn(PredIterInfo info) static bool predicate_implied_by_simple_clause(Expr *predicate, Node *clause) { + /* Allow interrupting long proof attempts */ + CHECK_FOR_INTERRUPTS(); + /* First try the equal() test */ if (equal((Node *) predicate, clause)) return true; @@ -959,7 +1046,7 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause) Expr *nonnullarg = ((NullTest *) predicate)->arg; /* row IS NOT NULL does not act in the simple way we have in mind */ - if (!type_is_rowtype(exprType((Node *) nonnullarg))) + if (!((NullTest *) predicate)->argisrow) { if (is_opclause(clause) && list_member_strip(((OpExpr *) clause)->args, nonnullarg) && @@ -990,9 +1077,11 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause) * When the predicate is of the form "foo IS NULL", we can conclude that * the predicate is refuted if the clause is a strict operator or function * that has "foo" as an input (see notes for implication case), or if the - * clause is "foo IS NOT NULL". Conversely a clause "foo IS NULL" refutes - * predicates of those types. (The motivation for covering these cases is - * to support using IS NULL/IS NOT NULL as partition-defining constraints.) + * clause is "foo IS NOT NULL". A clause "foo IS NULL" refutes a predicate + * "foo IS NOT NULL", but unfortunately does not refute strict predicates, + * because we are looking for strong refutation. (The motivation for covering + * these cases is to support using IS NULL/IS NOT NULL as partition-defining + * constraints.) * * Finally, we may be able to deduce something using knowledge about btree * operator families; this is encapsulated in btree_predicate_proof(). @@ -1001,6 +1090,9 @@ predicate_implied_by_simple_clause(Expr *predicate, Node *clause) static bool predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) { + /* Allow interrupting long proof attempts */ + CHECK_FOR_INTERRUPTS(); + /* A simple clause can't refute itself */ /* Worth checking because of relation_excluded_by_constraints() */ if ((Node *) predicate == clause) @@ -1010,8 +1102,29 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) if (predicate && IsA(predicate, NullTest) && ((NullTest *) predicate)->nulltesttype == IS_NULL) { - if (is_null_contradicts((NullTest *) predicate, clause)) + Expr *isnullarg = ((NullTest *) predicate)->arg; + + /* row IS NULL does not act in the simple way we have in mind */ + if (((NullTest *) predicate)->argisrow) + return false; + + /* Any strict op/func on foo refutes foo IS NULL */ + if (is_opclause(clause) && + list_member_strip(((OpExpr *) clause)->args, isnullarg) && + op_strict(((OpExpr *) clause)->opno)) return true; + if (is_funcclause(clause) && + list_member_strip(((FuncExpr *) clause)->args, isnullarg) && + func_strict(((FuncExpr *) clause)->funcid)) + return true; + + /* foo IS NOT NULL refutes foo IS NULL */ + if (clause && IsA(clause, NullTest) && + ((NullTest *) clause)->nulltesttype == IS_NOT_NULL && + !((NullTest *) clause)->argisrow && + equal(((NullTest *) clause)->arg, isnullarg)) + return true; + return false; /* we can't succeed below... */ } @@ -1019,8 +1132,19 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) if (clause && IsA(clause, NullTest) && ((NullTest *) clause)->nulltesttype == IS_NULL) { - if (is_null_contradicts((NullTest *) clause, (Node *) predicate)) + Expr *isnullarg = ((NullTest *) clause)->arg; + + /* row IS NULL does not act in the simple way we have in mind */ + if (((NullTest *) clause)->argisrow) + return false; + + /* foo IS NULL refutes foo IS NOT NULL */ + if (predicate && IsA(predicate, NullTest) && + ((NullTest *) predicate)->nulltesttype == IS_NOT_NULL && + !((NullTest *) predicate)->argisrow && + equal(((NullTest *) predicate)->arg, isnullarg)) return true; + return false; /* we can't succeed below... */ } @@ -1030,45 +1154,39 @@ predicate_refuted_by_simple_clause(Expr *predicate, Node *clause) /* - * Check whether a "foo IS NULL" test contradicts clause. (We say - * "contradicts" rather than "refutes" because the refutation goes - * both ways.) + * If clause asserts the non-truth of a subclause, return that subclause; + * otherwise return NULL. */ -static bool -is_null_contradicts(NullTest *ntest, Node *clause) +static Node * +extract_not_arg(Node *clause) { - Expr *isnullarg = ntest->arg; - - /* row IS NULL does not act in the simple way we have in mind */ - if (type_is_rowtype(exprType((Node *) isnullarg))) - return false; - - /* foo IS NULL contradicts any strict op/func on foo */ - if (is_opclause(clause) && - list_member_strip(((OpExpr *) clause)->args, isnullarg) && - op_strict(((OpExpr *) clause)->opno)) - return true; - if (is_funcclause(clause) && - list_member_strip(((FuncExpr *) clause)->args, isnullarg) && - func_strict(((FuncExpr *) clause)->funcid)) - return true; + if (clause == NULL) + return NULL; + if (IsA(clause, BoolExpr)) + { + BoolExpr *bexpr = (BoolExpr *) clause; - /* foo IS NULL contradicts foo IS NOT NULL */ - if (clause && IsA(clause, NullTest) && - ((NullTest *) clause)->nulltesttype == IS_NOT_NULL && - equal(((NullTest *) clause)->arg, isnullarg)) - return true; + if (bexpr->boolop == NOT_EXPR) + return (Node *) linitial(bexpr->args); + } + else if (IsA(clause, BooleanTest)) + { + BooleanTest *btest = (BooleanTest *) clause; - return false; + if (btest->booltesttype == IS_NOT_TRUE || + btest->booltesttype == IS_FALSE || + btest->booltesttype == IS_UNKNOWN) + return (Node *) btest->arg; + } + return NULL; } - /* - * If clause asserts the non-truth of a subclause, return that subclause; + * If clause asserts the falsity of a subclause, return that subclause; * otherwise return NULL. */ static Node * -extract_not_arg(Node *clause) +extract_strong_not_arg(Node *clause) { if (clause == NULL) return NULL; @@ -1083,9 +1201,7 @@ extract_not_arg(Node *clause) { BooleanTest *btest = (BooleanTest *) clause; - if (btest->booltesttype == IS_NOT_TRUE || - btest->booltesttype == IS_FALSE || - btest->booltesttype == IS_UNKNOWN) + if (btest->booltesttype == IS_FALSE) return (Node *) btest->arg; } return NULL; @@ -1131,6 +1247,7 @@ list_member_strip(List *list, Expr *datum) * and in addition we use (6) to represent <>. <> is not a btree-indexable * operator, but we assume here that if an equality operator of a btree * opfamily has a negator operator, the negator behaves as <> for the opfamily. + * (This convention is also known to get_op_btree_interpretation().) * * The interpretation of: * @@ -1167,7 +1284,7 @@ list_member_strip(List *list, Expr *datum) #define BTEQ BTEqualStrategyNumber #define BTGE BTGreaterEqualStrategyNumber #define BTGT BTGreaterStrategyNumber -#define BTNE 6 +#define BTNE ROWCOMPARE_NE static const StrategyNumber BT_implic_table[6][6] = { /* @@ -1230,25 +1347,16 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) Const *pred_const, *clause_const; bool pred_var_on_left, - clause_var_on_left, - pred_op_negated; + clause_var_on_left; + Oid pred_collation, + clause_collation; Oid pred_op, clause_op, - pred_op_negator, - clause_op_negator, - test_op = InvalidOid; - Oid opfamily_id; - bool found = false; - StrategyNumber pred_strategy, - clause_strategy, - test_strategy; - Oid clause_righttype; + test_op; Expr *test_expr; ExprState *test_exprstate; Datum test_result; bool isNull; - CatCList *catlist; - int i; EState *estate; MemoryContext oldcontext; @@ -1317,6 +1425,14 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) if (!equal(pred_var, clause_var)) return false; + /* + * They'd better have the same collation, too. + */ + pred_collation = ((OpExpr *) predicate)->inputcollid; + clause_collation = ((OpExpr *) clause)->inputcollid; + if (pred_collation != clause_collation) + return false; + /* * Okay, get the operators in the two clauses we're comparing. Commute * them if needed so that we can assume the variables are on the left. @@ -1338,150 +1454,238 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) } /* - * Try to find a btree opfamily containing the needed operators. + * Lookup the comparison operator using the system catalogs and the + * operator implication tables. + */ + test_op = get_btree_test_op(pred_op, clause_op, refute_it); + + if (!OidIsValid(test_op)) + { + /* couldn't find a suitable comparison operator */ + return false; + } + + /* + * Evaluate the test. For this we need an EState. + */ + estate = CreateExecutorState(); + + /* We can use the estate's working context to avoid memory leaks. */ + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + /* Build expression tree */ + test_expr = make_opclause(test_op, + BOOLOID, + false, + (Expr *) pred_const, + (Expr *) clause_const, + InvalidOid, + pred_collation); + + /* Fill in opfuncids */ + fix_opfuncids((Node *) test_expr); + + /* Prepare it for execution */ + test_exprstate = ExecInitExpr(test_expr, NULL); + + /* And execute it. */ + test_result = ExecEvalExprSwitchContext(test_exprstate, + GetPerTupleExprContext(estate), + &isNull, NULL); + + /* Get back to outer memory context */ + MemoryContextSwitchTo(oldcontext); + + /* Release all the junk we just created */ + FreeExecutorState(estate); + + if (isNull) + { + /* Treat a null result as non-proof ... but it's a tad fishy ... */ + elog(DEBUG2, "null predicate test result"); + return false; + } + return DatumGetBool(test_result); +} + + +/* + * We use a lookaside table to cache the result of btree proof operator + * lookups, since the actual lookup is pretty expensive and doesn't change + * for any given pair of operators (at least as long as pg_amop doesn't + * change). A single hash entry stores both positive and negative results + * for a given pair of operators. + */ +typedef struct OprProofCacheKey +{ + Oid pred_op; /* predicate operator */ + Oid clause_op; /* clause operator */ +} OprProofCacheKey; + +typedef struct OprProofCacheEntry +{ + /* the hash lookup key MUST BE FIRST */ + OprProofCacheKey key; + + bool have_implic; /* do we know the implication result? */ + bool have_refute; /* do we know the refutation result? */ + Oid implic_test_op; /* OID of the operator, or 0 if none */ + Oid refute_test_op; /* OID of the operator, or 0 if none */ +} OprProofCacheEntry; + +static HTAB *OprProofCacheHash = NULL; + + +/* + * get_btree_test_op + * Identify the comparison operator needed for a btree-operator + * proof or refutation. + * + * Given the truth of a predicate "var pred_op const1", we are attempting to + * prove or refute a clause "var clause_op const2". The identities of the two + * operators are sufficient to determine the operator (if any) to compare + * const2 to const1 with. + * + * Returns the OID of the operator to use, or InvalidOid if no proof is + * possible. + */ +static Oid +get_btree_test_op(Oid pred_op, Oid clause_op, bool refute_it) +{ + OprProofCacheKey key; + OprProofCacheEntry *cache_entry; + bool cfound; + Oid test_op = InvalidOid; + bool found = false; + List *pred_op_infos, + *clause_op_infos; + ListCell *lcp, + *lcc; + + /* + * Find or make a cache entry for this pair of operators. + */ + if (OprProofCacheHash == NULL) + { + /* First time through: initialize the hash table */ + HASHCTL ctl; + + MemSet(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(OprProofCacheKey); + ctl.entrysize = sizeof(OprProofCacheEntry); + ctl.hash = tag_hash; + OprProofCacheHash = hash_create("Btree proof lookup cache", 256, + &ctl, HASH_ELEM | HASH_FUNCTION); + + /* Arrange to flush cache on pg_amop changes */ + CacheRegisterSyscacheCallback(AMOPOPID, + InvalidateOprProofCacheCallBack, + (Datum) 0); + } + + key.pred_op = pred_op; + key.clause_op = clause_op; + cache_entry = (OprProofCacheEntry *) hash_search(OprProofCacheHash, + (void *) &key, + HASH_ENTER, &cfound); + if (!cfound) + { + /* new cache entry, set it invalid */ + cache_entry->have_implic = false; + cache_entry->have_refute = false; + } + else + { + /* pre-existing cache entry, see if we know the answer */ + if (refute_it) + { + if (cache_entry->have_refute) + return cache_entry->refute_test_op; + } + else + { + if (cache_entry->have_implic) + return cache_entry->implic_test_op; + } + } + + /* + * Try to find a btree opfamily containing the given operators. * * We must find a btree opfamily that contains both operators, else the * implication can't be determined. Also, the opfamily must contain a - * suitable test operator taking the pred_const and clause_const - * datatypes. + * suitable test operator taking the operators' righthand datatypes. * * If there are multiple matching opfamilies, assume we can use any one to * determine the logical relationship of the two operators and the correct * corresponding test operator. This should work for any logically * consistent opfamilies. */ - catlist = SearchSysCacheList(AMOPOPID, 1, - ObjectIdGetDatum(pred_op), - 0, 0, 0); + clause_op_infos = get_op_btree_interpretation(clause_op); + if (clause_op_infos) + pred_op_infos = get_op_btree_interpretation(pred_op); + else /* no point in looking */ + pred_op_infos = NIL; - /* - * If we couldn't find any opfamily containing the pred_op, perhaps it is - * a <> operator. See if it has a negator that is in an opfamily. - */ - pred_op_negated = false; - if (catlist->n_members == 0) + foreach(lcp, pred_op_infos) { - pred_op_negator = get_negator(pred_op); - if (OidIsValid(pred_op_negator)) - { - pred_op_negated = true; - ReleaseSysCacheList(catlist); - catlist = SearchSysCacheList(AMOPOPID, 1, - ObjectIdGetDatum(pred_op_negator), - 0, 0, 0); - } - } + OpBtreeInterpretation *pred_op_info = lfirst(lcp); + Oid opfamily_id = pred_op_info->opfamily_id; - /* Also may need the clause_op's negator */ - clause_op_negator = get_negator(clause_op); + foreach(lcc, clause_op_infos) + { + OpBtreeInterpretation *clause_op_info = lfirst(lcc); + StrategyNumber pred_strategy, + clause_strategy, + test_strategy; - /* Now search the opfamilies */ - for (i = 0; i < catlist->n_members; i++) - { - HeapTuple pred_tuple = &catlist->members[i]->tuple; - Form_pg_amop pred_form = (Form_pg_amop) GETSTRUCT(pred_tuple); - HeapTuple clause_tuple; + /* Must find them in same opfamily */ + if (opfamily_id != clause_op_info->opfamily_id) + continue; + /* Lefttypes should match */ + Assert(clause_op_info->oplefttype == pred_op_info->oplefttype); - /* Must be btree */ - if (pred_form->amopmethod != BTREE_AM_OID) - continue; + pred_strategy = pred_op_info->strategy; + clause_strategy = clause_op_info->strategy; - /* Get the predicate operator's btree strategy number */ - opfamily_id = pred_form->amopfamily; - pred_strategy = (StrategyNumber) pred_form->amopstrategy; - Assert(pred_strategy >= 1 && pred_strategy <= 5); + /* + * Look up the "test" strategy number in the implication table + */ + if (refute_it) + test_strategy = BT_refute_table[clause_strategy - 1][pred_strategy - 1]; + else + test_strategy = BT_implic_table[clause_strategy - 1][pred_strategy - 1]; - if (pred_op_negated) - { - /* Only consider negators that are = */ - if (pred_strategy != BTEqualStrategyNumber) + if (test_strategy == 0) + { + /* Can't determine implication using this interpretation */ continue; - pred_strategy = BTNE; - } + } - /* - * From the same opfamily, find a strategy number for the clause_op, - * if possible - */ - clause_tuple = SearchSysCache(AMOPOPID, - ObjectIdGetDatum(clause_op), - ObjectIdGetDatum(opfamily_id), - 0, 0); - if (HeapTupleIsValid(clause_tuple)) - { - Form_pg_amop clause_form = (Form_pg_amop) GETSTRUCT(clause_tuple); - - /* Get the restriction clause operator's strategy/datatype */ - clause_strategy = (StrategyNumber) clause_form->amopstrategy; - Assert(clause_strategy >= 1 && clause_strategy <= 5); - Assert(clause_form->amoplefttype == pred_form->amoplefttype); - clause_righttype = clause_form->amoprighttype; - ReleaseSysCache(clause_tuple); - } - else if (OidIsValid(clause_op_negator)) - { - clause_tuple = SearchSysCache(AMOPOPID, - ObjectIdGetDatum(clause_op_negator), - ObjectIdGetDatum(opfamily_id), - 0, 0); - if (HeapTupleIsValid(clause_tuple)) + /* + * See if opfamily has an operator for the test strategy and the + * datatypes. + */ + if (test_strategy == BTNE) { - Form_pg_amop clause_form = (Form_pg_amop) GETSTRUCT(clause_tuple); - - /* Get the restriction clause operator's strategy/datatype */ - clause_strategy = (StrategyNumber) clause_form->amopstrategy; - Assert(clause_strategy >= 1 && clause_strategy <= 5); - Assert(clause_form->amoplefttype == pred_form->amoplefttype); - clause_righttype = clause_form->amoprighttype; - ReleaseSysCache(clause_tuple); - - /* Only consider negators that are = */ - if (clause_strategy != BTEqualStrategyNumber) - continue; - clause_strategy = BTNE; + test_op = get_opfamily_member(opfamily_id, + pred_op_info->oprighttype, + clause_op_info->oprighttype, + BTEqualStrategyNumber); + if (OidIsValid(test_op)) + test_op = get_negator(test_op); } else - continue; - } - else - continue; - - /* - * Look up the "test" strategy number in the implication table - */ - if (refute_it) - test_strategy = BT_refute_table[clause_strategy - 1][pred_strategy - 1]; - else - test_strategy = BT_implic_table[clause_strategy - 1][pred_strategy - 1]; + { + test_op = get_opfamily_member(opfamily_id, + pred_op_info->oprighttype, + clause_op_info->oprighttype, + test_strategy); + } - if (test_strategy == 0) - { - /* Can't determine implication using this interpretation */ - continue; - } + if (!OidIsValid(test_op)) + continue; - /* - * See if opfamily has an operator for the test strategy and the - * datatypes. - */ - if (test_strategy == BTNE) - { - test_op = get_opfamily_member(opfamily_id, - pred_form->amoprighttype, - clause_righttype, - BTEqualStrategyNumber); - if (OidIsValid(test_op)) - test_op = get_negator(test_op); - } - else - { - test_op = get_opfamily_member(opfamily_id, - pred_form->amoprighttype, - clause_righttype, - test_strategy); - } - if (OidIsValid(test_op)) - { /* * Last check: test_op must be immutable. * @@ -1497,50 +1701,53 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) break; } } + + if (found) + break; } - ReleaseSysCacheList(catlist); + list_free_deep(pred_op_infos); + list_free_deep(clause_op_infos); if (!found) { - /* couldn't find a btree opfamily to interpret the operators */ - return false; + /* couldn't find a suitable comparison operator */ + test_op = InvalidOid; } - /* - * Evaluate the test. For this we need an EState. - */ - estate = CreateExecutorState(); - - /* We can use the estate's working context to avoid memory leaks. */ - oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + /* Cache the result, whether positive or negative */ + if (refute_it) + { + cache_entry->refute_test_op = test_op; + cache_entry->have_refute = true; + } + else + { + cache_entry->implic_test_op = test_op; + cache_entry->have_implic = true; + } - /* Build expression tree */ - test_expr = make_opclause(test_op, - BOOLOID, - false, - (Expr *) pred_const, - (Expr *) clause_const); + return test_op; +} - /* Prepare it for execution */ - test_exprstate = ExecPrepareExpr(test_expr, estate); - /* And execute it. */ - test_result = ExecEvalExprSwitchContext(test_exprstate, - GetPerTupleExprContext(estate), - &isNull, NULL); +/* + * Callback for pg_amop inval events + */ +static void +InvalidateOprProofCacheCallBack(Datum arg, int cacheid, uint32 hashvalue) +{ + HASH_SEQ_STATUS status; + OprProofCacheEntry *hentry; - /* Get back to outer memory context */ - MemoryContextSwitchTo(oldcontext); + Assert(OprProofCacheHash != NULL); - /* Release all the junk we just created */ - FreeExecutorState(estate); + /* Currently we just reset all entries; hard to be smarter ... */ + hash_seq_init(&status, OprProofCacheHash); - if (isNull) + while ((hentry = (OprProofCacheEntry *) hash_seq_search(&status)) != NULL) { - /* Treat a null result as non-proof ... but it's a tad fishy ... */ - elog(DEBUG2, "null predicate test result"); - return false; + hentry->have_implic = false; + hentry->have_refute = false; } - return DatumGetBool(test_result); }