From e64964aa354048fd014d09edb9a07bc87be312c3 Mon Sep 17 00:00:00 2001
From: Taiki Koshino <koshino@sraoss.co.jp>
Date: Fri, 23 Oct 2026 09:45:05 +0900
Subject: [PATCH v1] Report CHECK expression in constraint violation details

CHECK constraint violations currently report the constraint name and the
failing row, but not the expression that was violated.  Users therefore
need to inspect the table definition to determine the constraint
condition.

Make ExecRelCheck return the failed ConstrCheck instead of only its name.
This allows ExecConstraints to deparse ccbin and include the CHECK
expression in the error detail.

For example:

ERROR:  new row for relation "staff_sales" violates check constraint "staff_age_check"
DETAIL:  Failing row contains (1, 15). Check constraint expression: (age >= 18).
---
 src/backend/executor/execMain.c | 23 ++++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 24df5d50a18..ad13bbf9747 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -63,6 +63,7 @@
 #include "utils/lsyscache.h"
 #include "utils/partcache.h"
 #include "utils/rls.h"
+#include "utils/ruleutils.h"
 #include "utils/snapmgr.h"
 
 
@@ -1803,9 +1804,9 @@ ExecutePlan(QueryDesc *queryDesc,
 /*
  * ExecRelCheck --- check that tuple meets check constraints for result relation
  *
- * Returns NULL if OK, else name of failed check constraint
+ * Returns NULL if OK, else struct of failed check constraint
  */
-static const char *
+static const ConstrCheck *
 ExecRelCheck(ResultRelInfo *resultRelInfo,
 			 TupleTableSlot *slot, EState *estate)
 {
@@ -1869,7 +1870,7 @@ ExecRelCheck(ResultRelInfo *resultRelInfo,
 		 * ExecQual.
 		 */
 		if (checkconstr && !ExecCheck(checkconstr, econtext))
-			return check[i].ccname;
+			return &check[i];
 	}
 
 	/* NULL result means no error */
@@ -2056,11 +2057,13 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 	 */
 	if (rel->rd_rel->relchecks > 0)
 	{
-		const char *failed;
+		const ConstrCheck *failed;
 
 		if ((failed = ExecRelCheck(resultRelInfo, slot, estate)) != NULL)
 		{
+			char	   *check_desc;
 			char	   *val_desc;
+			List	   *context;
 			Relation	orig_rel = rel;
 
 			/*
@@ -2095,6 +2098,10 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			else
 				modifiedCols = bms_union(ExecGetInsertedCols(resultRelInfo, estate),
 										 ExecGetUpdatedCols(resultRelInfo, estate));
+
+			context = deparse_context_for(RelationGetRelationName(orig_rel), RelationGetRelid(orig_rel));
+			check_desc = deparse_expression(stringToNode(failed->ccbin), context, false, false);
+
 			val_desc = ExecBuildSlotValueDescription(RelationGetRelid(rel),
 													 slot,
 													 tupdesc,
@@ -2103,9 +2110,11 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 			ereport(ERROR,
 					(errcode(ERRCODE_CHECK_VIOLATION),
 					 errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
-							RelationGetRelationName(orig_rel), failed),
-					 val_desc ? errdetail("Failing row contains %s.", val_desc) : 0,
-					 errtableconstraint(orig_rel, failed)));
+							RelationGetRelationName(orig_rel), failed->ccname),
+					 val_desc ?
+					 errdetail("Failing row contains %s. Check constraint expression: %s.",
+							   val_desc, check_desc) : 0,
+					 errtableconstraint(orig_rel, failed->ccname)));
 		}
 	}
 }
-- 
2.52.0

