When a row fails a CHECK constraint, show row's contents in errdetail.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Nov 2011 20:02:10 +0000 (15:02 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Nov 2011 20:02:49 +0000 (15:02 -0500)
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
src/test/regress/expected/alter_table.out
src/test/regress/expected/domain.out
src/test/regress/expected/inherit.out
src/test/regress/output/constraints.source

index d19e0978e4ea45f7f198ca841c5903bd8098e388..a6b26f6668aeb77a5101bcbcc94bcdffbd950511 100644 (file)
@@ -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
index 1aa4f09ed28687954180c1594d295fcbfad8f8bf..065d8fdcb2729dad3362076e0f3679787b534a26 100644 (file)
@@ -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+test2<test3*4);
 -- should fail
 insert into atacc1 (test,test2,test3) values (4,4,2);
 ERROR:  new row for relation "atacc1" violates check constraint "atacc_test1"
+DETAIL:  Failing row contains (4, 4, 2).
 -- should succeed
 insert into atacc1 (test,test2,test3) values (4,4,5);
 drop table atacc1;
@@ -424,6 +426,7 @@ alter table atacc1 add check (test2>test);
 -- 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
index 521fe01fa17eca8c41ded32d0c3e0e600ed08685..0e09f898a9045c0223a9d495a3bc56020b0a528c 100644 (file)
@@ -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 
index 72986c78a2339eb827a994929db5e80d88e996aa..e6365752050c1abe21595b5e2d90f62dd27ae5ba 100644 (file)
@@ -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"
index e2f2939931580e1796fc3fde7f4cebf25553d02e..9a813535f7acaf60767515841dae5a9cf7f7d509 100644 (file)
@@ -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