Prevent display of dropped columns in row constraint violation messages.
authorTom Lane
Thu, 7 Nov 2013 19:41:36 +0000 (14:41 -0500)
committerTom Lane
Thu, 7 Nov 2013 19:41:36 +0000 (14:41 -0500)
ExecBuildSlotValueDescription() printed "null" for each dropped column in
a row being complained of by ExecConstraints().  This has some sanity in
terms of the underlying implementation, but is of course pretty surprising
to users.  To fix, we must pass the target relation's descriptor to
ExecBuildSlotValueDescription(), because the slot descriptor it had been
using doesn't get labeled with attisdropped markers.

Per bug #8408 from Maxim Boguk.  Back-patch to 9.2 where the feature of
printing row values in NOT NULL and CHECK constraint violation messages
was introduced.

Michael Paquier and Tom Lane

src/backend/executor/execMain.c
src/test/regress/expected/alter_table.out
src/test/regress/sql/alter_table.sql

index 0b4710646aa64c2f74bb569bcca195c2393107bf..6be17a9912ee9bd552d741ae460e5eebca8e2eb6 100644 (file)
@@ -83,6 +83,7 @@ static void ExecutePlan(EState *estate, PlanState *planstate,
 static bool ExecCheckRTEPerms(RangeTblEntry *rte);
 static void ExecCheckXactReadOnly(PlannedStmt *plannedstmt);
 static char *ExecBuildSlotValueDescription(TupleTableSlot *slot,
+                             TupleDesc tupdesc,
                              int maxfieldlen);
 static void EvalPlanQualStart(EPQState *epqstate, EState *parentestate,
                  Plan *planTree);
@@ -1586,25 +1587,28 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
                TupleTableSlot *slot, EState *estate)
 {
    Relation    rel = resultRelInfo->ri_RelationDesc;
-   TupleConstr *constr = rel->rd_att->constr;
+   TupleDesc   tupdesc = RelationGetDescr(rel);
+   TupleConstr *constr = tupdesc->constr;
 
    Assert(constr);
 
    if (constr->has_not_null)
    {
-       int         natts = rel->rd_att->natts;
+       int         natts = tupdesc->natts;
        int         attrChk;
 
        for (attrChk = 1; attrChk <= natts; attrChk++)
        {
-           if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
+           if (tupdesc->attrs[attrChk - 1]->attnotnull &&
                slot_attisnull(slot, attrChk))
                ereport(ERROR,
                        (errcode(ERRCODE_NOT_NULL_VIOLATION),
                         errmsg("null value in column \"%s\" violates not-null constraint",
-                         NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
+                             NameStr(tupdesc->attrs[attrChk - 1]->attname)),
                         errdetail("Failing row contains %s.",
-                                  ExecBuildSlotValueDescription(slot, 64)),
+                                  ExecBuildSlotValueDescription(slot,
+                                                                tupdesc,
+                                                                64)),
                         errtablecol(rel, attrChk)));
        }
    }
@@ -1619,7 +1623,9 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
                     errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
                            RelationGetRelationName(rel), failed),
                     errdetail("Failing row contains %s.",
-                              ExecBuildSlotValueDescription(slot, 64)),
+                              ExecBuildSlotValueDescription(slot,
+                                                            tupdesc,
+                                                            64)),
                     errtableconstraint(rel, failed)));
    }
 }
@@ -1663,7 +1669,9 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
                     errmsg("new row violates WITH CHECK OPTION for view \"%s\"",
                            wco->viewname),
                     errdetail("Failing row contains %s.",
-                              ExecBuildSlotValueDescription(slot, 64))));
+                              ExecBuildSlotValueDescription(slot,
+                           RelationGetDescr(resultRelInfo->ri_RelationDesc),
+                                                            64))));
    }
 }
 
@@ -1671,15 +1679,22 @@ ExecWithCheckOptions(ResultRelInfo *resultRelInfo,
  * 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.
+ * unlike that function, we truncate long field values (to at most maxfieldlen
+ * bytes). That seems necessary here since heap field values could be very
+ * long, whereas index entries typically aren't so wide.
+ *
+ * Also, unlike the case with index entries, we need to be prepared to ignore
+ * dropped columns.  We used to use the slot's tuple descriptor to decode the
+ * data, but the slot's descriptor doesn't identify dropped columns, so we
+ * now need to be passed the relation's descriptor.
  */
 static char *
-ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen)
+ExecBuildSlotValueDescription(TupleTableSlot *slot,
+                             TupleDesc tupdesc,
+                             int maxfieldlen)
 {
    StringInfoData buf;
-   TupleDesc   tupdesc = slot->tts_tupleDescriptor;
+   bool        write_comma = false;
    int         i;
 
    /* Make sure the tuple is fully deconstructed */
@@ -1694,6 +1709,10 @@ ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen)
        char       *val;
        int         vallen;
 
+       /* ignore dropped columns */
+       if (tupdesc->attrs[i]->attisdropped)
+           continue;
+
        if (slot->tts_isnull[i])
            val = "null";
        else
@@ -1706,8 +1725,10 @@ ExecBuildSlotValueDescription(TupleTableSlot *slot, int maxfieldlen)
            val = OidOutputFunctionCall(foutoid, slot->tts_values[i]);
        }
 
-       if (i > 0)
+       if (write_comma)
            appendStringInfoString(&buf, ", ");
+       else
+           write_comma = true;
 
        /* truncate if needed */
        vallen = strlen(val);
index 7cc0084b920a5eef9a62567f62f2a27cc9de598a..7366392457ec40173ed5afecd9684f8b92fd7d0b 100644 (file)
@@ -1196,6 +1196,20 @@ select * from atacc1;
 --
 (1 row)
 
+drop table atacc1;
+-- test constraint error reporting in presence of dropped columns
+create table atacc1 (id serial primary key, value int check (value < 10));
+insert into atacc1(value) values (100);
+ERROR:  new row for relation "atacc1" violates check constraint "atacc1_value_check"
+DETAIL:  Failing row contains (1, 100).
+alter table atacc1 drop column value;
+alter table atacc1 add column value int check (value < 10);
+insert into atacc1(value) values (100);
+ERROR:  new row for relation "atacc1" violates check constraint "atacc1_value_check"
+DETAIL:  Failing row contains (2, 100).
+insert into atacc1(id, value) values (null, 0);
+ERROR:  null value in column "id" violates not-null constraint
+DETAIL:  Failing row contains (null, 0).
 drop table atacc1;
 -- test inheritance
 create table parent (a int, b int, c int);
index a546ba74af36f6742b7aa4a5125cbda3407fa6d4..0d3b79bfcfd26531e6cbc59411024f295fc18154 100644 (file)
@@ -874,6 +874,15 @@ select * from atacc1;
 
 drop table atacc1;
 
+-- test constraint error reporting in presence of dropped columns
+create table atacc1 (id serial primary key, value int check (value < 10));
+insert into atacc1(value) values (100);
+alter table atacc1 drop column value;
+alter table atacc1 add column value int check (value < 10);
+insert into atacc1(value) values (100);
+insert into atacc1(id, value) values (null, 0);
+drop table atacc1;
+
 -- test inheritance
 create table parent (a int, b int, c int);
 insert into parent values (1, 2, 3);