diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 13bb0ff8555..2fbd094cc9c 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4918,6 +4918,18 @@ var_is_nonnullable(PlannerInfo *root, Var *var, NotNullSource source) attr = TupleDescCompactAttr(RelationGetDescr(rel), var->varattno - 1); result = (attr->attnullability == ATTNULLABLE_VALID); + + /* + * We cannot trust a NOT NULL constraint on a virtual + * generated column of a partitioned table. Each partition + * enforces it against its own generation expression, which + * can differ from the parent's expression used by the query. + */ + 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..b2f8e16e63b 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -1886,3 +1886,24 @@ select * from gtest_cursor order by id; commit; drop table gtest_cursor; +-- NOT NULL on a partitioned table's virtual column does not prove non-nullness, +-- because a partition can have a different generation expression +CREATE TABLE gtestnn_parent2 (a int NOT NULL, 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 + 1) VIRTUAL) FOR VALUES IN (0); +INSERT INTO gtestnn_parent2 VALUES (0); +EXPLAIN (COSTS OFF) +SELECT a FROM gtestnn_parent2 WHERE a NOT IN (SELECT b FROM gtestnn_parent2); + QUERY PLAN +--------------------------------------------------------- + Seq Scan on gtestnn_child2 gtestnn_parent2 + Filter: (NOT (ANY (a = (hashed SubPlan any_1).col1))) + SubPlan any_1 + -> Seq Scan on gtestnn_child2 gtestnn_parent2_1 +(4 rows) + +SELECT a FROM gtestnn_parent2 WHERE a NOT IN (SELECT b FROM gtestnn_parent2); + a +--- +(0 rows) + +DROP TABLE gtestnn_parent2; diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index e4ea63bb3a1..c4c4f3d5ef1 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -1004,3 +1004,13 @@ select * from gtest_cursor order by id; commit; drop table gtest_cursor; + +-- NOT NULL on a partitioned table's virtual column does not prove non-nullness, +-- because a partition can have a different generation expression +CREATE TABLE gtestnn_parent2 (a int NOT NULL, 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 + 1) VIRTUAL) FOR VALUES IN (0); +INSERT INTO gtestnn_parent2 VALUES (0); +EXPLAIN (COSTS OFF) +SELECT a FROM gtestnn_parent2 WHERE a NOT IN (SELECT b FROM gtestnn_parent2); +SELECT a FROM gtestnn_parent2 WHERE a NOT IN (SELECT b FROM gtestnn_parent2); +DROP TABLE gtestnn_parent2;