From f1e13001b2ffff676b4b803db9ab439a1619dc4e Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 29 Nov 2011 15:02:10 -0500 Subject: [PATCH] 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 +++++++++++++++++++++- src/test/regress/expected/alter_table.out | 9 +++ src/test/regress/expected/domain.out | 2 + src/test/regress/expected/inherit.out | 6 ++ src/test/regress/output/constraints.source | 20 +++++++ 5 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d19e0978e4..a6b26f6668 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 diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out index 1aa4f09ed2..065d8fdcb2 100644 --- a/src/test/regress/expected/alter_table.out +++ b/src/test/regress/expected/alter_table.out @@ -390,6 +390,7 @@ alter table atacc1 add constraint atacc_test1 check (test>3); -- should fail insert into atacc1 (test) values (2); ERROR: new row for relation "atacc1" violates check constraint "atacc_test1" +DETAIL: Failing row contains (2). -- should succeed insert into atacc1 (test) values (4); drop table atacc1; @@ -415,6 +416,7 @@ alter table atacc1 add constraint atacc_test1 check (test+test2test); -- should fail for $2 insert into atacc1 (test2, test) values (3, 4); ERROR: new row for relation "atacc1" violates check constraint "atacc1_check" +DETAIL: Failing row contains (4, 3). drop table atacc1; -- inheritance related tests create table atacc1 (test int); @@ -433,10 +436,12 @@ alter table atacc2 add constraint foo check (test2>0); -- fail and then succeed on atacc2 insert into atacc2 (test2) values (-3); ERROR: new row for relation "atacc2" violates check constraint "foo" +DETAIL: Failing row contains (-3). insert into atacc2 (test2) values (3); -- fail and then succeed on atacc3 insert into atacc3 (test2) values (-3); ERROR: new row for relation "atacc3" violates check constraint "foo" +DETAIL: Failing row contains (null, -3, null). insert into atacc3 (test2) values (3); drop table atacc3; drop table atacc2; @@ -507,6 +512,7 @@ insert into atacc1 (test) values (3); -- check constraint is there on child insert into atacc2 (test) values (-3); ERROR: new row for relation "atacc2" violates check constraint "foo" +DETAIL: Failing row contains (-3, null). insert into atacc2 (test) values (3); drop table atacc2; drop table atacc1; @@ -1450,6 +1456,7 @@ NOTICE: merging definition of column "f2" for child "c1" insert into p1 values (1,2,'abc'); insert into c1 values(11,'xyz',33,0); -- should fail ERROR: new row for relation "c1" violates check constraint "p1_a1_check" +DETAIL: Failing row contains (11, xyz, 33, 0). insert into c1 values(11,'xyz',33,22); select * from p1; f1 | a1 | f2 @@ -1537,6 +1544,7 @@ select * from anothertab; insert into anothertab (atcol1, atcol2) values (45, null); -- fails ERROR: new row for relation "anothertab" violates check constraint "anothertab_chk" +DETAIL: Failing row contains (45, null). insert into anothertab (atcol1, atcol2) values (default, null); select * from anothertab; atcol1 | atcol2 @@ -2110,5 +2118,6 @@ ALTER TABLE ONLY test_drop_constr_parent DROP CONSTRAINT "test_drop_constr_paren -- should fail INSERT INTO test_drop_constr_child (c) VALUES (NULL); ERROR: new row for relation "test_drop_constr_child" violates check constraint "test_drop_constr_parent_c_check" +DETAIL: Failing row contains (null). DROP TABLE test_drop_constr_parent CASCADE; NOTICE: drop cascades to table test_drop_constr_child diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out index 521fe01fa1..0e09f898a9 100644 --- a/src/test/regress/expected/domain.out +++ b/src/test/regress/expected/domain.out @@ -199,6 +199,7 @@ insert into nulltest values ('a', 'b', 'c', 'd', NULL); ERROR: domain dcheck does not allow null values insert into nulltest values ('a', 'b', 'c', 'd', 'a'); ERROR: new row for relation "nulltest" violates check constraint "nulltest_col5_check" +DETAIL: Failing row contains (a, b, c, d, a). INSERT INTO nulltest values (NULL, 'b', 'c', 'd', 'd'); ERROR: domain dnotnull does not allow null values INSERT INTO nulltest values ('a', NULL, 'c', 'd', 'c'); @@ -216,6 +217,7 @@ CONTEXT: COPY nulltest, line 1, column col5: null input -- Last row is bad COPY nulltest FROM stdin; ERROR: new row for relation "nulltest" violates check constraint "nulltest_col5_check" +DETAIL: Failing row contains (a, b, c, null, a). CONTEXT: COPY nulltest, line 3: "a b c \N a" select * from nulltest; col1 | col2 | col3 | col4 | col5 diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out index 72986c78a2..e636575205 100644 --- a/src/test/regress/expected/inherit.out +++ b/src/test/regress/expected/inherit.out @@ -640,6 +640,7 @@ INSERT INTO inhg VALUES ('x', 'text', 'y'); /* Succeeds */ INSERT INTO inhg VALUES ('x', 'text', 'y'); /* Succeeds -- Unique constraints not copied */ INSERT INTO inhg VALUES ('x', 'foo', 'y'); /* fails due to constraint */ ERROR: new row for relation "inhg" violates check constraint "foo" +DETAIL: Failing row contains (x, foo, y). SELECT * FROM inhg; /* Two records with three columns in order x=x, xx=text, y=y */ x | xx | y ---+------+--- @@ -721,8 +722,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg insert into ac (aa) values (NULL); ERROR: new row for relation "ac" violates check constraint "ac_check" +DETAIL: Failing row contains (null). insert into bc (aa) values (NULL); ERROR: new row for relation "bc" violates check constraint "ac_check" +DETAIL: Failing row contains (null, null). alter table bc drop constraint ac_check; -- fail, disallowed ERROR: cannot drop inherited constraint "ac_check" of relation "bc" alter table ac drop constraint ac_check; @@ -742,8 +745,10 @@ select pc.relname, pgc.conname, pgc.contype, pgc.conislocal, pgc.coninhcount, pg insert into ac (aa) values (NULL); ERROR: new row for relation "ac" violates check constraint "ac_aa_check" +DETAIL: Failing row contains (null). insert into bc (aa) values (NULL); ERROR: new row for relation "bc" violates check constraint "ac_aa_check" +DETAIL: Failing row contains (null, null). alter table bc drop constraint ac_aa_check; -- fail, disallowed ERROR: cannot drop inherited constraint "ac_aa_check" of relation "bc" alter table ac drop constraint ac_aa_check; @@ -830,6 +835,7 @@ insert into c1 values(1,1,2); alter table p2 add check (f2>0); insert into c1 values(1,-1,2); -- fail ERROR: new row for relation "c1" violates check constraint "p2_f2_check" +DETAIL: Failing row contains (1, -1, 2). create table c2(f3 int) inherits(p1,p2); \d c2 Table "public.c2" diff --git a/src/test/regress/output/constraints.source b/src/test/regress/output/constraints.source index e2f2939931..9a813535f7 100644 --- a/src/test/regress/output/constraints.source +++ b/src/test/regress/output/constraints.source @@ -68,11 +68,14 @@ INSERT INTO CHECK_TBL VALUES (5); INSERT INTO CHECK_TBL VALUES (4); INSERT INTO CHECK_TBL VALUES (3); ERROR: new row for relation "check_tbl" violates check constraint "check_con" +DETAIL: Failing row contains (3). INSERT INTO CHECK_TBL VALUES (2); ERROR: new row for relation "check_tbl" violates check constraint "check_con" +DETAIL: Failing row contains (2). INSERT INTO CHECK_TBL VALUES (6); INSERT INTO CHECK_TBL VALUES (1); ERROR: new row for relation "check_tbl" violates check constraint "check_con" +DETAIL: Failing row contains (1). SELECT '' AS three, * FROM CHECK_TBL; three | x -------+--- @@ -88,12 +91,16 @@ CREATE TABLE CHECK2_TBL (x int, y text, z int, INSERT INTO CHECK2_TBL VALUES (4, 'check ok', -2); INSERT INTO CHECK2_TBL VALUES (1, 'x check failed', -2); ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con" +DETAIL: Failing row contains (1, x check failed, -2). INSERT INTO CHECK2_TBL VALUES (5, 'z check failed', 10); ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con" +DETAIL: Failing row contains (5, z check failed, 10). INSERT INTO CHECK2_TBL VALUES (0, 'check failed', -2); ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con" +DETAIL: Failing row contains (0, check failed, -2). INSERT INTO CHECK2_TBL VALUES (6, 'check failed', 11); ERROR: new row for relation "check2_tbl" violates check constraint "sequence_con" +DETAIL: Failing row contains (6, check failed, 11). INSERT INTO CHECK2_TBL VALUES (7, 'check ok', 7); SELECT '' AS two, * from CHECK2_TBL; two | x | y | z @@ -113,6 +120,7 @@ CREATE TABLE INSERT_TBL (x INT DEFAULT nextval('insert_seq'), CHECK (x + z = 0)); INSERT INTO INSERT_TBL(x,z) VALUES (2, -2); ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (2, -NULL-, -2). SELECT '' AS zero, * FROM INSERT_TBL; zero | x | y | z ------+---+---+--- @@ -126,12 +134,15 @@ SELECT 'one' AS one, nextval('insert_seq'); INSERT INTO INSERT_TBL(y) VALUES ('Y'); ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (2, Y, -2). INSERT INTO INSERT_TBL(y) VALUES ('Y'); INSERT INTO INSERT_TBL(x,z) VALUES (1, -2); ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_check" +DETAIL: Failing row contains (1, -NULL-, -2). INSERT INTO INSERT_TBL(z,x) VALUES (-7, 7); INSERT INTO INSERT_TBL VALUES (5, 'check failed', -5); ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (5, check failed, -5). INSERT INTO INSERT_TBL VALUES (7, '!check failed', -7); INSERT INTO INSERT_TBL(y) VALUES ('-!NULL-'); SELECT '' AS four, * FROM INSERT_TBL; @@ -145,8 +156,10 @@ SELECT '' AS four, * FROM INSERT_TBL; INSERT INTO INSERT_TBL(y,z) VALUES ('check failed', 4); ERROR: new row for relation "insert_tbl" violates check constraint "insert_tbl_check" +DETAIL: Failing row contains (5, check failed, 4). INSERT INTO INSERT_TBL(x,y) VALUES (5, 'check failed'); ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (5, check failed, -5). INSERT INTO INSERT_TBL(x,y) VALUES (5, '!check failed'); INSERT INTO INSERT_TBL(y) VALUES ('-!NULL-'); SELECT '' AS six, * FROM INSERT_TBL; @@ -168,6 +181,7 @@ SELECT 'seven' AS one, nextval('insert_seq'); INSERT INTO INSERT_TBL(y) VALUES ('Y'); ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (8, Y, -8). SELECT 'eight' AS one, currval('insert_seq'); one | currval -------+--------- @@ -199,10 +213,13 @@ CREATE TABLE INSERT_CHILD (cx INT default 42, INSERT INTO INSERT_CHILD(x,z,cy) VALUES (7,-7,11); INSERT INTO INSERT_CHILD(x,z,cy) VALUES (7,-7,6); ERROR: new row for relation "insert_child" violates check constraint "insert_child_check" +DETAIL: Failing row contains (7, -NULL-, -7, 42, 6). INSERT INTO INSERT_CHILD(x,z,cy) VALUES (6,-7,7); ERROR: new row for relation "insert_child" violates check constraint "insert_tbl_check" +DETAIL: Failing row contains (6, -NULL-, -7, 42, 7). INSERT INTO INSERT_CHILD(x,y,z,cy) VALUES (6,'check failed',-6,7); ERROR: new row for relation "insert_child" violates check constraint "insert_con" +DETAIL: Failing row contains (6, check failed, -6, 42, 7). SELECT * FROM INSERT_CHILD; x | y | z | cx | cy ---+--------+----+----+---- @@ -232,6 +249,7 @@ INSERT INTO INSERT_TBL SELECT * FROM tmp WHERE yd = 'try again'; INSERT INTO INSERT_TBL(y,z) SELECT yd, -7 FROM tmp WHERE yd = 'try again'; INSERT INTO INSERT_TBL(y,z) SELECT yd, -8 FROM tmp WHERE yd = 'try again'; ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (8, try again, -8). SELECT '' AS four, * FROM INSERT_TBL; four | x | y | z ------+---+---------------+---- @@ -251,6 +269,7 @@ UPDATE INSERT_TBL SET x = 6 WHERE x = 6; UPDATE INSERT_TBL SET x = -z, z = -x; UPDATE INSERT_TBL SET x = z, z = x; ERROR: new row for relation "insert_tbl" violates check constraint "insert_con" +DETAIL: Failing row contains (-4, Y, 4). SELECT * FROM INSERT_TBL; x | y | z ---+---------------+---- @@ -278,6 +297,7 @@ SELECT '' AS two, * FROM COPY_TBL; COPY COPY_TBL FROM '@abs_srcdir@/data/constrf.data'; ERROR: new row for relation "copy_tbl" violates check constraint "copy_con" +DETAIL: Failing row contains (7, check failed, 6). CONTEXT: COPY copy_tbl, line 2: "7 check failed 6" SELECT * FROM COPY_TBL; x | y | z