mirror of
https://github.com/postgres/postgres.git
synced 2025-12-31 00:03:29 -05:00
Teach expr_is_nonnullable() to handle more expression types
Currently, the function expr_is_nonnullable() checks only Const and
Var expressions to determine if an expression is non-nullable. This
patch extends the detection logic to handle more expression types.
This can enable several downstream optimizations, such as reducing
NullTest quals to constant truth values (e.g., "COALESCE(var, 1) IS
NULL" becomes FALSE) and converting "COUNT(expr)" to the more
efficient "COUNT(*)" when the expression is proven non-nullable.
This breaks a test case in test_predtest.sql, since we now simplify
"ARRAY[] IS NULL" to constant FALSE, preventing it from weakly
refuting a strict ScalarArrayOpExpr ("x = any(ARRAY[])"). To ensure
the refutation logic is still exercised as intended, wrap the array
argument in opaque_array().
Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: Tender Wang <tndrwang@gmail.com>
Reviewed-by: Dagfinn Ilmari Mannsåker <ilmari@ilmari.org>
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Reviewed-by: Matheus Alcantara <matheusssilv97@gmail.com>
Discussion: https://postgr.es/m/CAMbWs49UhPBjm+NRpxerjaeuFKyUZJ_AjM3NBcSYK2JgZ6VTEQ@mail.gmail.com
This commit is contained in:
parent
cb7b7ec7a1
commit
c8d2f68cc8
@ -4341,16 +4341,127 @@ var_is_nonnullable(PlannerInfo *root, Var *var, bool use_rel_info)
|
||||
* nullability information before RelOptInfos are generated. These should
|
||||
* pass 'use_rel_info' as false.
|
||||
*
|
||||
* For now, we only support Var and Const. Support for other node types may
|
||||
* be possible.
|
||||
* For now, we support only a limited set of expression types. Support for
|
||||
* additional node types can be added in the future.
|
||||
*/
|
||||
bool
|
||||
expr_is_nonnullable(PlannerInfo *root, Expr *expr, bool use_rel_info)
|
||||
{
|
||||
if (IsA(expr, Var) && root)
|
||||
return var_is_nonnullable(root, (Var *) expr, use_rel_info);
|
||||
if (IsA(expr, Const))
|
||||
return !castNode(Const, expr)->constisnull;
|
||||
/* since this function recurses, it could be driven to stack overflow */
|
||||
check_stack_depth();
|
||||
|
||||
switch (nodeTag(expr))
|
||||
{
|
||||
case T_Var:
|
||||
{
|
||||
if (root)
|
||||
return var_is_nonnullable(root, (Var *) expr, use_rel_info);
|
||||
}
|
||||
break;
|
||||
case T_Const:
|
||||
return !((Const *) expr)->constisnull;
|
||||
case T_CoalesceExpr:
|
||||
{
|
||||
/*
|
||||
* A CoalesceExpr returns NULL if and only if all its
|
||||
* arguments are NULL. Therefore, we can determine that a
|
||||
* CoalesceExpr cannot be NULL if at least one of its
|
||||
* arguments can be proven non-nullable.
|
||||
*/
|
||||
CoalesceExpr *coalesceexpr = (CoalesceExpr *) expr;
|
||||
|
||||
foreach_ptr(Expr, arg, coalesceexpr->args)
|
||||
{
|
||||
if (expr_is_nonnullable(root, arg, use_rel_info))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case T_MinMaxExpr:
|
||||
{
|
||||
/*
|
||||
* Like CoalesceExpr, a MinMaxExpr returns NULL only if all
|
||||
* its arguments evaluate to NULL.
|
||||
*/
|
||||
MinMaxExpr *minmaxexpr = (MinMaxExpr *) expr;
|
||||
|
||||
foreach_ptr(Expr, arg, minmaxexpr->args)
|
||||
{
|
||||
if (expr_is_nonnullable(root, arg, use_rel_info))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case T_CaseExpr:
|
||||
{
|
||||
/*
|
||||
* A CASE expression is non-nullable if all branch results are
|
||||
* non-nullable. We must also verify that the default result
|
||||
* (ELSE) exists and is non-nullable.
|
||||
*/
|
||||
CaseExpr *caseexpr = (CaseExpr *) expr;
|
||||
|
||||
/* The default result must be present and non-nullable */
|
||||
if (caseexpr->defresult == NULL ||
|
||||
!expr_is_nonnullable(root, caseexpr->defresult, use_rel_info))
|
||||
return false;
|
||||
|
||||
/* All branch results must be non-nullable */
|
||||
foreach_ptr(CaseWhen, casewhen, caseexpr->args)
|
||||
{
|
||||
if (!expr_is_nonnullable(root, casewhen->result, use_rel_info))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case T_ArrayExpr:
|
||||
{
|
||||
/*
|
||||
* An ARRAY[] expression always returns a valid Array object,
|
||||
* even if it is empty (ARRAY[]) or contains NULLs
|
||||
* (ARRAY[NULL]). It never evaluates to a SQL NULL.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
case T_NullTest:
|
||||
{
|
||||
/*
|
||||
* An IS NULL / IS NOT NULL expression always returns a
|
||||
* boolean value. It never returns SQL NULL.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
case T_BooleanTest:
|
||||
{
|
||||
/*
|
||||
* A BooleanTest expression always evaluates to a boolean
|
||||
* value. It never returns SQL NULL.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
case T_DistinctExpr:
|
||||
{
|
||||
/*
|
||||
* IS DISTINCT FROM never returns NULL, effectively acting as
|
||||
* though NULL were a normal data value.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
case T_RelabelType:
|
||||
{
|
||||
/*
|
||||
* RelabelType does not change the nullability of the data.
|
||||
* The result is non-nullable if and only if the argument is
|
||||
* non-nullable.
|
||||
*/
|
||||
return expr_is_nonnullable(root, ((RelabelType *) expr)->arg,
|
||||
use_rel_info);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1066,7 +1066,7 @@ w_r_holds | t
|
||||
|
||||
-- as does nullness of the array
|
||||
select * from test_predtest($$
|
||||
select x = any(opaque_array(array[y])), array[y] is null
|
||||
select x = any(opaque_array(array[y])), opaque_array(array[y]) is null
|
||||
from integers
|
||||
$$);
|
||||
-[ RECORD 1 ]-----+--
|
||||
|
||||
@ -431,7 +431,7 @@ $$);
|
||||
|
||||
-- as does nullness of the array
|
||||
select * from test_predtest($$
|
||||
select x = any(opaque_array(array[y])), array[y] is null
|
||||
select x = any(opaque_array(array[y])), opaque_array(array[y]) is null
|
||||
from integers
|
||||
$$);
|
||||
|
||||
|
||||
@ -488,7 +488,7 @@ DROP TABLE pred_tab;
|
||||
-- Test that COALESCE expressions in predicates are simplified using
|
||||
-- non-nullable arguments.
|
||||
--
|
||||
CREATE TABLE pred_tab (a int NOT NULL, b int);
|
||||
CREATE TABLE pred_tab (a int NOT NULL, b int, c int);
|
||||
-- Ensure that constant NULL arguments are dropped
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE COALESCE(NULL, b, NULL, a) > 1;
|
||||
@ -516,4 +516,119 @@ SELECT * FROM pred_tab WHERE COALESCE(a, b) > 1;
|
||||
Filter: (a > 1)
|
||||
(2 rows)
|
||||
|
||||
--
|
||||
-- Test detection of non-nullable expressions in predicates
|
||||
--
|
||||
-- CoalesceExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE COALESCE(b, a) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE COALESCE(b, c) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------------
|
||||
Seq Scan on pred_tab
|
||||
Filter: (COALESCE(b, c) IS NULL)
|
||||
(2 rows)
|
||||
|
||||
-- MinMaxExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE GREATEST(b, a) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE GREATEST(b, c) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------------
|
||||
Seq Scan on pred_tab
|
||||
Filter: (GREATEST(b, c) IS NULL)
|
||||
(2 rows)
|
||||
|
||||
-- CaseExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (CASE WHEN c > 0 THEN a ELSE a END) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (CASE WHEN c > 0 THEN b ELSE a END) IS NULL;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------
|
||||
Seq Scan on pred_tab
|
||||
Filter: (CASE WHEN (c > 0) THEN b ELSE a END IS NULL)
|
||||
(2 rows)
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (CASE WHEN c > 0 THEN a END) IS NULL;
|
||||
QUERY PLAN
|
||||
---------------------------------------------------------------------
|
||||
Seq Scan on pred_tab
|
||||
Filter: (CASE WHEN (c > 0) THEN a ELSE NULL::integer END IS NULL)
|
||||
(2 rows)
|
||||
|
||||
-- ArrayExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE ARRAY[b] IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
-- NullTest
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (b IS NULL) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
-- BooleanTest
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE ((a > 1) IS TRUE) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
-- DistinctExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (a IS DISTINCT FROM b) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
-- RelabelType
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (a::oid) IS NULL;
|
||||
QUERY PLAN
|
||||
------------------------------
|
||||
Result
|
||||
Replaces: Scan on pred_tab
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
DROP TABLE pred_tab;
|
||||
|
||||
@ -245,7 +245,7 @@ DROP TABLE pred_tab;
|
||||
-- Test that COALESCE expressions in predicates are simplified using
|
||||
-- non-nullable arguments.
|
||||
--
|
||||
CREATE TABLE pred_tab (a int NOT NULL, b int);
|
||||
CREATE TABLE pred_tab (a int NOT NULL, b int, c int);
|
||||
|
||||
-- Ensure that constant NULL arguments are dropped
|
||||
EXPLAIN (COSTS OFF)
|
||||
@ -259,4 +259,52 @@ SELECT * FROM pred_tab WHERE COALESCE(b, a, b*a) > 1;
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE COALESCE(a, b) > 1;
|
||||
|
||||
--
|
||||
-- Test detection of non-nullable expressions in predicates
|
||||
--
|
||||
|
||||
-- CoalesceExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE COALESCE(b, a) IS NULL;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE COALESCE(b, c) IS NULL;
|
||||
|
||||
-- MinMaxExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE GREATEST(b, a) IS NULL;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE GREATEST(b, c) IS NULL;
|
||||
|
||||
-- CaseExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (CASE WHEN c > 0 THEN a ELSE a END) IS NULL;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (CASE WHEN c > 0 THEN b ELSE a END) IS NULL;
|
||||
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (CASE WHEN c > 0 THEN a END) IS NULL;
|
||||
|
||||
-- ArrayExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE ARRAY[b] IS NULL;
|
||||
|
||||
-- NullTest
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (b IS NULL) IS NULL;
|
||||
|
||||
-- BooleanTest
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE ((a > 1) IS TRUE) IS NULL;
|
||||
|
||||
-- DistinctExpr
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (a IS DISTINCT FROM b) IS NULL;
|
||||
|
||||
-- RelabelType
|
||||
EXPLAIN (COSTS OFF)
|
||||
SELECT * FROM pred_tab WHERE (a::oid) IS NULL;
|
||||
|
||||
DROP TABLE pred_tab;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user