]> granicus.if.org Git - postgresql/blobdiff - src/backend/optimizer/util/predtest.c
Update copyright for 2014
[postgresql] / src / backend / optimizer / util / predtest.c
index 53f8db6d224de7ff8002bc1b0a51d0efd1d76ee3..eadd2d5104a5abd80838799dfaab069d8db2a8a6 100644 (file)
@@ -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);
 }