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.
+
+
PostgreSQL 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;