1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-21 16:02:15 +03:00

Use a hash table to speed up NOT IN(values)

Similar to 50e17ad28, which allowed hash tables to be used for IN clauses
with a set of constants, here we add the same feature for NOT IN clauses.

NOT IN evaluates the same as: WHERE a <> v1 AND a <> v2 AND a <> v3.
Obviously, if we're using a hash table we must be exactly equivalent to
that and return the same result taking into account that either side of
the condition could contain a NULL.  This requires a little bit of
special handling to make work with the hash table version.

When processing NOT IN, the ScalarArrayOpExpr's operator will be the <>
operator.  To be able to build and lookup a hash table we must use the
<>'s negator operator.  The planner checks if that exists and is hashable
and sets the relevant fields in ScalarArrayOpExpr to instruct the executor
to use hashing.

Author: David Rowley, James Coleman
Reviewed-by: James Coleman, Zhihong Yu
Discussion: https://postgr.es/m/CAApHDvoF1mum_FRk6D621edcB6KSHBi2+GAgWmioj5AhOu2vwQ@mail.gmail.com
This commit is contained in:
David Rowley
2021-07-07 16:29:17 +12:00
parent d854720df6
commit 29f45e299e
16 changed files with 242 additions and 29 deletions

View File

@ -1205,19 +1205,34 @@ ExecInitExprRec(Expr *node, ExprState *state,
AclResult aclresult;
FmgrInfo *hash_finfo;
FunctionCallInfo hash_fcinfo;
Oid cmpfuncid;
/*
* Select the correct comparison function. When we do hashed
* NOT IN clauses, the opfuncid will be the inequality
* comparison function and negfuncid will be set to equality.
* We need to use the equality function for hash probes.
*/
if (OidIsValid(opexpr->negfuncid))
{
Assert(OidIsValid(opexpr->hashfuncid));
cmpfuncid = opexpr->negfuncid;
}
else
cmpfuncid = opexpr->opfuncid;
Assert(list_length(opexpr->args) == 2);
scalararg = (Expr *) linitial(opexpr->args);
arrayarg = (Expr *) lsecond(opexpr->args);
/* Check permission to call function */
aclresult = pg_proc_aclcheck(opexpr->opfuncid,
aclresult = pg_proc_aclcheck(cmpfuncid,
GetUserId(),
ACL_EXECUTE);
if (aclresult != ACLCHECK_OK)
aclcheck_error(aclresult, OBJECT_FUNCTION,
get_func_name(opexpr->opfuncid));
InvokeFunctionExecuteHook(opexpr->opfuncid);
get_func_name(cmpfuncid));
InvokeFunctionExecuteHook(cmpfuncid);
if (OidIsValid(opexpr->hashfuncid))
{
@ -1233,7 +1248,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
/* Set up the primary fmgr lookup information */
finfo = palloc0(sizeof(FmgrInfo));
fcinfo = palloc0(SizeForFunctionCallInfo(2));
fmgr_info(opexpr->opfuncid, finfo);
fmgr_info(cmpfuncid, finfo);
fmgr_info_set_expr((Node *) node, finfo);
InitFunctionCallInfoData(*fcinfo, finfo, 2,
opexpr->inputcollid, NULL, NULL);
@ -1274,6 +1289,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
/* And perform the operation */
scratch.opcode = EEOP_HASHED_SCALARARRAYOP;
scratch.d.hashedscalararrayop.inclause = opexpr->useOr;
scratch.d.hashedscalararrayop.finfo = finfo;
scratch.d.hashedscalararrayop.fcinfo_data = fcinfo;
scratch.d.hashedscalararrayop.fn_addr = finfo->fn_addr;

View File

@ -3493,6 +3493,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
{
ScalarArrayOpExprHashTable *elements_tab = op->d.hashedscalararrayop.elements_tab;
FunctionCallInfo fcinfo = op->d.hashedscalararrayop.fcinfo_data;
bool inclause = op->d.hashedscalararrayop.inclause;
bool strictfunc = op->d.hashedscalararrayop.finfo->fn_strict;
Datum scalar = fcinfo->args[0].value;
bool scalar_isnull = fcinfo->args[0].isnull;
@ -3596,7 +3597,12 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
/* Check the hash to see if we have a match. */
hashfound = NULL != saophash_lookup(elements_tab->hashtab, scalar);
result = BoolGetDatum(hashfound);
/* the result depends on if the clause is an IN or NOT IN clause */
if (inclause)
result = BoolGetDatum(hashfound); /* IN */
else
result = BoolGetDatum(!hashfound); /* NOT IN */
resultnull = false;
/*
@ -3605,7 +3611,7 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
* hashtable, but instead marked if we found any when building the table
* in has_nulls.
*/
if (!DatumGetBool(result) && op->d.hashedscalararrayop.has_nulls)
if (!hashfound && op->d.hashedscalararrayop.has_nulls)
{
if (strictfunc)
{
@ -3633,6 +3639,13 @@ ExecEvalHashedScalarArrayOp(ExprState *state, ExprEvalStep *op, ExprContext *eco
result = op->d.hashedscalararrayop.fn_addr(fcinfo);
resultnull = fcinfo->isnull;
/*
* Reverse the result for NOT IN clauses since the above function
* is the equality function and we need not-equals.
*/
if (!inclause)
result = !result;
}
}