* 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;
}
-- 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 ]-----+--
-- 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
$$);
-- 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;
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;
-- 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)
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;