From f7cca7a65f869e988cd21d9fe52e8fd96198ada8 Mon Sep 17 00:00:00 2001
From: jcoleman <jtc331@gmail.com>
Date: Sat, 20 Jan 2024 16:40:45 -0500
Subject: [PATCH v1] Helpful hint for qualified target column in UPDATE

Target columns in UPDATE ... SET must not be qualified with the target
table. The error message when a query contains this mistake is
unnecessarily confusing: "column "foo" of relation "foo" does not
exist". In the case where the column is qualified and the qualification
matches the table name (or alias) we can help the user by reminding them
of the syntax restriction for target columns.
---
 src/backend/parser/analyze.c                  | 25 ++++++++++++----
 src/test/regress/expected/insert_conflict.out | 29 +++++++++++++++++++
 src/test/regress/sql/insert_conflict.sql      | 10 +++++++
 3 files changed, 58 insertions(+), 6 deletions(-)

diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 06fc8ce98b..a93bf9bdb2 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2513,12 +2513,25 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		attrno = attnameAttNum(pstate->p_target_relation,
 							   origTarget->name, true);
 		if (attrno == InvalidAttrNumber)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" of relation \"%s\" does not exist",
-							origTarget->name,
-							RelationGetRelationName(pstate->p_target_relation)),
-					 parser_errposition(pstate, origTarget->location)));
+		{
+			char *relname = RelationGetRelationName(pstate->p_target_relation);
+			char *aliasname = pstate->p_target_nsitem->p_names->aliasname;
+
+			if (origTarget->indirection != NIL &&
+					strcmp(origTarget->name, aliasname) == 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" of relation \"%s\" does not exist",
+								origTarget->name, relname),
+						 errhint("target columns cannot be qualified with the target relation name"),
+						 parser_errposition(pstate, origTarget->location)));
+			else
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_COLUMN),
+						 errmsg("column \"%s\" of relation \"%s\" does not exist",
+								origTarget->name, relname),
+						 parser_errposition(pstate, origTarget->location)));
+		}
 
 		updateTargetListEntry(pstate, tle, origTarget->name,
 							  attrno,
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index 9e9e3bd00c..a22eae4da7 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -272,6 +272,35 @@ ERROR:  invalid reference to FROM-clause entry for table "insertconflicttest"
 LINE 1: ...onfruit') on conflict (key) do update set fruit = insertconf...
                                                              ^
 HINT:  Perhaps you meant to reference the table alias "ict".
+-- Helpful hint when qualifying set column with target table
+insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set insertconflicttest.fruit = 'Mango';
+ERROR:  column "insertconflicttest" of relation "insertconflicttest" does not exist
+LINE 1: ...3, 'Kiwi') on conflict (key, fruit) do update set insertconf...
+                                                             ^
+HINT:  target columns cannot be qualified with the target relation name
+insert into insertconflicttest AS ict values (3, 'Kiwi') on conflict (key, fruit) do update set ict.fruit = 'Mango';
+ERROR:  column "ict" of relation "insertconflicttest" does not exist
+LINE 1: ...3, 'Kiwi') on conflict (key, fruit) do update set ict.fruit ...
+                                                             ^
+HINT:  target columns cannot be qualified with the target relation name
+-- ... but not when the column doesn't match the table name.
+insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set insertconflicttest2.fruit = 'Mango';
+ERROR:  column "insertconflicttest2" of relation "insertconflicttest" does not exist
+LINE 1: ...3, 'Kiwi') on conflict (key, fruit) do update set insertconf...
+                                                             ^
+insert into insertconflicttest AS ict values (3, 'Kiwi') on conflict (key, fruit) do update set ict2.fruit = 'Mango';
+ERROR:  column "ict2" of relation "insertconflicttest" does not exist
+LINE 1: ...3, 'Kiwi') on conflict (key, fruit) do update set ict2.fruit...
+                                                             ^
+-- ... also not when the column isn't qualified but matches the table name.
+insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set insertconflicttest = 'Mango';
+ERROR:  column "insertconflicttest" of relation "insertconflicttest" does not exist
+LINE 1: ...3, 'Kiwi') on conflict (key, fruit) do update set insertconf...
+                                                             ^
+insert into insertconflicttest AS ict values (3, 'Kiwi') on conflict (key, fruit) do update set ict = 'Mango';
+ERROR:  column "ict" of relation "insertconflicttest" does not exist
+LINE 1: ...3, 'Kiwi') on conflict (key, fruit) do update set ict = 'Man...
+                                                             ^
 drop index key_index;
 --
 -- Composite key tests
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index 23d5778b82..435ae42024 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -118,6 +118,16 @@ insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (ke
 insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = ict.fruit; -- ok, alias
 insert into insertconflicttest AS ict values (6, 'Passionfruit') on conflict (key) do update set fruit = insertconflicttest.fruit; -- error, references aliased away name
 
+-- Helpful hint when qualifying set column with target table
+insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set insertconflicttest.fruit = 'Mango';
+insert into insertconflicttest AS ict values (3, 'Kiwi') on conflict (key, fruit) do update set ict.fruit = 'Mango';
+-- ... but not when the column doesn't match the table name.
+insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set insertconflicttest2.fruit = 'Mango';
+insert into insertconflicttest AS ict values (3, 'Kiwi') on conflict (key, fruit) do update set ict2.fruit = 'Mango';
+-- ... also not when the column isn't qualified but matches the table name.
+insert into insertconflicttest values (3, 'Kiwi') on conflict (key, fruit) do update set insertconflicttest = 'Mango';
+insert into insertconflicttest AS ict values (3, 'Kiwi') on conflict (key, fruit) do update set ict = 'Mango';
+
 drop index key_index;
 
 --
-- 
2.39.3 (Apple Git-145)

