* 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
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);
/*
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);
}
/*
* 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,
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);
}
/*----------
*
* 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().
*----------
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:
*
* 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)
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;
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;
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 */
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;
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) &&
* 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().
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)
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... */
}
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... */
}
/*
- * 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;
{
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;
* 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:
*
#define BTEQ BTEqualStrategyNumber
#define BTGE BTGreaterEqualStrategyNumber
#define BTGT BTGreaterStrategyNumber
-#define BTNE 6
+#define BTNE ROWCOMPARE_NE
static const StrategyNumber BT_implic_table[6][6] = {
/*
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;
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.
}
/*
- * 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.
*
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);
}