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