Commit e7d8bfb9 authored by Tom Lane's avatar Tom Lane

Arrange to cache the results of looking up a btree predicate proof comparison

operator.  The result depends only on the two input operators and the proof
direction (imply or refute), so it's easy to cache.  This provides a very
large savings in cases such as Sergey Konoplev's long NOT-IN-list example,
where predtest spends all its time repeatedly figuring out that the same pair
of operators cannot be used to prove anything.  (But of course the O(N^2)
behavior still catches up with you eventually.)  I'm not convinced it buys
a whole lot when constraint_exclusion isn't turned on, but it's not a lot
of added code so we might as well cache all the time.
parent fdf8d062
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* *
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.21 2008/11/12 23:08:37 tgl Exp $ * $PostgreSQL: pgsql/src/backend/optimizer/util/predtest.c,v 1.22 2008/11/13 00:20:45 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/predtest.h" #include "optimizer/predtest.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/inval.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/syscache.h" #include "utils/syscache.h"
...@@ -96,6 +97,8 @@ static Node *extract_not_arg(Node *clause); ...@@ -96,6 +97,8 @@ static Node *extract_not_arg(Node *clause);
static bool list_member_strip(List *list, Expr *datum); static bool list_member_strip(List *list, Expr *datum);
static bool btree_predicate_proof(Expr *predicate, Node *clause, static bool btree_predicate_proof(Expr *predicate, Node *clause,
bool refute_it); 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, ItemPointer tuplePtr);
/* /*
...@@ -1279,25 +1282,14 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) ...@@ -1279,25 +1282,14 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it)
Const *pred_const, Const *pred_const,
*clause_const; *clause_const;
bool pred_var_on_left, bool pred_var_on_left,
clause_var_on_left, clause_var_on_left;
pred_op_negated;
Oid pred_op, Oid pred_op,
clause_op, clause_op,
pred_op_negator, test_op;
clause_op_negator,
test_op = InvalidOid;
Oid opfamily_id;
bool found = false;
StrategyNumber pred_strategy,
clause_strategy,
test_strategy;
Oid clause_righttype;
Expr *test_expr; Expr *test_expr;
ExprState *test_exprstate; ExprState *test_exprstate;
Datum test_result; Datum test_result;
bool isNull; bool isNull;
CatCList *catlist;
int i;
EState *estate; EState *estate;
MemoryContext oldcontext; MemoryContext oldcontext;
...@@ -1387,12 +1379,171 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) ...@@ -1387,12 +1379,171 @@ 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);
/* Prepare it for execution */
test_exprstate = ExecPrepareExpr(test_expr, estate);
/* 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;
bool pred_op_negated;
Oid 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;
CatCList *catlist;
int i;
/*
* Find or make a cache entry for this pair of operators.
*/
if (OprProofCacheHash == NULL)
{
/* First time through: initialize the hash table */
HASHCTL ctl;
if (!CacheMemoryContext)
CreateCacheMemoryContext();
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 * We must find a btree opfamily that contains both operators, else the
* implication can't be determined. Also, the opfamily must contain a * implication can't be determined. Also, the opfamily must contain a
* suitable test operator taking the pred_const and clause_const * suitable test operator taking the operators' righthand datatypes.
* datatypes.
* *
* If there are multiple matching opfamilies, assume we can use any one to * If there are multiple matching opfamilies, assume we can use any one to
* determine the logical relationship of the two operators and the correct * determine the logical relationship of the two operators and the correct
...@@ -1552,44 +1703,43 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it) ...@@ -1552,44 +1703,43 @@ btree_predicate_proof(Expr *predicate, Node *clause, bool refute_it)
if (!found) if (!found)
{ {
/* couldn't find a btree opfamily to interpret the operators */ /* couldn't find a suitable comparison operator */
return false; test_op = InvalidOid;
} }
/* /* Cache the result, whether positive or negative */
* Evaluate the test. For this we need an EState. if (refute_it)
*/ {
estate = CreateExecutorState(); cache_entry->refute_test_op = test_op;
cache_entry->have_refute = true;
/* We can use the estate's working context to avoid memory leaks. */ }
oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); else
{
cache_entry->implic_test_op = test_op;
cache_entry->have_implic = true;
}
/* Build expression tree */ return test_op;
test_expr = make_opclause(test_op, }
BOOLOID,
false,
(Expr *) pred_const,
(Expr *) clause_const);
/* Prepare it for execution */
test_exprstate = ExecPrepareExpr(test_expr, estate);
/* And execute it. */ /*
test_result = ExecEvalExprSwitchContext(test_exprstate, * Callback for pg_amop inval events
GetPerTupleExprContext(estate), */
&isNull, NULL); static void
InvalidateOprProofCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
{
HASH_SEQ_STATUS status;
OprProofCacheEntry *hentry;
/* Get back to outer memory context */ Assert(OprProofCacheHash != NULL);
MemoryContextSwitchTo(oldcontext);
/* Release all the junk we just created */ /* Currently we just reset all entries; hard to be smarter ... */
FreeExecutorState(estate); 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 ... */ hentry->have_implic = false;
elog(DEBUG2, "null predicate test result"); hentry->have_refute = false;
return false;
} }
return DatumGetBool(test_result);
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment