diff --git a/doc/src/sgml/errcodes.sgml b/doc/src/sgml/errcodes.sgml index 16cb6c7fcdb..40b4191c104 100644 --- a/doc/src/sgml/errcodes.sgml +++ b/doc/src/sgml/errcodes.sgml @@ -27,7 +27,7 @@ According to the standard, the first two characters of an error code denote a class of errors, while the last three characters indicate a specific condition within that class. Thus, an application that - does not recognize the specific error code can still be able to infer + does not recognize the specific error code might still be able to infer what to do from the error class. @@ -42,13 +42,25 @@ - The symbol shown in the column Condition Name is also + The symbol shown in the column Condition Name is the condition name to use in PL/pgSQL. Condition names can be written in either upper or lower case. (Note that PL/pgSQL does not recognize warning, as opposed to error, condition names; those are classes 00, 01, and 02.) + + For some types of errors, the server reports the name of a database object + (a table, table column, data type, or constraint) associated with the error; + for example, the name of the unique constraint that caused a + unique_violation error. Such names are supplied in separate + fields of the error report message so that applications need not try to + extract them from the possibly-localized human-readable text of the message. + As of PostgreSQL 9.3, complete coverage for this feature + exists only for errors in SQLSTATE class 23 (integrity constraint + violation), but this is likely to be expanded in future. + + <productname>PostgreSQL</productname> Error Codes diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index e36dd4b1d12..aa2ec2ab7bb 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2679,6 +2679,62 @@ char *PQresultErrorField(const PGresult *res, int fieldcode); + + PG_DIAG_SCHEMA_NAME + + + If the error was associated with a specific database object, + the name of the schema containing that object, if any. + + + + + + PG_DIAG_TABLE_NAME + + + If the error was associated with a specific table, the name of + the table. (When this field is present, the schema name field + provides the name of the table's schema.) + + + + + + PG_DIAG_COLUMN_NAME + + + If the error was associated with a specific table column, the + name of the column. (When this field is present, the schema + and table name fields identify the table.) + + + + + + PG_DIAG_DATATYPE_NAME + + + If the error was associated with a specific datatype, the name + of the datatype. (When this field is present, the schema name + field provides the name of the datatype's schema.) + + + + + + PG_DIAG_CONSTRAINT_NAME + + + If the error was associated with a specific constraint, + the name of the constraint. The table or domain that the + constraint belongs to is reported using the fields listed + above. (For this purpose, indexes are treated as constraints, + even if they weren't created with constraint syntax.) + + + + PG_DIAG_SOURCE_FILE @@ -2710,6 +2766,14 @@ char *PQresultErrorField(const PGresult *res, int fieldcode); + + + The fields for schema name, table name, column name, datatype + name, and constraint name are supplied only for a limited number + of error types; see . + + + The client is responsible for formatting displayed information to meet its needs; in particular it should break long lines as needed. diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index baae59de6e9..1a750593707 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -4757,6 +4757,72 @@ message. + + +s + + + + Schema name: if the error was associated with a specific database + object, the name of the schema containing that object, if any. + + + + + + +t + + + + Table name: if the error was associated with a specific table, the + name of the table. (When this field is present, the schema name field + provides the name of the table's schema.) + + + + + + +c + + + + Column name: if the error was associated with a specific table column, + the name of the column. (When this field is present, the schema and + table name fields identify the table.) + + + + + + +d + + + + Datatype name: if the error was associated with a specific datatype, + the name of the datatype. (When this field is present, the schema + name field provides the name of the datatype's schema.) + + + + + + +n + + + + Constraint name: if the error was associated with a specific + constraint, the name of the constraint. The table or domain that the + constraint belongs to is reported using the fields listed above. (For + this purpose, indexes are treated as constraints, even if they weren't + created with constraint syntax.) + + + + F @@ -4794,6 +4860,14 @@ message. + + + The fields for schema name, table name, column name, datatype name, and + constraint name are supplied only for a limited number of error types; + see . + + + The client is responsible for formatting displayed information to meet its needs; in particular it should break long lines as needed. Newline characters diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml index 4ed83d6189b..4b78679d32d 100644 --- a/doc/src/sgml/sources.sgml +++ b/doc/src/sgml/sources.sgml @@ -273,6 +273,45 @@ ereport(ERROR, query processing. + + + errtable(Relation rel) specifies a relation whose + name and schema name should be included as auxiliary fields in the error + report. + + + + + errtablecol(Relation rel, int attnum) specifies + a column whose name, table name, and schema name should be included as + auxiliary fields in the error report. + + + + + errtableconstraint(Relation rel, const char *conname) + specifies a table constraint whose name, table name, and schema name + should be included as auxiliary fields in the error report. Indexes + should be considered to be constraints for this purpose, whether or + not they have an associated pg_constraint entry. Be + careful to pass the underlying heap relation, not the index itself, as + rel. + + + + + errdatatype(Oid datatypeOid) specifies a data + type whose name and schema name should be included as auxiliary fields + in the error report. + + + + + errdomainconstraint(Oid datatypeOid, const char *conname) + specifies a domain constraint whose name, domain name, and schema name + should be included as auxiliary fields in the error report. + + errcode_for_file_access() is a convenience function that @@ -301,6 +340,23 @@ ereport(ERROR, + + + At most one of the functions errtable, + errtablecol, errtableconstraint, + errdatatype, or errdomainconstraint should + be used in an ereport call. These functions exist to + allow applications to extract the name of a database object associated + with the error condition without having to examine the + potentially-localized error message text. + These functions should be used in error reports for which it's likely + that applications would wish to have automatic error handling. As of + PostgreSQL 9.3, complete coverage exists only for + errors in SQLSTATE class 23 (integrity constraint violation), but this + is likely to be expanded in future. + + + There is an older function elog that is still heavily used. An elog call: diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 0639ad286af..2813dd1e749 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -86,7 +86,7 @@ hashbuild(PG_FUNCTION_ARGS) * one page. */ if (num_buckets >= (uint32) NBuffers) - buildstate.spool = _h_spoolinit(index, num_buckets); + buildstate.spool = _h_spoolinit(heap, index, num_buckets); else buildstate.spool = NULL; diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c index 21fcb6bab8c..1dfa7109ca6 100644 --- a/src/backend/access/hash/hashsort.c +++ b/src/backend/access/hash/hashsort.c @@ -44,7 +44,7 @@ struct HSpool * create and initialize a spool structure */ HSpool * -_h_spoolinit(Relation index, uint32 num_buckets) +_h_spoolinit(Relation heap, Relation index, uint32 num_buckets) { HSpool *hspool = (HSpool *) palloc0(sizeof(HSpool)); uint32 hash_mask; @@ -67,7 +67,8 @@ _h_spoolinit(Relation index, uint32 num_buckets) * speed index creation. This should be OK since a single backend can't * run multiple index creations in parallel. */ - hspool->sortstate = tuplesort_begin_index_hash(index, + hspool->sortstate = tuplesort_begin_index_hash(heap, + index, hash_mask, maintenance_work_mem, false); diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 4432bb1bdae..6ed51fea156 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -393,7 +393,9 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel, RelationGetRelationName(rel)), errdetail("Key %s already exists.", BuildIndexValueDescription(rel, - values, isnull)))); + values, isnull)), + errtableconstraint(heapRel, + RelationGetRelationName(rel)))); } } else if (all_dead) @@ -455,7 +457,9 @@ _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."))); + errhint("This may be because of a non-immutable index expression."), + errtableconstraint(heapRel, + RelationGetRelationName(rel)))); if (nbuf != InvalidBuffer) _bt_relbuf(rel, nbuf); @@ -523,6 +527,8 @@ _bt_findinsertloc(Relation rel, * able to fit three items on every page, so restrict any one item to 1/3 * the per-page available space. Note that at this point, itemsz doesn't * include the ItemId. + * + * NOTE: if you change this, see also the similar code in _bt_buildadd(). */ if (itemsz > BTMaxItemSize(page)) ereport(ERROR, @@ -533,7 +539,9 @@ _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."))); + "or use full text indexing."), + errtableconstraint(heapRel, + RelationGetRelationName(rel)))); /*---------- * If we will need to split the page to put the item on this page, diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 159b57fcd1f..0e041683652 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -109,14 +109,14 @@ btbuild(PG_FUNCTION_ARGS) elog(ERROR, "index \"%s\" already contains data", RelationGetRelationName(index)); - buildstate.spool = _bt_spoolinit(index, indexInfo->ii_Unique, false); + buildstate.spool = _bt_spoolinit(heap, index, indexInfo->ii_Unique, false); /* * If building a unique index, put dead tuples in a second spool to keep * them out of the uniqueness check. */ if (indexInfo->ii_Unique) - buildstate.spool2 = _bt_spoolinit(index, false, true); + buildstate.spool2 = _bt_spoolinit(heap, index, false, true); /* do the heap scan */ reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c index b369e86e079..df867a6a1a7 100644 --- a/src/backend/access/nbtree/nbtsort.c +++ b/src/backend/access/nbtree/nbtsort.c @@ -83,6 +83,7 @@ struct BTSpool { Tuplesortstate *sortstate; /* state data for tuplesort.c */ + Relation heap; Relation index; bool isunique; }; @@ -116,6 +117,7 @@ typedef struct BTPageState */ typedef struct BTWriteState { + Relation heap; Relation index; bool btws_use_wal; /* dump pages to WAL? */ BlockNumber btws_pages_alloced; /* # pages allocated */ @@ -145,11 +147,12 @@ static void _bt_load(BTWriteState *wstate, * create and initialize a spool structure */ BTSpool * -_bt_spoolinit(Relation index, bool isunique, bool isdead) +_bt_spoolinit(Relation heap, Relation index, bool isunique, bool isdead) { BTSpool *btspool = (BTSpool *) palloc0(sizeof(BTSpool)); int btKbytes; + btspool->heap = heap; btspool->index = index; btspool->isunique = isunique; @@ -162,7 +165,7 @@ _bt_spoolinit(Relation index, bool isunique, bool isdead) * work_mem. */ btKbytes = isdead ? work_mem : maintenance_work_mem; - btspool->sortstate = tuplesort_begin_index_btree(index, isunique, + btspool->sortstate = tuplesort_begin_index_btree(heap, index, isunique, btKbytes, false); return btspool; @@ -208,6 +211,7 @@ _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2) if (btspool2) tuplesort_performsort(btspool2->sortstate); + wstate.heap = btspool->heap; wstate.index = btspool->index; /* @@ -486,7 +490,9 @@ _bt_buildadd(BTWriteState *wstate, BTPageState *state, IndexTuple itup) RelationGetRelationName(wstate->index)), 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."))); + "or use full text indexing."), + errtableconstraint(wstate->heap, + RelationGetRelationName(wstate->index)))); /* * Check to see if page is "full". It's definitely full if the item won't diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1d5e0c646a4..eeddd9a80b9 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -3828,7 +3828,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" contains null values", - NameStr(newTupDesc->attrs[attn]->attname)))); + NameStr(newTupDesc->attrs[attn]->attname)), + errtablecol(oldrel, attn + 1))); } foreach(l, tab->constraints) @@ -3842,7 +3843,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", - con->name))); + con->name), + errtableconstraint(oldrel, con->name))); break; case CONSTR_FOREIGN: /* Nothing to do here */ @@ -6659,7 +6661,8 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup) ereport(ERROR, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("check constraint \"%s\" is violated by some row", - NameStr(constrForm->conname)))); + NameStr(constrForm->conname)), + errtableconstraint(rel, NameStr(constrForm->conname)))); ResetExprContext(econtext); } diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 7a724161f11..0e55263d4ef 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2264,11 +2264,22 @@ AlterDomainNotNull(List *names, bool notNull) int attnum = rtc->atts[i]; if (heap_attisnull(tuple, attnum)) + { + /* + * In principle the auxiliary information for this + * error should be errdatatype(), but errtablecol() + * seems considerably more useful in practice. Since + * this code only executes in an ALTER DOMAIN command, + * the client should already know which domain is in + * question. + */ ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" of table \"%s\" contains null values", NameStr(tupdesc->attrs[attnum - 1]->attname), - RelationGetRelationName(testrel)))); + RelationGetRelationName(testrel)), + errtablecol(testrel, attnum))); + } } } heap_endscan(scan); @@ -2469,7 +2480,7 @@ AlterDomainAddConstraint(List *names, Node *newConstraint) * to pg_constraint. */ - ccbin = domainAddConstraint(HeapTupleGetOid(tup), typTup->typnamespace, + ccbin = domainAddConstraint(domainoid, typTup->typnamespace, typTup->typbasetype, typTup->typtypmod, constr, NameStr(typTup->typname)); @@ -2641,11 +2652,22 @@ validateDomainConstraint(Oid domainoid, char *ccbin) &isNull, NULL); if (!isNull && !DatumGetBool(conResult)) + { + /* + * In principle the auxiliary information for this error + * should be errdomainconstraint(), but errtablecol() + * seems considerably more useful in practice. Since this + * code only executes in an ALTER DOMAIN command, the + * client should already know which domain is in question, + * and which constraint too. + */ 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)))); + RelationGetRelationName(testrel)), + errtablecol(testrel, attnum))); + } } ResetExprContext(econtext); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index e39911f16b2..632644f1d8f 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1451,6 +1451,8 @@ ExecutePlan(EState *estate, /* * ExecRelCheck --- check that tuple meets constraints for result relation + * + * Returns NULL if OK, else name of failed check constraint */ static const char * ExecRelCheck(ResultRelInfo *resultRelInfo, @@ -1534,7 +1536,8 @@ ExecConstraints(ResultRelInfo *resultRelInfo, 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)))); + ExecBuildSlotValueDescription(slot, 64)), + errtablecol(rel, attrChk))); } } @@ -1548,7 +1551,8 @@ 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, 64)), + errtableconstraint(rel, failed))); } } diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index d9981a56f9c..62d27a75747 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -3881,7 +3881,8 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("domain %s does not allow null values", - format_type_be(ctest->resulttype)))); + format_type_be(ctest->resulttype)), + errdatatype(ctest->resulttype))); break; case DOM_CONSTRAINT_CHECK: { @@ -3911,7 +3912,9 @@ ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext, (errcode(ERRCODE_CHECK_VIOLATION), errmsg("value for domain %s violates check constraint \"%s\"", format_type_be(ctest->resulttype), - con->name))); + con->name), + errdomainconstraint(ctest->resulttype, + con->name))); econtext->domainValue_datum = save_datum; econtext->domainValue_isNull = save_isNull; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 9206195277a..11be62e9153 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -1307,14 +1307,18 @@ retry: errmsg("could not create exclusion constraint \"%s\"", RelationGetRelationName(index)), errdetail("Key %s conflicts with key %s.", - error_new, error_existing))); + error_new, error_existing), + errtableconstraint(heap, + 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))); + error_new, error_existing), + errtableconstraint(heap, + RelationGetRelationName(index)))); } index_endscan(index_scan); diff --git a/src/backend/utils/adt/domains.c b/src/backend/utils/adt/domains.c index 9d2fb1e600e..0a26222c39b 100644 --- a/src/backend/utils/adt/domains.c +++ b/src/backend/utils/adt/domains.c @@ -31,11 +31,14 @@ */ #include "postgres.h" +#include "access/htup_details.h" +#include "catalog/pg_type.h" #include "commands/typecmds.h" #include "executor/executor.h" #include "lib/stringinfo.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" /* @@ -126,7 +129,8 @@ domain_check_input(Datum value, bool isnull, DomainIOData *my_extra) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("domain %s does not allow null values", - format_type_be(my_extra->domain_type)))); + format_type_be(my_extra->domain_type)), + errdatatype(my_extra->domain_type))); break; case DOM_CONSTRAINT_CHECK: { @@ -163,7 +167,9 @@ 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))); + con->name), + errdomainconstraint(my_extra->domain_type, + con->name))); break; } default: @@ -310,7 +316,8 @@ domain_recv(PG_FUNCTION_ARGS) * setup is repeated for each call. */ void -domain_check(Datum value, bool isnull, Oid domainType, void **extra, MemoryContext mcxt) +domain_check(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt) { DomainIOData *my_extra = NULL; @@ -339,3 +346,40 @@ domain_check(Datum value, bool isnull, Oid domainType, void **extra, MemoryConte */ domain_check_input(value, isnull, my_extra); } + +/* + * errdatatype --- stores schema_name and datatype_name of a datatype + * within the current errordata. + */ +int +errdatatype(Oid datatypeOid) +{ + HeapTuple tup; + Form_pg_type typtup; + + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(datatypeOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for type %u", datatypeOid); + typtup = (Form_pg_type) GETSTRUCT(tup); + + err_generic_string(PG_DIAG_SCHEMA_NAME, + get_namespace_name(typtup->typnamespace)); + err_generic_string(PG_DIAG_DATATYPE_NAME, NameStr(typtup->typname)); + + ReleaseSysCache(tup); + + return 0; /* return value does not matter */ +} + +/* + * errdomainconstraint --- stores schema_name, datatype_name and + * constraint_name of a domain-related constraint within the current errordata. + */ +int +errdomainconstraint(Oid datatypeOid, const char *conname) +{ + errdatatype(datatypeOid); + err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname); + + return 0; /* return value does not matter */ +} diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 243bdebbd22..43228447ea4 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -337,9 +337,11 @@ RI_FKey_check(TriggerData *trigdata) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", - RelationGetRelationName(trigdata->tg_relation), + RelationGetRelationName(fk_rel), NameStr(riinfo->conname)), - errdetail("MATCH FULL does not allow mixing of null and nonnull key values."))); + errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), + errtableconstraint(fk_rel, + NameStr(riinfo->conname)))); heap_close(pk_rel, RowShareLock); return PointerGetDatum(NULL); @@ -2470,7 +2472,9 @@ 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."))); + errdetail("MATCH FULL does not allow mixing of null and nonnull key values."), + errtableconstraint(fk_rel, + NameStr(fake_riinfo.conname)))); /* * We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK @@ -3222,7 +3226,8 @@ 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)))); + RelationGetRelationName(pk_rel)), + errtableconstraint(fk_rel, NameStr(riinfo->conname)))); else ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), @@ -3232,7 +3237,8 @@ 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)))); + RelationGetRelationName(fk_rel)), + errtableconstraint(fk_rel, NameStr(riinfo->conname)))); } diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index fa48b1ce1ab..b9c03c31156 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -3998,6 +3998,82 @@ RelationGetExclusionInfo(Relation indexRelation, } +/* + * Routines to support ereport() reports of relation-related errors + * + * These could have been put into elog.c, but it seems like a module layering + * violation to have elog.c calling relcache or syscache stuff --- and we + * definitely don't want elog.h including rel.h. So we put them here. + */ + +/* + * errtable --- stores schema_name and table_name of a table + * within the current errordata. + */ +int +errtable(Relation rel) +{ + err_generic_string(PG_DIAG_SCHEMA_NAME, + get_namespace_name(RelationGetNamespace(rel))); + err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel)); + + return 0; /* return value does not matter */ +} + +/* + * errtablecol --- stores schema_name, table_name and column_name + * of a table column within the current errordata. + * + * The column is specified by attribute number --- for most callers, this is + * easier and less error-prone than getting the column name for themselves. + */ +int +errtablecol(Relation rel, int attnum) +{ + TupleDesc reldesc = RelationGetDescr(rel); + const char *colname; + + /* Use reldesc if it's a user attribute, else consult the catalogs */ + if (attnum > 0 && attnum <= reldesc->natts) + colname = NameStr(reldesc->attrs[attnum - 1]->attname); + else + colname = get_relid_attribute_name(RelationGetRelid(rel), attnum); + + return errtablecolname(rel, colname); +} + +/* + * errtablecolname --- stores schema_name, table_name and column_name + * of a table column within the current errordata, where the column name is + * given directly rather than extracted from the relation's catalog data. + * + * Don't use this directly unless errtablecol() is inconvenient for some + * reason. This might possibly be needed during intermediate states in ALTER + * TABLE, for instance. + */ +int +errtablecolname(Relation rel, const char *colname) +{ + errtable(rel); + err_generic_string(PG_DIAG_COLUMN_NAME, colname); + + return 0; /* return value does not matter */ +} + +/* + * errtableconstraint --- stores schema_name, table_name and constraint_name + * of a table-related constraint within the current errordata. + */ +int +errtableconstraint(Relation rel, const char *conname) +{ + errtable(rel); + err_generic_string(PG_DIAG_CONSTRAINT_NAME, conname); + + return 0; /* return value does not matter */ +} + + /* * load_relcache_init_file, write_relcache_init_file * diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 1db62eb1b0b..3a211bf4cd9 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -87,6 +87,7 @@ err_gettext(const char *str) /* This extension allows gcc to check the format string for consistency with the supplied arguments. */ __attribute__((format_arg(1))); +static void set_errdata_field(char **ptr, const char *str); /* Global variables */ ErrorContextCallback *error_context_stack = NULL; @@ -475,6 +476,16 @@ errfinish(int dummy,...) pfree(edata->hint); if (edata->context) pfree(edata->context); + if (edata->schema_name) + pfree(edata->schema_name); + if (edata->table_name) + pfree(edata->table_name); + if (edata->column_name) + pfree(edata->column_name); + if (edata->datatype_name) + pfree(edata->datatype_name); + if (edata->constraint_name) + pfree(edata->constraint_name); if (edata->internalquery) pfree(edata->internalquery); @@ -1100,6 +1111,59 @@ internalerrquery(const char *query) return 0; /* return value does not matter */ } +/* + * err_generic_string -- used to set individual ErrorData string fields + * identified by PG_DIAG_xxx codes. + * + * This intentionally only supports fields that don't use localized strings, + * so that there are no translation considerations. + * + * Most potential callers should not use this directly, but instead prefer + * higher-level abstractions, such as errtablecol() (see relcache.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_SCHEMA_NAME: + set_errdata_field(&edata->schema_name, str); + break; + case PG_DIAG_TABLE_NAME: + set_errdata_field(&edata->table_name, str); + break; + case PG_DIAG_COLUMN_NAME: + set_errdata_field(&edata->column_name, str); + break; + case PG_DIAG_DATATYPE_NAME: + set_errdata_field(&edata->datatype_name, str); + break; + case PG_DIAG_CONSTRAINT_NAME: + set_errdata_field(&edata->constraint_name, str); + break; + default: + elog(ERROR, "unsupported ErrorData field id: %d", field); + break; + } + + 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 * @@ -1373,6 +1437,16 @@ CopyErrorData(void) newedata->hint = pstrdup(newedata->hint); if (newedata->context) newedata->context = pstrdup(newedata->context); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->datatype_name) + newedata->datatype_name = pstrdup(newedata->datatype_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); if (newedata->internalquery) newedata->internalquery = pstrdup(newedata->internalquery); @@ -1398,6 +1472,16 @@ FreeErrorData(ErrorData *edata) pfree(edata->hint); if (edata->context) pfree(edata->context); + if (edata->schema_name) + pfree(edata->schema_name); + if (edata->table_name) + pfree(edata->table_name); + if (edata->column_name) + pfree(edata->column_name); + if (edata->datatype_name) + pfree(edata->datatype_name); + if (edata->constraint_name) + pfree(edata->constraint_name); if (edata->internalquery) pfree(edata->internalquery); pfree(edata); @@ -1470,6 +1554,16 @@ ReThrowError(ErrorData *edata) newedata->hint = pstrdup(newedata->hint); if (newedata->context) newedata->context = pstrdup(newedata->context); + if (newedata->schema_name) + newedata->schema_name = pstrdup(newedata->schema_name); + if (newedata->table_name) + newedata->table_name = pstrdup(newedata->table_name); + if (newedata->column_name) + newedata->column_name = pstrdup(newedata->column_name); + if (newedata->datatype_name) + newedata->datatype_name = pstrdup(newedata->datatype_name); + if (newedata->constraint_name) + newedata->constraint_name = pstrdup(newedata->constraint_name); if (newedata->internalquery) newedata->internalquery = pstrdup(newedata->internalquery); @@ -2657,6 +2751,36 @@ send_message_to_frontend(ErrorData *edata) err_sendstring(&msgbuf, edata->context); } + if (edata->schema_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME); + err_sendstring(&msgbuf, edata->schema_name); + } + + if (edata->table_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_TABLE_NAME); + err_sendstring(&msgbuf, edata->table_name); + } + + if (edata->column_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_COLUMN_NAME); + err_sendstring(&msgbuf, edata->column_name); + } + + if (edata->datatype_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_DATATYPE_NAME); + err_sendstring(&msgbuf, edata->datatype_name); + } + + if (edata->constraint_name) + { + pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME); + err_sendstring(&msgbuf, edata->constraint_name); + } + if (edata->cursorpos > 0) { snprintf(tbuf, sizeof(tbuf), "%d", edata->cursorpos); diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index 38077ec9c4e..d876369c6bb 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -364,6 +364,7 @@ struct Tuplesortstate * These variables are specific to the IndexTuple case; they are set by * tuplesort_begin_index_xxx and used only by the IndexTuple routines. */ + Relation heapRel; /* table the index is being built on */ Relation indexRel; /* index being built */ /* These are specific to the index_btree subcase: */ @@ -720,7 +721,8 @@ tuplesort_begin_cluster(TupleDesc tupDesc, } Tuplesortstate * -tuplesort_begin_index_btree(Relation indexRel, +tuplesort_begin_index_btree(Relation heapRel, + Relation indexRel, bool enforceUnique, int workMem, bool randomAccess) { @@ -751,6 +753,7 @@ tuplesort_begin_index_btree(Relation indexRel, state->readtup = readtup_index; state->reversedirection = reversedirection_index_btree; + state->heapRel = heapRel; state->indexRel = indexRel; state->indexScanKey = _bt_mkscankey_nodata(indexRel); state->enforceUnique = enforceUnique; @@ -761,7 +764,8 @@ tuplesort_begin_index_btree(Relation indexRel, } Tuplesortstate * -tuplesort_begin_index_hash(Relation indexRel, +tuplesort_begin_index_hash(Relation heapRel, + Relation indexRel, uint32 hash_mask, int workMem, bool randomAccess) { @@ -786,6 +790,7 @@ tuplesort_begin_index_hash(Relation indexRel, state->readtup = readtup_index; state->reversedirection = reversedirection_index_hash; + state->heapRel = heapRel; state->indexRel = indexRel; state->hash_mask = hash_mask; @@ -3171,7 +3176,9 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b, RelationGetRelationName(state->indexRel)), errdetail("Key %s is duplicated.", BuildIndexValueDescription(state->indexRel, - values, isnull)))); + values, isnull)), + errtableconstraint(state->heapRel, + RelationGetRelationName(state->indexRel)))); } /* diff --git a/src/include/access/hash.h b/src/include/access/hash.h index c50bbd0d005..f66111f0c22 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -334,7 +334,7 @@ extern bool _hash_step(IndexScanDesc scan, Buffer *bufP, ScanDirection dir); /* hashsort.c */ typedef struct HSpool HSpool; /* opaque struct in hashsort.c */ -extern HSpool *_h_spoolinit(Relation index, uint32 num_buckets); +extern HSpool *_h_spoolinit(Relation heap, Relation index, uint32 num_buckets); extern void _h_spooldestroy(HSpool *hspool); extern void _h_spool(IndexTuple itup, HSpool *hspool); extern void _h_indexbuild(HSpool *hspool); diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 0e35d7ad25b..3997f94f46e 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -686,7 +686,8 @@ extern void BTreeShmemInit(void); */ typedef struct BTSpool BTSpool; /* opaque type known only within nbtsort.c */ -extern BTSpool *_bt_spoolinit(Relation index, bool isunique, bool isdead); +extern BTSpool *_bt_spoolinit(Relation heap, Relation index, + bool isunique, bool isdead); extern void _bt_spooldestroy(BTSpool *btspool); extern void _bt_spool(IndexTuple itup, BTSpool *btspool); extern void _bt_leafbuild(BTSpool *btspool, BTSpool *spool2); diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h index 5ba379f869b..48d5dd31e53 100644 --- a/src/include/postgres_ext.h +++ b/src/include/postgres_ext.h @@ -57,6 +57,11 @@ typedef PG_INT64_TYPE pg_int64; #define PG_DIAG_INTERNAL_POSITION 'p' #define PG_DIAG_INTERNAL_QUERY 'q' #define PG_DIAG_CONTEXT 'W' +#define PG_DIAG_SCHEMA_NAME 's' +#define PG_DIAG_TABLE_NAME 't' +#define PG_DIAG_COLUMN_NAME 'c' +#define PG_DIAG_DATATYPE_NAME 'd' +#define PG_DIAG_CONSTRAINT_NAME 'n' #define PG_DIAG_SOURCE_FILE 'F' #define PG_DIAG_SOURCE_LINE 'L' #define PG_DIAG_SOURCE_FUNCTION 'R' diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index ad4d68cd50a..533539ca293 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -143,7 +143,10 @@ extern Datum char_text(PG_FUNCTION_ARGS); /* domains.c */ extern Datum domain_in(PG_FUNCTION_ARGS); extern Datum domain_recv(PG_FUNCTION_ARGS); -extern void domain_check(Datum value, bool isnull, Oid domainType, void **extra, MemoryContext mcxt); +extern void domain_check(Datum value, bool isnull, Oid domainType, + void **extra, MemoryContext mcxt); +extern int errdatatype(Oid datatypeOid); +extern int errdomainconstraint(Oid datatypeOid, const char *conname); /* encode.c */ extern Datum binary_encode(PG_FUNCTION_ARGS); diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h index 5e937fb10c3..d5fec89a480 100644 --- a/src/include/utils/elog.h +++ b/src/include/utils/elog.h @@ -220,6 +220,8 @@ extern int errposition(int cursorpos); extern int internalerrposition(int cursorpos); extern int internalerrquery(const char *query); +extern int err_generic_string(int field, const char *str); + extern int geterrcode(void); extern int geterrposition(void); extern int getinternalerrposition(void); @@ -386,6 +388,11 @@ typedef struct ErrorData char *detail_log; /* detail error message for server log only */ char *hint; /* hint message */ char *context; /* context message */ + char *schema_name; /* name of schema */ + char *table_name; /* name of table */ + char *column_name; /* name of column */ + char *datatype_name; /* name of datatype */ + char *constraint_name; /* name of constraint */ int cursorpos; /* cursor index into query string */ int internalpos; /* cursor index into internalquery */ char *internalquery; /* text of internally-generated query */ diff --git a/src/include/utils/relcache.h b/src/include/utils/relcache.h index 1ec2683eacb..8ac2549cb3a 100644 --- a/src/include/utils/relcache.h +++ b/src/include/utils/relcache.h @@ -52,6 +52,14 @@ extern void RelationSetIndexList(Relation relation, extern void RelationInitIndexAccessInfo(Relation relation); +/* + * Routines to support ereport() reports of relation-related errors + */ +extern int errtable(Relation rel); +extern int errtablecol(Relation rel, int attnum); +extern int errtablecolname(Relation rel, const char *colname); +extern int errtableconstraint(Relation rel, const char *conname); + /* * Routines for backend startup */ diff --git a/src/include/utils/tuplesort.h b/src/include/utils/tuplesort.h index 8fdc6a1bf43..e69aafcd903 100644 --- a/src/include/utils/tuplesort.h +++ b/src/include/utils/tuplesort.h @@ -66,10 +66,12 @@ extern Tuplesortstate *tuplesort_begin_heap(TupleDesc tupDesc, extern Tuplesortstate *tuplesort_begin_cluster(TupleDesc tupDesc, Relation indexRel, int workMem, bool randomAccess); -extern Tuplesortstate *tuplesort_begin_index_btree(Relation indexRel, +extern Tuplesortstate *tuplesort_begin_index_btree(Relation heapRel, + Relation indexRel, bool enforceUnique, int workMem, bool randomAccess); -extern Tuplesortstate *tuplesort_begin_index_hash(Relation indexRel, +extern Tuplesortstate *tuplesort_begin_index_hash(Relation heapRel, + Relation indexRel, uint32 hash_mask, int workMem, bool randomAccess); extern Tuplesortstate *tuplesort_begin_datum(Oid datumType, diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 40e75d4b554..1e26e199198 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -919,6 +919,29 @@ pqGetErrorNotice3(PGconn *conn, bool isError) appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"), val); } if (conn->verbosity == PQERRORS_VERBOSE) + { + val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("SCHEMA 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_COLUMN_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("COLUMN NAME: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("DATATYPE NAME: %s\n"), val); + val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME); + if (val) + appendPQExpBuffer(&workBuf, + libpq_gettext("CONSTRAINT NAME: %s\n"), val); + } + if (conn->verbosity == PQERRORS_VERBOSE) { const char *valf; const char *vall;