mirror of
https://github.com/postgres/postgres.git
synced 2026-04-22 22:59:54 -04:00
Teach planner to transform "x IS [NOT] DISTINCT FROM NULL" to a NullTest
In the spirit of 8d19d0e13, this patch teaches the planner about the
principle that NullTest with !argisrow is fully equivalent to SQL's IS
[NOT] DISTINCT FROM NULL.
The parser already performs this transformation for literal NULLs.
However, a DistinctExpr expression with one input evaluating to NULL
during planning (e.g., via const-folding of "1 + NULL" or parameter
substitution in custom plans) currently remains as a DistinctExpr
node.
This patch closes the gap for const-folded NULLs. It specifically
targets the case where one input is a constant NULL and the other is a
nullable non-constant expression. (If the other input were otherwise,
the DistinctExpr node would have already been simplified to a constant
TRUE or FALSE.)
This transformation can be beneficial because NullTest is much more
amenable to optimization than DistinctExpr, since the planner knows a
good deal about the former and next to nothing about the latter.
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Tender Wang <tndrwang@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49BMAOWvkdSHxpUDnniqJcEcGq3_8dd_5wTR4xrQY8urA@mail.gmail.com
This commit is contained in:
parent
0aaf0de7fe
commit
f41ab51573
3 changed files with 118 additions and 2 deletions
|
|
@ -2837,6 +2837,30 @@ eval_const_expressions_mutator(Node *node,
|
|||
return eval_const_expressions_mutator(negate_clause((Node *) eqexpr),
|
||||
context);
|
||||
}
|
||||
else if (has_null_input)
|
||||
{
|
||||
/*
|
||||
* One input is a nullable non-constant expression, and
|
||||
* the other is an explicit NULL constant. We can
|
||||
* transform this to a NullTest with !argisrow, which is
|
||||
* much more amenable to optimization.
|
||||
*/
|
||||
|
||||
NullTest *nt = makeNode(NullTest);
|
||||
|
||||
nt->arg = (Expr *) (IsA(linitial(args), Const) ?
|
||||
lsecond(args) : linitial(args));
|
||||
nt->nulltesttype = IS_NOT_NULL;
|
||||
|
||||
/*
|
||||
* argisrow = false is correct whether or not arg is
|
||||
* composite
|
||||
*/
|
||||
nt->argisrow = false;
|
||||
nt->location = expr->location;
|
||||
|
||||
return eval_const_expressions_mutator((Node *) nt, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* The expression cannot be simplified any further, so build
|
||||
|
|
|
|||
|
|
@ -633,7 +633,7 @@ SELECT * FROM pred_tab WHERE (a::oid) IS NULL;
|
|||
|
||||
DROP TABLE pred_tab;
|
||||
--
|
||||
-- Test optimization of IS [NOT] DISTINCT FROM on non-nullable inputs
|
||||
-- Test optimization of IS [NOT] DISTINCT FROM
|
||||
--
|
||||
CREATE TYPE dist_row_t AS (a int, b int);
|
||||
CREATE TABLE dist_tab (id int, val_nn int NOT NULL, val_null int, row_nn dist_row_t NOT NULL);
|
||||
|
|
@ -766,6 +766,73 @@ SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.
|
|||
(3 rows)
|
||||
|
||||
RESET enable_nestloop;
|
||||
-- Ensure that the predicate is converted to IS NOT NULL
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE val_null IS DISTINCT FROM NULL::INT;
|
||||
QUERY PLAN
|
||||
----------------------------------
|
||||
Seq Scan on dist_tab
|
||||
Filter: (val_null IS NOT NULL)
|
||||
(2 rows)
|
||||
|
||||
SELECT id FROM dist_tab WHERE val_null IS DISTINCT FROM NULL::INT;
|
||||
id
|
||||
----
|
||||
1
|
||||
3
|
||||
(2 rows)
|
||||
|
||||
-- Ensure that the predicate is converted to IS NULL
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE val_null IS NOT DISTINCT FROM NULL::INT;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Seq Scan on dist_tab
|
||||
Filter: (val_null IS NULL)
|
||||
(2 rows)
|
||||
|
||||
SELECT id FROM dist_tab WHERE val_null IS NOT DISTINCT FROM NULL::INT;
|
||||
id
|
||||
----
|
||||
2
|
||||
(1 row)
|
||||
|
||||
-- Safety check for rowtypes
|
||||
-- The predicate is converted to IS NOT NULL, and get_rule_expr prints it as IS
|
||||
-- DISTINCT FROM because argisrow is false, indicating that we're applying a
|
||||
-- scalar test
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS DISTINCT FROM NULL::RECORD;
|
||||
QUERY PLAN
|
||||
-----------------------------------------------------------
|
||||
Seq Scan on dist_tab
|
||||
Filter: (ROW(val_null, val_null) IS DISTINCT FROM NULL)
|
||||
(2 rows)
|
||||
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS DISTINCT FROM NULL::RECORD;
|
||||
id
|
||||
----
|
||||
1
|
||||
2
|
||||
3
|
||||
(3 rows)
|
||||
|
||||
-- The predicate is converted to IS NULL, and get_rule_expr prints it as IS NOT
|
||||
-- DISTINCT FROM because argisrow is false, indicating that we're applying a
|
||||
-- scalar test
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS NOT DISTINCT FROM NULL::RECORD;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------
|
||||
Seq Scan on dist_tab
|
||||
Filter: (ROW(val_null, val_null) IS NOT DISTINCT FROM NULL)
|
||||
(2 rows)
|
||||
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS NOT DISTINCT FROM NULL::RECORD;
|
||||
id
|
||||
----
|
||||
(0 rows)
|
||||
|
||||
DROP TABLE dist_tab;
|
||||
DROP TYPE dist_row_t;
|
||||
--
|
||||
|
|
|
|||
|
|
@ -310,7 +310,7 @@ SELECT * FROM pred_tab WHERE (a::oid) IS NULL;
|
|||
DROP TABLE pred_tab;
|
||||
|
||||
--
|
||||
-- Test optimization of IS [NOT] DISTINCT FROM on non-nullable inputs
|
||||
-- Test optimization of IS [NOT] DISTINCT FROM
|
||||
--
|
||||
|
||||
CREATE TYPE dist_row_t AS (a int, b int);
|
||||
|
|
@ -367,6 +367,31 @@ SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.
|
|||
SELECT * FROM dist_tab t1 JOIN dist_tab t2 ON t1.val_nn IS NOT DISTINCT FROM t2.val_nn;
|
||||
RESET enable_nestloop;
|
||||
|
||||
-- Ensure that the predicate is converted to IS NOT NULL
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE val_null IS DISTINCT FROM NULL::INT;
|
||||
SELECT id FROM dist_tab WHERE val_null IS DISTINCT FROM NULL::INT;
|
||||
|
||||
-- Ensure that the predicate is converted to IS NULL
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE val_null IS NOT DISTINCT FROM NULL::INT;
|
||||
SELECT id FROM dist_tab WHERE val_null IS NOT DISTINCT FROM NULL::INT;
|
||||
|
||||
-- Safety check for rowtypes
|
||||
-- The predicate is converted to IS NOT NULL, and get_rule_expr prints it as IS
|
||||
-- DISTINCT FROM because argisrow is false, indicating that we're applying a
|
||||
-- scalar test
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS DISTINCT FROM NULL::RECORD;
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS DISTINCT FROM NULL::RECORD;
|
||||
|
||||
-- The predicate is converted to IS NULL, and get_rule_expr prints it as IS NOT
|
||||
-- DISTINCT FROM because argisrow is false, indicating that we're applying a
|
||||
-- scalar test
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS NOT DISTINCT FROM NULL::RECORD;
|
||||
SELECT id FROM dist_tab WHERE (val_null, val_null) IS NOT DISTINCT FROM NULL::RECORD;
|
||||
|
||||
DROP TABLE dist_tab;
|
||||
DROP TYPE dist_row_t;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue