mirror of
https://github.com/postgres/postgres.git
synced 2025-07-12 21:01:52 +03:00
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.
This commit is contained in:
@ -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 @@
|
|||||||
#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);
|
|||||||
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)
|
|||||||
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)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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)
|
|||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
/* 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 ... */
|
cache_entry->refute_test_op = test_op;
|
||||||
elog(DEBUG2, "null predicate test result");
|
cache_entry->have_refute = true;
|
||||||
return false;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cache_entry->implic_test_op = test_op;
|
||||||
|
cache_entry->have_implic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return test_op;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Callback for pg_amop inval events
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
InvalidateOprProofCacheCallBack(Datum arg, int cacheid, ItemPointer tuplePtr)
|
||||||
|
{
|
||||||
|
HASH_SEQ_STATUS status;
|
||||||
|
OprProofCacheEntry *hentry;
|
||||||
|
|
||||||
|
Assert(OprProofCacheHash != NULL);
|
||||||
|
|
||||||
|
/* Currently we just reset all entries; hard to be smarter ... */
|
||||||
|
hash_seq_init(&status, OprProofCacheHash);
|
||||||
|
|
||||||
|
while ((hentry = (OprProofCacheEntry *) hash_seq_search(&status)) != NULL)
|
||||||
|
{
|
||||||
|
hentry->have_implic = false;
|
||||||
|
hentry->have_refute = false;
|
||||||
}
|
}
|
||||||
return DatumGetBool(test_result);
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user