diff doc/src/sgml/errcodes.sgml index 16cb6c7..7c153b5 *** a/doc/src/sgml/errcodes.sgml --- b/doc/src/sgml/errcodes.sgml *************** *** 42,47 **** --- 42,58 ---- + A small number of error codes listed in + are guaranteed to provide + additional fields to facilitate applications in recognizing + particular domain-specific errors, provided that the the errors are + emitted from within the PostgreSQL server itself (it is not + strictly guaranteed that an application will not emit an + ill-considered error without these field, but with an errorcode + documented as providing them). + + + The symbol shown in the column Condition Name is also the condition name to use in PL/pgSQL. Condition names can be written in either upper or lower case. (Note that diff doc/src/sgml/generate-errcodes-table.pl index 2ed12ce..7535395 *** a/doc/src/sgml/generate-errcodes-table.pl --- b/doc/src/sgml/generate-errcodes-table.pl *************** while (<$errcodes>) *** 41,46 **** --- 41,71 ---- next; } + # Emit requirement headers + if (/^Requirement:/) + { + # Replace the Requirement: string + s/^Requirement: /Provides: /; + + # Replace "unused" + if (!s/unused/\(no fields\)/) + { + # Use literal for field names if appropriate + s/: (.*)/: $1<\/symbol>/; + } + + # Escape dashes for SGML + s/-/—/; + + print "\n\n"; + print "\n"; + print ""; + print "$_\n"; + print "\n"; + + next; + } + die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/; (my $sqlstate, my $type, my $errcode_macro, my $condition_name) = diff doc/src/sgml/protocol.sgml index e14627c..9286dfc *** a/doc/src/sgml/protocol.sgml --- b/doc/src/sgml/protocol.sgml *************** message. *** 4786,4791 **** --- 4786,4849 ---- + + + c + + + + Column name: the name of the column associated with the the + error, if any. This must be a column within the table of the + Table name field. + + + + + + + t + + + + Table name: the name of the table associated with the error, + if any. + + + + + + + n + + + + Constraint name: the name of the constraint associated with + the error, if any. Note that NOT NULL constraints are not + cataloged, and as such will never have a constraint name, + though in the case of NOT NULL constraints on columns the + column name will be available. This value is not guaranteed + to uniquely identify the constraint. Even when combined with + other fields, constraint name will not unambiguously identify + the constraint in all cases, since it is possible for the + constraint to be in a different schema to the table associated + with the error, or for the constraint to not be associated + with a table at all. + + + + + + + s + + + + Schema name: the name of schema that contains the table + associated with the error, if any. + + + + diff src/backend/access/nbtree/nbtinsert.c index 4432bb1..25d4f09 *** a/src/backend/access/nbtree/nbtinsert.c --- b/src/backend/access/nbtree/nbtinsert.c *************** _bt_check_unique(Relation rel, IndexTupl *** 393,399 **** RelationGetRelationName(rel)), errdetail("Key %s already exists.", BuildIndexValueDescription(rel, ! values, isnull)))); } } else if (all_dead) --- 393,401 ---- RelationGetRelationName(rel)), errdetail("Key %s already exists.", BuildIndexValueDescription(rel, ! values, isnull)), ! errtable(heapRel), ! errconstraint(RelationGetRelationName(rel)))); } } else if (all_dead) *************** _bt_check_unique(Relation rel, IndexTupl *** 455,461 **** (errcode(ERRCODE_INTERNAL_ERROR), errmsg("failed to re-find tuple within index \"%s\"", RelationGetRelationName(rel)), ! errhint("This may be because of a non-immutable index expression."))); if (nbuf != InvalidBuffer) _bt_relbuf(rel, nbuf); --- 457,464 ---- (errcode(ERRCODE_INTERNAL_ERROR), errmsg("failed to re-find tuple within index \"%s\"", RelationGetRelationName(rel)), ! errhint("This may be because of a non-immutable index expression."), ! errtable(heapRel))); if (nbuf != InvalidBuffer) _bt_relbuf(rel, nbuf); *************** _bt_findinsertloc(Relation rel, *** 533,539 **** RelationGetRelationName(rel)), errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n" "Consider a function index of an MD5 hash of the value, " ! "or use full text indexing."))); /*---------- * If we will need to split the page to put the item on this page, --- 536,543 ---- RelationGetRelationName(rel)), errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n" "Consider a function index of an MD5 hash of the value, " ! "or use full text indexing."), ! errtable(heapRel))); /*---------- * If we will need to split the page to put the item on this page, diff src/backend/commands/tablecmds.c index cad8311..e3a33b6 *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** ATRewriteTable(AlteredTableInfo *tab, Oi *** 3819,3828 **** int attn = lfirst_int(l); if (heap_attisnull(tuple, attn + 1)) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" contains null values", ! NameStr(newTupDesc->attrs[attn]->attname)))); } foreach(l, tab->constraints) --- 3819,3835 ---- int attn = lfirst_int(l); if (heap_attisnull(tuple, attn + 1)) + { + Form_pg_attribute att = newTupDesc->attrs[attn]; + ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" contains null values", ! NameStr(att->attname)), ! (newrel) ? ! errtablecol(newrel, NameStr(att->attname)) : ! errtablecol(oldrel, NameStr(att->attname)))); ! } } foreach(l, tab->constraints) *************** ATRewriteTable(AlteredTableInfo *tab, Oi *** 3836,3842 **** ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", ! con->name))); break; case CONSTR_FOREIGN: /* Nothing to do here */ --- 3843,3853 ---- ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", ! con->name), ! (newrel) ? ! errtable(newrel) : ! errtable(oldrel), ! errconstraint(con->name))); break; case CONSTR_FOREIGN: /* Nothing to do here */ *************** validateCheckConstraint(Relation rel, He *** 6653,6659 **** ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", ! NameStr(constrForm->conname)))); ResetExprContext(econtext); } --- 6664,6672 ---- ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", ! NameStr(constrForm->conname)), ! errtable(rel), ! errconstraint(NameStr(constrForm->conname)))); ResetExprContext(econtext); } diff src/backend/commands/typecmds.c index 7a72416..e88d306 *** a/src/backend/commands/typecmds.c --- b/src/backend/commands/typecmds.c *************** static Oid findTypeAnalyzeFunction(List *** 98,104 **** static Oid findRangeSubOpclass(List *opcname, Oid subtype); static Oid findRangeCanonicalFunction(List *procname, Oid typeOid); static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype); ! static void validateDomainConstraint(Oid domainoid, char *ccbin); static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode); static void checkEnumOwner(HeapTuple tup); static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, --- 98,104 ---- static Oid findRangeSubOpclass(List *opcname, Oid subtype); static Oid findRangeCanonicalFunction(List *procname, Oid typeOid); static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype); ! static void validateDomainConstraint(Oid domainoid, char *ccbin, char *conname); static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode); static void checkEnumOwner(HeapTuple tup); static char *domainAddConstraint(Oid domainOid, Oid domainNamespace, *************** AlterDomainNotNull(List *names, bool not *** 2262,2274 **** for (i = 0; i < rtc->natts; i++) { int attnum = rtc->atts[i]; if (heap_attisnull(tuple, attnum)) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" of table \"%s\" contains null values", ! NameStr(tupdesc->attrs[attnum - 1]->attname), ! RelationGetRelationName(testrel)))); } } heap_endscan(scan); --- 2262,2275 ---- for (i = 0; i < rtc->natts; i++) { int attnum = rtc->atts[i]; + Form_pg_attribute att = tupdesc->attrs[attnum - 1]; if (heap_attisnull(tuple, attnum)) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" of table \"%s\" contains null values", ! NameStr(att->attname), RelationGetRelationName(testrel)), ! errtablecol(testrel, NameStr(att->attname)))); } } heap_endscan(scan); *************** AlterDomainAddConstraint(List *names, No *** 2478,2484 **** * attributes based on the domain the constraint is being added to. */ if (!constr->skip_validation) ! validateDomainConstraint(domainoid, ccbin); /* Clean up */ heap_close(typrel, RowExclusiveLock); --- 2479,2485 ---- * attributes based on the domain the constraint is being added to. */ if (!constr->skip_validation) ! validateDomainConstraint(domainoid, ccbin, constr->conname); /* Clean up */ heap_close(typrel, RowExclusiveLock); *************** AlterDomainValidateConstraint(List *name *** 2565,2571 **** HeapTupleGetOid(tuple)); conbin = TextDatumGetCString(val); ! validateDomainConstraint(domainoid, conbin); /* * Now update the catalog, while we have the door open. --- 2566,2572 ---- HeapTupleGetOid(tuple)); conbin = TextDatumGetCString(val); ! validateDomainConstraint(domainoid, conbin, constrName); /* * Now update the catalog, while we have the door open. *************** AlterDomainValidateConstraint(List *name *** 2588,2594 **** } static void ! validateDomainConstraint(Oid domainoid, char *ccbin) { Expr *expr = (Expr *) stringToNode(ccbin); List *rels; --- 2589,2595 ---- } static void ! validateDomainConstraint(Oid domainoid, char *ccbin, char *conname) { Expr *expr = (Expr *) stringToNode(ccbin); List *rels; *************** validateDomainConstraint(Oid domainoid, *** 2630,2635 **** --- 2631,2637 ---- Datum d; bool isNull; Datum conResult; + const char *col = NameStr(tupdesc->attrs[attnum - 1]->attname); d = heap_getattr(tuple, attnum, tupdesc, &isNull); *************** validateDomainConstraint(Oid domainoid, *** 2644,2651 **** ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint", ! NameStr(tupdesc->attrs[attnum - 1]->attname), ! RelationGetRelationName(testrel)))); } ResetExprContext(econtext); --- 2646,2656 ---- ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint", ! col, ! RelationGetRelationName(testrel)), ! errtablecol(testrel, col), ! conname? ! errconstraint(conname):0)); } ResetExprContext(econtext); diff src/backend/executor/execMain.c index 9d5d829..f94e18f *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** ExecConstraints(ResultRelInfo *resultRel *** 1522,1535 **** for (attrChk = 1; attrChk <= natts; attrChk++) { if (rel->rd_att->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)), errdetail("Failing row contains %s.", ! ExecBuildSlotValueDescription(slot, 64)))); } } --- 1522,1538 ---- for (attrChk = 1; attrChk <= natts; attrChk++) { + Form_pg_attribute Chk = rel->rd_att->attrs[attrChk - 1]; + if (rel->rd_att->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(Chk->attname)), errdetail("Failing row contains %s.", ! ExecBuildSlotValueDescription(slot, 64)), ! errtablecol(rel, NameStr(Chk->attname)))); } } *************** ExecConstraints(ResultRelInfo *resultRel *** 1543,1549 **** errmsg("new row for relation \"%s\" violates check constraint \"%s\"", RelationGetRelationName(rel), failed), errdetail("Failing row contains %s.", ! ExecBuildSlotValueDescription(slot, 64)))); } } --- 1546,1554 ---- errmsg("new row for relation \"%s\" violates check constraint \"%s\"", RelationGetRelationName(rel), failed), errdetail("Failing row contains %s.", ! ExecBuildSlotValueDescription(slot, 64)), ! errtable(rel), ! errconstraint(failed))); } } diff src/backend/executor/execQual.c index d9981a5..3bacd55 *** a/src/backend/executor/execQual.c --- b/src/backend/executor/execQual.c *************** *** 54,59 **** --- 54,60 ---- #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/memutils.h" + #include "utils/rel.h" #include "utils/typcache.h" #include "utils/xml.h" *************** ExecEvalCoerceToDomain(CoerceToDomainSta *** 3863,3870 **** --- 3864,3877 ---- { CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr; Datum result; + Relation heapRel = NULL; + struct EState *estate = econtext->ecxt_estate; ListCell *l; + /* Build relation description, if possible */ + if (estate && estate->es_result_relation_info) + heapRel = estate->es_result_relation_info->ri_RelationDesc; + result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone); if (isDone && *isDone == ExprEndResult) *************** ExecEvalCoerceToDomain(CoerceToDomainSta *** 3878,3887 **** { case DOM_CONSTRAINT_NOTNULL: if (*isNull) ! ereport(ERROR, ! (errcode(ERRCODE_NOT_NULL_VIOLATION), ! errmsg("domain %s does not allow null values", ! format_type_be(ctest->resulttype)))); break; case DOM_CONSTRAINT_CHECK: { --- 3885,3904 ---- { case DOM_CONSTRAINT_NOTNULL: if (*isNull) ! { ! if (heapRel) ! ereport(ERROR, ! (errcode(ERRCODE_NOT_NULL_VIOLATION), ! errmsg("domain %s does not allow null values", ! format_type_be(ctest->resulttype)), ! errtable(heapRel))); ! ! else ! ereport(ERROR, ! (errcode(ERRCODE_NOT_NULL_VIOLATION), ! errmsg("domain %s does not allow null values", ! format_type_be(ctest->resulttype)))); ! } break; case DOM_CONSTRAINT_CHECK: { *************** ExecEvalCoerceToDomain(CoerceToDomainSta *** 3907,3917 **** if (!conIsNull && !DatumGetBool(conResult)) ! ereport(ERROR, ! (errcode(ERRCODE_CHECK_VIOLATION), ! errmsg("value for domain %s violates check constraint \"%s\"", ! format_type_be(ctest->resulttype), ! con->name))); econtext->domainValue_datum = save_datum; econtext->domainValue_isNull = save_isNull; --- 3924,3946 ---- if (!conIsNull && !DatumGetBool(conResult)) ! { ! if (heapRel) ! ereport(ERROR, ! (errcode(ERRCODE_CHECK_VIOLATION), ! errmsg("value for domain %s violates check constraint \"%s\"", ! format_type_be(ctest->resulttype), ! con->name), ! errtable(heapRel), ! errconstraint(con->name))); ! else ! ereport(ERROR, ! (errcode(ERRCODE_CHECK_VIOLATION), ! errmsg("value for domain %s violates check constraint \"%s\"", ! format_type_be(ctest->resulttype), ! con->name), ! errconstraint(con->name))); ! } econtext->domainValue_datum = save_datum; econtext->domainValue_isNull = save_isNull; diff src/backend/executor/execUtils.c index 9206195..114a8af *** a/src/backend/executor/execUtils.c --- b/src/backend/executor/execUtils.c *************** retry: *** 1307,1320 **** errmsg("could not create exclusion constraint \"%s\"", RelationGetRelationName(index)), errdetail("Key %s conflicts with key %s.", ! error_new, error_existing))); else ereport(ERROR, (errcode(ERRCODE_EXCLUSION_VIOLATION), errmsg("conflicting key value violates exclusion constraint \"%s\"", RelationGetRelationName(index)), errdetail("Key %s conflicts with existing key %s.", ! error_new, error_existing))); } index_endscan(index_scan); --- 1307,1324 ---- errmsg("could not create exclusion constraint \"%s\"", RelationGetRelationName(index)), errdetail("Key %s conflicts with key %s.", ! error_new, error_existing), ! errtable(heap), ! errconstraint(RelationGetRelationName(index)))); else ereport(ERROR, (errcode(ERRCODE_EXCLUSION_VIOLATION), errmsg("conflicting key value violates exclusion constraint \"%s\"", RelationGetRelationName(index)), errdetail("Key %s conflicts with existing key %s.", ! error_new, error_existing), ! errtable(heap), ! errconstraint(RelationGetRelationName(index)))); } index_endscan(index_scan); diff src/backend/utils/adt/domains.c index 9d2fb1e..8a3d645 *** a/src/backend/utils/adt/domains.c --- b/src/backend/utils/adt/domains.c *************** *** 36,41 **** --- 36,42 ---- #include "lib/stringinfo.h" #include "utils/builtins.h" #include "utils/lsyscache.h" + #include "utils/rel.h" /* *************** domain_check_input(Datum value, bool isn *** 123,128 **** --- 124,134 ---- { case DOM_CONSTRAINT_NOTNULL: if (isnull) + /* + * We don't provide errtable here, because it is + * unavailable, though we require it anywhere where it is + * available in principle. + */ ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("domain %s does not allow null values", *************** domain_check_input(Datum value, bool isn *** 163,169 **** (errcode(ERRCODE_CHECK_VIOLATION), errmsg("value for domain %s violates check constraint \"%s\"", format_type_be(my_extra->domain_type), ! con->name))); break; } default: --- 169,176 ---- (errcode(ERRCODE_CHECK_VIOLATION), errmsg("value for domain %s violates check constraint \"%s\"", format_type_be(my_extra->domain_type), ! con->name), ! errconstraint(con->name))); break; } default: diff src/backend/utils/adt/ri_triggers.c index 601d5ec..09396e6 *** a/src/backend/utils/adt/ri_triggers.c --- b/src/backend/utils/adt/ri_triggers.c *************** RI_FKey_check(TriggerData *trigdata) *** 339,345 **** errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(trigdata->tg_relation), NameStr(riinfo->conname)), ! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); --- 339,347 ---- errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(trigdata->tg_relation), NameStr(riinfo->conname)), ! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), ! errtable(trigdata->tg_relation), ! errconstraint(NameStr(riinfo->conname)))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); *************** RI_Initial_Check(Trigger *trigger, Relat *** 2467,2473 **** errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(fk_rel), NameStr(fake_riinfo.conname)), ! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."))); /* * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK --- 2469,2478 ---- errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", RelationGetRelationName(fk_rel), NameStr(fake_riinfo.conname)), ! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), ! errtable(fk_rel), ! errconstraint(NameStr(fake_riinfo.conname)))); ! /* * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK *************** ri_ReportViolation(const RI_ConstraintIn *** 3219,3225 **** NameStr(riinfo->conname)), errdetail("Key (%s)=(%s) is not present in table \"%s\".", key_names.data, key_values.data, ! RelationGetRelationName(pk_rel)))); else ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), --- 3224,3232 ---- NameStr(riinfo->conname)), errdetail("Key (%s)=(%s) is not present in table \"%s\".", key_names.data, key_values.data, ! RelationGetRelationName(pk_rel)), ! errtable(fk_rel), ! errconstraint(NameStr(riinfo->conname)))); else ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), *************** ri_ReportViolation(const RI_ConstraintIn *** 3229,3235 **** RelationGetRelationName(fk_rel)), errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", key_names.data, key_values.data, ! RelationGetRelationName(fk_rel)))); } --- 3236,3244 ---- RelationGetRelationName(fk_rel)), errdetail("Key (%s)=(%s) is still referenced from table \"%s\".", key_names.data, key_values.data, ! RelationGetRelationName(fk_rel)), ! errtable(pk_rel), ! errconstraint(NameStr(riinfo->conname)))); } diff src/backend/utils/errcodes.txt index 0b129b1..e53c94e *** a/src/backend/utils/errcodes.txt --- b/src/backend/utils/errcodes.txt *************** Section: Class 22 - Data Exception *** 200,213 **** 2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment 2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction Section: Class 23 - Integrity Constraint Violation ! 23000 E ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION integrity_constraint_violation 23001 E ERRCODE_RESTRICT_VIOLATION restrict_violation 23502 E ERRCODE_NOT_NULL_VIOLATION not_null_violation 23503 E ERRCODE_FOREIGN_KEY_VIOLATION foreign_key_violation 23505 E ERRCODE_UNIQUE_VIOLATION unique_violation 23514 E ERRCODE_CHECK_VIOLATION check_violation 23P01 E ERRCODE_EXCLUSION_VIOLATION exclusion_violation Section: Class 24 - Invalid Cursor State --- 200,227 ---- 2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment 2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction + # Postgres coding standards mandate that certain fields be available in all + # instances for some of the Class 23 errcodes, documented under "Requirement: " + # here. Some other errcode's ereport sites may, at their own discretion, make + # errcolumn, errtable, errconstraint and errschema fields available too. + # Furthermore, it is possible to make some fields available beyond those + # formally required at callsites involving these Class 23 errcodes with + # "Requirements: ". Section: Class 23 - Integrity Constraint Violation ! Requirement: unused 23000 E ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION integrity_constraint_violation + Requirement: unused 23001 E ERRCODE_RESTRICT_VIOLATION restrict_violation + # Note that requirements for ERRCODE_NOT_NULL do not apply to domains: + Requirement: schema_name, table_name 23502 E ERRCODE_NOT_NULL_VIOLATION not_null_violation + Requirement: schema_name, table_name, constraint_name 23503 E ERRCODE_FOREIGN_KEY_VIOLATION foreign_key_violation + Requirement: schema_name, table_name, constraint_name 23505 E ERRCODE_UNIQUE_VIOLATION unique_violation + Requirement: constraint_name 23514 E ERRCODE_CHECK_VIOLATION check_violation + Requirement: schema_name, table_name, constraint_name 23P01 E ERRCODE_EXCLUSION_VIOLATION exclusion_violation Section: Class 24 - Invalid Cursor State diff src/backend/utils/error/Makefile index 4c313b7..c2ba4d0 *** a/src/backend/utils/error/Makefile --- b/src/backend/utils/error/Makefile *************** subdir = src/backend/utils/error *** 12,17 **** top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global ! OBJS = assert.o elog.o include $(top_srcdir)/src/backend/common.mk --- 12,17 ---- top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global ! OBJS = assert.o elog.o relerror.o include $(top_srcdir)/src/backend/common.mk diff src/backend/utils/error/elog.c index e710f22..a28e4bd *** a/src/backend/utils/error/elog.c --- b/src/backend/utils/error/elog.c *************** static void write_syslog(int level, cons *** 130,135 **** --- 130,136 ---- #endif static void write_console(const char *line, int len); + static void set_errdata_field(char **ptr, const char *str); #ifdef WIN32 extern char *event_source; *************** errfinish(int dummy,...) *** 477,482 **** --- 478,491 ---- pfree(edata->context); if (edata->internalquery) pfree(edata->internalquery); + if (edata->column_name) + pfree(edata->column_name); + if (edata->table_name) + pfree(edata->table_name); + if (edata->schema_name) + pfree(edata->schema_name); + if (edata->constraint_name) + pfree(edata->constraint_name); errordata_stack_depth--; *************** internalerrquery(const char *query) *** 1101,1106 **** --- 1110,1180 ---- } /* + * err_generic_string -- used to set individual ErrorData string fields. + * + * Callers should prefer higher-level abstractions, such as the utility + * functions within relerror.c. + */ + int + err_generic_string(int field, const char *str) + { + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + switch (field) + { + case PG_DIAG_MESSAGE_PRIMARY: + set_errdata_field(&edata->message, str); + break; + + case PG_DIAG_MESSAGE_DETAIL: + set_errdata_field(&edata->detail, str); + break; + + case PG_DIAG_MESSAGE_HINT: + set_errdata_field(&edata->hint, str); + break; + + case PG_DIAG_CONTEXT: + set_errdata_field(&edata->context, str); + break; + + case PG_DIAG_COLUMN_NAME: + set_errdata_field(&edata->column_name, str); + break; + + case PG_DIAG_TABLE_NAME: + set_errdata_field(&edata->table_name, str); + break; + + case PG_DIAG_SCHEMA_NAME: + set_errdata_field(&edata->schema_name, str); + break; + + case PG_DIAG_CONSTRAINT_NAME: + set_errdata_field(&edata->constraint_name, str); + break; + + default: + elog(ERROR, "unsupported or unknown ErrorData field id: %d", field); + } + + return 0; /* return value does not matter */ + } + + /* + * set_errdata_field --- set an ErrorData string field + */ + static void + set_errdata_field(char **ptr, const char *str) + { + Assert(*ptr == NULL); + *ptr = MemoryContextStrdup(ErrorContext, str); + } + + /* * geterrcode --- return the currently set SQLSTATE error code * * This is only intended for use in error callback subroutines, since there *************** CopyErrorData(void) *** 1374,1379 **** --- 1448,1461 ---- newedata->context = pstrdup(newedata->context); if (newedata->internalquery) newedata->internalquery = pstrdup(newedata->internalquery); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); return newedata; } *************** FreeErrorData(ErrorData *edata) *** 1399,1404 **** --- 1481,1494 ---- pfree(edata->context); if (edata->internalquery) pfree(edata->internalquery); + if (edata->column_name) + pfree(edata->column_name); + if (edata->table_name) + pfree(edata->table_name); + if (edata->schema_name) + pfree(edata->schema_name); + if (edata->constraint_name) + pfree(edata->constraint_name); pfree(edata); } *************** ReThrowError(ErrorData *edata) *** 1471,1476 **** --- 1561,1574 ---- newedata->context = pstrdup(newedata->context); if (newedata->internalquery) newedata->internalquery = pstrdup(newedata->internalquery); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); recursion_depth--; PG_RE_THROW(); *************** send_message_to_frontend(ErrorData *edat *** 2695,2700 **** --- 2793,2822 ---- err_sendstring(&msgbuf, edata->funcname); } + if (edata->column_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_COLUMN_NAME); + err_sendstring(&msgbuf, edata->column_name); + } + + if (edata->table_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_TABLE_NAME); + err_sendstring(&msgbuf, edata->table_name); + } + + if (edata->constraint_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME); + err_sendstring(&msgbuf, edata->constraint_name); + } + + if (edata->schema_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME); + err_sendstring(&msgbuf, edata->schema_name); + } + pq_sendbyte(&msgbuf, '\0'); /* terminator */ } else diff src/backend/utils/error/relerror.c new file mode 100644 index ...d327845 *** a/src/backend/utils/error/relerror.c --- b/src/backend/utils/error/relerror.c *************** *** 0 **** --- 1,64 ---- + /*------------------------------------------------------------------------- + * + * relerror.c relation error logging utility functions + * + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/utils/error/relerror.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "utils/elog.h" + #include "utils/lsyscache.h" + #include "utils/rel.h" + + /* + * errtablecol --- sets schema_name, table_name and column_name of a column within + * errordata. Since table and table schema are set, rel must be an ordinary + * table. + */ + int + errtablecol(Relation table, const char *colname) + { + Assert(table->rd_rel->relkind == RELKIND_RELATION); + + err_generic_string(PG_DIAG_SCHEMA_NAME, + get_namespace_name(RelationGetNamespace(table))); + err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(table)); + err_generic_string(PG_DIAG_COLUMN_NAME, colname); + + return 0; + } + + /* + * errtable --- sets schema_name and table_name within errordata. Since table and + * table schema are set, rel must be an ordinary table. + */ + int + errtable(Relation table) + { + Assert(table->rd_rel->relkind == RELKIND_RELATION); + + err_generic_string(PG_DIAG_SCHEMA_NAME, + get_namespace_name(RelationGetNamespace(table))); + err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(table)); + + return 0; + } + + /* + * errcontraint --- sets constraint_name within errordata. + */ + int + errconstraint(const char *cname) + { + err_generic_string(PG_DIAG_CONSTRAINT_NAME, cname); + + return 0; + } diff src/backend/utils/generate-errcodes.pl index b04076f..87e101d *** a/src/backend/utils/generate-errcodes.pl --- b/src/backend/utils/generate-errcodes.pl *************** while (<$errcodes>) *** 28,33 **** --- 28,39 ---- print "\n/* $header */\n"; next; } + elsif (/(^Requirement:.*)/) + { + my $header = $1; + print "/* $header */\n"; + next; + } die "unable to parse errcodes.txt" unless /^([^\s]{5})\s+[EWS]\s+([^\s]+)/; diff src/backend/utils/sort/tuplesort.c index d63c24d..8b750dd *** a/src/backend/utils/sort/tuplesort.c --- b/src/backend/utils/sort/tuplesort.c *************** comparetup_index_btree(const SortTuple * *** 3014,3019 **** --- 3014,3020 ---- * for equal keys at the end. */ ScanKey scanKey = state->indexScanKey; + Relation indexRel = state->indexRel; IndexTuple tuple1; IndexTuple tuple2; int keysz; *************** comparetup_index_btree(const SortTuple * *** 3038,3044 **** tuple1 = (IndexTuple) a->tuple; tuple2 = (IndexTuple) b->tuple; keysz = state->nKeys; ! tupDes = RelationGetDescr(state->indexRel); scanKey++; for (nkey = 2; nkey <= keysz; nkey++, scanKey++) { --- 3039,3045 ---- tuple1 = (IndexTuple) a->tuple; tuple2 = (IndexTuple) b->tuple; keysz = state->nKeys; ! tupDes = RelationGetDescr(indexRel); scanKey++; for (nkey = 2; nkey <= keysz; nkey++, scanKey++) { *************** comparetup_index_btree(const SortTuple * *** 3075,3080 **** --- 3076,3083 ---- { Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + const char *indrelname = RelationGetRelationName(indexRel); + Relation heapRel = RelationIdGetRelation(indexRel->rd_index->indrelid); /* * Some rather brain-dead implementations of qsort (such as the one in *************** comparetup_index_btree(const SortTuple * *** 3088,3097 **** ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION), errmsg("could not create unique index \"%s\"", ! RelationGetRelationName(state->indexRel)), errdetail("Key %s is duplicated.", ! BuildIndexValueDescription(state->indexRel, ! values, isnull)))); } /* --- 3091,3101 ---- ereport(ERROR, (errcode(ERRCODE_UNIQUE_VIOLATION), errmsg("could not create unique index \"%s\"", ! indrelname), errdetail("Key %s is duplicated.", ! BuildIndexValueDescription(indexRel, values, isnull)), ! errtable(heapRel), ! errconstraint(indrelname))); } /* diff src/include/postgres_ext.h index 5ba379f..9776541 *** a/src/include/postgres_ext.h --- b/src/include/postgres_ext.h *************** typedef PG_INT64_TYPE pg_int64; *** 60,64 **** --- 60,68 ---- #define PG_DIAG_SOURCE_FILE 'F' #define PG_DIAG_SOURCE_LINE 'L' #define PG_DIAG_SOURCE_FUNCTION 'R' + #define PG_DIAG_COLUMN_NAME 'c' + #define PG_DIAG_TABLE_NAME 't' + #define PG_DIAG_CONSTRAINT_NAME 'n' + #define PG_DIAG_SCHEMA_NAME 's' #endif /* POSTGRES_EXT_H */ diff src/include/utils/elog.h index cbbda04..27b84d5 *** a/src/include/utils/elog.h --- b/src/include/utils/elog.h *************** extern int geterrcode(void); *** 206,211 **** --- 206,212 ---- extern int geterrposition(void); extern int getinternalerrposition(void); + extern int err_generic_string(int field, const char *str); /*---------- * Old-style error reporting API: to be used in this way: *************** typedef struct ErrorData *** 338,343 **** --- 339,348 ---- char *detail_log; /* detail error message for server log only */ char *hint; /* hint message */ char *context; /* context message */ + char *column_name; /* name of column */ + char *table_name; /* name of table */ + char *constraint_name; /* name of constraint */ + char *schema_name; /* name of schema */ int cursorpos; /* cursor index into query string */ int internalpos; /* cursor index into internalquery */ char *internalquery; /* text of internally-generated query */ diff src/include/utils/rel.h index bde5f17..0357b04 *** a/src/include/utils/rel.h --- b/src/include/utils/rel.h *************** typedef struct StdRdOptions *** 398,401 **** --- 398,406 ---- extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); + /* routines in utils/error/relerror.c */ + extern int errtablecol(Relation table, const char *colname); + extern int errtable(Relation table); + extern int errconstraint(const char *cname); + #endif /* REL_H */ diff src/interfaces/libpq/fe-protocol3.c index 40e75d4..d71e331 *** a/src/interfaces/libpq/fe-protocol3.c --- b/src/interfaces/libpq/fe-protocol3.c *************** pqGetErrorNotice3(PGconn *conn, bool isE *** 936,941 **** --- 936,958 ---- valf, vall); appendPQExpBufferChar(&workBuf, '\n'); } + + val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("COLUMN NAME: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_TABLE_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("TABLE NAME: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("CONSTRAINT NAME: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("SCHEMA NAME: %s\n"), val); } /* diff src/pl/plpgsql/src/generate-plerrcodes.pl index 90e1010..ccd94bd *** a/src/pl/plpgsql/src/generate-plerrcodes.pl --- b/src/pl/plpgsql/src/generate-plerrcodes.pl *************** while (<$errcodes>) *** 20,27 **** next if /^#/; next if /^\s*$/; ! # Skip section headers ! next if /^Section:/; die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/; --- 20,27 ---- next if /^#/; next if /^\s*$/; ! # Skip section headers, and requirement notes ! next if /^Section:/ or /^Requirement:/; die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;