*** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** *** 4246,4255 **** FROM pg_stat_activity; error context, user query that led to the error (if any and enabled by log_min_error_statement), ! character count of the error position therein, ! location of the error in the PostgreSQL source code ! (if log_error_verbosity is set to verbose), ! and application name. Here is a sample table definition for storing CSV-format log output: --- 4246,4256 ---- error context, user query that led to the error (if any and enabled by log_min_error_statement), ! character count of the error position therein, and, if ! log_error_verbosity is set to verbose, ! column name, table name, constraint name and location of the ! error in the PostgreSQL source code. ! Application name is also listed. Here is a sample table definition for storing CSV-format log output: *************** *** 4277,4282 **** CREATE TABLE postgres_log --- 4278,4291 ---- query text, query_pos integer, location text, + column_name text, + table_name text, + constraint_name text, + schema_name text, + routine_name text, + routine_schema text, + routine_oid oid, + trigger_name text, application_name text, PRIMARY KEY (session_id, session_line_num) ); *** a/doc/src/sgml/errcodes.sgml --- b/doc/src/sgml/errcodes.sgml *************** *** 42,47 **** --- 42,59 ---- + 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). These additional fields are not + described by the SQL standard. + + + 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 *** a/doc/src/sgml/generate-errcodes-table.pl --- b/doc/src/sgml/generate-errcodes-table.pl *************** *** 41,46 **** while (<$errcodes>) --- 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) = *** a/doc/src/sgml/protocol.sgml --- b/doc/src/sgml/protocol.sgml *************** *** 4786,4791 **** message. --- 4786,4881 ---- + + + 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. + + + + + + + r + + + + Routine name: the name of routine that reports error. + + + + + + + u + + + + Routine schema: the schema of routine that reports error. + + + + + + + O + + + + Routine oid: the object id of routine that reports error. + + + + + + + T + + + + Trigger name: the name of trigger that caused error. + + + + *** a/src/backend/access/nbtree/nbtinsert.c --- b/src/backend/access/nbtree/nbtinsert.c *************** *** 393,399 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, 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) *************** *** 455,461 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, (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); *************** *** 533,539 **** _bt_findinsertloc(Relation rel, 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, *** a/src/backend/commands/tablecmds.c --- b/src/backend/commands/tablecmds.c *************** *** 3819,3828 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) 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) *************** *** 3836,3842 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) 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 */ *************** *** 6653,6659 **** validateCheckConstraint(Relation rel, HeapTuple constrtup) 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); } *** a/src/backend/commands/typecmds.c --- b/src/backend/commands/typecmds.c *************** *** 98,104 **** static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid); 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, *************** *** 2258,2270 **** AlterDomainNotNull(List *names, bool notNull) 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); --- 2258,2271 ---- 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); *************** *** 2470,2476 **** AlterDomainAddConstraint(List *names, Node *newConstraint) * attributes based on the domain the constraint is being added to. */ if (!constr->skip_validation) ! validateDomainConstraint(domainoid, ccbin); /* Clean up */ heap_close(typrel, RowExclusiveLock); --- 2471,2477 ---- * 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); *************** *** 2555,2561 **** AlterDomainValidateConstraint(List *names, char *constrName) HeapTupleGetOid(tuple)); conbin = TextDatumGetCString(val); ! validateDomainConstraint(domainoid, conbin); /* * Now update the catalog, while we have the door open. --- 2556,2562 ---- HeapTupleGetOid(tuple)); conbin = TextDatumGetCString(val); ! validateDomainConstraint(domainoid, conbin, constrName); /* * Now update the catalog, while we have the door open. *************** *** 2576,2582 **** AlterDomainValidateConstraint(List *names, char *constrName) } static void ! validateDomainConstraint(Oid domainoid, char *ccbin) { Expr *expr = (Expr *) stringToNode(ccbin); List *rels; --- 2577,2583 ---- } static void ! validateDomainConstraint(Oid domainoid, char *ccbin, char *conname) { Expr *expr = (Expr *) stringToNode(ccbin); List *rels; *************** *** 2618,2623 **** validateDomainConstraint(Oid domainoid, char *ccbin) --- 2619,2625 ---- Datum d; bool isNull; Datum conResult; + const char *col = NameStr(tupdesc->attrs[attnum - 1]->attname); d = heap_getattr(tuple, attnum, tupdesc, &isNull); *************** *** 2632,2639 **** validateDomainConstraint(Oid domainoid, char *ccbin) 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); --- 2634,2644 ---- 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); *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 1522,1535 **** ExecConstraints(ResultRelInfo *resultRelInfo, 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)))); } } *************** *** 1543,1549 **** ExecConstraints(ResultRelInfo *resultRelInfo, 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))); } } *** 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" *************** *** 3864,3869 **** ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, --- 3865,3876 ---- CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr; Datum result; ListCell *l; + Relation heapRel = NULL; + struct EState *estate = econtext->ecxt_estate; + + /* 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); *************** *** 3878,3887 **** ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, { 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: { *************** *** 3907,3917 **** ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, 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; *** a/src/backend/executor/execUtils.c --- b/src/backend/executor/execUtils.c *************** *** 1307,1320 **** retry: 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); *** 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" /* *************** *** 123,132 **** domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) --- 124,140 ---- { 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", format_type_be(my_extra->domain_type)))); + } break; case DOM_CONSTRAINT_CHECK: { *************** *** 163,169 **** domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) (errcode(ERRCODE_CHECK_VIOLATION), errmsg("value for domain %s violates check constraint \"%s\"", format_type_be(my_extra->domain_type), ! con->name))); break; } default: --- 171,178 ---- (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: *** a/src/backend/utils/adt/ri_triggers.c --- b/src/backend/utils/adt/ri_triggers.c *************** *** 339,345 **** RI_FKey_check(TriggerData *trigdata) 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); *************** *** 2467,2473 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) 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 *************** *** 3219,3225 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo, 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), *************** *** 3229,3235 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo, 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)))); } *** a/src/backend/utils/errcodes.txt --- b/src/backend/utils/errcodes.txt *************** *** 200,213 **** Section: Class 22 - Data Exception 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 *** a/src/backend/utils/error/Makefile --- b/src/backend/utils/error/Makefile *************** *** 12,17 **** subdir = src/backend/utils/error 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 *** a/src/backend/utils/error/elog.c --- b/src/backend/utils/error/elog.c *************** *** 130,135 **** static void write_syslog(int level, const char *line); --- 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; *************** *** 477,482 **** errfinish(int dummy,...) --- 478,497 ---- 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); + if (edata->routine_name) + pfree(edata->routine_name); + if (edata->routine_schema) + pfree(edata->routine_schema); + if (edata->trigger_name) + pfree(edata->trigger_name); errordata_stack_depth--; *************** *** 1101,1106 **** internalerrquery(const char *query) --- 1116,1214 ---- } /* + * errroutineid -- set routine_oid of top custom functions + */ + int + errroutineid(Oid routineid) + { + ErrorData *edata = &errordata[errordata_stack_depth]; + + /* we don't bother incrementing recursion_depth */ + CHECK_STACK_DEPTH(); + + edata->routine_oid = routineid; + + return 0; /* return value does not matter */ + } + + /* + * err_generic_string -- used to set individual ErrorData string fields. + * + * Callers should prefer higher-level abstractions such as those 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; + + case PG_DIAG_ROUTINE_NAME: + set_errdata_field(&edata->routine_name, str); + break; + + case PG_DIAG_ROUTINE_SCHEMA: + set_errdata_field(&edata->routine_schema, str); + break; + + case PG_DIAG_TRIGGER_NAME: + set_errdata_field(&edata->trigger_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 *************** *** 1374,1379 **** CopyErrorData(void) --- 1482,1501 ---- 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); + if (newedata->routine_name) + newedata->routine_name = pstrdup(newedata->routine_name); + if (newedata->routine_schema) + newedata->routine_schema = pstrdup(newedata->routine_schema); + if (newedata->trigger_name) + newedata->trigger_name = pstrdup(newedata->trigger_name); return newedata; } *************** *** 1399,1404 **** FreeErrorData(ErrorData *edata) --- 1521,1541 ---- 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); + if (edata->routine_name) + pfree(edata->routine_name); + if (edata->routine_schema) + pfree(edata->routine_schema); + if (edata->trigger_name) + pfree(edata->trigger_name); + pfree(edata); } *************** *** 1471,1476 **** ReThrowError(ErrorData *edata) --- 1608,1627 ---- 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); + if (newedata->routine_name) + newedata->routine_name = pstrdup(newedata->routine_name); + if (newedata->routine_schema) + newedata->routine_schema = pstrdup(newedata->routine_schema); + if (newedata->trigger_name) + newedata->trigger_name = pstrdup(newedata->trigger_name); recursion_depth--; PG_RE_THROW(); *************** *** 2275,2282 **** write_csvlog(ErrorData *edata) edata->filename, edata->lineno); appendCSVLiteral(&buf, msgbuf.data); pfree(msgbuf.data); } - appendStringInfoChar(&buf, ','); /* application name */ if (application_name) --- 2426,2457 ---- edata->filename, edata->lineno); appendCSVLiteral(&buf, msgbuf.data); pfree(msgbuf.data); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->column_name); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->table_name); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->schema_name); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->constraint_name); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->routine_name); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->routine_schema); + appendStringInfoChar(&buf, ','); + + appendStringInfo(&buf, "%d", edata->routine_oid); + appendStringInfoChar(&buf, ','); + + appendCSVLiteral(&buf, edata->trigger_name); + appendStringInfoChar(&buf, ','); } /* application name */ if (application_name) *************** *** 2385,2391 **** send_message_to_server_log(ErrorData *edata) } if (Log_error_verbosity >= PGERROR_VERBOSE) { ! /* assume no newlines in funcname or filename... */ if (edata->funcname && edata->filename) { log_line_prefix(&buf, edata); --- 2560,2569 ---- } if (Log_error_verbosity >= PGERROR_VERBOSE) { ! /* ! * assume no newlines in funcname, filename, column_name, ! * table_name, constraint_name or schema_name... ! */ if (edata->funcname && edata->filename) { log_line_prefix(&buf, edata); *************** *** 2399,2404 **** send_message_to_server_log(ErrorData *edata) --- 2577,2630 ---- appendStringInfo(&buf, _("LOCATION: %s:%d\n"), edata->filename, edata->lineno); } + if (edata->column_name && edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"), + edata->table_name, edata->column_name); + } + else if (edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("TABLE NAME: %s\n"), + edata->table_name); + } + if (edata->constraint_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("CONSTRAINT NAME: %s\n"), + edata->constraint_name); + } + if (edata->schema_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("SCHEMA NAME: %s\n"), + edata->schema_name); + } + if (edata->routine_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("ROUTINE NAME: %s\n"), + edata->routine_name); + } + if (edata->routine_schema) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("ROUTINE SCHEMA: %s\n"), + edata->routine_schema); + } + if (edata->routine_oid != InvalidOid) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("ROUTINE OID: %d\n"), + edata->routine_oid); + } + if (edata->trigger_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("TRIGGER NAME: %s\n"), + edata->trigger_name); + } } } *************** *** 2695,2700 **** send_message_to_frontend(ErrorData *edata) --- 2921,2975 ---- 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); + } + + if (edata->routine_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME); + err_sendstring(&msgbuf, edata->routine_name); + } + + if (edata->routine_schema) + { + pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA); + err_sendstring(&msgbuf, edata->routine_schema); + } + + if (edata->routine_oid != InvalidOid) + { + snprintf(tbuf, sizeof(tbuf), "%d", edata->routine_oid); + pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_OID); + err_sendstring(&msgbuf, tbuf); + } + + if (edata->trigger_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME); + err_sendstring(&msgbuf, edata->trigger_name); + } + pq_sendbyte(&msgbuf, '\0'); /* terminator */ } else *** a/src/backend/utils/generate-errcodes.pl --- b/src/backend/utils/generate-errcodes.pl *************** *** 28,33 **** while (<$errcodes>) --- 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]+)/; *** a/src/backend/utils/sort/tuplesort.c --- b/src/backend/utils/sort/tuplesort.c *************** *** 3014,3019 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b, --- 3014,3020 ---- * for equal keys at the end. */ ScanKey scanKey = state->indexScanKey; + Relation indexRel = state->indexRel; IndexTuple tuple1; IndexTuple tuple2; int keysz; *************** *** 3038,3044 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b, 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++) { *************** *** 3075,3080 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b, --- 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 *************** *** 3088,3097 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b, 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))); } /* *** a/src/include/postgres_ext.h --- b/src/include/postgres_ext.h *************** *** 60,64 **** typedef PG_INT64_TYPE pg_int64; --- 60,72 ---- #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' + #define PG_DIAG_ROUTINE_NAME 'r' + #define PG_DIAG_ROUTINE_SCHEMA 'u' + #define PG_DIAG_ROUTINE_OID 'O' + #define PG_DIAG_TRIGGER_NAME 'T' #endif /* POSTGRES_EXT_H */ *** a/src/include/utils/elog.h --- b/src/include/utils/elog.h *************** *** 206,211 **** extern int geterrcode(void); --- 206,213 ---- extern int geterrposition(void); extern int getinternalerrposition(void); + extern int errroutineid(Oid routineid); + extern int err_generic_string(int field, const char *str); /*---------- * Old-style error reporting API: to be used in this way: *************** *** 338,343 **** typedef struct ErrorData --- 340,353 ---- 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 */ + char *routine_name; /* name of routine */ + char *routine_schema; /* schema of routine */ + Oid routine_oid; /* oid of routine */ + char *trigger_name; /* name of trigger */ int cursorpos; /* cursor index into query string */ int internalpos; /* cursor index into internalquery */ char *internalquery; /* text of internally-generated query */ *** a/src/include/utils/rel.h --- b/src/include/utils/rel.h *************** *** 398,401 **** typedef struct StdRdOptions --- 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 */ *** a/src/interfaces/libpq/fe-protocol3.c --- b/src/interfaces/libpq/fe-protocol3.c *************** *** 936,941 **** pqGetErrorNotice3(PGconn *conn, bool isError) --- 936,974 ---- 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); + val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("ROUTINE NAME: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("ROUTINE SHEMA: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_ROUTINE_OID); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("ROUTINE OID: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("TRIGGER NAME: %s\n"), val); } /* *** a/src/pl/plpgsql/src/generate-plerrcodes.pl --- b/src/pl/plpgsql/src/generate-plerrcodes.pl *************** *** 20,27 **** while (<$errcodes>) 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]+)?/; *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** *** 344,349 **** do_compile(FunctionCallInfo fcinfo, --- 344,351 ---- compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid); + function->fn_name = get_func_name(fcinfo->flinfo->fn_oid); + function->fn_schema = get_namespace_name(get_func_namespace(fcinfo->flinfo->fn_oid)); function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_tid = procTup->t_self; *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** *** 583,588 **** plpgsql_exec_trigger(PLpgSQL_function *func, --- 583,590 ---- var->isnull = false; var->freeval = true; + estate.trigger_name = trigdata->tg_trigger->tgname; + var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]); if (TRIGGER_FIRED_BEFORE(trigdata->tg_event)) var->value = CStringGetTextDatum("BEFORE"); *************** *** 917,922 **** plpgsql_exec_error_callback(void *arg) --- 919,941 ---- else errcontext("PL/pgSQL function %s", estate->func->fn_signature); + + /* + * if execution of this function is on top of error_context_stack, + * then set enhanced diagnostics fields related to function. + */ + if (error_context_stack->callback == plpgsql_exec_error_callback && + (PLpgSQL_execstate *) error_context_stack->arg == estate) + { + PLpgSQL_function *func = estate->func; + + errroutineid(func->fn_oid); + err_generic_string(PG_DIAG_ROUTINE_NAME, func->fn_name); + err_generic_string(PG_DIAG_ROUTINE_SCHEMA, func->fn_schema); + + if (func->fn_is_trigger != PLPGSQL_NOT_TRIGGER) + err_generic_string(PG_DIAG_TRIGGER_NAME, estate->trigger_name); + } } *************** *** 3053,3058 **** plpgsql_estate_setup(PLpgSQL_execstate *estate, --- 3072,3078 ---- estate->err_stmt = NULL; estate->err_text = NULL; + estate->trigger_name = NULL; estate->plugin_info = NULL; *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** *** 686,691 **** typedef enum PLpgSQL_trigtype --- 686,693 ---- typedef struct PLpgSQL_function { /* Complete compiled function */ char *fn_signature; + char *fn_schema; + char *fn_name; /* fields for enhanced diagnostics */ Oid fn_oid; TransactionId fn_xmin; ItemPointerData fn_tid; *************** *** 754,759 **** typedef struct PLpgSQL_execstate --- 756,762 ---- char *exitlabel; /* the "target" label of the current EXIT or * CONTINUE stmt, if any */ ErrorData *cur_error; /* current exception handler's error */ + char *trigger_name; /* name of trigger for enhanced diagnostics */ Tuplestorestate *tuple_store; /* SRFs accumulate results here */ MemoryContext tuple_store_cxt;