From f5635c2786af2322e1e21006a13a2c19f68fa591 Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Sat, 10 Oct 2026 10:52:52 +0900 Subject: [PATCH] Fix expr_is_nonnullable() for IS DISTINCT FROM Commit c8d2f68cc taught expr_is_nonnullable() to treat a DistinctExpr as never returning NULL. That is wrong: when neither input is NULL, the result is NULL if the underlying equality operator returns NULL, which nothing forbids. This could lead to wrong query results. To fix, remove the DistinctExpr case, along with its test case. While at it, test the relkind before calling has_subclass() in var_is_nonnullable(), to avoid a useless syscache lookup for partitioned tables. Reported-by: Robert Haas Author: Richard Guo Discussion: https://postgr.es/m/CA+TgmoaLcsmc9weJr1yo1X1SwZJX1t4XXHj0SJzH8XztmN9Hdg@mail.gmail.com Backpatch-through: 19 --- src/backend/optimizer/util/clauses.c | 12 ++---------- src/test/regress/expected/predicate.out | 10 ---------- src/test/regress/sql/predicate.sql | 4 ---- 3 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 60e663c9cc8..95134bfeda3 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4950,8 +4950,8 @@ var_is_nonnullable(PlannerInfo *root, Var *var, NotNullSource source) * Note that we need to check if the relation actually has any * children, as we might not have done that yet. */ - if (rte->inh && has_subclass(rte->relid) && - rte->relkind != RELKIND_PARTITIONED_TABLE) + if (rte->relkind != RELKIND_PARTITIONED_TABLE && + rte->inh && has_subclass(rte->relid)) return false; /* We need not lock the relation since it was already locked */ @@ -5100,14 +5100,6 @@ expr_is_nonnullable(PlannerInfo *root, Expr *expr, NotNullSource source) */ 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: { /* diff --git a/src/test/regress/expected/predicate.out b/src/test/regress/expected/predicate.out index feae77cb840..22fe4308af3 100644 --- a/src/test/regress/expected/predicate.out +++ b/src/test/regress/expected/predicate.out @@ -611,16 +611,6 @@ SELECT * FROM pred_tab WHERE ((a > 1) IS TRUE) IS NULL; 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; diff --git a/src/test/regress/sql/predicate.sql b/src/test/regress/sql/predicate.sql index 0f92bb52435..9dd236efb56 100644 --- a/src/test/regress/sql/predicate.sql +++ b/src/test/regress/sql/predicate.sql @@ -299,10 +299,6 @@ SELECT * FROM pred_tab WHERE (b IS NULL) IS NULL; 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;