From 51b62a455fdfa88a2fb15e5ab38cdd1e2de8aaf8 Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Mon, 5 Oct 2026 11:46:22 -0400 Subject: [PATCH v1 4/5] Don't trust NOT NULL on virtual generated columns of a parent A partition can define a virtual generated column with its own expression, and checks NOT NULL against that one. Reading through the parent uses the parent's expression, so the constraint must not be used to turn NOT IN into an anti join. --- src/backend/optimizer/util/clauses.c | 12 ++++++++++++ src/test/regress/expected/generated_virtual.out | 13 +++++++++++++ src/test/regress/sql/generated_virtual.sql | 9 +++++++++ 3 files changed, 34 insertions(+) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index eb9d77560a2..a0f4b89bbe6 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4946,6 +4946,18 @@ var_is_nonnullable(PlannerInfo *root, Var *var, NotNullSource source) attr = TupleDescCompactAttr(RelationGetDescr(rel), var->varattno - 1); result = (attr->attnullability == ATTNULLABLE_VALID); + + /* + * A partition can define a virtual generated column with its + * own expression, and enforces NOT NULL against that one. + * Reading through the parent uses the parent's expression, so + * the constraint proves nothing here. + */ + if (result && rte->relkind == RELKIND_PARTITIONED_TABLE && + TupleDescAttr(RelationGetDescr(rel), + var->varattno - 1)->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL) + result = false; + table_close(rel, NoLock); return result; diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 6ee029796f1..070386df4a3 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -812,6 +812,19 @@ ERROR: column "c" of relation "gtestnn_childdef" contains null values ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 13) + nullif(f2, 5)) VIRTUAL; -- error ERROR: column "c" of relation "gtestnn_child" contains null values ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok +-- NOT NULL is checked with each partition's own expression, so NOT IN must +-- not rely on it when reading through the parent +CREATE TABLE gtestnn_notin (id int NOT NULL); +INSERT INTO gtestnn_notin VALUES (1), (2), (3); +CREATE TABLE gtestnn_parent2 (a int, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL) PARTITION BY LIST (a); +CREATE TABLE gtestnn_child2 PARTITION OF gtestnn_parent2 (b GENERATED ALWAYS AS (a + 100) VIRTUAL) FOR VALUES IN (0, 1); +INSERT INTO gtestnn_parent2 VALUES (0), (1); +SELECT id FROM gtestnn_notin WHERE id NOT IN (SELECT b FROM gtestnn_parent2); -- no rows + id +---- +(0 rows) + +DROP TABLE gtestnn_notin, gtestnn_parent2; -- index constraints CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE); ERROR: unique constraints on virtual generated columns are not supported diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index e4ea63bb3a1..95c90c6124b 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -428,6 +428,15 @@ SELECT * FROM gtestnn_parent ORDER BY f1, f2, f3; ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 14) + nullif(f2, 10)) VIRTUAL; -- error ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 13) + nullif(f2, 5)) VIRTUAL; -- error ALTER TABLE gtestnn_parent ADD COLUMN c int NOT NULL GENERATED ALWAYS AS (nullif(f1, 4) + nullif(f2, 6)) VIRTUAL; -- ok +-- NOT NULL is checked with each partition's own expression, so NOT IN must +-- not rely on it when reading through the parent +CREATE TABLE gtestnn_notin (id int NOT NULL); +INSERT INTO gtestnn_notin VALUES (1), (2), (3); +CREATE TABLE gtestnn_parent2 (a int, b int GENERATED ALWAYS AS (nullif(a, 0)) VIRTUAL NOT NULL) PARTITION BY LIST (a); +CREATE TABLE gtestnn_child2 PARTITION OF gtestnn_parent2 (b GENERATED ALWAYS AS (a + 100) VIRTUAL) FOR VALUES IN (0, 1); +INSERT INTO gtestnn_parent2 VALUES (0), (1); +SELECT id FROM gtestnn_notin WHERE id NOT IN (SELECT b FROM gtestnn_parent2); -- no rows +DROP TABLE gtestnn_notin, gtestnn_parent2; -- index constraints CREATE TABLE gtest22a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a / 2) VIRTUAL UNIQUE); -- 2.37.1 (Apple Git-137.1)