summaryrefslogtreecommitdiff
path: root/src/backend/optimizer
diff options
context:
space:
mode:
authorDavid Rowley <drowley@postgresql.org>2021-07-07 16:29:17 +1200
committerDavid Rowley <drowley@postgresql.org>2021-07-07 16:29:17 +1200
commit29f45e299e7ffa1df0db44b8452228625479487f (patch)
tree948f286a1db23d164aeb20d4cb3d172ed986e758 /src/backend/optimizer
parentd854720df6df68cfe1432342e33c9e3020572a51 (diff)
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
Diffstat (limited to 'src/backend/optimizer')
-rw-r--r--src/backend/optimizer/plan/setrefs.c3
-rw-r--r--src/backend/optimizer/prep/prepqual.c1
-rw-r--r--src/backend/optimizer/util/clauses.c76
3 files changed, 64 insertions, 16 deletions
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b3..210c4b3b14c 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1687,6 +1687,9 @@ fix_expr_common(PlannerInfo *root, Node *node)
if (!OidIsValid(saop->hashfuncid))
record_plan_function_dependency(root, saop->hashfuncid);
+
+ if (!OidIsValid(saop->negfuncid))
+ record_plan_function_dependency(root, saop->negfuncid);
}
else if (IsA(node, Const))
{
diff --git a/src/backend/optimizer/prep/prepqual.c b/src/backend/optimizer/prep/prepqual.c
index 42c3e4dc046..8908a9652ed 100644
--- a/src/backend/optimizer/prep/prepqual.c
+++ b/src/backend/optimizer/prep/prepqual.c
@@ -128,6 +128,7 @@ negate_clause(Node *node)
newopexpr->opno = negator;
newopexpr->opfuncid = InvalidOid;
newopexpr->hashfuncid = InvalidOid;
+ newopexpr->negfuncid = InvalidOid;
newopexpr->useOr = !saopexpr->useOr;
newopexpr->inputcollid = saopexpr->inputcollid;
newopexpr->args = saopexpr->args;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 059fa702549..8506165d683 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2140,27 +2140,71 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context)
Oid lefthashfunc;
Oid righthashfunc;
- if (saop->useOr && arrayarg && IsA(arrayarg, Const) &&
- !((Const *) arrayarg)->constisnull &&
- get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
- lefthashfunc == righthashfunc)
+ if (arrayarg && IsA(arrayarg, Const) &&
+ !((Const *) arrayarg)->constisnull)
{
- Datum arrdatum = ((Const *) arrayarg)->constvalue;
- ArrayType *arr = (ArrayType *) DatumGetPointer(arrdatum);
- int nitems;
+ if (saop->useOr)
+ {
+ if (get_op_hash_functions(saop->opno, &lefthashfunc, &righthashfunc) &&
+ lefthashfunc == righthashfunc)
+ {
+ Datum arrdatum = ((Const *) arrayarg)->constvalue;
+ ArrayType *arr = (ArrayType *) DatumGetPointer(arrdatum);
+ int nitems;
- /*
- * Only fill in the hash functions if the array looks large enough
- * for it to be worth hashing instead of doing a linear search.
- */
- nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+ /*
+ * Only fill in the hash functions if the array looks
+ * large enough for it to be worth hashing instead of
+ * doing a linear search.
+ */
+ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
- if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+ if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+ {
+ /* Looks good. Fill in the hash functions */
+ saop->hashfuncid = lefthashfunc;
+ }
+ return true;
+ }
+ }
+ else /* !saop->useOr */
{
- /* Looks good. Fill in the hash functions */
- saop->hashfuncid = lefthashfunc;
+ Oid negator = get_negator(saop->opno);
+
+ /*
+ * Check if this is a NOT IN using an operator whose negator
+ * is hashable. If so we can still build a hash table and
+ * just ensure the lookup items are not in the hash table.
+ */
+ if (OidIsValid(negator) &&
+ get_op_hash_functions(negator, &lefthashfunc, &righthashfunc) &&
+ lefthashfunc == righthashfunc)
+ {
+ Datum arrdatum = ((Const *) arrayarg)->constvalue;
+ ArrayType *arr = (ArrayType *) DatumGetPointer(arrdatum);
+ int nitems;
+
+ /*
+ * Only fill in the hash functions if the array looks
+ * large enough for it to be worth hashing instead of
+ * doing a linear search.
+ */
+ nitems = ArrayGetNItems(ARR_NDIM(arr), ARR_DIMS(arr));
+
+ if (nitems >= MIN_ARRAY_SIZE_FOR_HASHED_SAOP)
+ {
+ /* Looks good. Fill in the hash functions */
+ saop->hashfuncid = lefthashfunc;
+
+ /*
+ * Also set the negfuncid. The executor will need
+ * that to perform hashtable lookups.
+ */
+ saop->negfuncid = get_opcode(negator);
+ }
+ return true;
+ }
}
- return true;
}
}