From af1e2fe6dac300cddd8e80126dd1308c6f1e5ac9 Mon Sep 17 00:00:00 2001
From: Evdokimov Ilia <ilya.evdokimov@tantorlabs.com>
Date: Thu, 23 Jul 2026 13:26:07 +0300
Subject: [PATCH v2 3/9] Fix false NOT NULL marking of subquery-derived
 UniqueKey columns

convert_unique_keys_for_rel() marked a subquery output column as
NOT NULL solely because it was covered by a UniqueKey, without
checking whether the underlying key can still contain NULLs. A
unique index does not by itself rule out multiple NULLs, so this
produced wrong query results: relation_is_distinct_for() would then
treat the column as NULL-safe and elide a DISTINCT step that was
still needed.

    CREATE TABLE t (a int, b int);
    CREATE UNIQUE INDEX ON t(a);
    INSERT INTO t VALUES (NULL,1), (NULL,2), (1,3);
    SELECT DISTINCT a FROM (SELECT a, b FROM t OFFSET 0) s;

returned 3 rows instead of 2, silently dropping the DISTINCT.

Only mark the output column NOT NULL when the underlying expression
is a plain Var that is itself already known not-null in the
subquery's own planning context, per var_is_nullable().
---
 src/backend/optimizer/path/uniquekey.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/src/backend/optimizer/path/uniquekey.c b/src/backend/optimizer/path/uniquekey.c
index 42ba7e7c35c..3974c624799 100644
--- a/src/backend/optimizer/path/uniquekey.c
+++ b/src/backend/optimizer/path/uniquekey.c
@@ -1020,12 +1020,17 @@ convert_unique_keys_for_rel(PlannerInfo *root, RelOptInfo *rel,
 					if (matched >= 0)
 					{
 						/*
-						 * By generating the unique key the subquery
-						 * guarantees that the value of the output column
-						 * cannot be NULL.
+						 * A unique index does not by itself guarantee the
+						 * column is never NULL (Postgres unique indexes
+						 * permit multiple NULLs). Only mark the output
+						 * not-null when the underlying expression is a
+						 * plain Var that is itself already known not-null
+						 * in the subquery's own planning context.
 						 */
-						rel->notnullattrs = bms_add_member(rel->notnullattrs,
-														   target_var->varattno - FirstLowInvalidHeapAttributeNumber);
+						if (IsA(expr, Var) &&
+							!var_is_nullable(rel->subroot, (Var *) expr, input_rel))
+							rel->notnullattrs = bms_add_member(rel->notnullattrs,
+															   target_var->varattno - FirstLowInvalidHeapAttributeNumber);
 					}
 					else
 						matched = -1;
-- 
2.34.1

