From 40fc14a0a15aa85bcaf863007e972c8fe6250fe2 Mon Sep 17 00:00:00 2001
From: Yugo Nagata <nagata@sraoss.co.jp>
Date: Tue, 1 Jul 2025 18:36:01 +0900
Subject: [PATCH v7 4/4] Improve error reporting for unique key violations in
 system catalogs

Previously, when a unique constraint violation occurred in a system catalog,
typically due to a concurrent session creating an object with the same key,
a low-level error like the following was raised by nbtree code:

 ERROR:  duplicate key value violates unique constraint ...

However, this message is not very user-friendly, as users are not directly
inserting rows into the system catalogs.

This commit improves the error reporting by generating a more descriptive and
user-facing error message in such cases, making it easier to understand the
cause of the failure and its likely relation to concurrent DDL activity.
---
 src/backend/catalog/indexing.c      | 24 ++++++++++++++++++++++++
 src/backend/executor/execIndexing.c | 19 +++++++++++++++++++
 src/include/executor/executor.h     |  5 +++++
 3 files changed, 48 insertions(+)

diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 25c4b6bdc87..5d91eeae16f 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -164,6 +164,30 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple,
 					   values,
 					   isnull);
 
+		/* Check if a concurrent command inserted an entry with the same key */
+		if (index->rd_index->indisunique && IsCatalogRelation(heapRelation))
+		{
+			bool	satisfied;
+			EState  *estate = CreateExecutorState();
+
+			BuildSpeculativeIndexInfo(index, indexInfo);
+			satisfied = check_unique_constraint(heapRelation,
+												 index, indexInfo,
+												 &(heapTuple->t_self), values, isnull,
+												 estate);
+
+			if (!satisfied)
+			{
+				char *key_desc = BuildIndexValueDescription(index, values, isnull);
+				ereport(ERROR,
+						(errcode(ERRCODE_UNIQUE_VIOLATION),
+						 errmsg("could not create object because a conflicting object already exists"),
+						 errdetail("Key %s conflicts with existing entry in unique index %s.",
+								   key_desc, RelationGetRelationName(index)),
+						 errhint("Another session might have created an object with the same key concurrently.")));
+			}
+		}
+
 		/*
 		 * The index AM does the rest.
 		 */
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index bdf862b2406..889573feb6b 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -965,6 +965,25 @@ check_exclusion_constraint(Relation heap, Relation index,
 												CEOUC_WAIT, false, NULL);
 }
 
+/*
+ * Check for violation of a unique constraint
+ *
+ * This is a dumbed down version of check_exclusion_or_unique_constraint
+ * for external callers. They don't need all the special modes.
+ */
+bool
+check_unique_constraint(Relation heap, Relation index,
+						   IndexInfo *indexInfo,
+						   ItemPointer tupleid,
+						   const Datum *values, const bool *isnull,
+						   EState *estate)
+{
+	return check_exclusion_or_unique_constraint(heap, index, indexInfo, tupleid,
+												values, isnull,
+												estate, false,
+												CEOUC_WAIT, true, NULL);
+}
+
 /*
  * Check existing tuple's index values to see if it really matches the
  * exclusion condition against the new_values.  Returns true if conflict.
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 104b059544d..2653e85e5ea 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -749,6 +749,11 @@ extern void check_exclusion_constraint(Relation heap, Relation index,
 									   ItemPointer tupleid,
 									   const Datum *values, const bool *isnull,
 									   EState *estate, bool newIndex);
+extern bool check_unique_constraint(Relation heap, Relation index,
+									IndexInfo *indexInfo,
+									ItemPointer tupleid,
+									const Datum *values, const bool *isnull,
+									EState *estate);
 
 /*
  * prototypes from functions in execReplication.c
-- 
2.43.0

