From a73557c3361f655d3394acd691ebb3095ff7349e Mon Sep 17 00:00:00 2001 From: shihao zhong Date: Sat, 10 Oct 2026 22:29:14 -0400 Subject: [PATCH] Use the simplified argument's typmod when reducing NULLIF Commit d8ce0050a relabels a NULLIF that is reduced to its first argument with the typmod the NULLIF had before its arguments were simplified. A NULLIF that is kept exposes the typmod of the simplified argument. So simplifying twice could differ from simplifying once, and ON CONFLICT inference and partial index matching could fail. Discussion: https://postgr.es/m/CAHGQGwHmL1M2S5vmoNAMm2aSP9uFnXEE9GQmvnbo3SyWcb9knQ@mail.gmail.com --- src/backend/optimizer/util/clauses.c | 5 +++-- src/test/regress/expected/insert_conflict.out | 18 ++++++++++++++++++ src/test/regress/sql/insert_conflict.sql | 13 +++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 0fb8732c25f..cc1ddfff173 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -3265,7 +3265,8 @@ eval_const_expressions_mutator(Node *node, /* * If either argument is NULL they can't be equal, so the * result is the first argument, relabeled if needed to expose - * the NULLIF's typmod and collation + * the NULLIF's collation. Use the typmod of the simplified + * first argument, which is what a NULLIF we keep exposes. */ foreach(arg, expr->args) { @@ -3274,7 +3275,7 @@ eval_const_expressions_mutator(Node *node, else if (((Const *) lfirst(arg))->constisnull) return applyRelabelType((Node *) linitial(expr->args), expr->opresulttype, - exprTypmod(node), + exprTypmod((Node *) expr), expr->opcollid, COERCE_IMPLICIT_CAST, -1, diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out index 5563bf24454..d81ceca99d3 100644 --- a/src/test/regress/expected/insert_conflict.out +++ b/src/test/regress/expected/insert_conflict.out @@ -244,6 +244,24 @@ explain (costs off) insert into insertconflict_typmod values('x', 'y') on confli drop table insertconflict_typmod; -- +-- An index expression with a NULLIF whose argument changes typmod when +-- simplified can be inferred +-- +create function insertconflict_f(numeric) returns numeric language sql immutable as 'select $1'; +create table insertconflict_nullif (a numeric(10) not null); +create unique index nullif_key on insertconflict_nullif(nullif(insertconflict_f(a), case when a is null then a end)); +explain (costs off) insert into insertconflict_nullif values(1) on conflict (nullif(insertconflict_f(a), case when a is null then a end)) do nothing; + QUERY PLAN +---------------------------------------- + Insert on insertconflict_nullif + Conflict Resolution: NOTHING + Conflict Arbiter Indexes: nullif_key + -> Result +(4 rows) + +drop table insertconflict_nullif; +drop function insertconflict_f; +-- -- Single key tests -- create unique index key_index on insertconflicttest(key); diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql index dfd2dacb836..6fd707119f3 100644 --- a/src/test/regress/sql/insert_conflict.sql +++ b/src/test/regress/sql/insert_conflict.sql @@ -106,6 +106,19 @@ explain (costs off) insert into insertconflict_typmod values('x', 'y') on confli drop table insertconflict_typmod; +-- +-- An index expression with a NULLIF whose argument changes typmod when +-- simplified can be inferred +-- +create function insertconflict_f(numeric) returns numeric language sql immutable as 'select $1'; +create table insertconflict_nullif (a numeric(10) not null); +create unique index nullif_key on insertconflict_nullif(nullif(insertconflict_f(a), case when a is null then a end)); + +explain (costs off) insert into insertconflict_nullif values(1) on conflict (nullif(insertconflict_f(a), case when a is null then a end)) do nothing; + +drop table insertconflict_nullif; +drop function insertconflict_f; + -- -- Single key tests -- -- 2.37.1 (Apple Git-137.1)