diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index 87dc6f56b57..7bf4e55c7a1 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -994,6 +994,7 @@ expand_virtual_generated_columns(PlannerInfo *root) { List *tlist = NIL; pullup_replace_vars_context rvcontext; + List *save_exclRelTlist = NIL; for (int i = 0; i < tupdesc->natts; i++) { @@ -1061,8 +1062,26 @@ expand_virtual_generated_columns(PlannerInfo *root) /* * Apply pullup variable replacement throughout the query tree. + * + * We intentionally do not touch the EXCLUDED pseudo-relation's + * targetlist here. Various places in the planner assume that it + * contains only Vars, and we want that to remain the case. More + * importantly, we don't want setrefs.c to turn any expanded + * EXCLUDED.virtual_column expressions in other parts of the query + * back into Vars referencing the original virtual column, which + * set_plan_refs() would do if exclRelTlist contained matching + * expressions. */ + if (parse->onConflict) + { + save_exclRelTlist = parse->onConflict->exclRelTlist; + parse->onConflict->exclRelTlist = NIL; + } + parse = (Query *) pullup_replace_vars((Node *) parse, &rvcontext); + + if (parse->onConflict) + parse->onConflict->exclRelTlist = save_exclRelTlist; } table_close(rel, NoLock); diff --git a/src/test/regress/expected/generated_virtual.out b/src/test/regress/expected/generated_virtual.out index 15760f4eae6..72da55a90bf 100644 --- a/src/test/regress/expected/generated_virtual.out +++ b/src/test/regress/expected/generated_virtual.out @@ -1692,3 +1692,51 @@ select * from gtest33 where b is null; reset constraint_exclusion; drop table gtest33; +-- Ensure that EXCLUDED. in INSERT ... ON CONFLICT +-- DO UPDATE is expanded to the generation expression, both for plain and +-- partitioned target relations. +create table gtest34 (id int primary key, a int, + c int generated always as (a * 10) virtual); +insert into gtest34 values (1, 5); +insert into gtest34 values (1, 7) + on conflict (id) do update set a = excluded.c returning *; + id | a | c +----+----+----- + 1 | 70 | 700 +(1 row) + +insert into gtest34 values (1, 2) + on conflict (id) do update set a = gtest34.c + excluded.c returning *; + id | a | c +----+-----+------ + 1 | 720 | 7200 +(1 row) + +insert into gtest34 values (1, 3) + on conflict (id) do update set a = 999 where excluded.c > 20 returning *; + id | a | c +----+-----+------ + 1 | 999 | 9990 +(1 row) + +drop table gtest34; +create table gtest34p (id int primary key, a int, + c int generated always as (a * 10) virtual) + partition by range (id); +create table gtest34p_1 partition of gtest34p for values from (1) to (100); +insert into gtest34p values (1, 5); +insert into gtest34p values (1, 7) + on conflict (id) do update set a = excluded.c returning *; + id | a | c +----+----+----- + 1 | 70 | 700 +(1 row) + +insert into gtest34p values (1, 2) + on conflict (id) do update set a = gtest34p.c + excluded.c returning *; + id | a | c +----+-----+------ + 1 | 720 | 7200 +(1 row) + +drop table gtest34p; diff --git a/src/test/regress/sql/generated_virtual.sql b/src/test/regress/sql/generated_virtual.sql index fdfc764e24a..01af4ae0e34 100644 --- a/src/test/regress/sql/generated_virtual.sql +++ b/src/test/regress/sql/generated_virtual.sql @@ -904,3 +904,28 @@ select * from gtest33 where b is null; reset constraint_exclusion; drop table gtest33; + +-- Ensure that EXCLUDED. in INSERT ... ON CONFLICT +-- DO UPDATE is expanded to the generation expression, both for plain and +-- partitioned target relations. +create table gtest34 (id int primary key, a int, + c int generated always as (a * 10) virtual); +insert into gtest34 values (1, 5); +insert into gtest34 values (1, 7) + on conflict (id) do update set a = excluded.c returning *; +insert into gtest34 values (1, 2) + on conflict (id) do update set a = gtest34.c + excluded.c returning *; +insert into gtest34 values (1, 3) + on conflict (id) do update set a = 999 where excluded.c > 20 returning *; +drop table gtest34; + +create table gtest34p (id int primary key, a int, + c int generated always as (a * 10) virtual) + partition by range (id); +create table gtest34p_1 partition of gtest34p for values from (1) to (100); +insert into gtest34p values (1, 5); +insert into gtest34p values (1, 7) + on conflict (id) do update set a = excluded.c returning *; +insert into gtest34p values (1, 2) + on conflict (id) do update set a = gtest34p.c + excluded.c returning *; +drop table gtest34p;