From f1e13001b2ffff676b4b803db9ab439a1619dc4e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 29 Nov 2011 15:02:10 -0500 Subject: When a row fails a CHECK constraint, show row's contents in errdetail. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should make it easier to identify which row is problematic when an insert or update is processing many rows. The formatting is similar to that for unique-index violation messages, except that we limit field widths to 64 bytes since otherwise the message could get unreasonably long. (In particular, there's currently no attempt to quote or escape field values that contain commas etc.) Jan Kundrát, reviewed by Royce Ausburn, somewhat rewritten by me. --- src/backend/executor/execMain.c | 66 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) (limited to 'src/backend/executor/execMain.c') diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d19e0978e4e..a6b26f6668a 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -47,6 +47,7 @@ #include "commands/tablespace.h" #include "commands/trigger.h" #include "executor/execdebug.h" +#include "mb/pg_wchar.h" #include "miscadmin.h" #include "optimizer/clauses.h" #include "parser/parse_clause.h" @@ -85,6 +86,8 @@ static void ExecutePlan(EState *estate, PlanState *planstate, DestReceiver *dest); static bool ExecCheckRTEPerms(RangeTblEntry *rte); static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt); +static char *ExecBuildSlotValueDescription(TupleTableSlot *slot, + int maxfieldlen); static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate, Plan *planTree); static void OpenIntoRel(QueryDesc *queryDesc); @@ -1585,10 +1588,71 @@ ExecConstraints(ResultRelInfo *resultRelInfo, ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("new row for relation \"%s\" violates check constraint \"%s\"", - RelationGetRelationName(rel), failed))); + RelationGetRelationName(rel), failed), + errdetail("Failing row contains %s.", + ExecBuildSlotValueDescription(slot, 64)))); } } +/* + * ExecBuildSlotValueDescription -- construct a string representing a tuple + * + * This is intentionally very similar to BuildIndexValueDescription, but + * unlike that function, we truncate long field values. That seems necessary + * here since heap field values could be very long, whereas index entries + * typically aren't so wide. + */ +static char * +ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen) +{ + StringInfoData buf; + TupleDesc tupdesc = slot->tts_tupleDescriptor; + int i; + + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + + initStringInfo(&buf); + + appendStringInfoChar(&buf, '('); + + for (i = 0; i < tupdesc->natts; i++) + { + char *val; + int vallen; + + if (slot->tts_isnull[i]) + val = "null"; + else + { + Oid foutoid; + bool typisvarlena; + + getTypeOutputInfo(tupdesc->attrs[i]->atttypid, + &foutoid, &typisvarlena); + val = OidOutputFunctionCall(foutoid, slot->tts_values[i]); + } + + if (i > 0) + appendStringInfoString(&buf, ", "); + + /* truncate if needed */ + vallen = strlen(val); + if (vallen <= maxfieldlen) + appendStringInfoString(&buf, val); + else + { + vallen = pg_mbcliplen(val, vallen, maxfieldlen); + appendBinaryStringInfo(&buf, val, vallen); + appendStringInfoString(&buf, "..."); + } + } + + appendStringInfoChar(&buf, ')'); + + return buf.data; +} + /* * ExecFindRowMark -- find the ExecRowMark struct for given rangetable index -- cgit v1.2.3