enhanced error fields
Hello
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fields
Regards
Pavel Stehule
Attachments:
eelog-2012-05-09.diffapplication/octet-stream; name=eelog-2012-05-09.diffDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e58dc18..fadc9a8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4158,6 +4158,14 @@ CREATE TABLE postgres_log
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 71c40cc..03b2bc2 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -4720,6 +4720,105 @@ message.
</listitem>
</varlistentry>
+<varlistentry>
+<term>
+<literal>c</>
+</term>
+<listitem>
+<para>
+ Column name: the name of column related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>t</>
+</term>
+<listitem>
+<para>
+ Table name: the name of table related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>s</>
+</term>
+<listitem>
+<para>
+ Schema name: the name of schema related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>n</>
+</term>
+<listitem>
+<para>
+ Constraint name: the name of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>m</>
+</term>
+<listitem>
+<para>
+ Constraint schema: the schema of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>r</>
+</term>
+<listitem>
+<para>
+ Routine name: the name of routine related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>u</>
+</term>
+<listitem>
+<para>
+ Routine schema: the schema of routine related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>g</>
+</term>
+<listitem>
+<para>
+ Trigger name: the name of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>h</>
+</term>
+<listitem>
+<para>
+ Trigger schema: the schema of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
</variablelist>
<para>
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 3ed9b5c..a5e5d12 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -393,7 +393,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
- values, isnull))));
+ values, isnull)),
+ constraint_relation_error(rel)));
}
}
else if (all_dead)
@@ -455,7 +456,8 @@ _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."),
+ relation_error(rel)));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
@@ -533,7 +535,8 @@ _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."),
+ relation_error(rel)));
/*----------
* If we will need to split the page to put the item on this page,
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index fbb36fa..ef9740d 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1522,7 +1522,9 @@ 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)),
+ relation_column_error(rel, NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
+ constraint_error(rel, "not_null_violation")));
}
}
@@ -1536,7 +1538,9 @@ 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)),
+ relation_error(rel),
+ constraint_error(rel, failed)));
}
}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 40cd5ce..153ea1a 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1304,14 +1304,16 @@ 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),
+ constraint_relation_error(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),
+ constraint_relation_error(index)));
}
index_endscan(index_scan);
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dd58f4e..c8f13b1 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -409,7 +409,9 @@ RI_FKey_check(PG_FUNCTION_ARGS)
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.")));
+ errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
+ relation_error(trigdata->tg_relation),
+ constraint_error(trigdata->tg_relation, NameStr(riinfo.conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
@@ -2841,7 +2843,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),
constrname),
- 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."),
+ relation_error(fk_rel),
+ constraint_error(fk_rel, constrname)));
}
/*
@@ -3536,7 +3540,9 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel), constrname),
errdetail("No rows were found in \"%s\".",
- RelationGetRelationName(pk_rel))));
+ RelationGetRelationName(pk_rel)),
+ relation_error(fk_rel),
+ constraint_error(fk_rel, constrname)));
}
/* Get printable versions of the keys involved */
@@ -3569,7 +3575,9 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
RelationGetRelationName(fk_rel), constrname),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
- RelationGetRelationName(pk_rel))));
+ RelationGetRelationName(pk_rel)),
+ relation_error(fk_rel),
+ constraint_error(fk_rel, constrname)));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
@@ -3578,7 +3586,9 @@ ri_ReportViolation(RI_QueryKey *qkey, const char *constrname,
constrname, 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)),
+ relation_error(pk_rel),
+ constraint_error(fk_rel, constrname)));
}
/* ----------
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 44dab82..d9fbdf0 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -2924,3 +2924,18 @@ get_range_subtype(Oid rangeOid)
else
return InvalidOid;
}
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData
+ */
+inline int
+rel_column_error(Oid table_oid, const char *colname)
+{
+ if (colname != NULL)
+ erritem(PG_DIAG_COLUMN_NAME, colname);
+
+ erritem(PG_DIAG_TABLE_NAME, get_rel_name(table_oid));
+ erritem(PG_DIAG_SCHEMA_NAME, get_namespace_name(get_rel_namespace(table_oid)));
+
+ return 0;
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7f0e20e..4d3fd87 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -4623,3 +4623,51 @@ unlink_initfile(const char *initfilename)
elog(LOG, "could not remove cache file \"%s\": %m", initfilename);
}
}
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+inline int
+relation_column_error(Relation rel, const char *colname)
+{
+ if (colname != NULL)
+ erritem(PG_DIAG_COLUMN_NAME, colname);
+
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+inline int
+relation_error(Relation rel)
+{
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * Sets constraint_name and constraint_schema in ErrorData
+ */
+inline int
+constraint_error(Relation rel, const char *cname)
+{
+ erritem(PG_DIAG_CONSTRAINT_NAME, cname);
+ erritem(PG_DIAG_CONSTRAINT_SCHEMA, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+inline int
+constraint_relation_error(Relation rel)
+{
+ erritem(PG_DIAG_CONSTRAINT_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_CONSTRAINT_SCHEMA, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 65c28a7..77aa9b9 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -477,6 +477,24 @@ errfinish(int dummy,...)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
@@ -1078,6 +1096,98 @@ internalerrquery(const char *query)
return 0; /* return value does not matter */
}
+static inline void
+set_field(char **ptr, const char *str, bool overwrite)
+{
+ if (*ptr != NULL)
+ {
+ /*
+ * for some cases like ROUTINE_NAME, ROUTINE_SCHEMA we would
+ * to get the most older value.
+ */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != NULL)
+ *ptr = MemoryContextStrdup(ErrorContext, str);
+}
+
+/*
+ * erritem -- generic setting of ErrorData string fields
+ */
+int
+erritem(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_field(&edata->message, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_field(&edata->detail, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_field(&edata->hint, str, true);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_field(&edata->context, str, true);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_field(&edata->column_name, str, true);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_field(&edata->table_name, str, true);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_field(&edata->schema_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_field(&edata->constraint_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_field(&edata->constraint_schema, str, true);
+ break;
+
+ case PG_DIAG_ROUTINE_NAME:
+ set_field(&edata->routine_name, str, false);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_field(&edata->routine_schema, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_field(&edata->trigger_name, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_field(&edata->trigger_schema, str, false);
+ break;
+
+ default:
+ elog(ERROR, "unknown ErrorData field id %d", field);
+ }
+
+ return 0; /* return value does not matter */
+}
+
/*
* geterrcode --- return the currently set SQLSTATE error code
*
@@ -1352,6 +1462,24 @@ CopyErrorData(void)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
@@ -1377,6 +1505,24 @@ FreeErrorData(ErrorData *edata)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
@@ -1449,6 +1595,24 @@ ReThrowError(ErrorData *edata)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
@@ -2259,6 +2423,33 @@ write_csvlog(ErrorData *edata)
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
@@ -2377,6 +2568,69 @@ send_message_to_server_log(ErrorData *edata)
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
@@ -2673,6 +2927,60 @@ send_message_to_frontend(ErrorData *edata)
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
@@ -2977,3 +3285,19 @@ trace_recovery(int trace_level)
return trace_level;
}
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData
+ */
+inline int
+schema_table_column_error(const char *schema_name, const char *table_name, const char *colname)
+{
+ if (colname != NULL)
+ erritem(PG_DIAG_COLUMN_NAME, colname);
+ if (table_name != NULL)
+ erritem(PG_DIAG_TABLE_NAME, table_name);
+ if (schema_name != NULL)
+ erritem(PG_DIAG_SCHEMA_NAME, schema_name);
+
+ return 0; /* return value does not matter */
+}
diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h
index b6ebb7a..833c59e 100644
--- a/src/include/postgres_ext.h
+++ b/src/include/postgres_ext.h
@@ -55,5 +55,14 @@ typedef unsigned int Oid;
#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_SCHEMA_NAME 's'
+#define PG_DIAG_CONSTRAINT_NAME 'n'
+#define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+#define PG_DIAG_ROUTINE_NAME 'r'
+#define PG_DIAG_ROUTINE_SCHEMA 'u'
+#define PG_DIAG_TRIGGER_NAME 'g'
+#define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 1bbfd2b..3ee120c 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -80,7 +80,6 @@
#endif
#endif
-
/*----------
* New-style error reporting API: to be used in this way:
* ereport(ERROR,
@@ -190,6 +189,8 @@ extern int geterrcode(void);
extern int geterrposition(void);
extern int getinternalerrposition(void);
+extern int erritem(int field, const char *str);
+
/*----------
* Old-style error reporting API: to be used in this way:
@@ -296,6 +297,7 @@ extern PGDLLIMPORT ErrorContextCallback *error_context_stack;
extern PGDLLIMPORT sigjmp_buf *PG_exception_stack;
+
/* Stuff that error handlers might want to use */
/*
@@ -321,6 +323,15 @@ typedef struct ErrorData
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 *schema_name; /* name of schema */
+ char *constraint_name; /* name of constraint */
+ char *constraint_schema; /* name of schema with constraint */
+ char *routine_name; /* name of function that caused error */
+ char *routine_schema; /* schema name of function that caused error */
+ char *trigger_name; /* name of trigger that caused error */
+ char *trigger_schema; /* schema of trigger that caused error */
int cursorpos; /* cursor index into query string */
int internalpos; /* cursor index into internalquery */
char *internalquery; /* text of internally-generated query */
@@ -378,4 +389,7 @@ write_stderr(const char *fmt,...)
the supplied arguments. */
__attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
+extern inline int schema_table_column_error(const char *schema_name, const char *table_name,
+ const char *colname);
+
#endif /* ELOG_H */
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 696ca77..4bd17da 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -157,4 +157,6 @@ extern Oid get_range_subtype(Oid rangeOid);
#define TypeIsToastable(typid) (get_typstorage(typid) != 'p')
+extern inline int rel_column_error(Oid table_oid, const char *colname);
+
#endif /* LSYSCACHE_H */
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index d404c2a..9ea47d1 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -394,4 +394,11 @@ typedef struct StdRdOptions
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+extern inline int relation_column_error(Relation rel, const char *colname);
+extern inline int relation_error(Relation rel);
+extern inline int constraint_error(Relation rel, const char *ccname);
+extern inline int constraint_relation_error(Relation rel);
+extern inline int column_error(const char *cname);
+
+
#endif /* REL_H */
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index a773d7a..b935a84 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -980,6 +980,34 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("SCHEMA 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_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
eelog-plpgsql-2012-05-09.diffapplication/octet-stream; name=eelog-plpgsql-2012-05-09.diffDownload
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c5e9c2e..2316d3f 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -2639,6 +2639,51 @@ GET STACKED DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item<
<entry>text</entry>
<entry>line(s) of text describing the call stack</entry>
</row>
+ <row>
+ <entry><literal>COLUMN_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of column related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TABLE_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of table related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>SCHEMA_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name table's schema related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>CONSTRAINT_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of constraint related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>CONSTRAINT_SCHEMA</literal></entry>
+ <entry>text</entry>
+ <entry>the constaint's schema related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>ROUTINE_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of routine related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>ROUTINE_SCHEMA</literal></entry>
+ <entry>text</entry>
+ <entry>the schema of routine related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TRIGGER_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of trigger related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TRIGGER_SCHEMA</literal></entry>
+ <entry>text</entry>
+ <entry>the schema of trigger related to error, if any</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -3277,7 +3322,11 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
class="parameter">expression</replaceable> items. The allowed
<replaceable class="parameter">option</replaceable> keywords are
<literal>MESSAGE</>, <literal>DETAIL</>, <literal>HINT</>, and
- <literal>ERRCODE</>, while each <replaceable
+ , <literal>COLUMN_NAME</>, <literal>TABLE_NAME</>,
+ <literal>SCHEMA_NAME</>, <literal>CONSTRAINT_NAME</>,
+ <literal>CONSTRAINT_SCHEMA</>, <literal>ROUTINE_NAME</>,
+ <literal>ROUTINE_SCHEMA</>, <literal>TRIGGER_NAME</>,
+ <literal>TRIGGER_SCHEMA</> while each <replaceable
class="parameter">expression</replaceable> can be any string-valued
expression.
<literal>MESSAGE</> sets the error message text (this option can't
@@ -3287,7 +3336,15 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
<literal>HINT</> supplies a hint message.
<literal>ERRCODE</> specifies the error code (SQLSTATE) to report,
either by condition name as shown in <xref linkend="errcodes-appendix">,
- or directly as a five-character SQLSTATE code.
+ or directly as a five-character SQLSTATE code. <literal>COLUMN_NAME</>
+ sets the name of column related to error. <literal>TABLE_NAME</>
+ sets the name of table related to error. <literal>SCHEMA_NAME</> sets
+ the schema of table related to error. <literal>CONSTRAINT_NAME</> sets
+ name of constraint. <literal>CONSTRAINT_SCHEMA</> sets a schema of constraint
+ related to error. <literal>ROUTINE_NAME</> sets a name of routine related
+ to error. <literal>ROUTINE_SCHEMA</> sets a schema of routine related to error.
+ <literal>TRIGGER_NAME</> sets a name of trigger related to error.
+ <literal>TRIGGER_SCHEMA</> sets a schema of trigger related to error.
</para>
<para>
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index c991765..4ad447e 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -259,6 +259,15 @@ static List *read_raise_options(void);
%token <keyword> K_DECLARE
%token <keyword> K_DEFAULT
%token <keyword> K_DETAIL
+%token <keyword> K_DIAG_COLUMN_NAME
+%token <keyword> K_DIAG_TABLE_NAME
+%token <keyword> K_DIAG_SCHEMA_NAME
+%token <keyword> K_DIAG_CONSTRAINT_NAME
+%token <keyword> K_DIAG_CONSTRAINT_SCHEMA
+%token <keyword> K_DIAG_ROUTINE_NAME
+%token <keyword> K_DIAG_ROUTINE_SCHEMA
+%token <keyword> K_DIAG_TRIGGER_NAME
+%token <keyword> K_DIAG_TRIGGER_SCHEMA
%token <keyword> K_DIAGNOSTICS
%token <keyword> K_DUMP
%token <keyword> K_ELSE
@@ -877,6 +886,15 @@ stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
case PLPGSQL_GETDIAG_ERROR_HINT:
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ case PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA:
+ case PLPGSQL_GETDIAG_ROUTINE_NAME:
+ case PLPGSQL_GETDIAG_ROUTINE_SCHEMA:
+ case PLPGSQL_GETDIAG_TRIGGER_NAME:
+ case PLPGSQL_GETDIAG_TRIGGER_SCHEMA:
if (!new->is_stacked)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -956,6 +974,33 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval,
K_RETURNED_SQLSTATE, "returned_sqlstate"))
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_COLUMN_NAME, "column_name"))
+ $$ = PLPGSQL_GETDIAG_COLUMN_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TABLE_NAME, "table_name"))
+ $$ = PLPGSQL_GETDIAG_TABLE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_SCHEMA_NAME, "schema_name"))
+ $$ = PLPGSQL_GETDIAG_SCHEMA_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_NAME, "constraint_name"))
+ $$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_SCHEMA, "constraint_schema"))
+ $$ = PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_NAME, "routine_name"))
+ $$ = PLPGSQL_GETDIAG_ROUTINE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_SCHEMA, "routine_schema"))
+ $$ = PLPGSQL_GETDIAG_ROUTINE_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_NAME, "trigger_name"))
+ $$ = PLPGSQL_GETDIAG_TRIGGER_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_SCHEMA, "trigger_schema"))
+ $$ = PLPGSQL_GETDIAG_TRIGGER_SCHEMA;
else
yyerror("unrecognized GET DIAGNOSTICS item");
}
@@ -2204,7 +2249,10 @@ unreserved_keyword :
| K_ALIAS
| K_ARRAY
| K_BACKWARD
+ | K_DIAG_COLUMN_NAME
| K_CONSTANT
+ | K_DIAG_CONSTRAINT_NAME
+ | K_DIAG_CONSTRAINT_SCHEMA
| K_CURRENT
| K_CURSOR
| K_DEBUG
@@ -2234,12 +2282,18 @@ unreserved_keyword :
| K_RESULT_OID
| K_RETURNED_SQLSTATE
| K_REVERSE
+ | K_DIAG_ROUTINE_NAME
+ | K_DIAG_ROUTINE_SCHEMA
| K_ROW_COUNT
| K_ROWTYPE
+ | K_DIAG_SCHEMA_NAME
| K_SCROLL
| K_SLICE
| K_SQLSTATE
| K_STACKED
+ | K_DIAG_TABLE_NAME
+ | K_DIAG_TRIGGER_NAME
+ | K_DIAG_TRIGGER_SCHEMA
| K_TYPE
| K_USE_COLUMN
| K_USE_VARIABLE
@@ -3586,6 +3640,33 @@ read_raise_options(void)
else if (tok_is_keyword(tok, &yylval,
K_HINT, "hint"))
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_COLUMN_NAME, "column_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TABLE_NAME, "table_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TABLE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_SCHEMA_NAME, "schema_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_NAME, "constraint_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_SCHEMA, "constraint_schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_NAME, "routine_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_ROUTINE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_SCHEMA, "routine_schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_NAME, "trigger_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TRIGGER_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_SCHEMA, "trigger_schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA;
else
yyerror("unrecognized RAISE statement option");
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a385b9a..24cc263 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -1489,6 +1489,51 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
estate->cur_error->message);
break;
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->column_name);
+ break;
+
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->table_name);
+ break;
+
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->schema_name);
+ break;
+
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->constraint_name);
+ break;
+
+ case PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->constraint_schema);
+ break;
+
+ case PLPGSQL_GETDIAG_ROUTINE_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->routine_name);
+ break;
+
+ case PLPGSQL_GETDIAG_ROUTINE_SCHEMA:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->routine_schema);
+ break;
+
+ case PLPGSQL_GETDIAG_TRIGGER_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->trigger_name);
+ break;
+
+ case PLPGSQL_GETDIAG_TRIGGER_SCHEMA:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->trigger_schema);
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
@@ -2665,6 +2710,15 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
char *err_message = NULL;
char *err_detail = NULL;
char *err_hint = NULL;
+ char *column_name = NULL;
+ char *table_name = NULL;
+ char *schema_name = NULL;
+ char *constraint_name = NULL;
+ char *constraint_schema = NULL;
+ char *routine_name = NULL;
+ char *routine_schema = NULL;
+ char *trigger_name = NULL;
+ char *trigger_schema = NULL;
ListCell *lc;
/* RAISE with no parameters: re-throw current exception */
@@ -2804,6 +2858,78 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
"HINT")));
err_hint = pstrdup(extval);
break;
+ case PLPGSQL_RAISEOPTION_COLUMN_NAME:
+ if (column_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "COLUMN_NAME")));
+ column_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TABLE_NAME:
+ if (table_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TABLE_NAME")));
+ table_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_SCHEMA_NAME:
+ if (schema_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "SCHEMA_NAME")));
+ schema_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_NAME:
+ if (constraint_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "CONSTRAINT_NAME")));
+ constraint_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA:
+ if (constraint_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "CONSTRAINT_SCHEMA")));
+ constraint_schema = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_NAME:
+ if (routine_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "ROUTINE_NAME")));
+ routine_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA:
+ if (routine_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "ROUTINE_SCHEMA")));
+ routine_schema = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_NAME:
+ if (trigger_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TRIGGER_NAME")));
+ trigger_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA:
+ if (trigger_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TRIGGER_SCHEMA")));
+ trigger_schema = pstrdup(extval);
+ break;
default:
elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
}
@@ -2836,7 +2962,16 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
(err_code ? errcode(err_code) : 0,
errmsg_internal("%s", err_message),
(err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0,
- (err_hint != NULL) ? errhint("%s", err_hint) : 0));
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0,
+ (column_name != NULL) ? erritem(PG_DIAG_COLUMN_NAME, column_name) : 0,
+ (table_name != NULL) ? erritem(PG_DIAG_TABLE_NAME, table_name) : 0,
+ (schema_name != NULL) ? erritem(PG_DIAG_SCHEMA_NAME, schema_name) : 0,
+ (constraint_name != NULL) ? erritem(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0,
+ (constraint_schema != NULL) ? erritem(PG_DIAG_CONSTRAINT_SCHEMA, constraint_schema) : 0,
+ (routine_name != NULL) ? erritem(PG_DIAG_ROUTINE_NAME, routine_name) : 0,
+ (routine_schema != NULL) ? erritem(PG_DIAG_ROUTINE_SCHEMA, routine_schema) : 0,
+ (trigger_name != NULL) ? erritem(PG_DIAG_TRIGGER_NAME, trigger_name) : 0,
+ (trigger_schema != NULL) ? erritem(PG_DIAG_TRIGGER_SCHEMA, trigger_schema) : 0));
estate->err_text = NULL; /* un-suppress... */
@@ -2848,6 +2983,24 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
pfree(err_detail);
if (err_hint != NULL)
pfree(err_hint);
+ if (column_name != NULL)
+ pfree(column_name);
+ if (table_name != NULL)
+ pfree(table_name);
+ if (schema_name != NULL)
+ pfree(schema_name);
+ if (constraint_name != NULL)
+ pfree(constraint_name);
+ if (constraint_schema != NULL)
+ pfree(constraint_schema);
+ if (routine_name != NULL)
+ pfree(routine_name);
+ if (routine_schema != NULL)
+ pfree(routine_schema);
+ if (trigger_name != NULL)
+ pfree(trigger_name);
+ if (trigger_schema != NULL)
+ pfree(trigger_schema);
return PLPGSQL_RC_OK;
}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index b5a72dd..a729e3f 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -287,6 +287,24 @@ plpgsql_getdiag_kindname(int kind)
return "RETURNED_SQLSTATE";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ return "COLUMN_NAME";
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ return "TABLE_NAME";
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ return "SCHEMA_NAME";
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ return "CONSTRAINT_NAME";
+ case PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA:
+ return "CONSTRAINT_SCHEMA";
+ case PLPGSQL_GETDIAG_ROUTINE_NAME:
+ return "ROUTINE_NAME";
+ case PLPGSQL_GETDIAG_ROUTINE_SCHEMA:
+ return "ROUTINE_SCHEMA";
+ case PLPGSQL_GETDIAG_TRIGGER_NAME:
+ return "TRIGGER_NAME";
+ case PLPGSQL_GETDIAG_TRIGGER_SCHEMA:
+ return "TRIGGER_SCHEMA";
}
return "unknown";
@@ -1317,6 +1335,33 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
case PLPGSQL_RAISEOPTION_HINT:
printf(" HINT = ");
break;
+ case PLPGSQL_RAISEOPTION_COLUMN_NAME:
+ printf(" COLUMN_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TABLE_NAME:
+ printf(" TABLE_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_SCHEMA_NAME:
+ printf(" SCHEMA_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_NAME:
+ printf(" CONSTRAINT_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA:
+ printf(" CONSTRAINT_SCHEMA = ");
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_NAME:
+ printf(" ROUTINE_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA:
+ printf(" ROUTINE_SCHEMA = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_NAME:
+ printf(" TRIGGER_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA:
+ printf(" TRIGGER_SCHENA = ");
+ break;
}
dump_expr(opt->expr);
printf("\n");
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index ebce3fd..587387e 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -109,7 +109,10 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
+ PG_KEYWORD("column_name", K_DIAG_COLUMN_NAME, UNRESERVED_KEYWORD)
PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("constraint_name", K_DIAG_CONSTRAINT_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("constraint_schema", K_DIAG_CONSTRAINT_SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD)
PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
@@ -139,12 +142,18 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD)
PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("routine_name", K_DIAG_ROUTINE_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("routine_schema", K_DIAG_ROUTINE_SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("schema_name", K_DIAG_SCHEMA_NAME, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD)
+ PG_KEYWORD("table_name", K_DIAG_TABLE_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("trigger_name", K_DIAG_TRIGGER_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("trigger_schema", K_DIAG_TRIGGER_SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b63f336..5b056f1 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -127,7 +127,16 @@ enum
PLPGSQL_GETDIAG_ERROR_DETAIL,
PLPGSQL_GETDIAG_ERROR_HINT,
PLPGSQL_GETDIAG_RETURNED_SQLSTATE,
- PLPGSQL_GETDIAG_MESSAGE_TEXT
+ PLPGSQL_GETDIAG_MESSAGE_TEXT,
+ PLPGSQL_GETDIAG_COLUMN_NAME,
+ PLPGSQL_GETDIAG_TABLE_NAME,
+ PLPGSQL_GETDIAG_SCHEMA_NAME,
+ PLPGSQL_GETDIAG_CONSTRAINT_NAME,
+ PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA,
+ PLPGSQL_GETDIAG_ROUTINE_NAME,
+ PLPGSQL_GETDIAG_ROUTINE_SCHEMA,
+ PLPGSQL_GETDIAG_TRIGGER_NAME,
+ PLPGSQL_GETDIAG_TRIGGER_SCHEMA
};
/* --------
@@ -139,7 +148,16 @@ enum
PLPGSQL_RAISEOPTION_ERRCODE,
PLPGSQL_RAISEOPTION_MESSAGE,
PLPGSQL_RAISEOPTION_DETAIL,
- PLPGSQL_RAISEOPTION_HINT
+ PLPGSQL_RAISEOPTION_HINT,
+ PLPGSQL_RAISEOPTION_COLUMN_NAME,
+ PLPGSQL_RAISEOPTION_TABLE_NAME,
+ PLPGSQL_RAISEOPTION_SCHEMA_NAME,
+ PLPGSQL_RAISEOPTION_CONSTRAINT_NAME,
+ PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA,
+ PLPGSQL_RAISEOPTION_ROUTINE_NAME,
+ PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA,
+ PLPGSQL_RAISEOPTION_TRIGGER_NAME,
+ PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA
};
/* --------
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index 1e45919..1632f17 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4719,3 +4719,62 @@ ERROR: value for domain orderedarray violates check constraint "sorted"
CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+create function test_eelog_outer()
+returns void as $$
+declare
+ column_name text;
+ table_name text;
+ schema_name text;
+ constraint_name text;
+ constraint_schema text;
+ routine_name text;
+ routine_schema text;
+ trigger_name text;
+ trigger_schema text;
+begin
+ perform test_eelog_inner();
+exception when others then
+ get stacked diagnostics
+ column_name = column_name,
+ table_name = table_name,
+ schema_name = schema_name,
+ constraint_name = constraint_name,
+ constraint_schema = constraint_schema,
+ routine_name = routine_name,
+ routine_schema = routine_schema,
+ trigger_name = trigger_name,
+ trigger_schema = trigger_schema;
+
+ raise notice 'column name: "%":, table name: "%", schema name: "%"', column_name, table_name, schema_name;
+ raise notice 'constraint name: "%", constraint schema: "%"', constraint_name, constraint_schema;
+ raise notice 'routine name: "%", routine schema: "%"', routine_name, routine_schema;
+ raise notice 'trigger name: "%", trigger schema: "%"', trigger_name, trigger_schema;
+end;
+$$ language plpgsql;
+create function test_eelog_inner()
+returns void as $$
+begin
+ raise exception 'custom exception'
+ using column_name = 'some_column_name',
+ table_name = 'some_table_name',
+ schema_name = 'some_schema_name',
+ constraint_name = 'some_constraint_name',
+ constraint_schema = 'some_constraint_schema',
+ routine_name = 'some_routine',
+ routine_schema = 'some_routine_schema',
+ trigger_name = 'some_trigger_name',
+ trigger_schema = 'some_trigger_schema';
+end;
+$$ language plpgsql;
+select test_eelog_outer();
+NOTICE: column name: "some_column_name":, table name: "some_table_name", schema name: "some_schema_name"
+NOTICE: constraint name: "some_constraint_name", constraint schema: "some_constraint_schema"
+NOTICE: routine name: "some_routine", routine schema: "some_routine_schema"
+NOTICE: trigger name: "some_trigger_name", trigger schema: "some_trigger_schema"
+ test_eelog_outer
+------------------
+
+(1 row)
+
+drop function test_eelog_outer();
+drop function test_eelog_inner();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 2b60b67..8cc7693 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3723,3 +3723,57 @@ select testoa(1,2,1); -- fail at update
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+
+create function test_eelog_outer()
+returns void as $$
+declare
+ column_name text;
+ table_name text;
+ schema_name text;
+ constraint_name text;
+ constraint_schema text;
+ routine_name text;
+ routine_schema text;
+ trigger_name text;
+ trigger_schema text;
+begin
+ perform test_eelog_inner();
+exception when others then
+ get stacked diagnostics
+ column_name = column_name,
+ table_name = table_name,
+ schema_name = schema_name,
+ constraint_name = constraint_name,
+ constraint_schema = constraint_schema,
+ routine_name = routine_name,
+ routine_schema = routine_schema,
+ trigger_name = trigger_name,
+ trigger_schema = trigger_schema;
+
+ raise notice 'column name: "%":, table name: "%", schema name: "%"', column_name, table_name, schema_name;
+ raise notice 'constraint name: "%", constraint schema: "%"', constraint_name, constraint_schema;
+ raise notice 'routine name: "%", routine schema: "%"', routine_name, routine_schema;
+ raise notice 'trigger name: "%", trigger schema: "%"', trigger_name, trigger_schema;
+end;
+$$ language plpgsql;
+
+create function test_eelog_inner()
+returns void as $$
+begin
+ raise exception 'custom exception'
+ using column_name = 'some_column_name',
+ table_name = 'some_table_name',
+ schema_name = 'some_schema_name',
+ constraint_name = 'some_constraint_name',
+ constraint_schema = 'some_constraint_schema',
+ routine_name = 'some_routine',
+ routine_schema = 'some_routine_schema',
+ trigger_name = 'some_trigger_name',
+ trigger_schema = 'some_trigger_schema';
+end;
+$$ language plpgsql;
+
+select test_eelog_outer();
+
+drop function test_eelog_outer();
+drop function test_eelog_inner();
On Wed, May 9, 2012 at 9:33 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fields
Please add this to https://commitfest.postgresql.org/action/commitfest_view/open
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 9 May 2012 14:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fields
So I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.
The patch has bitrotted some, due to the fact that Tom bashed around
ri_triggers.c somewhat in recent weeks. I took the opportunity to
resolve merge conflicts, and attach a revised patch for everyone's
convenience.
The regression tests pass when the patch is applied.
The first thing I noticed about the patch was that inline functions
are used freely. While I personally don't find this unreasonable, we
recently revisited the question of whether or not it is necessary to
continue to support compilers that do not support something equivalent
to GNU C inline functions (or that cannot be made to support them via
macro hacks). The outcome of that was that it remains necessary to use
macros to provide non-inline equivalent functions. For a good example
of how that was dealt with quite extensively, see the sortsupport
commit:
In particular, take a look at the new file sortsupport.h .
However, whatever we might some day allow with inline functions, the
use of "extern inline", a C99 feature (or, according to some,
anti-feature) seems like a nonstarter into the foreseeable future,
unfortunately. Perhaps more to the point, are whatever performance
benefit is to be had by the use of inline functions here actually
going to pay for the maintenance cost? We already have functions
declarations like this, in the same header as your proposed new extern
inline functions:
extern int geterrcode(void);
This function's definition is trivial, and it would probably look like
a good candidate for inlining to the compiler, if it had the chance
(which, incidentally, it *still* may). However, why bother writing
code to support (or encourage) this, considering that this is hardly a
performance critical code-path, particularly unlikely to make any
appreciable difference?
Other style gripes: There are various points at which 80 columns are
exceeded, contrary to our style guidelines. Sorry to have to mention
it, but the comments need to be cleaned up (I am of course aware that
English is not your first language, so that's not a big deal, and I
don't think the content of the comments is lacking).
The patch does essentially work as advertised. Sites that use the new
infrastructure are limited to integrity constraint violations (which
includes referential integrity constraint violations). So, based on
your description, I'd have imagined that all of the sites using
errcodes under this banner would have been covered:
/* Class 23 - Integrity Constraint Violation */
This would be a reasonably well-defined place to require that this new
infrastructure be used going forward (emphasis on the well-defined).
Note that the authors of third-party database drivers define exception
classes whose structure reflects these errcodes.h codes. To be
inconsistent here seems unacceptable, since some future client of,
say, pqxx (the example that I am personally most familiar with) might
reasonably hope to always see some relation name when they call the
e.relation_name() of some pqxx::integrity_constraint_violation object.
If we were to commit the patch as-is, that would not be possible,
because the following such sites that have not been touched:
src/backend/commands/typecmds.c
2233: (errcode(ERRCODE_NOT_NULL_VIOLATION),
src/backend/commands/tablecmds.c
3809: (errcode(ERRCODE_NOT_NULL_VIOLATION),
src/backend/executor/execQual.c
3786: (errcode(ERRCODE_NOT_NULL_VIOLATION),
src/backend/utils/adt/domains.c
126: (errcode(ERRCODE_NOT_NULL_VIOLATION),
....
src/backend/utils/sort/tuplesort.c
3088: (errcode(ERRCODE_UNIQUE_VIOLATION),
....
src/backend/commands/typecmds.c
2602: (errcode(ERRCODE_CHECK_VIOLATION),
src/backend/commands/tablecmds.c
3823: (errcode(ERRCODE_CHECK_VIOLATION),
6633: (errcode(ERRCODE_CHECK_VIOLATION),
src/backend/executor/execQual.c
3815: (errcode(ERRCODE_CHECK_VIOLATION),
src/backend/utils/adt/domains.c
162: (errcode(ERRCODE_CHECK_VIOLATION),
This function appears to be entirely vestigial, and can be removed, as
it is never called:
+ extern inline int schema_table_column_error(const char *schema_name,
const char *table_name,
+ const char *colname);
This function is also vestigial and unused:
+ extern inline int rel_column_error(Oid table_oid, const char *colname);
I have taken the liberty of removing both functions for you within the
attached revision - I hope that's okay.
Further gripes with the mechanism you've chosen:
* Couldn't constraint_relation_error(Relation rel) just be implemented
in terms of constraint_error(Relation rel, const char* cname)?
* I doubt that relation_column_error() and friends belong in
relcache.c at all, but if they do, then their prototypes belong in
relcache.h, not rel.h
* This seems rather broken to me:
+ static inline void
+ set_field(char **ptr, const char *str, bool overwrite)
+ {
Why doesn't the function take "char *ptr" as its first argument? This
looks like a modularity violation, since it would be perfectly
possible to write the function as suggested.
* Those functions use underscores rather than CamelCase, which is not
consistent with the code that surround either the definitions or
declarations.
* ereport is used so frequently that it occurs to me that it would be
nice to build some error-detection code into this expansion of the
mechanism, to detect incorrect use (at least on debug configurations).
So maybe constraint_relation_error() lives alongside errdetail()
within elog.c. Maybe they return values of each function are some
magic value, that is later read within errfinish(int dummy,...) via
varargs. From there, on debug configurations, raise a warning if each
argument is in a less than idiomatic order (so that we formalise the
order). I'm not sure if that's worth the hassle of formalising, but
it's worth considering, particularly as there are calls to make our
error reporting mechanism more sophisticated.
In the original thread on this (which was, regrettably, sidetracked by
my tangential complaints about error severity), Robert expressed
concerns about the performance impact of this patch, when plpgsql
exception blocks were entered. I think it's a reasonable thing to be
concerned about in general, and I'll address it when I address
eelog-plpgsql-2012-05-09.diff separately.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog-2012-07-02.patchapplication/octet-stream; name=eelog-2012-07-02.patchDownload
diff doc/src/sgml/config.sgml
index 074afee..5cf2fe3
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** CREATE TABLE postgres_log
*** 4180,4185 ****
--- 4180,4193 ----
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
diff doc/src/sgml/protocol.sgml
index e725563..739d1fe
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
*************** message.
*** 4720,4725 ****
--- 4720,4824 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <literal>c</>
+ </term>
+ <listitem>
+ <para>
+ Column name: the name of column related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>t</>
+ </term>
+ <listitem>
+ <para>
+ Table name: the name of table related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>s</>
+ </term>
+ <listitem>
+ <para>
+ Schema name: the name of schema related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>n</>
+ </term>
+ <listitem>
+ <para>
+ Constraint name: the name of constraint related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>m</>
+ </term>
+ <listitem>
+ <para>
+ Constraint schema: the schema of constraint related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>r</>
+ </term>
+ <listitem>
+ <para>
+ Routine name: the name of routine related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>u</>
+ </term>
+ <listitem>
+ <para>
+ Routine schema: the schema of routine related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>g</>
+ </term>
+ <listitem>
+ <para>
+ Trigger name: the name of trigger related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>h</>
+ </term>
+ <listitem>
+ <para>
+ Trigger schema: the schema of trigger related to error
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
diff src/backend/access/nbtree/nbtinsert.c
index 3ed9b5c..a5e5d12
*** 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,400 ----
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull)),
! constraint_relation_error(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);
--- 456,463 ----
(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."),
! relation_error(rel)));
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,
--- 535,542 ----
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."),
! relation_error(rel)));
/*----------
* If we will need to split the page to put the item on this page,
diff src/backend/executor/execMain.c
index 440438b..11da994
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1522,1528 ****
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,1530 ----
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)),
! relation_column_error(rel, NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
! constraint_error(rel, "not_null_violation")));
}
}
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1536,1542 ****
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1538,1546 ----
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64)),
! relation_error(rel),
! constraint_error(rel, failed)));
}
}
diff src/backend/executor/execUtils.c
index 2bd8b42..d64c747
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
*************** retry:
*** 1304,1317 ****
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);
--- 1304,1319 ----
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing),
! constraint_relation_error(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),
! constraint_relation_error(index)));
}
index_endscan(index_scan);
diff src/backend/utils/adt/ri_triggers.c
index 983f631..49859a8
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** RI_FKey_check(TriggerData *trigdata)
*** 338,344 ****
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);
--- 338,346 ----
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."),
! relation_error(trigdata->tg_relation),
! constraint_error(trigdata->tg_relation, NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
*************** RI_Initial_Check(Trigger *trigger, Relat
*** 2466,2472 ****
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
--- 2468,2477 ----
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."),
! relation_error(fk_rel),
! constraint_error(fk_rel, NameStr(fake_riinfo.conname))));
!
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
*************** ri_ReportViolation(const RI_ConstraintIn
*** 3218,3224 ****
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),
--- 3223,3231 ----
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel)),
! relation_error(fk_rel),
! constraint_error(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
*************** ri_ReportViolation(const RI_ConstraintIn
*** 3228,3234 ****
RelationGetRelationName(fk_rel)),
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(fk_rel))));
}
--- 3235,3243 ----
RelationGetRelationName(fk_rel)),
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(fk_rel)),
! relation_error(pk_rel),
! constraint_error(fk_rel, NameStr(riinfo->conname))));
}
diff src/backend/utils/cache/relcache.c
index 2e6776e..80561b7
*** a/src/backend/utils/cache/relcache.c
--- b/src/backend/utils/cache/relcache.c
*************** unlink_initfile(const char *initfilename
*** 4624,4626 ****
--- 4624,4674 ----
elog(LOG, "could not remove cache file \"%s\": %m", initfilename);
}
}
+
+ /*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+ inline int
+ relation_column_error(Relation rel, const char *colname)
+ {
+ if (colname != NULL)
+ erritem(PG_DIAG_COLUMN_NAME, colname);
+
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
+
+ /*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+ inline int
+ relation_error(Relation rel)
+ {
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
+
+ /*
+ * Sets constraint_name and constraint_schema in ErrorData
+ */
+ inline int
+ constraint_error(Relation rel, const char *cname)
+ {
+ erritem(PG_DIAG_CONSTRAINT_NAME, cname);
+ erritem(PG_DIAG_CONSTRAINT_SCHEMA, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
+
+ inline int
+ constraint_relation_error(Relation rel)
+ {
+ erritem(PG_DIAG_CONSTRAINT_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_CONSTRAINT_SCHEMA, get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
diff src/backend/utils/error/elog.c
index a40b343..7fd952f
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
*************** errfinish(int dummy,...)
*** 477,482 ****
--- 477,500 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
*************** internalerrquery(const char *query)
*** 1078,1083 ****
--- 1096,1193 ----
return 0; /* return value does not matter */
}
+ static inline void
+ set_field(char **ptr, const char *str, bool overwrite)
+ {
+ if (*ptr != NULL)
+ {
+ /*
+ * for some cases like ROUTINE_NAME, ROUTINE_SCHEMA we would
+ * to get the most older value.
+ */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != NULL)
+ *ptr = MemoryContextStrdup(ErrorContext, str);
+ }
+
+ /*
+ * erritem -- generic setting of ErrorData string fields
+ */
+ int
+ erritem(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_field(&edata->message, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_field(&edata->detail, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_field(&edata->hint, str, true);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_field(&edata->context, str, true);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_field(&edata->column_name, str, true);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_field(&edata->table_name, str, true);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_field(&edata->schema_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_field(&edata->constraint_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_field(&edata->constraint_schema, str, true);
+ break;
+
+ case PG_DIAG_ROUTINE_NAME:
+ set_field(&edata->routine_name, str, false);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_field(&edata->routine_schema, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_field(&edata->trigger_name, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_field(&edata->trigger_schema, str, false);
+ break;
+
+ default:
+ elog(ERROR, "unknown ErrorData field id %d", field);
+ }
+
+ return 0; /* return value does not matter */
+ }
+
/*
* geterrcode --- return the currently set SQLSTATE error code
*
*************** CopyErrorData(void)
*** 1352,1357 ****
--- 1462,1485 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
*************** FreeErrorData(ErrorData *edata)
*** 1377,1382 ****
--- 1505,1528 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
*************** ReThrowError(ErrorData *edata)
*** 1449,1454 ****
--- 1595,1618 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
*************** write_csvlog(ErrorData *edata)
*** 2259,2264 ****
--- 2423,2455 ----
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
*************** send_message_to_server_log(ErrorData *ed
*** 2377,2382 ****
--- 2568,2636 ----
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
*************** send_message_to_frontend(ErrorData *edat
*** 2673,2678 ****
--- 2927,2986 ----
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
diff src/include/postgres_ext.h
index b6ebb7a..833c59e
*** a/src/include/postgres_ext.h
--- b/src/include/postgres_ext.h
*************** typedef unsigned int Oid;
*** 55,59 ****
--- 55,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_SCHEMA_NAME 's'
+ #define PG_DIAG_CONSTRAINT_NAME 'n'
+ #define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+ #define PG_DIAG_ROUTINE_NAME 'r'
+ #define PG_DIAG_ROUTINE_SCHEMA 'u'
+ #define PG_DIAG_TRIGGER_NAME 'g'
+ #define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif
diff src/include/utils/elog.h
index 1bbfd2b..3bfead9
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
*************** extern int geterrcode(void);
*** 190,195 ****
--- 189,196 ----
extern int geterrposition(void);
extern int getinternalerrposition(void);
+ extern int erritem(int field, const char *str);
+
/*----------
* Old-style error reporting API: to be used in this way:
*************** typedef struct ErrorData
*** 321,326 ****
--- 323,337 ----
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 *schema_name; /* name of schema */
+ char *constraint_name; /* name of constraint */
+ char *constraint_schema; /* name of schema with constraint */
+ char *routine_name; /* name of function that caused error */
+ char *routine_schema; /* schema name of function that caused error */
+ char *trigger_name; /* name of trigger that caused error */
+ char *trigger_schema; /* schema of trigger that caused error */
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 4669d8a..82d50e5
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
*************** typedef struct StdRdOptions
*** 394,397 ****
--- 394,404 ----
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+ extern inline int relation_column_error(Relation rel, const char *colname);
+ extern inline int relation_error(Relation rel);
+ extern inline int constraint_error(Relation rel, const char *ccname);
+ extern inline int constraint_relation_error(Relation rel);
+ extern inline int column_error(const char *cname);
+
+
#endif /* REL_H */
diff src/interfaces/libpq/fe-protocol3.c
index 173af2e..6f0a664
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 980,985 ****
--- 980,1013 ----
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("SCHEMA 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_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
On 2 July 2012 15:19, Peter Geoghegan <peter@2ndquadrant.com> wrote:
On 9 May 2012 14:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fieldsSo I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.
I decided to follow through and take a look at
eelog-plpgsql-2012-05-09.diff today too, while I have all of this
swapped into my head.
This patch is not an atomic unit - it builds upon the first patch. I
successfully merged the local feature branch that I'd created for
eelog-2012-05-09.diff without any merge conflicts, and I can build
Postgres and get the regression tests to pass (including a couple of
new ones, for this added functionality for plggsql - the functionality
is testing exclusively using the new (9.2) "get stacked diagnostics"
and "raise custom exception 'some_custom_exception' using...."
feature).
Since that feature branch had all my revisions committed, my
observations about redundancies in the other base patch still stand -
the 2 functions mentioned did not exist for the benefit of this
further patch either.
There is a typo here:
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA:
+ printf(" TRIGGER_SCHENA = ");
+ break;
}
I'm not sure about this inconsistency within unreserved_keyword:
For routines:
+ | K_DIAG_ROUTINE_NAME
+ | K_DIAG_ROUTINE_SCHEMA
....
For triggers:
+ | K_DIAG_TRIGGER_NAME
+ | K_DIAG_TRIGGER_SCHEMA
....
For tables:
+ | K_DIAG_SCHEMA_NAME
.
. **SNIP**
.
+ | K_DIAG_TABLE_NAME
The same inconsistency exists within the anonymous enum that contains
PLPGSQL_GETDIAG_TABLE_NAME (and other constants), as well as the new
token keywords within plpgsql's gram.y .
The doc changes need a little work here too.
I'm not sure that I agree with the extensive use of the term "routine"
in all of these constants - sure, information_schema has a view called
"routines". But wouldn't it be more appropriate to use a
Postgres-centric term within our own code?
So, what about the concern about performance taking a hit when plpgsql
exception blocks are entered as a result of this patch? Well, while I
think that an effort to reduce the overhead of PL exception handling
would be worthwhile, these patches do not appear to alter things
appreciable (though the overhead *is* measurable):
[peter@peterlaptop eelog]$ ls
exceptions.sql test_eelog_outer.sql
Patch (eelog-plpgsql):
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 305756
tps = 1019.026055 (including connections establishing)
tps = 1019.090135 (excluding connections establishing)
Master:
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 308376
tps = 1027.908182 (including connections establishing)
tps = 1027.977879 (excluding connections establishing)
An archive with simple scripts for repeating this are attached, if
anyone is interested.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
2012/7/2 Peter Geoghegan <peter@2ndquadrant.com>:
On 2 July 2012 15:19, Peter Geoghegan <peter@2ndquadrant.com> wrote:
On 9 May 2012 14:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fieldsSo I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.I decided to follow through and take a look at
eelog-plpgsql-2012-05-09.diff today too, while I have all of this
swapped into my head.This patch is not an atomic unit - it builds upon the first patch. I
successfully merged the local feature branch that I'd created for
eelog-2012-05-09.diff without any merge conflicts, and I can build
Postgres and get the regression tests to pass (including a couple of
new ones, for this added functionality for plggsql - the functionality
is testing exclusively using the new (9.2) "get stacked diagnostics"
and "raise custom exception 'some_custom_exception' using...."
feature).Since that feature branch had all my revisions committed, my
observations about redundancies in the other base patch still stand -
the 2 functions mentioned did not exist for the benefit of this
further patch either.There is a typo here:
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA: + printf(" TRIGGER_SCHENA = "); + break; }I'm not sure about this inconsistency within unreserved_keyword:
For routines: + | K_DIAG_ROUTINE_NAME + | K_DIAG_ROUTINE_SCHEMA ....For triggers: + | K_DIAG_TRIGGER_NAME + | K_DIAG_TRIGGER_SCHEMA ....For tables: + | K_DIAG_SCHEMA_NAME . . **SNIP** . + | K_DIAG_TABLE_NAMEThe same inconsistency exists within the anonymous enum that contains
PLPGSQL_GETDIAG_TABLE_NAME (and other constants), as well as the new
token keywords within plpgsql's gram.y .The doc changes need a little work here too.
I'm not sure that I agree with the extensive use of the term "routine"
in all of these constants - sure, information_schema has a view called
"routines". But wouldn't it be more appropriate to use a
Postgres-centric term within our own code?So, what about the concern about performance taking a hit when plpgsql
exception blocks are entered as a result of this patch? Well, while I
think that an effort to reduce the overhead of PL exception handling
would be worthwhile, these patches do not appear to alter things
appreciable (though the overhead *is* measurable):[peter@peterlaptop eelog]$ ls
exceptions.sql test_eelog_outer.sqlPatch (eelog-plpgsql):
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 305756
tps = 1019.026055 (including connections establishing)
tps = 1019.090135 (excluding connections establishing)Master:
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 308376
tps = 1027.908182 (including connections establishing)
tps = 1027.977879 (excluding connections establishing)An archive with simple scripts for repeating this are attached, if
anyone is interested.
yes, I think so slowdown is not significant for usual situations and
patterns. I got about 3% slowdown on code like
begin
insert into tab -- raise exception
exception when others ..
...
end;
and only when all inserts fails.
Regards
Pavel
Show quoted text
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Hello Peter,
thank you very much for review
2012/7/2 Peter Geoghegan <peter@2ndquadrant.com>:
On 9 May 2012 14:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fieldsSo I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.The patch has bitrotted some, due to the fact that Tom bashed around
ri_triggers.c somewhat in recent weeks. I took the opportunity to
resolve merge conflicts, and attach a revised patch for everyone's
convenience.The regression tests pass when the patch is applied.
The first thing I noticed about the patch was that inline functions
are used freely. While I personally don't find this unreasonable, we
recently revisited the question of whether or not it is necessary to
continue to support compilers that do not support something equivalent
to GNU C inline functions (or that cannot be made to support them via
macro hacks). The outcome of that was that it remains necessary to use
macros to provide non-inline equivalent functions. For a good example
of how that was dealt with quite extensively, see the sortsupport
commit:
using "inline" keyword is probably premature optimization in this
context and better to remove it. This code is not on critical path.
In particular, take a look at the new file sortsupport.h .
However, whatever we might some day allow with inline functions, the
use of "extern inline", a C99 feature (or, according to some,
anti-feature) seems like a nonstarter into the foreseeable future,
unfortunately. Perhaps more to the point, are whatever performance
benefit is to be had by the use of inline functions here actually
going to pay for the maintenance cost? We already have functions
declarations like this, in the same header as your proposed new extern
inline functions:extern int geterrcode(void);
This function's definition is trivial, and it would probably look like
a good candidate for inlining to the compiler, if it had the chance
(which, incidentally, it *still* may). However, why bother writing
code to support (or encourage) this, considering that this is hardly a
performance critical code-path, particularly unlikely to make any
appreciable difference?Other style gripes: There are various points at which 80 columns are
exceeded, contrary to our style guidelines. Sorry to have to mention
it, but the comments need to be cleaned up (I am of course aware that
English is not your first language, so that's not a big deal, and I
don't think the content of the comments is lacking).
it is ok. I'll reformat my code.
The patch does essentially work as advertised. Sites that use the new
infrastructure are limited to integrity constraint violations (which
includes referential integrity constraint violations). So, based on
your description, I'd have imagined that all of the sites using
errcodes under this banner would have been covered:/* Class 23 - Integrity Constraint Violation */
This would be a reasonably well-defined place to require that this new
infrastructure be used going forward (emphasis on the well-defined).
Note that the authors of third-party database drivers define exception
classes whose structure reflects these errcodes.h codes. To be
inconsistent here seems unacceptable, since some future client of,
say, pqxx (the example that I am personally most familiar with) might
reasonably hope to always see some relation name when they call the
e.relation_name() of some pqxx::integrity_constraint_violation object.
If we were to commit the patch as-is, that would not be possible,
because the following such sites that have not been touched:src/backend/commands/typecmds.c
2233: (errcode(ERRCODE_NOT_NULL_VIOLATION),src/backend/commands/tablecmds.c
3809: (errcode(ERRCODE_NOT_NULL_VIOLATION),src/backend/executor/execQual.c
3786: (errcode(ERRCODE_NOT_NULL_VIOLATION),src/backend/utils/adt/domains.c
126: (errcode(ERRCODE_NOT_NULL_VIOLATION),....
src/backend/utils/sort/tuplesort.c
3088: (errcode(ERRCODE_UNIQUE_VIOLATION),....
src/backend/commands/typecmds.c
2602: (errcode(ERRCODE_CHECK_VIOLATION),src/backend/commands/tablecmds.c
3823: (errcode(ERRCODE_CHECK_VIOLATION),
6633: (errcode(ERRCODE_CHECK_VIOLATION),src/backend/executor/execQual.c
3815: (errcode(ERRCODE_CHECK_VIOLATION),src/backend/utils/adt/domains.c
162: (errcode(ERRCODE_CHECK_VIOLATION),
yes, it should be fixed
This function appears to be entirely vestigial, and can be removed, as
it is never called:+ extern inline int schema_table_column_error(const char *schema_name, const char *table_name, + const char *colname);This function is also vestigial and unused:
+ extern inline int rel_column_error(Oid table_oid, const char *colname);
I have taken the liberty of removing both functions for you within the
attached revision - I hope that's okay.Further gripes with the mechanism you've chosen:
* Couldn't constraint_relation_error(Relation rel) just be implemented
in terms of constraint_error(Relation rel, const char* cname)?* I doubt that relation_column_error() and friends belong in
relcache.c at all, but if they do, then their prototypes belong in
relcache.h, not rel.h* This seems rather broken to me:
+ static inline void + set_field(char **ptr, const char *str, bool overwrite) + {Why doesn't the function take "char *ptr" as its first argument? This
looks like a modularity violation, since it would be perfectly
possible to write the function as suggested.
No, it is not possible - I have to modify structure's fields - so I
need addresses of pointers
* Those functions use underscores rather than CamelCase, which is not
consistent with the code that surround either the definitions or
declarations.
It is hard question again - functions related to Relation usually use
CamelCase, but functions related error processing use underscores -
and I used it, because they used together with ereport.
* ereport is used so frequently that it occurs to me that it would be
nice to build some error-detection code into this expansion of the
mechanism, to detect incorrect use (at least on debug configurations).
So maybe constraint_relation_error() lives alongside errdetail()
within elog.c. Maybe they return values of each function are some
magic value, that is later read within errfinish(int dummy,...) via
varargs. From there, on debug configurations, raise a warning if each
argument is in a less than idiomatic order (so that we formalise the
order). I'm not sure if that's worth the hassle of formalising, but
it's worth considering, particularly as there are calls to make our
error reporting mechanism more sophisticated.
It is question. If I move constraint_relation_error to elog.c, then I
have to include utils/rel.h to utils/elog.h. It was a issue previous
version - criticised by Tom
So current logic is - if you use "rel.h" and related structs, then you
can set these fields in ErrorData.
Regards
Pavel
Show quoted text
In the original thread on this (which was, regrettably, sidetracked by
my tangential complaints about error severity), Robert expressed
concerns about the performance impact of this patch, when plpgsql
exception blocks were entered. I think it's a reasonable thing to be
concerned about in general, and I'll address it when I address
eelog-plpgsql-2012-05-09.diff separately.--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
2012/7/2 Peter Geoghegan <peter@2ndquadrant.com>:
On 2 July 2012 15:19, Peter Geoghegan <peter@2ndquadrant.com> wrote:
On 9 May 2012 14:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fieldsSo I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.I decided to follow through and take a look at
eelog-plpgsql-2012-05-09.diff today too, while I have all of this
swapped into my head.This patch is not an atomic unit - it builds upon the first patch. I
successfully merged the local feature branch that I'd created for
eelog-2012-05-09.diff without any merge conflicts, and I can build
Postgres and get the regression tests to pass (including a couple of
new ones, for this added functionality for plggsql - the functionality
is testing exclusively using the new (9.2) "get stacked diagnostics"
and "raise custom exception 'some_custom_exception' using...."
feature).Since that feature branch had all my revisions committed, my
observations about redundancies in the other base patch still stand -
the 2 functions mentioned did not exist for the benefit of this
further patch either.There is a typo here:
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA: + printf(" TRIGGER_SCHENA = "); + break; }I'm not sure about this inconsistency within unreserved_keyword:
For routines: + | K_DIAG_ROUTINE_NAME + | K_DIAG_ROUTINE_SCHEMA ....For triggers: + | K_DIAG_TRIGGER_NAME + | K_DIAG_TRIGGER_SCHEMA ....For tables: + | K_DIAG_SCHEMA_NAME . . **SNIP** . + | K_DIAG_TABLE_NAME
This inconsistency is based on ANSI SQL design - we can use own
identifiers, but I prefer identifiers based on keyword strings.
The same inconsistency exists within the anonymous enum that contains
PLPGSQL_GETDIAG_TABLE_NAME (and other constants), as well as the new
token keywords within plpgsql's gram.y .
see standard, please :) - it is not consistent. But it use short and
clean names, and it is relative widely used.
The doc changes need a little work here too.
please, if you can enhance documentation, please, do it. I am not
native speaker.
I'm not sure that I agree with the extensive use of the term "routine"
in all of these constants - sure, information_schema has a view called
"routines". But wouldn't it be more appropriate to use a
Postgres-centric term within our own code?
for me - routine is general world for functions and procedures. So
using routine in PostgreSQL is ok. And I believe so we will support
procedures too some day.
So, what about the concern about performance taking a hit when plpgsql
exception blocks are entered as a result of this patch? Well, while I
think that an effort to reduce the overhead of PL exception handling
would be worthwhile, these patches do not appear to alter things
appreciable (though the overhead *is* measurable):[peter@peterlaptop eelog]$ ls
exceptions.sql test_eelog_outer.sqlPatch (eelog-plpgsql):
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 305756
tps = 1019.026055 (including connections establishing)
tps = 1019.090135 (excluding connections establishing)Master:
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 308376
tps = 1027.908182 (including connections establishing)
tps = 1027.977879 (excluding connections establishing)An archive with simple scripts for repeating this are attached, if
anyone is interested.
thank you for performance testing. It verify my own testing.
Thank you for review,
Regards
Pavel
Show quoted text
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Hi,
On Monday, July 02, 2012 04:19:56 PM Peter Geoghegan wrote:
The first thing I noticed about the patch was that inline functions
are used freely. While I personally don't find this unreasonable, we
recently revisited the question of whether or not it is necessary to
continue to support compilers that do not support something equivalent
to GNU C inline functions (or that cannot be made to support them via
macro hacks). The outcome of that was that it remains necessary to use
macros to provide non-inline equivalent functions. For a good example
of how that was dealt with quite extensively, see the sortsupport
commit:http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=c6e3ac11b60ac
4a8942ab964252d51c1c0bd8845In particular, take a look at the new file sortsupport.h .
I actually find sortsupport.h not to be a good example in general because it
duplicates the code. Unless youre dealing with minor amounts of code playing
macro tricks like I did in the ilist stuff seems to be a better idea.
Greetings,
Andres
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Excerpts from Pavel Stehule's message of mar jul 03 12:26:57 -0400 2012:
2012/7/2 Peter Geoghegan <peter@2ndquadrant.com>:
* ereport is used so frequently that it occurs to me that it would be
nice to build some error-detection code into this expansion of the
mechanism, to detect incorrect use (at least on debug configurations).
So maybe constraint_relation_error() lives alongside errdetail()
within elog.c. Maybe they return values of each function are some
magic value, that is later read within errfinish(int dummy,...) via
varargs. From there, on debug configurations, raise a warning if each
argument is in a less than idiomatic order (so that we formalise the
order). I'm not sure if that's worth the hassle of formalising, but
it's worth considering, particularly as there are calls to make our
error reporting mechanism more sophisticated.It is question. If I move constraint_relation_error to elog.c, then I
have to include utils/rel.h to utils/elog.h. It was a issue previous
version - criticised by Tom
Including rel.h in elog.h is really really bad. Even if it was only
relcache.h it would be bad, because elog.h is included *everywhere*.
So current logic is - if you use "rel.h" and related structs, then you
can set these fields in ErrorData.
Hm. These new functions do not operate on Relations at all, so having
them on relcache.c doesn't seem to me very good ...
How about putting the functions in elog.c as Peter suggests, and the
declarations in a new header (say relationerror.h or something like
that)? That new header would #include relcache.h so if you need to set
those fields you include the new header and you have everything you
need.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
2012/7/3 Alvaro Herrera <alvherre@commandprompt.com>:
Excerpts from Pavel Stehule's message of mar jul 03 12:26:57 -0400 2012:
2012/7/2 Peter Geoghegan <peter@2ndquadrant.com>:
* ereport is used so frequently that it occurs to me that it would be
nice to build some error-detection code into this expansion of the
mechanism, to detect incorrect use (at least on debug configurations).
So maybe constraint_relation_error() lives alongside errdetail()
within elog.c. Maybe they return values of each function are some
magic value, that is later read within errfinish(int dummy,...) via
varargs. From there, on debug configurations, raise a warning if each
argument is in a less than idiomatic order (so that we formalise the
order). I'm not sure if that's worth the hassle of formalising, but
it's worth considering, particularly as there are calls to make our
error reporting mechanism more sophisticated.It is question. If I move constraint_relation_error to elog.c, then I
have to include utils/rel.h to utils/elog.h. It was a issue previous
version - criticised by TomIncluding rel.h in elog.h is really really bad. Even if it was only
relcache.h it would be bad, because elog.h is included *everywhere*.So current logic is - if you use "rel.h" and related structs, then you
can set these fields in ErrorData.Hm. These new functions do not operate on Relations at all, so having
them on relcache.c doesn't seem to me very good ...How about putting the functions in elog.c as Peter suggests, and the
declarations in a new header (say relationerror.h or something like
that)? That new header would #include relcache.h so if you need to set
those fields you include the new header and you have everything you
need.
it could be
Pavel
Show quoted text
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On 3 July 2012 17:26, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Hello Peter,
thank you very much for review
No problem.
I'll do some copy-editing of comments and doc changes when you produce
another revision.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Peter Geoghegan <peter@2ndquadrant.com> writes:
So I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.
I've been trying out this patch for my own interest (I'm very pleased to
see work on this feature), and I have a couple of suggestions from a
user's point of view.
First: if a not null constraint is violated, the error report includes
CONSTRAINT NAME 'not_null_violation'. I think I would find it more
useful if CONSTRAINT NAME were left unset rather than given a value that
doesn't correspond to a real constraint. A client program can tell it's
a null constraint violation from the SQLSTATE.
Second: in the case where a foreign-key constraint is violated by a
change in the primary-key table, the error report gives the following
information:
TABLE NAME: name of primary-key table
SCHEMA NAME: schema of primary-key table
CONSTRAINT NAME: name of foreign-key constraint
CONSTRAINT SCHEMA: schema of foreign-key table
It doesn't include the name of the foreign-key table (except in the
human-readable error message). But in principle you need to know that
table name to reliably identify the constraint that was violated.
I think what's going on is a mismatch between the way the constraint
namespace works in the SQL standard and in PostgreSQL: it looks like the
standard expects constraint names to be unique within a schema, while
PostgreSQL only requires them to be unique within a table. (A similar
issue makes information_schema less useful than the pg_ tables for
foreign key constraints.)
So I think it would be helpful to go beyond the standard in this case
and include the foreign-key table name somewhere in the report.
Possibly the enhanced-error reports could somehow add the table name to
the string in the CONSTRAINT NAME field, so that the interface
PostgreSQL provides looks like the one the standard envisages (which
ought to make it easier to write cross-database client code).
Or it might be simpler to just add a new enhanced-error field; I can
imagine cases where that table name would be the main thing I'd be
interested in.
-M-
Hello
2012/7/3 Matthew Woodcraft <matthew@woodcraft.me.uk>:
Peter Geoghegan <peter@2ndquadrant.com> writes:
So I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.I've been trying out this patch for my own interest (I'm very pleased to
see work on this feature), and I have a couple of suggestions from a
user's point of view.First: if a not null constraint is violated, the error report includes
CONSTRAINT NAME 'not_null_violation'. I think I would find it more
useful if CONSTRAINT NAME were left unset rather than given a value that
doesn't correspond to a real constraint. A client program can tell it's
a null constraint violation from the SQLSTATE.
I don't think so generation some special name is good idea. In this
case - important values are in COLUMN_NAME, TABLE_NAME, SCHEMA_NAME
postgres=# create table ff(a int not null);
CREATE TABLE
postgres=# \set VERBOSITY verbose
postgres=# insert into ff values(null);
ERROR: 23502: null value in column "a" violates not-null constraint
DETAIL: Failing row contains (null).
LOCATION: ExecConstraints, execMain.c:1527
COLUMN NAME: a
TABLE NAME: ff
SCHEMA NAME: public
CONSTRAINT NAME: not_null_violation
CONSTRAINT SCHEMA: public
Second: in the case where a foreign-key constraint is violated by a
change in the primary-key table, the error report gives the following
information:TABLE NAME: name of primary-key table
SCHEMA NAME: schema of primary-key table
CONSTRAINT NAME: name of foreign-key constraint
CONSTRAINT SCHEMA: schema of foreign-key table
postgres=# create table a1(a int primary key);
NOTICE: 00000: CREATE TABLE / PRIMARY KEY will create implicit index
"a1_pkey" for table "a1"
LOCATION: DefineIndex, indexcmds.c:600
CREATE TABLE
postgres=# create table a2(a int references a1(a));
CREATE TABLE
postgres=# insert into a2 values(10);
ERROR: 23503: insert or update on table "a2" violates foreign key
constraint "a2_a_fkey"
DETAIL: Key (a)=(10) is not present in table "a1".
LOCATION: ri_ReportViolation, ri_triggers.c:3228
TABLE NAME: a2
SCHEMA NAME: public
CONSTRAINT NAME: a2_a_fkey
CONSTRAINT SCHEMA: public
postgres=# \d a2
Table "public.a2"
Column │ Type │ Modifiers
────────┼─────────┼───────────
a │ integer │
Foreign-key constraints:
"a2_a_fkey" FOREIGN KEY (a) REFERENCES a1(a)
I agree so access to related table is not simple, but you know
constraint name, and you can take referenced table from constraint
definition.
so any special column is not necessary. It can be done in future, but
for this moment I would add only really necessary fields.
It doesn't include the name of the foreign-key table (except in the
human-readable error message). But in principle you need to know that
table name to reliably identify the constraint that was violated.I think what's going on is a mismatch between the way the constraint
namespace works in the SQL standard and in PostgreSQL: it looks like the
standard expects constraint names to be unique within a schema, while
PostgreSQL only requires them to be unique within a table. (A similar
issue makes information_schema less useful than the pg_ tables for
foreign key constraints.)So I think it would be helpful to go beyond the standard in this case
and include the foreign-key table name somewhere in the report.Possibly the enhanced-error reports could somehow add the table name to
the string in the CONSTRAINT NAME field, so that the interface
PostgreSQL provides looks like the one the standard envisages (which
ought to make it easier to write cross-database client code).
same situation is with triggers
I prefer add two new fields CONSTRAINT_TABLE and TRIGGER_TABLE so
NAME, TABLE and SCHEMA is unique
Regards and thank you for comments
Pavel
Show quoted text
Or it might be simpler to just add a new enhanced-error field; I can
imagine cases where that table name would be the main thing I'd be
interested in.-M-
Excerpts from Pavel Stehule's message of mié jul 04 05:33:48 -0400 2012:
Hello
2012/7/3 Matthew Woodcraft <matthew@woodcraft.me.uk>:
Peter Geoghegan <peter@2ndquadrant.com> writes:
So I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.I've been trying out this patch for my own interest (I'm very pleased to
see work on this feature), and I have a couple of suggestions from a
user's point of view.First: if a not null constraint is violated, the error report includes
CONSTRAINT NAME 'not_null_violation'. I think I would find it more
useful if CONSTRAINT NAME were left unset rather than given a value that
doesn't correspond to a real constraint. A client program can tell it's
a null constraint violation from the SQLSTATE.I don't think so generation some special name is good idea. In this
case - important values are in COLUMN_NAME, TABLE_NAME, SCHEMA_NAMEpostgres=# create table ff(a int not null);
CREATE TABLE
postgres=# \set VERBOSITY verbose
postgres=# insert into ff values(null);
ERROR: 23502: null value in column "a" violates not-null constraint
DETAIL: Failing row contains (null).
LOCATION: ExecConstraints, execMain.c:1527
COLUMN NAME: a
TABLE NAME: ff
SCHEMA NAME: public
CONSTRAINT NAME: not_null_violation
CONSTRAINT SCHEMA: public
I think if you don't have a true constraint name to use here, you
shouldn't use anything. When and if we get NOT NULL constraints
catalogued, we can add a constraint name field as a new error field.
In other words +1 for Matthew's opinion.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera <alvherre@commandprompt.com> writes:
I think if you don't have a true constraint name to use here, you
shouldn't use anything.
Yeah, I agree. Don't invent a value, just omit the field.
regards, tom lane
2012/7/4 Tom Lane <tgl@sss.pgh.pa.us>:
Alvaro Herrera <alvherre@commandprompt.com> writes:
I think if you don't have a true constraint name to use here, you
shouldn't use anything.Yeah, I agree. Don't invent a value, just omit the field.
ok
Pavel
Show quoted text
regards, tom lane
Hello
there is a updated patch:
* renamed auxiliary functions and moved it elog.c - header is new file
"relerror.h"
* new fields "constraint_table" and "trigger_table" - constraints and
triggers are related to relation in pg, not just to schema
* removed using implicit constraints without unique name
* better coverage of enhancing errors in source code
* removed "inline" keywords
/* Class 23 - Integrity Constraint Violation */
This would be a reasonably well-defined place to require that this new
infrastructure be used going forward (emphasis on the well-defined).
Note that the authors of third-party database drivers define exception
classes whose structure reflects these errcodes.h codes. To be
inconsistent here seems unacceptable, since some future client of,
say, pqxx (the example that I am personally most familiar with) might
reasonably hope to always see some relation name when they call the
e.relation_name() of some pqxx::integrity_constraint_violation object.
If we were to commit the patch as-is, that would not be possible,
because the following such sites that have not been touched:
src/backend/executor/execQual.c
3786: (errcode(ERRCODE_NOT_NULL_VIOLATION),src/backend/utils/adt/domains.c
126: (errcode(ERRCODE_NOT_NULL_VIOLATION),
src/backend/executor/execQual.c
3815: (errcode(ERRCODE_CHECK_VIOLATION),src/backend/utils/adt/domains.c
162: (errcode(ERRCODE_CHECK_VIOLATION),
these exceptions are related to domains - we has not adequate fields
now - and these fields are not in standards
it needs some like DOMAIN_NAME and DOMAIN_SCHEMA ???
Regards
Pavel
Attachments:
eelog-2012-07-05.patchapplication/octet-stream; name=eelog-2012-07-05.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4e0492b..ddaf900 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4183,6 +4183,16 @@ CREATE TABLE postgres_log
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_table text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_table text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index e725563..6f34448 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -4720,6 +4720,127 @@ message.
</listitem>
</varlistentry>
+<varlistentry>
+<term>
+<literal>c</>
+</term>
+<listitem>
+<para>
+ Column name: the name of column related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>t</>
+</term>
+<listitem>
+<para>
+ Table name: the name of table related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>s</>
+</term>
+<listitem>
+<para>
+ Schema name: the name of schema related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>n</>
+</term>
+<listitem>
+<para>
+ Constraint name: the name of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>o</>
+</term>
+<listitem>
+<para>
+ Constraint table: the table name of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>m</>
+</term>
+<listitem>
+<para>
+ Constraint schema: the schema of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>r</>
+</term>
+<listitem>
+<para>
+ Routine name: the name of routine related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>u</>
+</term>
+<listitem>
+<para>
+ Routine schema: the schema of routine related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>g</>
+</term>
+<listitem>
+<para>
+ Trigger name: the name of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>i</>
+</term>
+<listitem>
+<para>
+ Trigger table: the table of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>h</>
+</term>
+<listitem>
+<para>
+ Trigger schema: the schema of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
</variablelist>
<para>
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 3ed9b5c..419823c 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -23,6 +23,7 @@
#include "storage/predicate.h"
#include "utils/inval.h"
#include "utils/tqual.h"
+#include "utils/relerror.h"
typedef struct
@@ -393,7 +394,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
- values, isnull))));
+ values, isnull)),
+ errrelation(rel)));
}
}
else if (all_dead)
@@ -455,7 +457,8 @@ _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."),
+ errrelation(rel)));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
@@ -533,7 +536,8 @@ _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."),
+ errrelation(rel)));
/*----------
* If we will need to split the page to put the item on this page,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d69809a..91b8665 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -81,6 +81,7 @@
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
+#include "utils/relerror.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
@@ -3808,6 +3809,10 @@ 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)),
+ (newrel) ? errrelation_column(newrel,
+ NameStr(newTupDesc->attrs[attn]->attname)) :
+ errrelation_column(oldrel,
NameStr(newTupDesc->attrs[attn]->attname))));
}
@@ -3822,7 +3827,9 @@ 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),
+ (newrel) ? errrelation(newrel) : errrelation(oldrel),
+ (newrel) ? errconstraint(newrel, con->name) : errconstraint(oldrel, con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
@@ -6632,7 +6639,9 @@ 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)),
+ errrelation(rel),
+ errconstraint(rel, NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 30850b2..040adab 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -69,6 +69,7 @@
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
+#include "utils/relerror.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
@@ -2233,7 +2234,9 @@ AlterDomainNotNull(List *names, bool notNull)
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains null values",
NameStr(tupdesc->attrs[attnum - 1]->attname),
- RelationGetRelationName(testrel))));
+ RelationGetRelationName(testrel)),
+ errrelation_column(testrel,
+ NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
}
heap_endscan(scan);
@@ -2602,7 +2605,9 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
(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)),
+ errrelation_column(testrel,
+ NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
ResetExprContext(econtext);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 440438b..8332ecf 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -53,6 +53,7 @@
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/relerror.h"
#include "utils/snapmgr.h"
#include "utils/tqual.h"
@@ -1522,7 +1523,9 @@ 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)),
+ errrelation_column(rel,
+ NameStr(rel->rd_att->attrs[attrChk - 1]->attname))));
}
}
@@ -1536,7 +1539,9 @@ 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)),
+ errrelation(rel),
+ errconstraint(rel, failed)));
}
}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 2bd8b42..7f0d331 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -50,6 +50,7 @@
#include "parser/parsetree.h"
#include "storage/lmgr.h"
#include "utils/memutils.h"
+#include "utils/relerror.h"
#include "utils/tqual.h"
@@ -1304,14 +1305,16 @@ 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),
+ errrelation(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),
+ errrelation(index)));
}
index_endscan(index_scan);
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 983f631..48b02d5 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -49,6 +49,7 @@
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/rel.h"
+#include "utils/relerror.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
#include "utils/tqual.h"
@@ -338,7 +339,9 @@ RI_FKey_check(TriggerData *trigdata)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
- errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
+ errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
+ errrelation(trigdata->tg_relation),
+ errconstraint(trigdata->tg_relation, NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
@@ -2466,7 +2469,10 @@ 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."),
+ errrelation(fk_rel),
+ errconstraint(fk_rel, NameStr(fake_riinfo.conname))));
+
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
@@ -3218,7 +3224,9 @@ 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)),
+ errrelation(fk_rel),
+ errconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
@@ -3228,7 +3236,9 @@ 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)),
+ errrelation(pk_rel),
+ errconstraint(fk_rel, NameStr(riinfo->conname))));
}
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index a40b343..93f39eb 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -75,9 +75,11 @@
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
-
+#include "utils/rel.h"
+#include "utils/relerror.h"
#undef _
#define _(x) err_gettext(x)
@@ -477,6 +479,28 @@ errfinish(int dummy,...)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
@@ -1079,6 +1103,157 @@ internalerrquery(const char *query)
}
/*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+int
+errrelation_column(Relation rel, const char *colname)
+{
+ erritem(PG_DIAG_COLUMN_NAME, colname);
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+int
+errrelation(Relation rel)
+{
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * Sets constraint_name, constraint_table and constraint_schema in ErrorData
+ */
+int
+errconstraint(Relation rel, const char *cname)
+{
+ erritem(PG_DIAG_CONSTRAINT_NAME, cname);
+ erritem(PG_DIAG_CONSTRAINT_TABLE, RelationGetRelationName(rel));
+ erritem(PG_DIAG_CONSTRAINT_SCHEMA,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * set ErrorData field - ensure not overwriting for selected fields
+ */
+static void
+set_field(char **ptr, const char *str, bool overwrite)
+{
+ if (*ptr != NULL)
+ {
+ /*
+ * for some cases like ROUTINE_NAME, ROUTINE_SCHEMA we would
+ * to get the most older value.
+ */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != NULL)
+ *ptr = MemoryContextStrdup(ErrorContext, str);
+}
+
+/*
+ * erritem -- generic setting of ErrorData string fields
+ */
+int
+erritem(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_field(&edata->message, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_field(&edata->detail, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_field(&edata->hint, str, true);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_field(&edata->context, str, true);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_field(&edata->column_name, str, true);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_field(&edata->table_name, str, true);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_field(&edata->schema_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_field(&edata->constraint_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_TABLE:
+ set_field(&edata->constraint_table, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_field(&edata->constraint_schema, str, true);
+ break;
+
+ /*
+ * setup of routine_name, routine_schema, trigger_name,
+ * trigger_table, trigger_schema is processed together
+ * with collecting context. Only first values are valid,
+ * so we should to protect these values.
+ */
+ case PG_DIAG_ROUTINE_NAME:
+ set_field(&edata->routine_name, str, false);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_field(&edata->routine_schema, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_field(&edata->trigger_name, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_TABLE:
+ set_field(&edata->trigger_table, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_field(&edata->trigger_schema, str, false);
+ break;
+
+ default:
+ elog(ERROR, "unknown ErrorData field id %d",
+ field);
+ }
+
+ return 0; /* return value does not matter */
+}
+
+/*
* geterrcode --- return the currently set SQLSTATE error code
*
* This is only intended for use in error callback subroutines, since there
@@ -1352,6 +1527,28 @@ CopyErrorData(void)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
@@ -1377,6 +1574,28 @@ FreeErrorData(ErrorData *edata)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
@@ -1449,6 +1668,28 @@ ReThrowError(ErrorData *edata)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
@@ -2259,6 +2500,39 @@ write_csvlog(ErrorData *edata)
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
@@ -2377,6 +2651,83 @@ send_message_to_server_log(ErrorData *edata)
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT TABLE: "));
+ append_with_tabs(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER TABLE: "));
+ append_with_tabs(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
@@ -2673,6 +3024,72 @@ send_message_to_frontend(ErrorData *edata)
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_TABLE);
+ err_sendstring(&msgbuf, edata->constraint_table);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_TABLE);
+ err_sendstring(&msgbuf, edata->trigger_table);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index d5a2003..3a3c663 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -112,6 +112,7 @@
#include "utils/memutils.h"
#include "utils/pg_rusage.h"
#include "utils/rel.h"
+#include "utils/relerror.h"
#include "utils/sortsupport.h"
#include "utils/tuplesort.h"
@@ -3090,7 +3091,8 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b,
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
- values, isnull))));
+ values, isnull)),
+ errrelation(state->indexRel)));
}
/*
diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h
index b6ebb7a..829114a 100644
--- a/src/include/postgres_ext.h
+++ b/src/include/postgres_ext.h
@@ -55,5 +55,16 @@ typedef unsigned int Oid;
#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_SCHEMA_NAME 's'
+#define PG_DIAG_CONSTRAINT_NAME 'n'
+#define PG_DIAG_CONSTRAINT_TABLE 'o'
+#define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+#define PG_DIAG_ROUTINE_NAME 'r'
+#define PG_DIAG_ROUTINE_SCHEMA 'u'
+#define PG_DIAG_TRIGGER_NAME 'g'
+#define PG_DIAG_TRIGGER_TABLE 'i'
+#define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 1bbfd2b..c897073 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -190,6 +190,8 @@ extern int geterrcode(void);
extern int geterrposition(void);
extern int getinternalerrposition(void);
+extern int erritem(int field, const char *str);
+
/*----------
* Old-style error reporting API: to be used in this way:
@@ -321,6 +323,17 @@ typedef struct ErrorData
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 *schema_name; /* name of schema */
+ char *constraint_name; /* name of constraint */
+ char *constraint_table; /* name of table related to constraint */
+ char *constraint_schema; /* name of schema with constraint */
+ char *routine_name; /* name of function that caused error */
+ char *routine_schema; /* schema name of function that caused error */
+ char *trigger_name; /* name of trigger that caused error */
+ char *trigger_table; /* table of trigger that caused error */
+ char *trigger_schema; /* schema of trigger that caused error */
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/relerror.h b/src/include/utils/relerror.h
new file mode 100644
index 0000000..357222e
--- /dev/null
+++ b/src/include/utils/relerror.h
@@ -0,0 +1,21 @@
+/*-------------------------------------------------------------------------
+ *
+ * relerror.h
+ * setting ErrorData fields related to Relation struct
+ *
+ * Copyright (c) 2007-2012, PostgreSQL Global Development Group
+ *
+ * src/include/utils/relerror.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RELERROR_H
+#define RELERROR_H
+
+#include "utils/relcache.h"
+
+extern int errrelation_column(Relation rel, const char *colname);
+extern int errrelation(Relation rel);
+extern int errconstraint(Relation rel, const char *cname);
+
+#endif /* RELERROR_H */
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 173af2e..e83a8b7 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -980,6 +980,40 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("SCHEMA 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_CONSTRAINT_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("CONSTRAINT TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
Hello
* fixed typo
* support two new fields: constraint_table and trigger_table
* routine_table, routine_schema, trigger_name, trigger_table,
trigger_schema has value when exception coming from plpgsql.
Regards
Pavel
2012/7/2 Peter Geoghegan <peter@2ndquadrant.com>:
Show quoted text
On 2 July 2012 15:19, Peter Geoghegan <peter@2ndquadrant.com> wrote:
On 9 May 2012 14:33, Pavel Stehule <pavel.stehule@gmail.com> wrote:
here is patch with enhancing ErrorData structure. Now constraints
errors and RI uses these fieldsSo I took a look at the patch eelog-2012-05-09.diff today. All of the
following remarks apply to it alone.I decided to follow through and take a look at
eelog-plpgsql-2012-05-09.diff today too, while I have all of this
swapped into my head.This patch is not an atomic unit - it builds upon the first patch. I
successfully merged the local feature branch that I'd created for
eelog-2012-05-09.diff without any merge conflicts, and I can build
Postgres and get the regression tests to pass (including a couple of
new ones, for this added functionality for plggsql - the functionality
is testing exclusively using the new (9.2) "get stacked diagnostics"
and "raise custom exception 'some_custom_exception' using...."
feature).Since that feature branch had all my revisions committed, my
observations about redundancies in the other base patch still stand -
the 2 functions mentioned did not exist for the benefit of this
further patch either.There is a typo here:
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA: + printf(" TRIGGER_SCHENA = "); + break; }I'm not sure about this inconsistency within unreserved_keyword:
For routines: + | K_DIAG_ROUTINE_NAME + | K_DIAG_ROUTINE_SCHEMA ....For triggers: + | K_DIAG_TRIGGER_NAME + | K_DIAG_TRIGGER_SCHEMA ....For tables: + | K_DIAG_SCHEMA_NAME . . **SNIP** . + | K_DIAG_TABLE_NAMEThe same inconsistency exists within the anonymous enum that contains
PLPGSQL_GETDIAG_TABLE_NAME (and other constants), as well as the new
token keywords within plpgsql's gram.y .The doc changes need a little work here too.
I'm not sure that I agree with the extensive use of the term "routine"
in all of these constants - sure, information_schema has a view called
"routines". But wouldn't it be more appropriate to use a
Postgres-centric term within our own code?So, what about the concern about performance taking a hit when plpgsql
exception blocks are entered as a result of this patch? Well, while I
think that an effort to reduce the overhead of PL exception handling
would be worthwhile, these patches do not appear to alter things
appreciable (though the overhead *is* measurable):[peter@peterlaptop eelog]$ ls
exceptions.sql test_eelog_outer.sqlPatch (eelog-plpgsql):
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 305756
tps = 1019.026055 (including connections establishing)
tps = 1019.090135 (excluding connections establishing)Master:
[peter@peterlaptop eelog]$ pgbench -T 300 -f exceptions.sql -c 10 -n
transaction type: Custom query
scaling factor: 1
query mode: simple
number of clients: 10
number of threads: 1
duration: 300 s
number of transactions actually processed: 308376
tps = 1027.908182 (including connections establishing)
tps = 1027.977879 (excluding connections establishing)An archive with simple scripts for repeating this are attached, if
anyone is interested.--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog-plpgsql-2012-07-06.patchapplication/octet-stream; name=eelog-plpgsql-2012-07-06.patchDownload
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index ba2c57b..a8f9a85 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -2640,6 +2640,61 @@ GET STACKED DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item<
<entry>text</entry>
<entry>line(s) of text describing the call stack</entry>
</row>
+ <row>
+ <entry><literal>COLUMN_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of column related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TABLE_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of table related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>SCHEMA_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name table's schema related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>CONSTRAINT_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of constraint related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>CONSTRAINT_TABLE</literal></entry>
+ <entry>text</entry>
+ <entry>the table of constraint related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>CONSTRAINT_SCHEMA</literal></entry>
+ <entry>text</entry>
+ <entry>the constaint's schema related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>ROUTINE_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of routine related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>ROUTINE_SCHEMA</literal></entry>
+ <entry>text</entry>
+ <entry>the schema of routine related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TRIGGER_NAME</literal></entry>
+ <entry>text</entry>
+ <entry>the name of trigger related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TRIGGER_TABLE</literal></entry>
+ <entry>text</entry>
+ <entry>the table of trigger related to error, if any</entry>
+ </row>
+ <row>
+ <entry><literal>TRIGGER_SCHEMA</literal></entry>
+ <entry>text</entry>
+ <entry>the schema of trigger related to error, if any</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -3277,7 +3332,11 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
class="parameter">expression</replaceable> items. The allowed
<replaceable class="parameter">option</replaceable> keywords are
<literal>MESSAGE</>, <literal>DETAIL</>, <literal>HINT</>, and
- <literal>ERRCODE</>, while each <replaceable
+ , <literal>COLUMN_NAME</>, <literal>TABLE_NAME</>,
+ <literal>SCHEMA_NAME</>, <literal>CONSTRAINT_NAME</>,
+ <literal>CONSTRAINT_TABLE</literal>, <literal>CONSTRAINT_SCHEMA</>,
+ <literal>ROUTINE_NAME</>, <literal>ROUTINE_SCHEMA</>, <literal>TRIGGER_NAME</>,
+ <literal>TRIGGER_TABLE</literal>, <literal>TRIGGER_SCHEMA</> while each <replaceable
class="parameter">expression</replaceable> can be any string-valued
expression.
<literal>MESSAGE</> sets the error message text (this option can't
@@ -3287,7 +3346,17 @@ RAISE NOTICE 'Calling cs_create_job(%)', v_job_id;
<literal>HINT</> supplies a hint message.
<literal>ERRCODE</> specifies the error code (SQLSTATE) to report,
either by condition name as shown in <xref linkend="errcodes-appendix">,
- or directly as a five-character SQLSTATE code.
+ or directly as a five-character SQLSTATE code. <literal>COLUMN_NAME</>
+ sets the name of column related to error. <literal>TABLE_NAME</>
+ sets the name of table related to error. <literal>SCHEMA_NAME</> sets
+ the schema of table related to error. <literal>CONSTRAINT_NAME</> sets
+ name of constraint. <literal>CONSTRAINT_TABLE</literal> sets a table
+ related to constraint. <literal>CONSTRAINT_SCHEMA</> sets a schema of constraint
+ related to error. <literal>ROUTINE_NAME</> sets a name of routine related
+ to error. <literal>ROUTINE_SCHEMA</> sets a schema of routine related to error.
+ <literal>TRIGGER_NAME</> sets a name of trigger related to error.
+ <literal>TRIGGER_TABLE</> sets a table of trigger related to error.
+ <literal>TRIGGER_SCHEMA</> sets a schema of trigger related to error.
</para>
<para>
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index 4b2dce8..e0e9970 100644
--- a/src/pl/plpgsql/src/gram.y
+++ b/src/pl/plpgsql/src/gram.y
@@ -259,6 +259,17 @@ static List *read_raise_options(void);
%token <keyword> K_DECLARE
%token <keyword> K_DEFAULT
%token <keyword> K_DETAIL
+%token <keyword> K_DIAG_COLUMN_NAME
+%token <keyword> K_DIAG_TABLE_NAME
+%token <keyword> K_DIAG_SCHEMA_NAME
+%token <keyword> K_DIAG_CONSTRAINT_NAME
+%token <keyword> K_DIAG_CONSTRAINT_TABLE
+%token <keyword> K_DIAG_CONSTRAINT_SCHEMA
+%token <keyword> K_DIAG_ROUTINE_NAME
+%token <keyword> K_DIAG_ROUTINE_SCHEMA
+%token <keyword> K_DIAG_TRIGGER_NAME
+%token <keyword> K_DIAG_TRIGGER_TABLE
+%token <keyword> K_DIAG_TRIGGER_SCHEMA
%token <keyword> K_DIAGNOSTICS
%token <keyword> K_DUMP
%token <keyword> K_ELSE
@@ -877,6 +888,17 @@ stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
case PLPGSQL_GETDIAG_ERROR_HINT:
case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ case PLPGSQL_GETDIAG_CONSTRAINT_TABLE:
+ case PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA:
+ case PLPGSQL_GETDIAG_ROUTINE_NAME:
+ case PLPGSQL_GETDIAG_ROUTINE_SCHEMA:
+ case PLPGSQL_GETDIAG_TRIGGER_NAME:
+ case PLPGSQL_GETDIAG_TRIGGER_TABLE:
+ case PLPGSQL_GETDIAG_TRIGGER_SCHEMA:
if (!new->is_stacked)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -956,6 +978,39 @@ getdiag_item :
else if (tok_is_keyword(tok, &yylval,
K_RETURNED_SQLSTATE, "returned_sqlstate"))
$$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_COLUMN_NAME, "column_name"))
+ $$ = PLPGSQL_GETDIAG_COLUMN_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TABLE_NAME, "table_name"))
+ $$ = PLPGSQL_GETDIAG_TABLE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_SCHEMA_NAME, "schema_name"))
+ $$ = PLPGSQL_GETDIAG_SCHEMA_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_NAME, "constraint_name"))
+ $$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_TABLE, "constraint_table"))
+ $$ = PLPGSQL_GETDIAG_CONSTRAINT_TABLE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_SCHEMA, "constraint_schema"))
+ $$ = PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_NAME, "routine_name"))
+ $$ = PLPGSQL_GETDIAG_ROUTINE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_SCHEMA, "routine_schema"))
+ $$ = PLPGSQL_GETDIAG_ROUTINE_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_NAME, "trigger_name"))
+ $$ = PLPGSQL_GETDIAG_TRIGGER_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_TABLE, "trigger_table"))
+ $$ = PLPGSQL_GETDIAG_TRIGGER_TABLE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_SCHEMA, "trigger_schema"))
+ $$ = PLPGSQL_GETDIAG_TRIGGER_SCHEMA;
else
yyerror("unrecognized GET DIAGNOSTICS item");
}
@@ -2204,7 +2259,11 @@ unreserved_keyword :
| K_ALIAS
| K_ARRAY
| K_BACKWARD
+ | K_DIAG_COLUMN_NAME
| K_CONSTANT
+ | K_DIAG_CONSTRAINT_NAME
+ | K_DIAG_CONSTRAINT_TABLE
+ | K_DIAG_CONSTRAINT_SCHEMA
| K_CURRENT
| K_CURSOR
| K_DEBUG
@@ -2234,12 +2293,19 @@ unreserved_keyword :
| K_RESULT_OID
| K_RETURNED_SQLSTATE
| K_REVERSE
+ | K_DIAG_ROUTINE_NAME
+ | K_DIAG_ROUTINE_SCHEMA
| K_ROW_COUNT
| K_ROWTYPE
+ | K_DIAG_SCHEMA_NAME
| K_SCROLL
| K_SLICE
| K_SQLSTATE
| K_STACKED
+ | K_DIAG_TABLE_NAME
+ | K_DIAG_TRIGGER_NAME
+ | K_DIAG_TRIGGER_TABLE
+ | K_DIAG_TRIGGER_SCHEMA
| K_TYPE
| K_USE_COLUMN
| K_USE_VARIABLE
@@ -3586,6 +3652,39 @@ read_raise_options(void)
else if (tok_is_keyword(tok, &yylval,
K_HINT, "hint"))
opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_COLUMN_NAME, "column_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TABLE_NAME, "table_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TABLE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_SCHEMA_NAME, "schema_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_NAME, "constraint_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_TABLE, "constraint_table"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT_TABLE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_CONSTRAINT_SCHEMA, "constraint_schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_NAME, "routine_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_ROUTINE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_ROUTINE_SCHEMA, "routine_schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_NAME, "trigger_name"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TRIGGER_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_TABLE, "trigger_table"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TRIGGER_TABLE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DIAG_TRIGGER_SCHEMA, "trigger_schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA;
else
yyerror("unrecognized RAISE statement option");
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 5d2f818..eb20b7a 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -342,6 +342,9 @@ do_compile(FunctionCallInfo fcinfo,
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
+ function->fn_name = pstrdup(NameStr(procStruct->proname));
+ function->fn_schema = get_namespace_name(procStruct->pronamespace);
+
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 8ca791c..2549d60 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -567,6 +567,9 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
else
elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
+ estate.tg_name = trigdata->tg_trigger->tgname;
+ estate.tg_schema = get_namespace_name(RelationGetNamespace(trigdata->tg_relation));
+
/*
* Assign the special tg_ variables
*/
@@ -782,6 +785,22 @@ plpgsql_exec_error_callback(void *arg)
{
PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ /*
+ * If we are on top of error callback stack, then set function and
+ * trigger related diagnostics fields.
+ */
+ if (error_context_stack->arg == arg)
+ {
+ erritem(PG_DIAG_ROUTINE_NAME, estate->func->fn_name);
+ erritem(PG_DIAG_ROUTINE_SCHEMA, estate->func->fn_schema);
+
+ if (estate->func->fn_is_trigger)
+ {
+ erritem(PG_DIAG_TRIGGER_NAME, estate->tg_name);
+ erritem(PG_DIAG_TRIGGER_SCHEMA, estate->tg_schema);
+ }
+ }
+
/* if we are doing RAISE, don't report its location */
if (estate->err_text == raise_skip_msg)
return;
@@ -1489,6 +1508,61 @@ exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
estate->cur_error->message);
break;
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->column_name);
+ break;
+
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->table_name);
+ break;
+
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->schema_name);
+ break;
+
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->constraint_name);
+ break;
+
+ case PLPGSQL_GETDIAG_CONSTRAINT_TABLE:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->constraint_table);
+ break;
+
+ case PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->constraint_schema);
+ break;
+
+ case PLPGSQL_GETDIAG_ROUTINE_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->routine_name);
+ break;
+
+ case PLPGSQL_GETDIAG_ROUTINE_SCHEMA:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->routine_schema);
+ break;
+
+ case PLPGSQL_GETDIAG_TRIGGER_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->trigger_name);
+ break;
+
+ case PLPGSQL_GETDIAG_TRIGGER_TABLE:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->trigger_table);
+ break;
+
+ case PLPGSQL_GETDIAG_TRIGGER_SCHEMA:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->trigger_schema);
+ break;
+
default:
elog(ERROR, "unrecognized diagnostic item kind: %d",
diag_item->kind);
@@ -2666,6 +2740,17 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
char *err_message = NULL;
char *err_detail = NULL;
char *err_hint = NULL;
+ char *column_name = NULL;
+ char *table_name = NULL;
+ char *schema_name = NULL;
+ char *constraint_name = NULL;
+ char *constraint_table = NULL;
+ char *constraint_schema = NULL;
+ char *routine_name = NULL;
+ char *routine_schema = NULL;
+ char *trigger_name = NULL;
+ char *trigger_table = NULL;
+ char *trigger_schema = NULL;
ListCell *lc;
/* RAISE with no parameters: re-throw current exception */
@@ -2805,6 +2890,94 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
"HINT")));
err_hint = pstrdup(extval);
break;
+ case PLPGSQL_RAISEOPTION_COLUMN_NAME:
+ if (column_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "COLUMN_NAME")));
+ column_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TABLE_NAME:
+ if (table_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TABLE_NAME")));
+ table_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_SCHEMA_NAME:
+ if (schema_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "SCHEMA_NAME")));
+ schema_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_NAME:
+ if (constraint_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "CONSTRAINT_NAME")));
+ constraint_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_TABLE:
+ if (constraint_table)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "CONSTRAINT_TABLE")));
+ constraint_table = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA:
+ if (constraint_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "CONSTRAINT_SCHEMA")));
+ constraint_schema = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_NAME:
+ if (routine_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "ROUTINE_NAME")));
+ routine_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA:
+ if (routine_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "ROUTINE_SCHEMA")));
+ routine_schema = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_NAME:
+ if (trigger_name)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TRIGGER_NAME")));
+ trigger_name = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_TABLE:
+ if (trigger_table)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TRIGGER_TABLE")));
+ trigger_table = pstrdup(extval);
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA:
+ if (trigger_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "TRIGGER_SCHEMA")));
+ trigger_schema = pstrdup(extval);
+ break;
default:
elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
}
@@ -2837,7 +3010,18 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
(err_code ? errcode(err_code) : 0,
errmsg_internal("%s", err_message),
(err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0,
- (err_hint != NULL) ? errhint("%s", err_hint) : 0));
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0,
+ (column_name != NULL) ? erritem(PG_DIAG_COLUMN_NAME, column_name) : 0,
+ (table_name != NULL) ? erritem(PG_DIAG_TABLE_NAME, table_name) : 0,
+ (schema_name != NULL) ? erritem(PG_DIAG_SCHEMA_NAME, schema_name) : 0,
+ (constraint_name != NULL) ? erritem(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0,
+ (constraint_table != NULL) ? erritem(PG_DIAG_CONSTRAINT_TABLE, constraint_table) : 0,
+ (constraint_schema != NULL) ? erritem(PG_DIAG_CONSTRAINT_SCHEMA, constraint_schema) : 0,
+ (routine_name != NULL) ? erritem(PG_DIAG_ROUTINE_NAME, routine_name) : 0,
+ (routine_schema != NULL) ? erritem(PG_DIAG_ROUTINE_SCHEMA, routine_schema) : 0,
+ (trigger_name != NULL) ? erritem(PG_DIAG_TRIGGER_NAME, trigger_name) : 0,
+ (trigger_table != NULL) ? erritem(PG_DIAG_TRIGGER_TABLE, trigger_table) : 0,
+ (trigger_schema != NULL) ? erritem(PG_DIAG_TRIGGER_SCHEMA, trigger_schema) : 0));
estate->err_text = NULL; /* un-suppress... */
@@ -2849,6 +3033,28 @@ exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
pfree(err_detail);
if (err_hint != NULL)
pfree(err_hint);
+ if (column_name != NULL)
+ pfree(column_name);
+ if (table_name != NULL)
+ pfree(table_name);
+ if (schema_name != NULL)
+ pfree(schema_name);
+ if (constraint_name != NULL)
+ pfree(constraint_name);
+ if (constraint_table != NULL)
+ pfree(constraint_table);
+ if (constraint_schema != NULL)
+ pfree(constraint_schema);
+ if (routine_name != NULL)
+ pfree(routine_name);
+ if (routine_schema != NULL)
+ pfree(routine_schema);
+ if (trigger_name != NULL)
+ pfree(trigger_name);
+ if (trigger_table != NULL)
+ pfree(trigger_table);
+ if (trigger_schema != NULL)
+ pfree(trigger_schema);
return PLPGSQL_RC_OK;
}
@@ -2930,6 +3136,9 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
if ((*plugin_ptr)->func_setup)
((*plugin_ptr)->func_setup) (estate, func);
}
+
+ estate->tg_name = NULL;
+ estate->tg_schema = NULL;
}
/* ----------
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index b5a72dd..b76bdca 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -287,6 +287,28 @@ plpgsql_getdiag_kindname(int kind)
return "RETURNED_SQLSTATE";
case PLPGSQL_GETDIAG_MESSAGE_TEXT:
return "MESSAGE_TEXT";
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ return "COLUMN_NAME";
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ return "TABLE_NAME";
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ return "SCHEMA_NAME";
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ return "CONSTRAINT_NAME";
+ case PLPGSQL_GETDIAG_CONSTRAINT_TABLE:
+ return "CONSTRAINT_TABLE";
+ case PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA:
+ return "CONSTRAINT_SCHEMA";
+ case PLPGSQL_GETDIAG_ROUTINE_NAME:
+ return "ROUTINE_NAME";
+ case PLPGSQL_GETDIAG_ROUTINE_SCHEMA:
+ return "ROUTINE_SCHEMA";
+ case PLPGSQL_GETDIAG_TRIGGER_NAME:
+ return "TRIGGER_NAME";
+ case PLPGSQL_GETDIAG_TRIGGER_TABLE:
+ return "TRIGGER_TABLE";
+ case PLPGSQL_GETDIAG_TRIGGER_SCHEMA:
+ return "TRIGGER_SCHEMA";
}
return "unknown";
@@ -1317,6 +1339,39 @@ dump_raise(PLpgSQL_stmt_raise *stmt)
case PLPGSQL_RAISEOPTION_HINT:
printf(" HINT = ");
break;
+ case PLPGSQL_RAISEOPTION_COLUMN_NAME:
+ printf(" COLUMN_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TABLE_NAME:
+ printf(" TABLE_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_SCHEMA_NAME:
+ printf(" SCHEMA_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_NAME:
+ printf(" CONSTRAINT_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_TABLE:
+ printf(" CONSTRAINT_TABLE = ");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA:
+ printf(" CONSTRAINT_SCHEMA = ");
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_NAME:
+ printf(" ROUTINE_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA:
+ printf(" ROUTINE_SCHEMA = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_NAME:
+ printf(" TRIGGER_NAME = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_TABLE:
+ printf(" TRIGGER_TABLE = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA:
+ printf(" TRIGGER_SCHEMA = ");
+ break;
}
dump_expr(opt->expr);
printf("\n");
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index ebce3fd..600f294 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -109,7 +109,11 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
+ PG_KEYWORD("column_name", K_DIAG_COLUMN_NAME, UNRESERVED_KEYWORD)
PG_KEYWORD("constant", K_CONSTANT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("constraint_name", K_DIAG_CONSTRAINT_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("constraint_schema", K_DIAG_CONSTRAINT_SCHEMA, UNRESERVED_KEYWORD)
+ PG_KEYWORD("constraint_table", K_DIAG_CONSTRAINT_TABLE, UNRESERVED_KEYWORD)
PG_KEYWORD("current", K_CURRENT, UNRESERVED_KEYWORD)
PG_KEYWORD("cursor", K_CURSOR, UNRESERVED_KEYWORD)
PG_KEYWORD("debug", K_DEBUG, UNRESERVED_KEYWORD)
@@ -139,12 +143,19 @@ static const ScanKeyword unreserved_keywords[] = {
PG_KEYWORD("result_oid", K_RESULT_OID, UNRESERVED_KEYWORD)
PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("reverse", K_REVERSE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("routine_name", K_DIAG_ROUTINE_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("routine_schema", K_DIAG_ROUTINE_SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("row_count", K_ROW_COUNT, UNRESERVED_KEYWORD)
PG_KEYWORD("rowtype", K_ROWTYPE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("schema_name", K_DIAG_SCHEMA_NAME, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", K_SCROLL, UNRESERVED_KEYWORD)
PG_KEYWORD("slice", K_SLICE, UNRESERVED_KEYWORD)
PG_KEYWORD("sqlstate", K_SQLSTATE, UNRESERVED_KEYWORD)
PG_KEYWORD("stacked", K_STACKED, UNRESERVED_KEYWORD)
+ PG_KEYWORD("table_name", K_DIAG_TABLE_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("trigger_name", K_DIAG_TRIGGER_NAME, UNRESERVED_KEYWORD)
+ PG_KEYWORD("trigger_schema", K_DIAG_TRIGGER_SCHEMA, UNRESERVED_KEYWORD)
+ PG_KEYWORD("trigger_table", K_DIAG_TRIGGER_TABLE, UNRESERVED_KEYWORD)
PG_KEYWORD("type", K_TYPE, UNRESERVED_KEYWORD)
PG_KEYWORD("use_column", K_USE_COLUMN, UNRESERVED_KEYWORD)
PG_KEYWORD("use_variable", K_USE_VARIABLE, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b63f336..cac98e1 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -127,7 +127,18 @@ enum
PLPGSQL_GETDIAG_ERROR_DETAIL,
PLPGSQL_GETDIAG_ERROR_HINT,
PLPGSQL_GETDIAG_RETURNED_SQLSTATE,
- PLPGSQL_GETDIAG_MESSAGE_TEXT
+ PLPGSQL_GETDIAG_MESSAGE_TEXT,
+ PLPGSQL_GETDIAG_COLUMN_NAME,
+ PLPGSQL_GETDIAG_TABLE_NAME,
+ PLPGSQL_GETDIAG_SCHEMA_NAME,
+ PLPGSQL_GETDIAG_CONSTRAINT_NAME,
+ PLPGSQL_GETDIAG_CONSTRAINT_TABLE,
+ PLPGSQL_GETDIAG_CONSTRAINT_SCHEMA,
+ PLPGSQL_GETDIAG_ROUTINE_NAME,
+ PLPGSQL_GETDIAG_ROUTINE_SCHEMA,
+ PLPGSQL_GETDIAG_TRIGGER_NAME,
+ PLPGSQL_GETDIAG_TRIGGER_TABLE,
+ PLPGSQL_GETDIAG_TRIGGER_SCHEMA
};
/* --------
@@ -139,7 +150,18 @@ enum
PLPGSQL_RAISEOPTION_ERRCODE,
PLPGSQL_RAISEOPTION_MESSAGE,
PLPGSQL_RAISEOPTION_DETAIL,
- PLPGSQL_RAISEOPTION_HINT
+ PLPGSQL_RAISEOPTION_HINT,
+ PLPGSQL_RAISEOPTION_COLUMN_NAME,
+ PLPGSQL_RAISEOPTION_TABLE_NAME,
+ PLPGSQL_RAISEOPTION_SCHEMA_NAME,
+ PLPGSQL_RAISEOPTION_CONSTRAINT_NAME,
+ PLPGSQL_RAISEOPTION_CONSTRAINT_TABLE,
+ PLPGSQL_RAISEOPTION_CONSTRAINT_SCHEMA,
+ PLPGSQL_RAISEOPTION_ROUTINE_NAME,
+ PLPGSQL_RAISEOPTION_ROUTINE_SCHEMA,
+ PLPGSQL_RAISEOPTION_TRIGGER_NAME,
+ PLPGSQL_RAISEOPTION_TRIGGER_TABLE,
+ PLPGSQL_RAISEOPTION_TRIGGER_SCHEMA
};
/* --------
@@ -679,6 +701,8 @@ typedef struct PLpgSQL_func_hashkey
typedef struct PLpgSQL_function
{ /* Complete compiled function */
char *fn_signature;
+ char *fn_name;
+ char *fn_schema;
Oid fn_oid;
TransactionId fn_xmin;
ItemPointerData fn_tid;
@@ -765,6 +789,10 @@ typedef struct PLpgSQL_execstate
const char *err_text; /* additional state info */
void *plugin_info; /* reserved for use by optional plugin */
+
+ /* trigger description */
+ char *tg_name;
+ char *tg_schema;
} PLpgSQL_execstate;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index fb47fa7..92f1d67 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4719,3 +4719,68 @@ ERROR: value for domain orderedarray violates check constraint "sorted"
CONTEXT: PL/pgSQL function testoa(integer,integer,integer) line 5 at assignment
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+create function test_eelog_outer()
+returns void as $$
+declare
+ column_name text;
+ table_name text;
+ schema_name text;
+ constraint_name text;
+ constraint_table text;
+ constraint_schema text;
+ routine_name text;
+ routine_schema text;
+ trigger_name text;
+ trigger_table text;
+ trigger_schema text;
+begin
+ perform test_eelog_inner();
+exception when others then
+ get stacked diagnostics
+ column_name = column_name,
+ table_name = table_name,
+ schema_name = schema_name,
+ constraint_name = constraint_name,
+ constraint_table = constraint_table,
+ constraint_schema = constraint_schema,
+ routine_name = routine_name,
+ routine_schema = routine_schema,
+ trigger_name = trigger_name,
+ trigger_table = trigger_table,
+ trigger_schema = trigger_schema;
+
+ raise notice 'column name: "%":, table name: "%", schema name: "%"', column_name, table_name, schema_name;
+ raise notice 'constraint name: "%", constraint table: %, constraint schema: "%"', constraint_name, constraint_table, constraint_schema;
+ raise notice 'routine name: "%", routine schema: "%"', routine_name, routine_schema;
+ raise notice 'trigger name: "%", trigger table: %, trigger schema: "%"', trigger_name, trigger_table, trigger_schema;
+end;
+$$ language plpgsql;
+create function test_eelog_inner()
+returns void as $$
+begin
+ raise exception 'custom exception'
+ using column_name = 'some_column_name',
+ table_name = 'some_table_name',
+ schema_name = 'some_schema_name',
+ constraint_name = 'some_constraint_name',
+ constraint_table = 'some_constraint_table',
+ constraint_schema = 'some_constraint_schema',
+ routine_name = 'some_routine',
+ routine_schema = 'some_routine_schema',
+ trigger_name = 'some_trigger_name',
+ trigger_table = 'some_trigger_table',
+ trigger_schema = 'some_trigger_schema';
+end;
+$$ language plpgsql;
+select test_eelog_outer();
+NOTICE: column name: "some_column_name":, table name: "some_table_name", schema name: "some_schema_name"
+NOTICE: constraint name: "some_constraint_name", constraint table: some_constraint_table, constraint schema: "some_constraint_schema"
+NOTICE: routine name: "some_routine", routine schema: "some_routine_schema"
+NOTICE: trigger name: "some_trigger_name", trigger table: some_trigger_table, trigger schema: "some_trigger_schema"
+ test_eelog_outer
+------------------
+
+(1 row)
+
+drop function test_eelog_outer();
+drop function test_eelog_inner();
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 2b60b67..96a3d2e 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3723,3 +3723,63 @@ select testoa(1,2,1); -- fail at update
drop function arrayassign1();
drop function testoa(x1 int, x2 int, x3 int);
+
+create function test_eelog_outer()
+returns void as $$
+declare
+ column_name text;
+ table_name text;
+ schema_name text;
+ constraint_name text;
+ constraint_table text;
+ constraint_schema text;
+ routine_name text;
+ routine_schema text;
+ trigger_name text;
+ trigger_table text;
+ trigger_schema text;
+begin
+ perform test_eelog_inner();
+exception when others then
+ get stacked diagnostics
+ column_name = column_name,
+ table_name = table_name,
+ schema_name = schema_name,
+ constraint_name = constraint_name,
+ constraint_table = constraint_table,
+ constraint_schema = constraint_schema,
+ routine_name = routine_name,
+ routine_schema = routine_schema,
+ trigger_name = trigger_name,
+ trigger_table = trigger_table,
+ trigger_schema = trigger_schema;
+
+ raise notice 'column name: "%":, table name: "%", schema name: "%"', column_name, table_name, schema_name;
+ raise notice 'constraint name: "%", constraint table: %, constraint schema: "%"', constraint_name, constraint_table, constraint_schema;
+ raise notice 'routine name: "%", routine schema: "%"', routine_name, routine_schema;
+ raise notice 'trigger name: "%", trigger table: %, trigger schema: "%"', trigger_name, trigger_table, trigger_schema;
+end;
+$$ language plpgsql;
+
+create function test_eelog_inner()
+returns void as $$
+begin
+ raise exception 'custom exception'
+ using column_name = 'some_column_name',
+ table_name = 'some_table_name',
+ schema_name = 'some_schema_name',
+ constraint_name = 'some_constraint_name',
+ constraint_table = 'some_constraint_table',
+ constraint_schema = 'some_constraint_schema',
+ routine_name = 'some_routine',
+ routine_schema = 'some_routine_schema',
+ trigger_name = 'some_trigger_name',
+ trigger_table = 'some_trigger_table',
+ trigger_schema = 'some_trigger_schema';
+end;
+$$ language plpgsql;
+
+select test_eelog_outer();
+
+drop function test_eelog_outer();
+drop function test_eelog_inner();
Attached is a revision of this patch, with a few clean-ups, mostly to
the wording of certain things.
On 5 July 2012 17:41, Pavel Stehule <pavel.stehule@gmail.com> wrote:
* renamed auxiliary functions and moved it elog.c - header is new file
"relerror.h"
In my revision, I've just added a pre-declaration and removed the
dedicated header, which didn't make too much sense to me:
+ /* Pre-declare Relation, in order to avoid a build dependency on rel.h. */
+ typedef struct RelationData *Relation;
This works fine, and is precedented. See
src/include/storage/buffile.h, for example. If you think it's
unreasonable that none of the functions now added to elog.h are
callable without also including rel.h, consider that all call sites
are naturally already doing that, for the errmsg() string itself.
Besides, this is a perfectly valid way of reducing build dependencies,
or at least a more valid way than creating a new header that does not
really represent a natural new division for these new functions, IMHO.
Opaque pointers are ordinarily used to encapsulate things in C, rather
than to prevent build dependencies, but I believe that's only because
in general that's something that C programmers are more likely to
want.
* new fields "constraint_table" and "trigger_table" - constraints and
triggers are related to relation in pg, not just to schema
I've added some remarks to that effect in the docs of my revision for
your consideration.
* better coverage of enhancing errors in source code
Good. I think it's important that we nail down just where these are
expected to be available. It would be nice if there was a quick and
easy answer to the question "Just what ErrorResponse fields should
this new "sub-category of class 23" ereport() site have?". We clearly
have yet to work those details out.
I have another concern with this patch. log_error_verbosity appears to
be intended as an exact analogue of client verbosity (as set by
PQsetErrorVerbosity()). While this generally holds for this patch,
there is one notable exception: You always log all of these new fields
within write_csvlog(), even if (Log_error_verbosity <
PGERROR_VERBOSE). Why? There will be a bunch of commas in most CSV
logs once this happens, so that the schema of the log is consistent.
That is kind of ugly, but I don't see a way around it. We already do
this for location and application_name (though that's separately
controlled by the application_name guc). I haven't touched that in the
attached revision, as I'd like to hear what you have to say.
Another problem that will need to be fixed is that of the follow values:
+#define PG_DIAG_COLUMN_NAME 'c'
+#define PG_DIAG_TABLE_NAME 't'
+#define PG_DIAG_SCHEMA_NAME 's'
+#define PG_DIAG_CONSTRAINT_NAME 'n'
+#define PG_DIAG_CONSTRAINT_TABLE 'o'
+#define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+#define PG_DIAG_ROUTINE_NAME 'r'
+#define PG_DIAG_ROUTINE_SCHEMA 'u'
+#define PG_DIAG_TRIGGER_NAME 'g'
+#define PG_DIAG_TRIGGER_TABLE 'i'
+#define PG_DIAG_TRIGGER_SCHEMA 'h'
Not all appear to have a way of setting the value within the ereport
interface. For example, there is nothing like "errrelation_column()"
(or "errrelcol()", as I call it) to set PG_DIAG_ROUTINE_NAME. This is
something I haven't touched.
src/backend/utils/adt/domains.c
162: (errcode(ERRCODE_CHECK_VIOLATION),these exceptions are related to domains - we has not adequate fields
now - and these fields are not in standardsit needs some like DOMAIN_NAME and DOMAIN_SCHEMA ???
Hmm. I'm not sure that it's worth it.
I took a look at recent JDBC documentation, because I'd expect it to
offer the most complete support for exposing this in exception
classes. Turns out that it does not expose things at as fine a
granularity as you have here at all, which is disappointing. It seems
to suppose that just a vendor code and "cause" will be sufficient. If
you're using this one proprietary database, there is a command that'll
let you get diagnostics. The wisdom for users of another proprietary
database seems to be that you just use stored procedures. So I agree
that CONSTRAINT NAME should be unset in the event of uncatalogued,
unnamed not null integrity constraint violations. The standard seems
to be loose on this, and I think we'll have to be too. However, I'd
find it pretty intolerable if we were inconsistent between ereport()
callsites that were *effectively the same error* - this could result
in a user's application breaking based on the phase of the moon.
Do you suppose it's worth stashing the last set of these fields to one
side, and exposing this through an SQL utility command, or even a
bundled function? I don't imagine that it's essential to have that
right away, but it's something to consider.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog.2012_07_07.patchapplication/octet-stream; name=eelog.2012_07_07.patchDownload
diff doc/src/sgml/config.sgml
index 4e0492b..ddaf900
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** CREATE TABLE postgres_log
*** 4183,4188 ****
--- 4183,4198 ----
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_table text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_table text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
diff doc/src/sgml/protocol.sgml
index e725563..0c1042f
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
*************** message.
*** 4720,4732 ****
</listitem>
</varlistentry>
</variablelist>
<para>
The client is responsible for formatting displayed information to meet its
needs; in particular it should break long lines as needed. Newline characters
appearing in the error message fields should be treated as paragraph breaks,
! not line breaks.
</para>
</sect1>
--- 4720,4865 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <literal>c</>
+ </term>
+ <listitem>
+ <para>
+ Column name: the name of the column associated with the the error, if
+ any.
+ </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>s</>
+ </term>
+ <listitem>
+ <para>
+ Schema name: the name of schema associated with the error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>n</>
+ </term>
+ <listitem>
+ <para>
+ Constraint name: the name of the constraint associated with the error,
+ if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>o</>
+ </term>
+ <listitem>
+ <para>
+ Constraint table: the table name of the constraint associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>m</>
+ </term>
+ <listitem>
+ <para>
+ Constraint schema: the schema name of the constraint associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>r</>
+ </term>
+ <listitem>
+ <para>
+ Routine name: the name of the routine associated with the error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>u</>
+ </term>
+ <listitem>
+ <para>
+ Routine schema: the schema of the routine associated with the error, if
+ any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>g</>
+ </term>
+ <listitem>
+ <para>
+ Trigger name: the name of the trigger associated with the error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>i</>
+ </term>
+ <listitem>
+ <para>
+ Trigger table: the name of the table of the trigger associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>h</>
+ </term>
+ <listitem>
+ <para>
+ Trigger schema: the schema name of the trigger associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
The client is responsible for formatting displayed information to meet its
needs; in particular it should break long lines as needed. Newline characters
appearing in the error message fields should be treated as paragraph breaks,
! not line breaks. Note that certain object names represented as ErrorResponse
! fields, such as Trigger name, are not guaranteed to be unique across a database,
! and such objects cannot generally be uniquely identified by name alone. It may
! be necessary in these cases to unambiguously establish the identity of the
! object of interest based on a unique combination of the object's name, schema
! name, and, in the case of integrity constraints, table name.
</para>
</sect1>
diff src/backend/access/nbtree/nbtinsert.c
index 3ed9b5c..9251508
*** 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,400 ----
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull)),
! errrel(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);
--- 456,463 ----
(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."),
! errrel(rel)));
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,
--- 535,542 ----
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."),
! errrel(rel)));
/*----------
* If we will need to split the page to put the item on this page,
diff src/backend/commands/tablecmds.c
index d69809a..679a89e
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATRewriteTable(AlteredTableInfo *tab, Oi
*** 3805,3814 ****
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)
--- 3805,3821 ----
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) ?
! errrelcol(newrel, NameStr(att->attname)):
! errrelcol(oldrel, NameStr(att->attname))));
! }
}
foreach(l, tab->constraints)
*************** ATRewriteTable(AlteredTableInfo *tab, Oi
*** 3822,3828 ****
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 */
--- 3829,3841 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name),
! (newrel) ?
! errrel(newrel):
! errrel(oldrel),
! (newrel) ?
! errconstraint(newrel, con->name):
! errconstraint(oldrel, con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
*************** validateCheckConstraint(Relation rel, He
*** 6632,6638 ****
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
--- 6645,6653 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname)),
! errrel(rel),
! errconstraint(rel, NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
diff src/backend/commands/typecmds.c
index 30850b2..2dfc7c6
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
*************** AlterDomainNotNull(List *names, bool not
*** 2227,2239 ****
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);
--- 2227,2240 ----
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)),
! errrelcol(testrel, NameStr(att->attname))));
}
}
heap_endscan(scan);
*************** validateDomainConstraint(Oid domainoid,
*** 2602,2608 ****
(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);
--- 2603,2611 ----
(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)),
! errrelcol(testrel,
! NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
ResetExprContext(econtext);
diff src/backend/executor/execMain.c
index 440438b..dc2d516
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1515,1528 ****
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))));
}
}
--- 1515,1530 ----
for (attrChk = 1; attrChk <= natts; attrChk++)
{
! Form_pg_attribute Chk = rel->rd_att->attrs[attrChk - 1];
!
! if (Chk->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)),
! errrelcol(rel, NameStr(Chk->attname))));
}
}
*************** ExecConstraints(ResultRelInfo *resultRel
*** 1534,1542 ****
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
! RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1536,1546 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
! RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64)),
! errrel(rel),
! errconstraint(rel, failed)));
}
}
diff src/backend/executor/execUtils.c
index 2bd8b42..59388c2
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
*************** retry:
*** 1304,1317 ****
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);
--- 1304,1319 ----
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing),
! errrel(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),
! errrel(index)));
}
index_endscan(index_scan);
diff src/backend/utils/adt/ri_triggers.c
index 983f631..fa99ad8
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
*************** RI_FKey_check(TriggerData *trigdata)
*** 338,344 ****
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);
--- 338,346 ----
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."),
! errrel(trigdata->tg_relation),
! errconstraint(trigdata->tg_relation, NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
*************** RI_Initial_Check(Trigger *trigger, Relat
*** 2466,2472 ****
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
--- 2468,2477 ----
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."),
! errrel(fk_rel),
! errconstraint(fk_rel, NameStr(fake_riinfo.conname))));
!
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
*************** ri_ReportViolation(const RI_ConstraintIn
*** 3218,3224 ****
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),
--- 3223,3231 ----
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel)),
! errrel(fk_rel),
! errconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
*************** ri_ReportViolation(const RI_ConstraintIn
*** 3226,3234 ****
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
! errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
! key_names.data, key_values.data,
! RelationGetRelationName(fk_rel))));
}
--- 3233,3243 ----
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
! errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
! key_names.data, key_values.data,
! RelationGetRelationName(fk_rel)),
! errrel(pk_rel),
! errconstraint(fk_rel, NameStr(riinfo->conname))));
}
diff src/backend/utils/error/elog.c
index a40b343..f76e88d
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
***************
*** 75,82 ****
--- 75,84 ----
#include "storage/proc.h"
#include "tcop/tcopprot.h"
#include "utils/guc.h"
+ #include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/ps_status.h"
+ #include "utils/rel.h"
#undef _
*************** static int syslog_facility = LOG_LOCAL0;
*** 129,134 ****
--- 131,139 ----
static void write_syslog(int level, const char *line);
#endif
+ static int erritem(int field, const char *str);
+ static void set_errdata_field(char **ptr, const char *str, bool overwrite);
+
static void write_console(const char *line, int len);
#ifdef WIN32
*************** errfinish(int dummy,...)
*** 477,482 ****
--- 482,509 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
*************** internalerrquery(const char *query)
*** 1079,1084 ****
--- 1106,1248 ----
}
/*
+ * errrelcol --- sets column_name, table_name and schema_name of a column
+ * within errordata
+ */
+ int
+ errrelcol(Relation rel, const char *colname)
+ {
+ erritem(PG_DIAG_COLUMN_NAME, colname);
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * errrel --- sets column_name, table_name and schema_name within errordata
+ */
+ int
+ errrel(Relation rel)
+ {
+ erritem(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ erritem(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * errcontraint --- sets constraint_name, constraint_table and constraint_schema
+ * within errordata.
+ */
+ int
+ errconstraint(Relation rel, const char *cname)
+ {
+ erritem(PG_DIAG_CONSTRAINT_NAME, cname);
+ erritem(PG_DIAG_CONSTRAINT_TABLE, RelationGetRelationName(rel));
+ erritem(PG_DIAG_CONSTRAINT_SCHEMA,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * erritem -- generic setting of ErrorData string fields
+ */
+ static int
+ erritem(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, true);
+ break;
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_errdata_field(&edata->detail, str, true);
+ break;
+ case PG_DIAG_MESSAGE_HINT:
+ set_errdata_field(&edata->hint, str, true);
+ break;
+ case PG_DIAG_CONTEXT:
+ set_errdata_field(&edata->context, str, true);
+ break;
+ case PG_DIAG_COLUMN_NAME:
+ set_errdata_field(&edata->column_name, str, true);
+ break;
+ case PG_DIAG_TABLE_NAME:
+ set_errdata_field(&edata->table_name, str, true);
+ break;
+ case PG_DIAG_SCHEMA_NAME:
+ set_errdata_field(&edata->schema_name, str, true);
+ break;
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_errdata_field(&edata->constraint_name, str, true);
+ break;
+ case PG_DIAG_CONSTRAINT_TABLE:
+ set_errdata_field(&edata->constraint_table, str, true);
+ break;
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_errdata_field(&edata->constraint_schema, str, true);
+ break;
+
+ /*
+ * The remaining fields, once set, will not be set again. We need to
+ * guard against resetting here because there is partial redundancy in
+ * the fields set between some of our potential callers, even though two
+ * or more of them might reasonably coexist within the same ereport
+ * call.
+ */
+ case PG_DIAG_ROUTINE_NAME:
+ set_errdata_field(&edata->routine_name, str, false);
+ break;
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_errdata_field(&edata->routine_schema, str, false);
+ break;
+ case PG_DIAG_TRIGGER_NAME:
+ set_errdata_field(&edata->trigger_name, str, false);
+ break;
+ case PG_DIAG_TRIGGER_TABLE:
+ set_errdata_field(&edata->trigger_table, str, false);
+ break;
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_errdata_field(&edata->trigger_schema, str, false);
+ break;
+ default:
+ elog(ERROR, "unknown ErrorData field identifier %d", field);
+ }
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * set_errdata_field --- set an ErrorData string field, while potentially
+ * avoiding overwriting any existing value
+ */
+ static void
+ set_errdata_field(char **ptr, const char *str, bool overwrite)
+ {
+ if (*ptr != NULL)
+ {
+ /* Avoid overwriting existing value entirely */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != 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)
*** 1352,1357 ****
--- 1516,1543 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
*************** FreeErrorData(ErrorData *edata)
*** 1377,1382 ****
--- 1563,1590 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
*************** ReThrowError(ErrorData *edata)
*** 1449,1454 ****
--- 1657,1684 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
*************** write_csvlog(ErrorData *edata)
*** 2259,2264 ****
--- 2489,2527 ----
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
*************** send_message_to_server_log(ErrorData *ed
*** 2377,2382 ****
--- 2640,2722 ----
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT TABLE: "));
+ append_with_tabs(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER TABLE: "));
+ append_with_tabs(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
*************** send_message_to_frontend(ErrorData *edat
*** 2673,2678 ****
--- 3013,3084 ----
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_TABLE);
+ err_sendstring(&msgbuf, edata->constraint_table);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_TABLE);
+ err_sendstring(&msgbuf, edata->trigger_table);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
diff src/backend/utils/sort/tuplesort.c
index d5a2003..5fce241
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
*************** comparetup_index_btree(const SortTuple *
*** 3090,3096 ****
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
! values, isnull))));
}
/*
--- 3090,3097 ----
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
! values, isnull)),
! errrel(state->indexRel)));
}
/*
diff src/include/postgres_ext.h
index b6ebb7a..b37adf0
*** a/src/include/postgres_ext.h
--- b/src/include/postgres_ext.h
*************** typedef unsigned int Oid;
*** 55,59 ****
--- 55,70 ----
#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_SCHEMA_NAME 's'
+ #define PG_DIAG_CONSTRAINT_NAME 'n'
+ #define PG_DIAG_CONSTRAINT_TABLE 'o'
+ #define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+ #define PG_DIAG_ROUTINE_NAME 'r'
+ #define PG_DIAG_ROUTINE_SCHEMA 'u'
+ #define PG_DIAG_TRIGGER_NAME 'g'
+ #define PG_DIAG_TRIGGER_TABLE 'i'
+ #define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif
diff src/include/utils/elog.h
index 1bbfd2b..48e9afe
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
*************** extern int errhidestmt(bool hide_stmt);
*** 183,188 ****
--- 183,195 ----
extern int errfunction(const char *funcname);
extern int errposition(int cursorpos);
+ /* Pre-declare Relation, in order to avoid a build dependency on rel.h. */
+ typedef struct RelationData *Relation;
+
+ extern int errrelcol(Relation rel, const char *colname);
+ extern int errrel(Relation rel);
+ extern int errconstraint(Relation rel, const char *cname);
+
extern int internalerrposition(int cursorpos);
extern int internalerrquery(const char *query);
*************** typedef struct ErrorData
*** 321,330 ****
char *detail_log; /* detail error message for server log only */
char *hint; /* hint message */
char *context; /* context message */
! int cursorpos; /* cursor index into query string */
! int internalpos; /* cursor index into internalquery */
! char *internalquery; /* text of internally-generated query */
! int saved_errno; /* errno at entry */
} ErrorData;
extern void EmitErrorReport(void);
--- 327,347 ----
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 *schema_name; /* name of schema */
! char *constraint_name; /* name of constraint */
! char *constraint_table; /* name of table related to constraint */
! char *constraint_schema; /* name of schema with constraint */
! char *routine_name; /* name of function that caused error */
! char *routine_schema; /* schema name of function that caused error */
! char *trigger_name; /* name of trigger that caused error */
! char *trigger_table; /* table of trigger that caused error */
! char *trigger_schema; /* schema of trigger that caused error */
! int cursorpos; /* cursor index into query string */
! int internalpos; /* cursor index into internalquery */
! char *internalquery; /* text of internally-generated query */
! int saved_errno; /* errno at entry */
} ErrorData;
extern void EmitErrorReport(void);
diff src/interfaces/libpq/fe-protocol3.c
index 173af2e..f2616b3
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 980,985 ****
--- 980,1029 ----
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("SCHEMA 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_CONSTRAINT_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
2012/7/7 Peter Geoghegan <peter@2ndquadrant.com>:
Attached is a revision of this patch, with a few clean-ups, mostly to
the wording of certain things.On 5 July 2012 17:41, Pavel Stehule <pavel.stehule@gmail.com> wrote:
* renamed auxiliary functions and moved it elog.c - header is new file
"relerror.h"In my revision, I've just added a pre-declaration and removed the
dedicated header, which didn't make too much sense to me:+ /* Pre-declare Relation, in order to avoid a build dependency on rel.h. */ + typedef struct RelationData *Relation;This works fine, and is precedented. See
src/include/storage/buffile.h, for example. If you think it's
unreasonable that none of the functions now added to elog.h are
callable without also including rel.h, consider that all call sites
are naturally already doing that, for the errmsg() string itself.
Besides, this is a perfectly valid way of reducing build dependencies,
or at least a more valid way than creating a new header that does not
really represent a natural new division for these new functions, IMHO.
Opaque pointers are ordinarily used to encapsulate things in C, rather
than to prevent build dependencies, but I believe that's only because
in general that's something that C programmers are more likely to
want.
It is question for Alvaro or Tom. I have not strong opinion on it.
* new fields "constraint_table" and "trigger_table" - constraints and
triggers are related to relation in pg, not just to schemaI've added some remarks to that effect in the docs of my revision for
your consideration.* better coverage of enhancing errors in source code
Good. I think it's important that we nail down just where these are
expected to be available. It would be nice if there was a quick and
easy answer to the question "Just what ErrorResponse fields should
this new "sub-category of class 23" ereport() site have?". We clearly
have yet to work those details out.I have another concern with this patch. log_error_verbosity appears to
be intended as an exact analogue of client verbosity (as set by
PQsetErrorVerbosity()). While this generally holds for this patch,
there is one notable exception: You always log all of these new fields
within write_csvlog(), even if (Log_error_verbosity <
PGERROR_VERBOSE). Why? There will be a bunch of commas in most CSV
logs once this happens, so that the schema of the log is consistent.
That is kind of ugly, but I don't see a way around it. We already do
this for location and application_name (though that's separately
controlled by the application_name guc). I haven't touched that in the
attached revision, as I'd like to hear what you have to say.
it is bug - these new fields should be used only when verbosity is >= VERBOSE
Another problem that will need to be fixed is that of the follow values:
+#define PG_DIAG_COLUMN_NAME 'c' +#define PG_DIAG_TABLE_NAME 't' +#define PG_DIAG_SCHEMA_NAME 's' +#define PG_DIAG_CONSTRAINT_NAME 'n' +#define PG_DIAG_CONSTRAINT_TABLE 'o' +#define PG_DIAG_CONSTRAINT_SCHEMA 'm' +#define PG_DIAG_ROUTINE_NAME 'r' +#define PG_DIAG_ROUTINE_SCHEMA 'u' +#define PG_DIAG_TRIGGER_NAME 'g' +#define PG_DIAG_TRIGGER_TABLE 'i' +#define PG_DIAG_TRIGGER_SCHEMA 'h'Not all appear to have a way of setting the value within the ereport
interface. For example, there is nothing like "errrelation_column()"
(or "errrelcol()", as I call it) to set PG_DIAG_ROUTINE_NAME. This is
something I haven't touched.
When I sent this patch first time, then one issue was new functions
for these fields. Tom proposal was using a generic function for these
new fields. These fields holds separated values, but in almost all
cases some combinations are used - "ROUTINE_NAME, ROUTINE_SCHEMA",
"TABLE_NAME, TABLE_SCHEMA" - so these fields are not independent -
this is difference from original ErrorData fields - so axillary
functions doesn't follow these fields - because it is not practical.
src/backend/utils/adt/domains.c
162: (errcode(ERRCODE_CHECK_VIOLATION),these exceptions are related to domains - we has not adequate fields
now - and these fields are not in standardsit needs some like DOMAIN_NAME and DOMAIN_SCHEMA ???
Hmm. I'm not sure that it's worth it.
I took a look at recent JDBC documentation, because I'd expect it to
offer the most complete support for exposing this in exception
classes. Turns out that it does not expose things at as fine a
granularity as you have here at all, which is disappointing. It seems
to suppose that just a vendor code and "cause" will be sufficient. If
you're using this one proprietary database, there is a command that'll
let you get diagnostics. The wisdom for users of another proprietary
database seems to be that you just use stored procedures. So I agree
that CONSTRAINT NAME should be unset in the event of uncatalogued,
unnamed not null integrity constraint violations. The standard seems
to be loose on this, and I think we'll have to be too. However, I'd
find it pretty intolerable if we were inconsistent between ereport()
callsites that were *effectively the same error* - this could result
in a user's application breaking based on the phase of the moon.Do you suppose it's worth stashing the last set of these fields to one
side, and exposing this through an SQL utility command, or even a
bundled function? I don't imagine that it's essential to have that
right away, but it's something to consider.
I understand, but fixing any ereport in core is difficult for
processing. So coverage only some subset is practical (first stage) -
with some basic infrastructure in core all other patches with better
covering will be simpler for review and for commit too. RI and
constraints is more often use cases where you would to parse error
messages - these will be covered in first stage.
Regards
Pavel
Show quoted text
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
On 7 July 2012 13:57, Pavel Stehule <pavel.stehule@gmail.com> wrote:
In my revision, I've just added a pre-declaration and removed the
dedicated header, which didn't make too much sense to me:+ /* Pre-declare Relation, in order to avoid a build dependency on rel.h. */ + typedef struct RelationData *Relation;
Opaque pointers are ordinarily used to encapsulate things in C, rather
than to prevent build dependencies, but I believe that's only because
in general that's something that C programmers are more likely to
want.It is question for Alvaro or Tom. I have not strong opinion on it.
Fair enough.
You always log all of these new fields within write_csvlog(), even if (Log_error_verbosity <
PGERROR_VERBOSE). Why?
it is bug - these new fields should be used only when verbosity is >= VERBOSE
Please fix it.
+#define PG_DIAG_TRIGGER_SCHEMA 'h'
Not all appear to have a way of setting the value within the ereport
interface. For example, there is nothing like "errrelation_column()"
(or "errrelcol()", as I call it) to set PG_DIAG_ROUTINE_NAME. This is
something I haven't touched.When I sent this patch first time, then one issue was new functions
for these fields. Tom proposal was using a generic function for these
new fields. These fields holds separated values, but in almost all
cases some combinations are used - "ROUTINE_NAME, ROUTINE_SCHEMA",
"TABLE_NAME, TABLE_SCHEMA" - so these fields are not independent -
this is difference from original ErrorData fields - so axillary
functions doesn't follow these fields - because it is not practical.
Maybe it isn't practical to do it that way, but I think that we need
to have a way of setting the fields from an ereport callsite. I am
willing to accept that it may make sense to add existing ereport sites
by piecemeal, in later patches, but I think you should figure out how
regular ereport sites are supposed to do this before anything is
committed. We need to nail down the interface first.
I understand, but fixing any ereport in core is difficult for
processing. So coverage only some subset is practical (first stage) -
with some basic infrastructure in core all other patches with better
covering will be simpler for review and for commit too. RI and
constraints is more often use cases where you would to parse error
messages - these will be covered in first stage.
Okay. What subset? I would hope that it was a well-defined subset.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Excerpts from Peter Geoghegan's message of mar jul 10 14:56:40 -0400 2012:
On 7 July 2012 13:57, Pavel Stehule <pavel.stehule@gmail.com> wrote:
+#define PG_DIAG_TRIGGER_SCHEMA 'h'
Not all appear to have a way of setting the value within the ereport
interface. For example, there is nothing like "errrelation_column()"
(or "errrelcol()", as I call it) to set PG_DIAG_ROUTINE_NAME. This is
something I haven't touched.When I sent this patch first time, then one issue was new functions
for these fields. Tom proposal was using a generic function for these
new fields. These fields holds separated values, but in almost all
cases some combinations are used - "ROUTINE_NAME, ROUTINE_SCHEMA",
"TABLE_NAME, TABLE_SCHEMA" - so these fields are not independent -
this is difference from original ErrorData fields - so axillary
functions doesn't follow these fields - because it is not practical.Maybe it isn't practical to do it that way, but I think that we need
to have a way of setting the fields from an ereport callsite. I am
willing to accept that it may make sense to add existing ereport sites
by piecemeal, in later patches, but I think you should figure out how
regular ereport sites are supposed to do this before anything is
committed. We need to nail down the interface first.
I think we should just define constants for the set of fields the patch
currently uses. When and if we later add new fields to other callsites,
we can define more constants.
FWIW about the new include: I feel a strong dislike about the forward
declaration you suggest. Defining Relation in elog.h seems completely
out of place. The one you suggested as precedent (BufFile) is
completely unlike it, in that the declaration is clearly placed in the
header (buffile.h) of the module that works with the struct in question.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On 10 July 2012 20:28, Alvaro Herrera <alvherre@commandprompt.com> wrote:
I think we should just define constants for the set of fields the patch
currently uses. When and if we later add new fields to other callsites,
we can define more constants.
Fair enough. Let's do that.
FWIW about the new include: I feel a strong dislike about the forward
declaration you suggest. Defining Relation in elog.h seems completely
out of place. The one you suggested as precedent (BufFile) is
completely unlike it, in that the declaration is clearly placed in the
header (buffile.h) of the module that works with the struct in question.
I haven't defined Relation in elog.h; I have pre-declared it there.
Maybe that isn't to your taste, but there is surely something to be
said for adding exactly one line of code in preference to adding an
entire new header file, and having a bunch of existing files include
that new header. That said, it's not as if I feel strongly about it.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Excerpts from Peter Geoghegan's message of mar jul 10 15:54:59 -0400 2012:
On 10 July 2012 20:28, Alvaro Herrera <alvherre@commandprompt.com> wrote:
FWIW about the new include: I feel a strong dislike about the forward
declaration you suggest. Defining Relation in elog.h seems completely
out of place. The one you suggested as precedent (BufFile) is
completely unlike it, in that the declaration is clearly placed in the
header (buffile.h) of the module that works with the struct in question.I haven't defined Relation in elog.h; I have pre-declared it there.
Maybe that isn't to your taste, but there is surely something to be
said for adding exactly one line of code in preference to adding an
entire new header file, and having a bunch of existing files include
that new header.
That is true. I'd like to hear others' opinions.
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
On 10 July 2012 21:26, Alvaro Herrera <alvherre@commandprompt.com> wrote:
I haven't defined Relation in elog.h; I have pre-declared it there.
Maybe that isn't to your taste, but there is surely something to be
said for adding exactly one line of code in preference to adding an
entire new header file, and having a bunch of existing files include
that new header.That is true. I'd like to hear others' opinions.
It seems that the code, exactly as written, relies upon a GNU
extension that didn't make it into the standard until C11 - the
redefinition of a typedef. Clang warns about this. Still, it ought to
be possible, if not entirely straightforward, to use a pre-declaration
all the same.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Alvaro Herrera <alvherre@commandprompt.com> writes:
FWIW about the new include: I feel a strong dislike about the forward
declaration you suggest. Defining Relation in elog.h seems completely
out of place.
Agreed. Maybe a reasonable solution is to allow some ereport helper
functions (or, really, wrappers for the helper functions) to be declared
someplace else than elog.h. They'd likely need to be implemented
someplace else than elog.c, too, so this doesn't seem unreasonable.
The generic helper function approach doesn't seem too unreasonable for
this: elog.h/.c would provide something like
err_generic_string(int fieldid, const char *str)
and then someplace else could provide functions built on this that
insert table/schema/column/constraint/etc names into suitable fields.
regards, tom lane
Hello
* renamed erritem to err_generic_string
* fixed CSVlog generation
* new file /utils/error/relerror.c with axillary functions -
declarations are in utils/rel.h
Regards
Pavel
2012/7/11 Tom Lane <tgl@sss.pgh.pa.us>:
Show quoted text
Alvaro Herrera <alvherre@commandprompt.com> writes:
FWIW about the new include: I feel a strong dislike about the forward
declaration you suggest. Defining Relation in elog.h seems completely
out of place.Agreed. Maybe a reasonable solution is to allow some ereport helper
functions (or, really, wrappers for the helper functions) to be declared
someplace else than elog.h. They'd likely need to be implemented
someplace else than elog.c, too, so this doesn't seem unreasonable.The generic helper function approach doesn't seem too unreasonable for
this: elog.h/.c would provide something likeerr_generic_string(int fieldid, const char *str)
and then someplace else could provide functions built on this that
insert table/schema/column/constraint/etc names into suitable fields.regards, tom lane
Attachments:
eelog-2012-07-18.patchapplication/octet-stream; name=eelog-2012-07-18.patchDownload
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3a0b16d..13c42fa 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -4153,6 +4153,16 @@ CREATE TABLE postgres_log
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_table text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_table text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index e725563..6f34448 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -4720,6 +4720,127 @@ message.
</listitem>
</varlistentry>
+<varlistentry>
+<term>
+<literal>c</>
+</term>
+<listitem>
+<para>
+ Column name: the name of column related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>t</>
+</term>
+<listitem>
+<para>
+ Table name: the name of table related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>s</>
+</term>
+<listitem>
+<para>
+ Schema name: the name of schema related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>n</>
+</term>
+<listitem>
+<para>
+ Constraint name: the name of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>o</>
+</term>
+<listitem>
+<para>
+ Constraint table: the table name of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>m</>
+</term>
+<listitem>
+<para>
+ Constraint schema: the schema of constraint related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>r</>
+</term>
+<listitem>
+<para>
+ Routine name: the name of routine related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>u</>
+</term>
+<listitem>
+<para>
+ Routine schema: the schema of routine related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>g</>
+</term>
+<listitem>
+<para>
+ Trigger name: the name of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>i</>
+</term>
+<listitem>
+<para>
+ Trigger table: the table of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
+<varlistentry>
+<term>
+<literal>h</>
+</term>
+<listitem>
+<para>
+ Trigger schema: the schema of trigger related to error
+</para>
+</listitem>
+</varlistentry>
+
</variablelist>
<para>
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index 3ed9b5c..7a5005f 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -393,7 +393,8 @@ _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
- values, isnull))));
+ values, isnull)),
+ errrelation(rel)));
}
}
else if (all_dead)
@@ -455,7 +456,8 @@ _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."),
+ errrelation(rel)));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
@@ -533,7 +535,8 @@ _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."),
+ errrelation(rel)));
/*----------
* If we will need to split the page to put the item on this page,
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 70e408c..1b1877b 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3808,6 +3808,10 @@ 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)),
+ (newrel) ? errrelation_column(newrel,
+ NameStr(newTupDesc->attrs[attn]->attname)) :
+ errrelation_column(oldrel,
NameStr(newTupDesc->attrs[attn]->attname))));
}
@@ -3822,7 +3826,9 @@ 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),
+ (newrel) ? errrelation(newrel) : errrelation(oldrel),
+ (newrel) ? errconstraint(newrel, con->name) : errconstraint(oldrel, con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
@@ -6619,7 +6625,9 @@ 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)),
+ errrelation(rel),
+ errconstraint(rel, NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 30850b2..0d78c34 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2233,7 +2233,9 @@ AlterDomainNotNull(List *names, bool notNull)
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains null values",
NameStr(tupdesc->attrs[attnum - 1]->attname),
- RelationGetRelationName(testrel))));
+ RelationGetRelationName(testrel)),
+ errrelation_column(testrel,
+ NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
}
heap_endscan(scan);
@@ -2602,7 +2604,9 @@ validateDomainConstraint(Oid domainoid, char *ccbin)
(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)),
+ errrelation_column(testrel,
+ NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
ResetExprContext(econtext);
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 440438b..607d661 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1522,7 +1522,9 @@ 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)),
+ errrelation_column(rel,
+ NameStr(rel->rd_att->attrs[attrChk - 1]->attname))));
}
}
@@ -1536,7 +1538,9 @@ 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)),
+ errrelation(rel),
+ errconstraint(rel, failed)));
}
}
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 2bd8b42..805515e 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -1304,14 +1304,16 @@ 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),
+ errrelation(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),
+ errrelation(index)));
}
index_endscan(index_scan);
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 983f631..11f431f 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -338,7 +338,9 @@ RI_FKey_check(TriggerData *trigdata)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
- errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
+ errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
+ errrelation(trigdata->tg_relation),
+ errconstraint(trigdata->tg_relation, NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
@@ -2466,7 +2468,10 @@ 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."),
+ errrelation(fk_rel),
+ errconstraint(fk_rel, NameStr(fake_riinfo.conname))));
+
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
@@ -3218,7 +3223,9 @@ 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)),
+ errrelation(fk_rel),
+ errconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
@@ -3228,7 +3235,9 @@ 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)),
+ errrelation(pk_rel),
+ errconstraint(fk_rel, NameStr(riinfo->conname))));
}
diff --git a/src/backend/utils/error/Makefile b/src/backend/utils/error/Makefile
index 4c313b7..c2ba4d0 100644
--- a/src/backend/utils/error/Makefile
+++ b/src/backend/utils/error/Makefile
@@ -12,6 +12,6 @@ subdir = src/backend/utils/error
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = assert.o elog.o
+OBJS = assert.o elog.o relerror.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index a40b343..9a47b99 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -477,6 +477,28 @@ errfinish(int dummy,...)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
@@ -1079,6 +1101,116 @@ internalerrquery(const char *query)
}
/*
+ * set ErrorData field - ensure not overwriting for selected fields
+ */
+static void
+set_field(char **ptr, const char *str, bool overwrite)
+{
+ if (*ptr != NULL)
+ {
+ /*
+ * for some cases like ROUTINE_NAME, ROUTINE_SCHEMA we would
+ * to get the most older value.
+ */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != NULL)
+ *ptr = MemoryContextStrdup(ErrorContext, str);
+}
+
+/*
+ * err_generic_string -- generic setting of ErrorData string fields
+ */
+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_field(&edata->message, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_field(&edata->detail, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_field(&edata->hint, str, true);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_field(&edata->context, str, true);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_field(&edata->column_name, str, true);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_field(&edata->table_name, str, true);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_field(&edata->schema_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_field(&edata->constraint_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_TABLE:
+ set_field(&edata->constraint_table, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_field(&edata->constraint_schema, str, true);
+ break;
+
+ /*
+ * setup of routine_name, routine_schema, trigger_name,
+ * trigger_table, trigger_schema is processed together
+ * with collecting context. Only first values are valid,
+ * so we should to protect these values.
+ */
+ case PG_DIAG_ROUTINE_NAME:
+ set_field(&edata->routine_name, str, false);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_field(&edata->routine_schema, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_field(&edata->trigger_name, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_TABLE:
+ set_field(&edata->trigger_table, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_field(&edata->trigger_schema, str, false);
+ break;
+
+ default:
+ elog(ERROR, "unknown ErrorData field id %d",
+ field);
+ }
+
+ return 0; /* return value does not matter */
+}
+
+/*
* geterrcode --- return the currently set SQLSTATE error code
*
* This is only intended for use in error callback subroutines, since there
@@ -1352,6 +1484,28 @@ CopyErrorData(void)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
@@ -1377,6 +1531,28 @@ FreeErrorData(ErrorData *edata)
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
@@ -1449,6 +1625,28 @@ ReThrowError(ErrorData *edata)
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
@@ -2238,7 +2436,7 @@ write_csvlog(ErrorData *edata)
appendStringInfoChar(&buf, ',');
/* file error location */
- if (Log_error_verbosity >= PGERROR_VERBOSE)
+
{
StringInfoData msgbuf;
@@ -2259,6 +2457,50 @@ write_csvlog(ErrorData *edata)
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
@@ -2377,6 +2619,83 @@ send_message_to_server_log(ErrorData *edata)
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT TABLE: "));
+ append_with_tabs(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER TABLE: "));
+ append_with_tabs(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
@@ -2673,6 +2992,72 @@ send_message_to_frontend(ErrorData *edata)
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_TABLE);
+ err_sendstring(&msgbuf, edata->constraint_table);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_TABLE);
+ err_sendstring(&msgbuf, edata->trigger_table);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
diff --git a/src/backend/utils/error/relerror.c b/src/backend/utils/error/relerror.c
new file mode 100644
index 0000000..5e50333
--- /dev/null
+++ b/src/backend/utils/error/relerror.c
@@ -0,0 +1,61 @@
+/*-------------------------------------------------------------------------
+ *
+ * relerror.c
+ * relation error loging 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"
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+int
+errrelation_column(Relation rel, const char *colname)
+{
+ err_generic_string(PG_DIAG_COLUMN_NAME, colname);
+ err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ err_generic_string(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * Sets column_name, table_name and schema_name in ErrorData related to relation
+ */
+int
+errrelation(Relation rel)
+{
+ err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ err_generic_string(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
+
+/*
+ * Sets constraint_name, constraint_table and constraint_schema in ErrorData
+ */
+int
+errconstraint(Relation rel, const char *cname)
+{
+ err_generic_string(PG_DIAG_CONSTRAINT_NAME, cname);
+ err_generic_string(PG_DIAG_CONSTRAINT_TABLE, RelationGetRelationName(rel));
+ err_generic_string(PG_DIAG_CONSTRAINT_SCHEMA,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+}
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index d5a2003..e6ecfeb 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -3090,7 +3090,8 @@ comparetup_index_btree(const SortTuple *a, const SortTuple *b,
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
- values, isnull))));
+ values, isnull)),
+ errrelation(state->indexRel)));
}
/*
diff --git a/src/include/postgres_ext.h b/src/include/postgres_ext.h
index b6ebb7a..829114a 100644
--- a/src/include/postgres_ext.h
+++ b/src/include/postgres_ext.h
@@ -55,5 +55,16 @@ typedef unsigned int Oid;
#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_SCHEMA_NAME 's'
+#define PG_DIAG_CONSTRAINT_NAME 'n'
+#define PG_DIAG_CONSTRAINT_TABLE 'o'
+#define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+#define PG_DIAG_ROUTINE_NAME 'r'
+#define PG_DIAG_ROUTINE_SCHEMA 'u'
+#define PG_DIAG_TRIGGER_NAME 'g'
+#define PG_DIAG_TRIGGER_TABLE 'i'
+#define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 1bbfd2b..b96adf3 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -190,6 +190,8 @@ extern int geterrcode(void);
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:
@@ -321,6 +323,17 @@ typedef struct ErrorData
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 *schema_name; /* name of schema */
+ char *constraint_name; /* name of constraint */
+ char *constraint_table; /* name of table related to constraint */
+ char *constraint_schema; /* name of schema with constraint */
+ char *routine_name; /* name of function that caused error */
+ char *routine_schema; /* schema name of function that caused error */
+ char *trigger_name; /* name of trigger that caused error */
+ char *trigger_table; /* table of trigger that caused error */
+ char *trigger_schema; /* schema of trigger that caused error */
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/rel.h b/src/include/utils/rel.h
index 4669d8a..32dcae4 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -394,4 +394,9 @@ typedef struct StdRdOptions
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+/* routines in utils/error/relerror.c */
+extern int errrelation_column(Relation rel, const char *colname);
+extern int errrelation(Relation rel);
+extern int errconstraint(Relation rel, const char *cname);
+
#endif /* REL_H */
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 173af2e..e83a8b7 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -980,6 +980,40 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("SCHEMA 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_CONSTRAINT_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("CONSTRAINT TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf, libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
On 18 July 2012 19:13, Pavel Stehule <pavel.stehule@gmail.com> wrote:
* renamed erritem to err_generic_string
* fixed CSVlog generation
* new file /utils/error/relerror.c with axillary functions -
declarations are in utils/rel.h
Why has this revision retained none of my editorialisations? In
particular, none of the documentation updates that I made were
retained.
You also haven't included changes where I attempted to make very large
ereport statements (often with verbose use of ternary conditionals)
clearer, nor have you included my adjustments to normalise the
appearance of new code to be consistent with existing code in various
ways.
You don't have to agree with all of those things of course, but you
should have at least commented on them. I didn't spend time cleaning
things up only to have those changes ignored. I'm particularly
surprised that the documentation alterations were not retained, as you
yourself asked me to make those revisions.
/* file error location */
- if (Log_error_verbosity >= PGERROR_VERBOSE)
+
{
StringInfoData msgbuf;
Why have you retained the scope here? Couldn't you have just moved the
single declaration instead?
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
2012/7/23 Peter Geoghegan <peter@2ndquadrant.com>:
On 18 July 2012 19:13, Pavel Stehule <pavel.stehule@gmail.com> wrote:
* renamed erritem to err_generic_string
* fixed CSVlog generation
* new file /utils/error/relerror.c with axillary functions -
declarations are in utils/rel.hWhy has this revision retained none of my editorialisations? In
particular, none of the documentation updates that I made were
retained.
I am sorry, I can't do merge - I try to use diff but without success,
so I did structure changes and merge with your patch postponed
You also haven't included changes where I attempted to make very large
ereport statements (often with verbose use of ternary conditionals)
clearer, nor have you included my adjustments to normalise the
appearance of new code to be consistent with existing code in various
ways.You don't have to agree with all of those things of course, but you
should have at least commented on them. I didn't spend time cleaning
things up only to have those changes ignored. I'm particularly
surprised that the documentation alterations were not retained, as you
yourself asked me to make those revisions.
again, I am sorry - my last patch should to define structure (files) -
because it was significant topic. When we will find a agreement, then
I'll merge changes messages, comments, doc
/* file error location */ - if (Log_error_verbosity >= PGERROR_VERBOSE) + { StringInfoData msgbuf;Why have you retained the scope here? Couldn't you have just moved the
single declaration instead?
I am not sure, I have to recheck it.
Regards
Pavel
Show quoted text
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
On Mon, Jul 23, 2012 at 7:03 AM, Pavel Stehule <pavel.stehule@gmail.com> wrote:
You don't have to agree with all of those things of course, but you
should have at least commented on them. I didn't spend time cleaning
things up only to have those changes ignored. I'm particularly
surprised that the documentation alterations were not retained, as you
yourself asked me to make those revisions.again, I am sorry - my last patch should to define structure (files) -
because it was significant topic. When we will find a agreement, then
I'll merge changes messages, comments, doc
It seems clear that it's not reasonable to expect any more review to
be done here this CommitFest, given that the changes from reviews
already made haven't been incorporated into the patch. Therefore, I'm
going to mark this one Returned with Feedback.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 23 July 2012 15:23, Robert Haas <robertmhaas@gmail.com> wrote:
It seems clear that it's not reasonable to expect any more review to
be done here this CommitFest, given that the changes from reviews
already made haven't been incorporated into the patch. Therefore, I'm
going to mark this one Returned with Feedback.
I agree that that's the right thing to do at this point.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Hello
here is updated patch - merge comments, docs, formatting, some
identifiers from Peter Geoghegan's patch
Regards
Pavel
2012/7/18 Pavel Stehule <pavel.stehule@gmail.com>:
Show quoted text
Hello
* renamed erritem to err_generic_string
* fixed CSVlog generation
* new file /utils/error/relerror.c with axillary functions -
declarations are in utils/rel.hRegards
Pavel
2012/7/11 Tom Lane <tgl@sss.pgh.pa.us>:
Alvaro Herrera <alvherre@commandprompt.com> writes:
FWIW about the new include: I feel a strong dislike about the forward
declaration you suggest. Defining Relation in elog.h seems completely
out of place.Agreed. Maybe a reasonable solution is to allow some ereport helper
functions (or, really, wrappers for the helper functions) to be declared
someplace else than elog.h. They'd likely need to be implemented
someplace else than elog.c, too, so this doesn't seem unreasonable.The generic helper function approach doesn't seem too unreasonable for
this: elog.h/.c would provide something likeerr_generic_string(int fieldid, const char *str)
and then someplace else could provide functions built on this that
insert table/schema/column/constraint/etc names into suitable fields.regards, tom lane
Attachments:
eelog-2012-08-20.diffapplication/octet-stream; name=eelog-2012-08-20.diffDownload
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
***************
*** 4165,4170 **** CREATE TABLE postgres_log
--- 4165,4180 ----
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_table text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_table text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
***************
*** 4720,4732 **** message.
</listitem>
</varlistentry>
</variablelist>
<para>
The client is responsible for formatting displayed information to meet its
needs; in particular it should break long lines as needed. Newline characters
appearing in the error message fields should be treated as paragraph breaks,
! not line breaks.
</para>
</sect1>
--- 4720,4865 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <literal>c</>
+ </term>
+ <listitem>
+ <para>
+ Column name: the name of the column associated with the the error, if
+ any.
+ </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>s</>
+ </term>
+ <listitem>
+ <para>
+ Schema name: the name of schema associated with the error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>n</>
+ </term>
+ <listitem>
+ <para>
+ Constraint name: the name of the constraint associated with the error,
+ if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>o</>
+ </term>
+ <listitem>
+ <para>
+ Constraint table: the table name of the constraint associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>m</>
+ </term>
+ <listitem>
+ <para>
+ Constraint schema: the schema name of the constraint associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>r</>
+ </term>
+ <listitem>
+ <para>
+ Routine name: the name of the routine associated with the error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>u</>
+ </term>
+ <listitem>
+ <para>
+ Routine schema: the schema of the routine associated with the error, if
+ any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>g</>
+ </term>
+ <listitem>
+ <para>
+ Trigger name: the name of the trigger associated with the error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>i</>
+ </term>
+ <listitem>
+ <para>
+ Trigger table: the name of the table of the trigger associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>h</>
+ </term>
+ <listitem>
+ <para>
+ Trigger schema: the schema name of the trigger associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
The client is responsible for formatting displayed information to meet its
needs; in particular it should break long lines as needed. Newline characters
appearing in the error message fields should be treated as paragraph breaks,
! not line breaks. Note that certain object names represented as ErrorResponse
! fields, such as Trigger name, are not guaranteed to be unique across a database,
! and such objects cannot generally be uniquely identified by name alone. It may
! be necessary in these cases to unambiguously establish the identity of the
! object of interest based on a unique combination of the object's name, schema
! name, and, in the case of integrity constraints, table name.
</para>
</sect1>
*** a/src/backend/access/nbtree/nbtinsert.c
--- b/src/backend/access/nbtree/nbtinsert.c
***************
*** 393,399 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull))));
}
}
else if (all_dead)
--- 393,400 ----
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull)),
! errrel(rel)));
}
}
else if (all_dead)
***************
*** 455,461 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("failed to re-find tuple within index \"%s\"",
RelationGetRelationName(rel)),
! errhint("This may be because of a non-immutable index expression.")));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
--- 456,463 ----
(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."),
! errrel(rel)));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
***************
*** 533,539 **** _bt_findinsertloc(Relation rel,
RelationGetRelationName(rel)),
errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n"
"Consider a function index of an MD5 hash of the value, "
! "or use full text indexing.")));
/*----------
* If we will need to split the page to put the item on this page,
--- 535,542 ----
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."),
! errrel(rel)));
/*----------
* If we will need to split the page to put the item on this page,
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 3805,3814 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
int attn = lfirst_int(l);
if (heap_attisnull(tuple, attn + 1))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" contains null values",
! NameStr(newTupDesc->attrs[attn]->attname))));
}
foreach(l, tab->constraints)
--- 3805,3821 ----
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) ?
! errrelcol(newrel, NameStr(att->attname)) :
! errrelcol(oldrel, NameStr(att->attname))));
! }
}
foreach(l, tab->constraints)
***************
*** 3822,3828 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
--- 3829,3841 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name),
! (newrel) ?
! errrel(newrel) :
! errrel(oldrel),
! (newrel) ?
! errconstraint(newrel, con->name) :
! errconstraint(oldrel, con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
***************
*** 6620,6626 **** validateCheckConstraint(Relation rel, HeapTuple constrtup)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
--- 6633,6641 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname)),
! errrel(rel),
! errconstraint(rel, NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2233,2245 **** AlterDomainNotNull(List *names, bool notNull)
for (i = 0; i < rtc->natts; i++)
{
int attnum = rtc->atts[i];
if (heap_attisnull(tuple, attnum))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains null values",
! NameStr(tupdesc->attrs[attnum - 1]->attname),
! RelationGetRelationName(testrel))));
}
}
heap_endscan(scan);
--- 2233,2246 ----
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)),
! errrelcol(testrel, NameStr(att->attname))));
}
}
heap_endscan(scan);
***************
*** 2608,2614 **** validateDomainConstraint(Oid domainoid, char *ccbin)
(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);
--- 2609,2617 ----
(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)),
! errrelcol(testrel,
! NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
ResetExprContext(econtext);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1515,1528 **** ExecConstraints(ResultRelInfo *resultRelInfo,
for (attrChk = 1; attrChk <= natts; attrChk++)
{
if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
slot_attisnull(slot, attrChk))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" violates not-null constraint",
! NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1515,1531 ----
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)),
! errrelcol(rel, NameStr(Chk->attname))));
}
}
***************
*** 1536,1542 **** ExecConstraints(ResultRelInfo *resultRelInfo,
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1539,1547 ----
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64)),
! errrel(rel),
! errconstraint(rel, failed)));
}
}
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 1304,1317 **** retry:
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing)));
else
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with existing key %s.",
! error_new, error_existing)));
}
index_endscan(index_scan);
--- 1304,1319 ----
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing),
! errrel(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),
! errrel(index)));
}
index_endscan(index_scan);
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 338,344 **** RI_FKey_check(TriggerData *trigdata)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
--- 338,346 ----
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."),
! errrel(trigdata->tg_relation),
! errconstraint(trigdata->tg_relation, NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
***************
*** 2466,2472 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
NameStr(fake_riinfo.conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
--- 2468,2477 ----
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."),
! errrel(fk_rel),
! errconstraint(fk_rel, NameStr(fake_riinfo.conname))));
!
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
***************
*** 3218,3224 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo,
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
--- 3223,3231 ----
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel)),
! errrel(fk_rel),
! errconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
***************
*** 3226,3234 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo,
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
! errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
! key_names.data, key_values.data,
! RelationGetRelationName(fk_rel))));
}
--- 3233,3243 ----
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
! errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
! key_names.data, key_values.data,
! RelationGetRelationName(fk_rel)),
! errrel(pk_rel),
! errconstraint(fk_rel, NameStr(riinfo->conname))));
}
*** a/src/backend/utils/error/Makefile
--- b/src/backend/utils/error/Makefile
***************
*** 12,17 **** subdir = src/backend/utils/error
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = assert.o elog.o
include $(top_srcdir)/src/backend/common.mk
--- 12,17 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = assert.o elog.o relerror.o
include $(top_srcdir)/src/backend/common.mk
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
***************
*** 131,136 **** static void write_syslog(int level, const char *line);
--- 131,139 ----
static void write_console(const char *line, int len);
+ static void set_errdata_field(char **ptr, const char *str, bool overwrite);
+
+
#ifdef WIN32
extern char *event_source;
static void write_eventlog(int level, const char *line, int len);
***************
*** 477,482 **** errfinish(int dummy,...)
--- 480,507 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
***************
*** 1079,1084 **** internalerrquery(const char *query)
--- 1104,1218 ----
}
/*
+ * err_generic_string -- generic setting of ErrorData string fields
+ */
+ 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, true);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_errdata_field(&edata->detail, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_errdata_field(&edata->hint, str, true);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_errdata_field(&edata->context, str, true);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_errdata_field(&edata->column_name, str, true);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_errdata_field(&edata->table_name, str, true);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_errdata_field(&edata->schema_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_errdata_field(&edata->constraint_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_TABLE:
+ set_errdata_field(&edata->constraint_table, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_errdata_field(&edata->constraint_schema, str, true);
+ break;
+
+ /*
+ * The remaining fields, once set, will not be set again. We need to
+ * guard against resetting here because there is partial redundancy in
+ * the fields set between some of our potential callers, even though two
+ * or more of them might reasonably coexist within the same ereport
+ * call.
+ */
+ case PG_DIAG_ROUTINE_NAME:
+ set_errdata_field(&edata->routine_name, str, false);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_errdata_field(&edata->routine_schema, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_errdata_field(&edata->trigger_name, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_TABLE:
+ set_errdata_field(&edata->trigger_table, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_errdata_field(&edata->trigger_schema, str, false);
+ break;
+
+ default:
+ elog(ERROR, "unknown ErrorData field id %d",
+ field);
+ }
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * set_errdata_field --- set an ErrorData string field, while potentially
+ * avoiding overwriting any existing value
+ */
+ static void
+ set_errdata_field(char **ptr, const char *str, bool overwrite)
+ {
+ if (*ptr != NULL)
+ {
+ /* Avoid overwriting existing value entirely */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != 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
***************
*** 1352,1357 **** CopyErrorData(void)
--- 1486,1513 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
***************
*** 1377,1382 **** FreeErrorData(ErrorData *edata)
--- 1533,1560 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
***************
*** 1449,1454 **** ReThrowError(ErrorData *edata)
--- 1627,1654 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
***************
*** 2238,2244 **** write_csvlog(ErrorData *edata)
appendStringInfoChar(&buf, ',');
/* file error location */
- if (Log_error_verbosity >= PGERROR_VERBOSE)
{
StringInfoData msgbuf;
--- 2438,2443 ----
***************
*** 2259,2264 **** write_csvlog(ErrorData *edata)
--- 2458,2507 ----
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
***************
*** 2377,2382 **** send_message_to_server_log(ErrorData *edata)
--- 2620,2702 ----
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT TABLE: "));
+ append_with_tabs(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER TABLE: "));
+ append_with_tabs(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
***************
*** 2673,2678 **** send_message_to_frontend(ErrorData *edata)
--- 2993,3064 ----
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_TABLE);
+ err_sendstring(&msgbuf, edata->constraint_table);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_TABLE);
+ err_sendstring(&msgbuf, edata->trigger_table);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 3090,3096 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b,
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
! values, isnull))));
}
/*
--- 3090,3097 ----
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
! values, isnull)),
! errrel(state->indexRel)));
}
/*
*** a/src/include/postgres_ext.h
--- b/src/include/postgres_ext.h
***************
*** 55,59 **** typedef unsigned int Oid;
--- 55,70 ----
#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_SCHEMA_NAME 's'
+ #define PG_DIAG_CONSTRAINT_NAME 'n'
+ #define PG_DIAG_CONSTRAINT_TABLE 'o'
+ #define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+ #define PG_DIAG_ROUTINE_NAME 'r'
+ #define PG_DIAG_ROUTINE_SCHEMA 'u'
+ #define PG_DIAG_TRIGGER_NAME 'g'
+ #define PG_DIAG_TRIGGER_TABLE 'i'
+ #define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 190,195 **** extern int geterrcode(void);
--- 190,197 ----
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:
***************
*** 321,330 **** typedef struct ErrorData
char *detail_log; /* detail error message for server log only */
char *hint; /* hint message */
char *context; /* context message */
! int cursorpos; /* cursor index into query string */
! int internalpos; /* cursor index into internalquery */
! char *internalquery; /* text of internally-generated query */
! int saved_errno; /* errno at entry */
} ErrorData;
extern void EmitErrorReport(void);
--- 323,343 ----
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 *schema_name; /* name of schema */
! char *constraint_name; /* name of constraint */
! char *constraint_table; /* name of table related to constraint */
! char *constraint_schema; /* name of schema with constraint */
! char *routine_name; /* name of function that caused error */
! char *routine_schema; /* schema name of function that caused error */
! char *trigger_name; /* name of trigger that caused error */
! char *trigger_table; /* table of trigger that caused error */
! char *trigger_schema; /* schema of trigger that caused error */
! int cursorpos; /* cursor index into query string */
! int internalpos; /* cursor index into internalquery */
! char *internalquery; /* text of internally-generated query */
! int saved_errno; /* errno at entry */
} ErrorData;
extern void EmitErrorReport(void);
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 394,397 **** typedef struct StdRdOptions
--- 394,402 ----
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+ /* routines in utils/error/relerror.c */
+ extern int errrelcol(Relation rel, const char *colname);
+ extern int errrel(Relation rel);
+ extern int errconstraint(Relation rel, const char *cname);
+
#endif /* REL_H */
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
***************
*** 938,943 **** pqGetErrorNotice3(PGconn *conn, bool isError)
--- 938,988 ----
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("SCHEMA 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_CONSTRAINT_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
On 20 August 2012 14:09, Pavel Stehule <pavel.stehule@gmail.com> wrote:
(eelog-2012-08-20.diff)
This patch has the following reference to relerror.c:
"""""""
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 394,397 **** typedef struct StdRdOptions
--- 394,402 ----
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+ /* routines in utils/error/relerror.c */
"""""""
Indeed, the new C file has been separately added to a makefile:
! OBJS = assert.o elog.o relerror.o
However, the new file itself does not appear in this patch. Therefore,
the code does not compile. Please post a revision with the new file
included.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Hello Peter
here is updated patch, sorry for missing file
Regards
Pavel
2012/10/8 Peter Geoghegan <peter@2ndquadrant.com>:
Show quoted text
On 20 August 2012 14:09, Pavel Stehule <pavel.stehule@gmail.com> wrote:
(eelog-2012-08-20.diff)
This patch has the following reference to relerror.c:
"""""""
*** a/src/include/utils/rel.h --- b/src/include/utils/rel.h *************** *** 394,397 **** typedef struct StdRdOptions --- 394,402 ---- extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel);+ /* routines in utils/error/relerror.c */
"""""""
Indeed, the new C file has been separately added to a makefile:
! OBJS = assert.o elog.o relerror.o
However, the new file itself does not appear in this patch. Therefore,
the code does not compile. Please post a revision with the new file
included.--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog3.diffapplication/octet-stream; name=eelog3.diffDownload
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
***************
*** 4258,4263 **** CREATE TABLE postgres_log
--- 4258,4273 ----
query_pos integer,
location text,
application_name text,
+ column_name text,
+ table_name text,
+ schema_name text,
+ constraint_name text,
+ constraint_table text,
+ constraint_schema text,
+ routine_name text,
+ trigger_name text,
+ trigger_table text,
+ trigger_schema text,
PRIMARY KEY (session_id, session_line_num)
);
</programlisting>
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
***************
*** 4722,4734 **** message.
</listitem>
</varlistentry>
</variablelist>
<para>
The client is responsible for formatting displayed information to meet its
needs; in particular it should break long lines as needed. Newline characters
appearing in the error message fields should be treated as paragraph breaks,
! not line breaks.
</para>
</sect1>
--- 4722,4867 ----
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>
+ <literal>c</>
+ </term>
+ <listitem>
+ <para>
+ Column name: the name of the column associated with the the error, if
+ any.
+ </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>s</>
+ </term>
+ <listitem>
+ <para>
+ Schema name: the name of schema associated with the error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>n</>
+ </term>
+ <listitem>
+ <para>
+ Constraint name: the name of the constraint associated with the error,
+ if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>o</>
+ </term>
+ <listitem>
+ <para>
+ Constraint table: the table name of the constraint associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>m</>
+ </term>
+ <listitem>
+ <para>
+ Constraint schema: the schema name of the constraint associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>r</>
+ </term>
+ <listitem>
+ <para>
+ Routine name: the name of the routine associated with the error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>u</>
+ </term>
+ <listitem>
+ <para>
+ Routine schema: the schema of the routine associated with the error, if
+ any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>g</>
+ </term>
+ <listitem>
+ <para>
+ Trigger name: the name of the trigger associated with the error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>i</>
+ </term>
+ <listitem>
+ <para>
+ Trigger table: the name of the table of the trigger associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>h</>
+ </term>
+ <listitem>
+ <para>
+ Trigger schema: the schema name of the trigger associated with the
+ error, if any.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
The client is responsible for formatting displayed information to meet its
needs; in particular it should break long lines as needed. Newline characters
appearing in the error message fields should be treated as paragraph breaks,
! not line breaks. Note that certain object names represented as ErrorResponse
! fields, such as Trigger name, are not guaranteed to be unique across a database,
! and such objects cannot generally be uniquely identified by name alone. It may
! be necessary in these cases to unambiguously establish the identity of the
! object of interest based on a unique combination of the object's name, schema
! name, and, in the case of integrity constraints, table name.
</para>
</sect1>
*** a/src/backend/access/nbtree/nbtinsert.c
--- b/src/backend/access/nbtree/nbtinsert.c
***************
*** 393,399 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull))));
}
}
else if (all_dead)
--- 393,400 ----
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull)),
! errrel(rel)));
}
}
else if (all_dead)
***************
*** 455,461 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("failed to re-find tuple within index \"%s\"",
RelationGetRelationName(rel)),
! errhint("This may be because of a non-immutable index expression.")));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
--- 456,463 ----
(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."),
! errrel(rel)));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
***************
*** 533,539 **** _bt_findinsertloc(Relation rel,
RelationGetRelationName(rel)),
errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n"
"Consider a function index of an MD5 hash of the value, "
! "or use full text indexing.")));
/*----------
* If we will need to split the page to put the item on this page,
--- 535,542 ----
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."),
! errrel(rel)));
/*----------
* If we will need to split the page to put the item on this page,
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 3805,3814 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
int attn = lfirst_int(l);
if (heap_attisnull(tuple, attn + 1))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" contains null values",
! NameStr(newTupDesc->attrs[attn]->attname))));
}
foreach(l, tab->constraints)
--- 3805,3821 ----
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) ?
! errrelcol(newrel, NameStr(att->attname)) :
! errrelcol(oldrel, NameStr(att->attname))));
! }
}
foreach(l, tab->constraints)
***************
*** 3822,3828 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
--- 3829,3841 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name),
! (newrel) ?
! errrel(newrel) :
! errrel(oldrel),
! (newrel) ?
! errconstraint(newrel, con->name) :
! errconstraint(oldrel, con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
***************
*** 6620,6626 **** validateCheckConstraint(Relation rel, HeapTuple constrtup)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
--- 6633,6641 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname)),
! errrel(rel),
! errconstraint(rel, NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 2235,2247 **** AlterDomainNotNull(List *names, bool notNull)
for (i = 0; i < rtc->natts; i++)
{
int attnum = rtc->atts[i];
if (heap_attisnull(tuple, attnum))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains null values",
! NameStr(tupdesc->attrs[attnum - 1]->attname),
! RelationGetRelationName(testrel))));
}
}
heap_endscan(scan);
--- 2235,2248 ----
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)),
! errrelcol(testrel, NameStr(att->attname))));
}
}
heap_endscan(scan);
***************
*** 2610,2616 **** validateDomainConstraint(Oid domainoid, char *ccbin)
(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);
--- 2611,2619 ----
(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)),
! errrelcol(testrel,
! NameStr(tupdesc->attrs[attnum - 1]->attname))));
}
ResetExprContext(econtext);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1516,1529 **** ExecConstraints(ResultRelInfo *resultRelInfo,
for (attrChk = 1; attrChk <= natts; attrChk++)
{
if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
slot_attisnull(slot, attrChk))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" violates not-null constraint",
! NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1516,1532 ----
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)),
! errrelcol(rel, NameStr(Chk->attname))));
}
}
***************
*** 1537,1543 **** ExecConstraints(ResultRelInfo *resultRelInfo,
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1540,1548 ----
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64)),
! errrel(rel),
! errconstraint(rel, failed)));
}
}
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 1304,1317 **** retry:
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing)));
else
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with existing key %s.",
! error_new, error_existing)));
}
index_endscan(index_scan);
--- 1304,1319 ----
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing),
! errrel(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),
! errrel(index)));
}
index_endscan(index_scan);
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 339,345 **** RI_FKey_check(TriggerData *trigdata)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
--- 339,347 ----
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
! errrel(trigdata->tg_relation),
! errconstraint(trigdata->tg_relation, NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
***************
*** 2467,2473 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
NameStr(fake_riinfo.conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
--- 2469,2478 ----
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
NameStr(fake_riinfo.conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
! errrel(fk_rel),
! errconstraint(fk_rel, NameStr(fake_riinfo.conname))));
!
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
***************
*** 3219,3225 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo,
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
--- 3224,3232 ----
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel)),
! errrel(fk_rel),
! errconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
***************
*** 3227,3235 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo,
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
! errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
! key_names.data, key_values.data,
! RelationGetRelationName(fk_rel))));
}
--- 3234,3244 ----
RelationGetRelationName(pk_rel),
NameStr(riinfo->conname),
RelationGetRelationName(fk_rel)),
! errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
! key_names.data, key_values.data,
! RelationGetRelationName(fk_rel)),
! errrel(pk_rel),
! errconstraint(fk_rel, NameStr(riinfo->conname))));
}
*** a/src/backend/utils/error/Makefile
--- b/src/backend/utils/error/Makefile
***************
*** 12,17 **** subdir = src/backend/utils/error
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = assert.o elog.o
include $(top_srcdir)/src/backend/common.mk
--- 12,17 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = assert.o elog.o relerror.o
include $(top_srcdir)/src/backend/common.mk
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
***************
*** 131,136 **** static void write_syslog(int level, const char *line);
--- 131,139 ----
static void write_console(const char *line, int len);
+ static void set_errdata_field(char **ptr, const char *str, bool overwrite);
+
+
#ifdef WIN32
extern char *event_source;
static void write_eventlog(int level, const char *line, int len);
***************
*** 477,482 **** errfinish(int dummy,...)
--- 480,507 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
errordata_stack_depth--;
***************
*** 1079,1084 **** internalerrquery(const char *query)
--- 1104,1218 ----
}
/*
+ * err_generic_string -- generic setting of ErrorData string fields
+ */
+ 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, true);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_errdata_field(&edata->detail, str, true);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_errdata_field(&edata->hint, str, true);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_errdata_field(&edata->context, str, true);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_errdata_field(&edata->column_name, str, true);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_errdata_field(&edata->table_name, str, true);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_errdata_field(&edata->schema_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_errdata_field(&edata->constraint_name, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_TABLE:
+ set_errdata_field(&edata->constraint_table, str, true);
+ break;
+
+ case PG_DIAG_CONSTRAINT_SCHEMA:
+ set_errdata_field(&edata->constraint_schema, str, true);
+ break;
+
+ /*
+ * The remaining fields, once set, will not be set again. We need to
+ * guard against resetting here because there is partial redundancy in
+ * the fields set between some of our potential callers, even though two
+ * or more of them might reasonably coexist within the same ereport
+ * call.
+ */
+ case PG_DIAG_ROUTINE_NAME:
+ set_errdata_field(&edata->routine_name, str, false);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_errdata_field(&edata->routine_schema, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_errdata_field(&edata->trigger_name, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_TABLE:
+ set_errdata_field(&edata->trigger_table, str, false);
+ break;
+
+ case PG_DIAG_TRIGGER_SCHEMA:
+ set_errdata_field(&edata->trigger_schema, str, false);
+ break;
+
+ default:
+ elog(ERROR, "unknown ErrorData field id %d",
+ field);
+ }
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * set_errdata_field --- set an ErrorData string field, while potentially
+ * avoiding overwriting any existing value
+ */
+ static void
+ set_errdata_field(char **ptr, const char *str, bool overwrite)
+ {
+ if (*ptr != NULL)
+ {
+ /* Avoid overwriting existing value entirely */
+ if (!overwrite)
+ return;
+
+ pfree(*ptr);
+ *ptr = NULL;
+ }
+
+ if (str != 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
***************
*** 1352,1357 **** CopyErrorData(void)
--- 1486,1513 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
return newedata;
}
***************
*** 1377,1382 **** FreeErrorData(ErrorData *edata)
--- 1533,1560 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->constraint_table)
+ pfree(edata->constraint_table);
+ if (edata->constraint_schema)
+ pfree(edata->constraint_schema);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+ if (edata->trigger_table)
+ pfree(edata->trigger_table);
+ if (edata->trigger_schema)
+ pfree(edata->trigger_schema);
pfree(edata);
}
***************
*** 1449,1454 **** ReThrowError(ErrorData *edata)
--- 1627,1654 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->constraint_table)
+ newedata->constraint_table = pstrdup(newedata->constraint_table);
+ if (newedata->constraint_schema)
+ newedata->constraint_schema = pstrdup(newedata->constraint_schema);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
+ if (newedata->trigger_table)
+ newedata->trigger_table = pstrdup(newedata->trigger_table);
+ if (newedata->trigger_schema)
+ newedata->trigger_schema = pstrdup(newedata->trigger_schema);
recursion_depth--;
PG_RE_THROW();
***************
*** 2238,2244 **** write_csvlog(ErrorData *edata)
appendStringInfoChar(&buf, ',');
/* file error location */
- if (Log_error_verbosity >= PGERROR_VERBOSE)
{
StringInfoData msgbuf;
--- 2438,2443 ----
***************
*** 2259,2264 **** write_csvlog(ErrorData *edata)
--- 2458,2507 ----
/* application name */
if (application_name)
appendCSVLiteral(&buf, application_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, ',');
+
+ if (Log_error_verbosity >= PGERROR_VERBOSE)
+ appendCSVLiteral(&buf, edata->trigger_schema);
appendStringInfoChar(&buf, '\n');
***************
*** 2377,2382 **** send_message_to_server_log(ErrorData *edata)
--- 2620,2702 ----
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("COLUMN NAME: "));
+ append_with_tabs(&buf, edata->column_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TABLE NAME: "));
+ append_with_tabs(&buf, edata->table_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("SCHEMA NAME: "));
+ append_with_tabs(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT NAME: "));
+ append_with_tabs(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT TABLE: "));
+ append_with_tabs(&buf, edata->constraint_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->constraint_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("CONSTRAINT SCHEMA: "));
+ append_with_tabs(&buf, edata->constraint_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE NAME: "));
+ append_with_tabs(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("ROUTINE SCHEMA: "));
+ append_with_tabs(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER NAME: "));
+ append_with_tabs(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_table)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER TABLE: "));
+ append_with_tabs(&buf, edata->trigger_table);
+ appendStringInfoChar(&buf, '\n');
+ }
+ if (edata->trigger_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfoString(&buf, _("TRIGGER SCHEMA: "));
+ append_with_tabs(&buf, edata->trigger_schema);
+ appendStringInfoChar(&buf, '\n');
+ }
}
}
***************
*** 2673,2678 **** send_message_to_frontend(ErrorData *edata)
--- 2993,3064 ----
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->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->constraint_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_TABLE);
+ err_sendstring(&msgbuf, edata->constraint_table);
+ }
+
+ if (edata->constraint_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_SCHEMA);
+ err_sendstring(&msgbuf, edata->constraint_schema);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
+ if (edata->trigger_table)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_TABLE);
+ err_sendstring(&msgbuf, edata->trigger_table);
+ }
+
+ if (edata->trigger_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_SCHEMA);
+ err_sendstring(&msgbuf, edata->trigger_schema);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
*** /dev/null
--- b/src/backend/utils/error/relerror.c
***************
*** 0 ****
--- 1,63 ----
+ /*-------------------------------------------------------------------------
+ *
+ * relerror.c
+ * relation error loging 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"
+
+ /*
+ * errrelcol --- sets column_name, table_name and schema_name of a column
+ * within errordata
+ */
+ int
+ errrelcol(Relation rel, const char *colname)
+ {
+ err_generic_string(PG_DIAG_COLUMN_NAME, colname);
+ err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ err_generic_string(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
+
+ /*
+ * errrel --- sets column_name, table_name and schema_name within errordata
+ */
+ int
+ errrel(Relation rel)
+ {
+ err_generic_string(PG_DIAG_TABLE_NAME, RelationGetRelationName(rel));
+ err_generic_string(PG_DIAG_SCHEMA_NAME,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
+
+ /*
+ * errcontraint --- sets constraint_name, constraint_table and constraint_schema
+ * within errordata.
+ */
+ int
+ errconstraint(Relation rel, const char *cname)
+ {
+ err_generic_string(PG_DIAG_CONSTRAINT_NAME, cname);
+ err_generic_string(PG_DIAG_CONSTRAINT_TABLE, RelationGetRelationName(rel));
+ err_generic_string(PG_DIAG_CONSTRAINT_SCHEMA,
+ get_namespace_name(RelationGetNamespace(rel)));
+
+ return 0;
+ }
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 3091,3097 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b,
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
! values, isnull))));
}
/*
--- 3091,3098 ----
RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
BuildIndexValueDescription(state->indexRel,
! values, isnull)),
! errrel(state->indexRel)));
}
/*
*** a/src/include/postgres_ext.h
--- b/src/include/postgres_ext.h
***************
*** 60,64 **** typedef PG_INT64_TYPE pg_int64;
--- 60,75 ----
#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_SCHEMA_NAME 's'
+ #define PG_DIAG_CONSTRAINT_NAME 'n'
+ #define PG_DIAG_CONSTRAINT_TABLE 'o'
+ #define PG_DIAG_CONSTRAINT_SCHEMA 'm'
+ #define PG_DIAG_ROUTINE_NAME 'r'
+ #define PG_DIAG_ROUTINE_SCHEMA 'u'
+ #define PG_DIAG_TRIGGER_NAME 'g'
+ #define PG_DIAG_TRIGGER_TABLE 'i'
+ #define PG_DIAG_TRIGGER_SCHEMA 'h'
#endif /* POSTGRES_EXT_H */
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 195,200 **** extern int geterrcode(void);
--- 195,202 ----
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:
***************
*** 326,335 **** typedef struct ErrorData
char *detail_log; /* detail error message for server log only */
char *hint; /* hint message */
char *context; /* context message */
! int cursorpos; /* cursor index into query string */
! int internalpos; /* cursor index into internalquery */
! char *internalquery; /* text of internally-generated query */
! int saved_errno; /* errno at entry */
} ErrorData;
extern void EmitErrorReport(void);
--- 328,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 *schema_name; /* name of schema */
! char *constraint_name; /* name of constraint */
! char *constraint_table; /* name of table related to constraint */
! char *constraint_schema; /* name of schema with constraint */
! char *routine_name; /* name of function that caused error */
! char *routine_schema; /* schema name of function that caused error */
! char *trigger_name; /* name of trigger that caused error */
! char *trigger_table; /* table of trigger that caused error */
! char *trigger_schema; /* schema of trigger that caused error */
! int cursorpos; /* cursor index into query string */
! int internalpos; /* cursor index into internalquery */
! char *internalquery; /* text of internally-generated query */
! int saved_errno; /* errno at entry */
} ErrorData;
extern void EmitErrorReport(void);
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 394,397 **** typedef struct StdRdOptions
--- 394,402 ----
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+ /* routines in utils/error/relerror.c */
+ extern int errrelcol(Relation rel, const char *colname);
+ extern int errrel(Relation rel);
+ extern int errconstraint(Relation rel, const char *cname);
+
#endif /* REL_H */
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
***************
*** 936,941 **** pqGetErrorNotice3(PGconn *conn, bool isError)
--- 936,986 ----
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_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("SCHEMA 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_CONSTRAINT_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE SCHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_TABLE);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER TABLE: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER SCHEMA: %s\n"), val);
}
/*
On 10 October 2012 14:56, Pavel Stehule <pavel.stehule@gmail.com> wrote:
(eelog3.diff)
This looks better.
You need a better comment here:
+ * relerror.c
+ * relation error loging functions
+ *
I'm still not satisfied with the lack of firm guarantees about what
errcodes one can assume these fields will be available for. I suggest
that this be explicitly documented within errcodes.h. For example,
right now if some client code wants to discriminate against a certain
check constraint in its error-handling code (typically to provide a
user-level message), it might do something like this:
try
{
...
}
catch(CheckViolation e)
{
// This works:
if (e.constraint_name == "postive_balance")
MessageBox("Your account must have a positive balance.");
// This is based on a check constraint in a domain, and is
therefore broken right now:
else if (e.constraint_name == "valid_barcode")
MessageBox("You inputted an invalid barcode - check digit was wrong");
}
This is broken right now, simply because the application cannot rely
on the constraint name being available, since for no particular reason
some of the ERRCODE_CHECK_VIOLATION ereport sites (like in execQual.c)
don't provide an errconstraint(). What is needed is a coding standard
that says "ERRCODE_CHECK_VIOLATION ereport call sites need to have an
errconstraint()". Without this, the patch is quite pointless.
My mind is not 100% closed to the idea that we provide these extra
fields on a "best-effort" basis per errcode, but it's pretty close to
there. Why should we allow this unreasonable inconsistency? The usage
pattern that I describe above is a real one, and I thought that the
whole point was to support it.
I have previously outlined places where this type of inconsistency
exists, in an earlier revision. [1]http://archives.postgresql.org/message-id/CAEYLb_VJK+AZe6fO_s0Md0ge5D=RenDf7wg+g4NxN8mhKQ4Gzg@mail.gmail.com
If you want to phase in the introduction of requiring that all
relevant callsites use this infrastructure, I guess I'm okay with
that. However, I'm going to have to insist that for each of these new
fields, for any errcode you identify as requiring the field, either
all callsites get all relevant fields, or no call sites get all
relevant fields, and that each errcode be documented as such. So you
should probably just bite the bullet and figure out a reasonable and
comprehensive set of rules on adding these fields based on errcode.
Loosey goosey isn't going to cut it.
I'm having a difficult time imagining why we'd only have the
constraint/tablename for these errcodes (with one exception, noted
below):
/* Class 23 - Integrity Constraint Violation */
#define ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION
MAKE_SQLSTATE('2','3','0','0','0')
#define ERRCODE_RESTRICT_VIOLATION MAKE_SQLSTATE('2','3','0','0','1')
#define ERRCODE_NOT_NULL_VIOLATION MAKE_SQLSTATE('2','3','5','0','2')
#define ERRCODE_FOREIGN_KEY_VIOLATION MAKE_SQLSTATE('2','3','5','0','3')
#define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
#define ERRCODE_CHECK_VIOLATION MAKE_SQLSTATE('2','3','5','1','4')
#define ERRCODE_EXCLUSION_VIOLATION MAKE_SQLSTATE('2','3','P','0','1')
You previously defending some omissions [2]CAFj8pRDtTDvoSvJT8PP08mQ_LW2HaOmWXvRUdoYLhk9xF7KMyw@mail.gmail.com on the basis that they
involved domains, so some fields were unavailable. This doesn't appear
to be quite valid, though. For example, consider this untouched
callsite within execQual, that relates to a domain:
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)));
There is no reason why you couldn't have at least given the constraint
name. It might represent an unreasonable burden for you to determine
the table that these constraints relate to by going through the rabbit
hole of executor state, since we haven't had any complaints about this
information being available within error messages before, AFAIK. If
that is the case, the general non-availability of this information for
domains needs to be documented. I guess that's logical enough, since
there doesn't necessarily have to be a table involved in the event of
a domain constraint violation. However, there does have to be a
constraint, for example, involved.
FWIW, I happen to think that not-null constraints at the domain level
are kind of stupid (though check constraints are great), but what do I
know...
Anyway, the bottom line is that authors of Postgres client libraries
(and their users) ought to have a reasonable set of guarantees about
when this information is available. If that means you have to make one
or two explicit, documented exceptions to my previous "all or nothing"
proviso, such as "table names won't be available in the event of
domain constraints", so be it.
I'm going to suggest you add a note to both the docs and errcodes.h
regarding all this in your next revision. People need to be adding
these fields for all errcodes that *require* them going forward. If,
in the future, ERRCODE_UNIQUE_VIOLATION errors, for example, cannot
supply a constraint name that was violated, then that is, almost by
definition, the wrong errcode to use.
[2]: CAFj8pRDtTDvoSvJT8PP08mQ_LW2HaOmWXvRUdoYLhk9xF7KMyw@mail.gmail.com
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Hello
2012/10/11 Peter Geoghegan <peter@2ndquadrant.com>:
On 10 October 2012 14:56, Pavel Stehule <pavel.stehule@gmail.com> wrote:
(eelog3.diff)
This looks better.
You need a better comment here:
+ * relerror.c + * relation error loging functions + *I'm still not satisfied with the lack of firm guarantees about what
errcodes one can assume these fields will be available for. I suggest
that this be explicitly documented within errcodes.h. For example,
right now if some client code wants to discriminate against a certain
check constraint in its error-handling code (typically to provide a
user-level message), it might do something like this:try
{
...
}
catch(CheckViolation e)
{
// This works:
if (e.constraint_name == "postive_balance")
MessageBox("Your account must have a positive balance.");
// This is based on a check constraint in a domain, and is
therefore broken right now:
else if (e.constraint_name == "valid_barcode")
MessageBox("You inputted an invalid barcode - check digit was wrong");
}This is broken right now, simply because the application cannot rely
on the constraint name being available, since for no particular reason
some of the ERRCODE_CHECK_VIOLATION ereport sites (like in execQual.c)
don't provide an errconstraint(). What is needed is a coding standard
that says "ERRCODE_CHECK_VIOLATION ereport call sites need to have an
errconstraint()". Without this, the patch is quite pointless.
I understand to your request, but I don't thing so this request is
100% valid. Check violation is good example. Constraint names are
"optional" in PostgreSQL - so we cannot require constraint_name. One
from first prototypes I used generated name for NULL constraints and
it was rejected - because It can be confusing, because a user doesn't
find these names in catalogue. I agree with it now - it better show
nothing, than show some phantom. More - a design of these feature from
SQL/PSM and ANSI/SQL is not too strict. There is no exception, when
you asking any unfilled value - you get a empty string instead. And
more - there are no info in standard, what fields are optional and
what fields are mandatory.
And although we don't checking consistence of exception fields, I
think so this patch is very usable. I have a three text fields now:
message, detail, hint - and I can do same error, that you are
described. This patch doesn't change it. But it creates a few new
basic variables (for all possible exceptions), that can be used for
simplification of error processing. It is not silver bullet. And it is
not C++. Creating some new tool for checking consistency of exceptions
is not good way - and you are newer ensure consistency of custom
exceptions.
My mind is not 100% closed to the idea that we provide these extra
fields on a "best-effort" basis per errcode, but it's pretty close to
there. Why should we allow this unreasonable inconsistency? The usage
pattern that I describe above is a real one, and I thought that the
whole point was to support it.I have previously outlined places where this type of inconsistency
exists, in an earlier revision. [1]If you want to phase in the introduction of requiring that all
relevant callsites use this infrastructure, I guess I'm okay with
that. However, I'm going to have to insist that for each of these new
fields, for any errcode you identify as requiring the field, either
all callsites get all relevant fields, or no call sites get all
relevant fields, and that each errcode be documented as such. So you
should probably just bite the bullet and figure out a reasonable and
comprehensive set of rules on adding these fields based on errcode.
Loosey goosey isn't going to cut it.I'm having a difficult time imagining why we'd only have the
constraint/tablename for these errcodes (with one exception, noted
below):/* Class 23 - Integrity Constraint Violation */
#define ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION
MAKE_SQLSTATE('2','3','0','0','0')
#define ERRCODE_RESTRICT_VIOLATION MAKE_SQLSTATE('2','3','0','0','1')
#define ERRCODE_NOT_NULL_VIOLATION MAKE_SQLSTATE('2','3','5','0','2')
#define ERRCODE_FOREIGN_KEY_VIOLATION MAKE_SQLSTATE('2','3','5','0','3')
#define ERRCODE_UNIQUE_VIOLATION MAKE_SQLSTATE('2','3','5','0','5')
#define ERRCODE_CHECK_VIOLATION MAKE_SQLSTATE('2','3','5','1','4')
#define ERRCODE_EXCLUSION_VIOLATION MAKE_SQLSTATE('2','3','P','0','1')
ERRCODE_UNIQUE_VIOLATION and ERRCODE_EXCLUSION_VIOLATION should be
related to index relation, not parent relation. Then we don't need set
COLUMN_NAME, that can be expression or more columns.
You previously defending some omissions [2] on the basis that they
involved domains, so some fields were unavailable. This doesn't appear
to be quite valid, though. For example, consider this untouched
callsite within execQual, that relates to a domain: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)));There is no reason why you couldn't have at least given the constraint
name. It might represent an unreasonable burden for you to determine
the table that these constraints relate to by going through the rabbit
hole of executor state, since we haven't had any complaints about this
information being available within error messages before, AFAIK. If
that is the case, the general non-availability of this information for
domains needs to be documented. I guess that's logical enough, since
there doesn't necessarily have to be a table involved in the event of
a domain constraint violation. However, there does have to be a
constraint, for example, involved.
yes, CONSTRAINT_NAME in this case should be used. TABLE_NAME can be or
should not be empty, but this information is not available, because
some facts can be changed in rewriter stage.
FWIW, I happen to think that not-null constraints at the domain level
are kind of stupid (though check constraints are great), but what do I
know...Anyway, the bottom line is that authors of Postgres client libraries
(and their users) ought to have a reasonable set of guarantees about
when this information is available. If that means you have to make one
or two explicit, documented exceptions to my previous "all or nothing"
proviso, such as "table names won't be available in the event of
domain constraints", so be it.I'm going to suggest you add a note to both the docs and errcodes.h
regarding all this in your next revision. People need to be adding
these fields for all errcodes that *require* them going forward. If,
in the future, ERRCODE_UNIQUE_VIOLATION errors, for example, cannot
supply a constraint name that was violated, then that is, almost by
definition, the wrong errcode to use.
I can agree, so some documentation is necessary (maybe some table) -
now we have not described context of all errors. Other needs a
searching of some consensus - or searching solution - our syntax
allows some variations that are unsupported in ANSI/SQL - and then we
have to use some generated name or we don't show this information. It
is a basic and most important question. So first we have to find reply
to following question: this patch should to follow our current
implementation of exceptions or we modify exceptions to be more close
to ANSI/SQL (and we have to modify model)?
Regards
Pavel
Show quoted text
[2] CAFj8pRDtTDvoSvJT8PP08mQ_LW2HaOmWXvRUdoYLhk9xF7KMyw@mail.gmail.com
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
On 12 October 2012 20:27, Pavel Stehule <pavel.stehule@gmail.com> wrote:
I understand to your request, but I don't thing so this request is
100% valid. Check violation is good example. Constraint names are
"optional" in PostgreSQL - so we cannot require constraint_name. One
from first prototypes I used generated name for NULL constraints and
it was rejected - because It can be confusing, because a user doesn't
find these names in catalogue. I agree with it now - it better show
nothing, than show some phantom. More - a design of these feature from
SQL/PSM and ANSI/SQL is not too strict. There is no exception, when
you asking any unfilled value - you get a empty string instead.
That's beside the point. NOT NULL constraints are not catalogued (for
now), so sure, the only reasonable thing to do is to have an empty
string in that case. Since no one expects to be able to get the name
of a NOT NULL constraint anyway, that isn't a problem.
Once again, the problem, in particular, is that there is no
well-defined set of rules that client code can follow to be sure that
a field name they're interested in will be available at all. In all
revisions thus far, you have seemingly arbitrarily decided to not add
some some fields in some places. I mentioned already that some
ERRCODE_CHECK_VIOLATION sites didn't name a constraint - other places
don't name a table when one is available, as with some
ERRCODE_NOT_NULL_VIOLATION sites. These fields need to be added, and
what's more, the rules for where they need to be added need to be
coherently described. So, that's about 3 sentences of extra
documentation, saying to both users and hackers (at the very least):
* NOT NULL constraints won't have a CONSTRAINT_NAME available, since
they aren't catalogued.
* Domains won't have a TABLE_NAME available, even though there may
actually be a table name associated with the error.
Have I missed one?
That all seems pretty simple to me, and I don't see what the problem is.
And although we don't checking consistence of exception fields, I
think so this patch is very usable. I have a three text fields now:
message, detail, hint - and I can do same error, that you are
described. This patch doesn't change it. But it creates a few new
basic variables (for all possible exceptions), that can be used for
simplification of error processing. It is not silver bullet. And it is
not C++.
The simplification of error processing is that they can now reliably
get these fields - they don't have to use some kludge like parsing a
(possibly localised) error message to look for a check constraint
name. I'm not asking you to add run-time verification - I'm asking you
to institute a coding standard, that is limited to backend code, and
to document what assumptions applications can make.
To my mind, if the user cannot rely on the fields accurately
indicating error conditions according to some coherent set of rules
(in actuality, one or two simple and obvious exceptions, only one of
which is slightly surprising), then this patch is not only not
helpful, it's harmful. If they're only available according to some
completely arbitrary and obscure criteria, (like the fact that you've
included one ERRCODE_CHECK_VIOLATION site but not another), that's a
footgun.
Creating some new tool for checking consistency of exceptions
is not good way - and you are newer ensure consistency of custom
exceptions.
Pointing out that some extension author, or pl/pgsql function, could
in principle ignore the documentation I'm asking you to write and not
supply a constraint, while raising their own, say, magical
ERRCODE_CHECK_VIOLATION is a bit of a cop-out. I should also mention
that things like ERRCODE_INTERNAL_ERROR are naturally going to be a
bit fuzzy, and that's fine. Nobody is ever going to expect those
anyway.
yes, CONSTRAINT_NAME in this case should be used. TABLE_NAME can be or
should not be empty, but this information is not available, because
some facts can be changed in rewriter stage.
Right, or because you could do this and get an exception:
select 'foo'::bar_domain;
I can agree, so some documentation is necessary (maybe some table) -
now we have not described context of all errors. Other needs a
searching of some consensus - or searching solution - our syntax
allows some variations that are unsupported in ANSI/SQL - and then we
have to use some generated name or we don't show this information. It
is a basic and most important question.
Can you give an example of when a generated name might be used, beyond
the example you've already given (that is, NULL constraints)? I'm not
completely opposed to the idea of a generated name. I think that it's
very much a secondary issue, though.
So first we have to find reply
to following question: this patch should to follow our current
implementation of exceptions or we modify exceptions to be more close
to ANSI/SQL (and we have to modify model)?
What does the option of following the SQL standard offer us? What have
I said that is fundamentally incompatible with how things work in this
patch right now?
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
I think that we're both going to be busy next week, since we're both
attending pgconf.eu. For that reason, I would like to spend some time
tomorrow to get something in shape, that I can mark "ready for
committer". I'd like to get this patch committed during this
commitfest. You are welcome to do this work instead. I want to avoid a
redundant effort.
Let me know if you think that that's a good idea.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Hello
2012/10/20 Peter Geoghegan <peter@2ndquadrant.com>:
I think that we're both going to be busy next week, since we're both
attending pgconf.eu. For that reason, I would like to spend some time
tomorrow to get something in shape, that I can mark "ready for
committer". I'd like to get this patch committed during this
commitfest. You are welcome to do this work instead. I want to avoid a
redundant effort.
I invite a materialization of your ideas :) - and I have to work on
preparing presentation for pgconf.eu :(
Regards
Pavel
Show quoted text
Let me know if you think that that's a good idea.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Peter Geoghegan escribió:
I think that we're both going to be busy next week, since we're both
attending pgconf.eu. For that reason, I would like to spend some time
tomorrow to get something in shape, that I can mark "ready for
committer". I'd like to get this patch committed during this
commitfest. You are welcome to do this work instead. I want to avoid a
redundant effort.Let me know if you think that that's a good idea.
I guess you didn't get around to it.
Here are my own notes about this patch.
* Why doesn't errconstraint() set the err table directly? Having to call errrel()
separately seems pointless. I propose errconstraint sets both things; when
the two tables differ, call errconstraint first and then errrel() to overwrite.
* Some constraints do not have an associated relation name; for example
constraints on domains. I think we should report the constraint name
there, if one exists (in domain_check_input, ExecEvalCoerceToDomain
it doesn't). How about errconstraint() does not take a relation, and
have a new errconstraintrel() that receives the relation to which the
constraint is attached. Alternatively, have errconstraint() accept a
NULL relation for the cases where there is none.
* The distinction between oldrel/newrel in certain callers seems
useless; for example if we're rewriting a table due to ALTER TABLE, both
the new and old rel have the same name. That could be cleaned up.
* Some errrel() calls are getting an index ... is this sane? I think we
should be reporting the table name, not the index name.
* There are some pointless whitespace changes in elog.h. I suggest
passing everything through pgindent.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
On 24 October 2012 23:29, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Let me know if you think that that's a good idea.
I guess you didn't get around to it.
I did get some work on this done, which does change things somewhat.
In particular, I think that the need to have so many new fields is
questionable, and so have removed some. I will get around to this, and
will incorporate those ideas.
The errrel() calls with index relations are not sane, but that's just
an oversight. The next revision will actually do this:
+ Assert(table->rd_rel->relkind == RELKIND_RELATION);
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Peter Geoghegan escribió:
On 24 October 2012 23:29, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Let me know if you think that that's a good idea.
I guess you didn't get around to it.
I did get some work on this done, which does change things somewhat.
In particular, I think that the need to have so many new fields is
questionable, and so have removed some. I will get around to this, and
will incorporate those ideas.
Excellent. We will await the next version of this patch then ... during
the upcoming commitfest. I'm closing it as returned with feedback.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
So, I took a look at this, and made some more changes.
I have a hard time seeing the utility of some fields that were in this
patch, and so they have been removed from this revision.
Consider, for example:
+ constraint_table text,
+ constraint_schema text,
While constraint_name remains, I find it really hard to believe that
applications need a perfectly unambiguous idea of what constraint
they're dealing with. If applications have a constraint that has a
different domain-specific meaning depending on what schema it is in,
while a table in one schema uses that constraint in another, well,
that's a fairly awful design. Is it something that we really need to
care about? You mentioned the SQL standard, but as far as I know the
SQL standard has nothing to say about these fields within something
like errdata - their names are lifted from information_schema, but
being unambiguous is hardly so critical here. We want to identify an
object for the purposes of displaying an error message only. Caring
deeply about ambiguity seems wrong-headed to me in this context.
+ routine_name text,
+ trigger_name text,
+ trigger_table text,
+ trigger_schema text,
The whole point of an exception (which ereport() is very similar to)
is to decouple errors from error handling as much as possible - any
application that magically takes a different course of action based on
where that error occurred as reported by the error itself (and not the
location of where handling the error occurs) seems kind of
wrong-headed to me. Sure, a backtrace may be supplied, but that's only
for diagnostic purposes, and a human can just as easily get that
information already. If you can present an example of this information
actually being present in a way that is amenable to that, either in
any other RDBMS or any major programming language or framework, I
might reconsider.
This just leaves:
+ column_name text,
+ table_name text,
+ constraint_name text,
+ schema_name text,
This seems like enough information for any reasonable use of this
infrastructure, and I highly doubt anyone would avail of the other
fields if they remained. I guess an application might have done
something with "constraint_table", as with foreign keys for example,
but I just can't see that being used when constraint_name can be used.
Removing these fields has also allowed me to remove the "avoid setting
field at lower point in the callstack" logic, which was itself kind of
ugly. Since fields like routine_name only set themselves at the top of
the callstack, the potential for astonishing outcomes was pretty high
- what if you expect one routine_name, but then that routine follows a
slightly different code-path one day and you get another function
setting routine_name and undermining that expectation?
There were some bugs left in the patch eelog3.diff, mostly due to
things like setting table name to what is actually an index name. As I
mentioned, we now assert that:
+ Assert(table->rd_rel->relkind == RELKIND_RELATION);
in the functions within relerror.c.
I have tightened up where these fields are available, and
appropriately documented that for the benefit of both application
developers and developers of client libraries. I went so far as to
hack the Perl scripts that generate .sgml and .h files from
errcodes.txt (i.e. the infrastrucutre that produces tables of errcodes
in various places from a single authoritative place) - I have
instituted a coding standard so that these fields are reliably
available and have documented that requirement at both the user and
hacker level.
It would be nice if a Perl hacker could eyeball those changes - this
is my first time writing Perl, and I suspect it may be worth having
someone else to polish the Perl code a bit.
I have emphasized the need for consistency and a sane contract for
application developers and third-party client driver authors - they
*need* to know that certain fields will always be available, or at
least won't be unavailable due to a change in the phase of the moon.
errcodes.txt now says:
+ # 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
Now, there are one or two places where these fields are not actually
available even though they're formally required according to a literal
reading of the above. This is only because there is clearly no such
field sensibly available, even in principle - to my mind this cannot
be a problem, because the application developer cannot have any
reasonable expectation of a field being set. I'm really talking about
two cases in particular:
* For ERRCODE_NOT_NULL_VIOLATION, we don't actually provide
schema_name and table_name in the event of domains. This was
previously identified as an issue. If it is judged better to not have
any requirements there at all, so be it.
* For the validateDomainConstraint() ERRCODE_CHECK_VIOLATION ereport
call, we may not provide a constraint name iff a Constraint.connname
is NULL. Since there isn't a constraint name to give even in
principle, and this is an isolated case, this seems reasonable.
To make logging less verbose, TABLE NAME isn't consistently split out
as a separate field - this seems fine to me, since application code
doesn't target logs:
+ if (edata->column_name && edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"),
+ edata->table_name, edata->column_name);
+ }
+ else if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("TABLE NAME: %s\n"),
+ edata->table_name);
+ }
I used pgindent to selectively indent parts of the codebase affected
by this patch. I am about ready to mark this one "ready for
committer", but it would be nice at this point to get some buy-in on
the basic view of how these things should work that informed this
revision. Does anyone disagree with my contention that there should be
a sane, well-defined contract, or any of the details of what that
should look like? Was I right to suggest that some of the set of
fields that appeared in Pavel's eelog3.diff revision are unnecessary?
I'm sorry it took me as long as it did to get back to you on this.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog4.diffapplication/octet-stream; name=eelog4.diffDownload
diff doc/src/sgml/config.sgml
index b7df8ce..4b1179f
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** FROM pg_stat_activity;
*** 4246,4255 ****
error context,
user query that led to the error (if any and enabled by
<varname>log_min_error_statement</>),
! character count of the error position therein,
! location of the error in the PostgreSQL source code
! (if <varname>log_error_verbosity</> is set to <literal>verbose</>),
! and application name.
Here is a sample table definition for storing CSV-format log output:
<programlisting>
--- 4246,4256 ----
error context,
user query that led to the error (if any and enabled by
<varname>log_min_error_statement</>),
! character count of the error position therein, and, if
! <varname>log_error_verbosity</> is set to <literal>verbose</>,
! column name, table name, constraint name and location of the
! error in the PostgreSQL source code.
! Application name is also listed.
Here is a sample table definition for storing CSV-format log output:
<programlisting>
*************** CREATE TABLE postgres_log
*** 4277,4282 ****
--- 4278,4287 ----
query text,
query_pos integer,
location text,
+ column_name text,
+ table_name text,
+ constraint_name text,
+ schema_name text,
application_name text,
PRIMARY KEY (session_id, session_line_num)
);
diff doc/src/sgml/errcodes.sgml
index 16cb6c7..b3407ef
*** a/doc/src/sgml/errcodes.sgml
--- b/doc/src/sgml/errcodes.sgml
***************
*** 42,47 ****
--- 42,59 ----
</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). These additional fields are not
+ described by the SQL standard.
+ </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 b9c14d3..b4041a9
*** 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/-/—/;
+
+ 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 f87020c..027c21f
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
*************** message.
*** 4729,4734 ****
--- 4729,4792 ----
</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 3ed9b5c..27ef0fe
*** 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 4065740..71f68f7
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
*************** ATRewriteTable(AlteredTableInfo *tab, Oi
*** 3803,3812 ****
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)
--- 3803,3819 ----
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
*** 3820,3826 ****
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 */
--- 3827,3837 ----
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
*** 6633,6639 ****
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
--- 6644,6652 ----
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 36de6d7..4b96d07
*** 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
*** 2254,2266 ****
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);
--- 2254,2267 ----
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
*** 2466,2472 ****
* attributes based on the domain the constraint is being added to.
*/
if (!constr->skip_validation)
! validateDomainConstraint(domainoid, ccbin);
/* Clean up */
heap_close(typrel, RowExclusiveLock);
--- 2467,2473 ----
* 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
*** 2551,2557 ****
HeapTupleGetOid(tuple));
conbin = TextDatumGetCString(val);
! validateDomainConstraint(domainoid, conbin);
/*
* Now update the catalog, while we have the door open.
--- 2552,2558 ----
HeapTupleGetOid(tuple));
conbin = TextDatumGetCString(val);
! validateDomainConstraint(domainoid, conbin, constrName);
/*
* Now update the catalog, while we have the door open.
*************** AlterDomainValidateConstraint(List *name
*** 2572,2578 ****
}
static void
! validateDomainConstraint(Oid domainoid, char *ccbin)
{
Expr *expr = (Expr *) stringToNode(ccbin);
List *rels;
--- 2573,2579 ----
}
static void
! validateDomainConstraint(Oid domainoid, char *ccbin, char *conname)
{
Expr *expr = (Expr *) stringToNode(ccbin);
List *rels;
*************** validateDomainConstraint(Oid domainoid,
*** 2614,2619 ****
--- 2615,2621 ----
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,
*** 2628,2635 ****
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);
--- 2630,2640 ----
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 0222d40..3b4022a
*** 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 56b106a..526d779
*** 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
*** 3864,3869 ****
--- 3865,3876 ----
CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr;
Datum result;
ListCell *l;
+ Relation heapRel = NULL;
+ struct EState *estate = econtext->ecxt_estate;
+
+ /* Build relation description, if possible */
+ if (estate && estate->es_result_relation_info)
+ heapRel = estate->es_result_relation_info->ri_RelationDesc;
result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
*************** 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 d6cf06c..a85d081
*** 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 8b51ccf..6d658bb
*** 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,132 ****
--- 124,140 ----
{
case DOM_CONSTRAINT_NOTNULL:
if (isnull)
+ {
+ /*
+ * We don't provide errtable here, because it is
+ * unavailable, though we require it anywhere where it is
+ * available in principle.
+ */
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("domain %s does not allow null values",
format_type_be(my_extra->domain_type))));
+ }
break;
case DOM_CONSTRAINT_CHECK:
{
*************** 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:
--- 171,178 ----
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("value for domain %s violates check constraint \"%s\"",
format_type_be(my_extra->domain_type),
! con->name),
! errconstraint(con->name)));
break;
}
default:
diff src/backend/utils/adt/ri_triggers.c
index 97e68b1..474dc2a
*** 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 3e04164..8e2ba49
*** 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 c22190a..daffe22
*** 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 those within
+ * relerror.c.
+ */
+ int
+ err_generic_string(int field, const char *str)
+ {
+ ErrorData *edata = &errordata[errordata_stack_depth];
+
+ /* we don't bother incrementing recursion_depth */
+ CHECK_STACK_DEPTH();
+
+ switch (field)
+ {
+ case PG_DIAG_MESSAGE_PRIMARY:
+ set_errdata_field(&edata->message, str);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_errdata_field(&edata->detail, str);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_errdata_field(&edata->hint, str);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_errdata_field(&edata->context, str);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_errdata_field(&edata->column_name, str);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_errdata_field(&edata->table_name, str);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_errdata_field(&edata->schema_name, str);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_errdata_field(&edata->constraint_name, str);
+ break;
+
+ 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();
*************** write_csvlog(ErrorData *edata)
*** 2275,2282 ****
edata->filename, edata->lineno);
appendCSVLiteral(&buf, msgbuf.data);
pfree(msgbuf.data);
}
- appendStringInfoChar(&buf, ',');
/* application name */
if (application_name)
--- 2373,2393 ----
edata->filename, edata->lineno);
appendCSVLiteral(&buf, msgbuf.data);
pfree(msgbuf.data);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
}
/* application name */
if (application_name)
*************** send_message_to_server_log(ErrorData *ed
*** 2385,2391 ****
}
if (Log_error_verbosity >= PGERROR_VERBOSE)
{
! /* assume no newlines in funcname or filename... */
if (edata->funcname && edata->filename)
{
log_line_prefix(&buf, edata);
--- 2496,2505 ----
}
if (Log_error_verbosity >= PGERROR_VERBOSE)
{
! /*
! * assume no newlines in funcname, filename, column_name,
! * table_name, constraint_name or schema_name...
! */
if (edata->funcname && edata->filename)
{
log_line_prefix(&buf, edata);
*************** send_message_to_server_log(ErrorData *ed
*** 2399,2404 ****
--- 2513,2542 ----
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name && edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"),
+ edata->table_name, edata->column_name);
+ }
+ else if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("TABLE NAME: %s\n"),
+ edata->table_name);
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("CONSTRAINT NAME: %s\n"),
+ edata->constraint_name);
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("SCHEMA NAME: %s\n"),
+ edata->schema_name);
+ }
}
}
*************** send_message_to_frontend(ErrorData *edat
*** 2695,2700 ****
--- 2833,2862 ----
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 ...19d1435
*** a/src/backend/utils/error/relerror.c
--- b/src/backend/utils/error/relerror.c
***************
*** 0 ****
--- 1,65 ----
+ /*-------------------------------------------------------------------------
+ *
+ * relerror.c
+ * relation error logging 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 ee76cef..e92b644
*** 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 ce27e40..e5d048f
*** 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 42c22cd..0358dfa
*** 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 4669d8a..9c0176f
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
*************** typedef struct StdRdOptions
*** 394,397 ****
--- 394,402 ----
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 c605bcd..4d70a1a
*** 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 89c6a13..c8b4b5d
*** 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]+)?/;
-----Original Message-----
From: pgsql-hackers-owner@postgresql.org [mailto:pgsql-hackers-
owner@postgresql.org] On Behalf Of Peter Geoghegan
Sent: Monday, December 10, 2012 3:29 PM
To: Pavel Stehule
Cc: PostgreSQL Hackers; Alvaro Herrera; Tom Lane
Subject: Re: [HACKERS] enhanced error fields
Now, there are one or two places where these fields are not actually
available even though they're formally required according to a literal
reading
of the above. This is only because there is clearly no such field sensibly
available, even in principle - to my mind this cannot be a problem,
because
the application developer cannot have any reasonable expectation of a
field
being set. I'm really talking about two cases in particular:
* For ERRCODE_NOT_NULL_VIOLATION, we don't actually provide
schema_name and table_name in the event of domains. This was previously
identified as an issue. If it is judged better to not have any
requirements
there at all, so be it.
* For the validateDomainConstraint() ERRCODE_CHECK_VIOLATION ereport
call, we may not provide a constraint name iff a Constraint.connname is
NULL. Since there isn't a constraint name to give even in principle, and
this is
an isolated case, this seems reasonable.
Just skimming this topic but if these enhanced error fields are going to be
used by software, and we have 99% adherence to a standard, then my first
reaction is why not just supply "<Not Applicable>" (or "<Not Available>" as
appropriate) instead of suppressing the field altogether in these (and
possibly other, future) cases and make adherence for these fields 100%?
From an "ease-of-use" aspect for the API if I can simply always query each
of those fields and know I will be receiving a string it does at least seem
theoretically easier to interface with. If I am expecting special string
values (enclosed in symbols making them invalid identifiers) I can then
handle those as desired without either receiving an error or a NULL when I
go to poll the missing field if those couple of instances.
I may be paranoid or mistaken regarding how this work but figured I'd at
least throw it out for consideration.
David J.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 10 December 2012 20:52, David Johnston <polobo@yahoo.com> wrote:
Just skimming this topic but if these enhanced error fields are going to be
used by software, and we have 99% adherence to a standard, then my first
reaction is why not just supply "<Not Applicable>" (or "<Not Available>" as
appropriate) instead of suppressing the field altogether in these (and
possibly other, future) cases and make adherence for these fields 100%?
Well, this is an area that the standard doesn't have anything to say
about. The standard defines errcodes, but not what fields they make
available. I suppose you could say that the patch proposes that they
become a Postgres extension to the standard.
I probably could have found more places to set the fields. Certainly,
I've already set them in some places where they are not actually
required to be set by the new contract of errcodes.txt/errcodes.h. My
immediate concern is getting something minimal committed, though. I
think the latest revision handles all of the important cases (i.e.
cases where applications may want a well-principled way of detecting a
particular kind of error, like an error due to the violation of a
particular, known constraint).
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2012/12/10 Peter Geoghegan <peter@2ndquadrant.com>:
So, I took a look at this, and made some more changes.
I have a hard time seeing the utility of some fields that were in this
patch, and so they have been removed from this revision.Consider, for example:
+ constraint_table text,
+ constraint_schema text,While constraint_name remains, I find it really hard to believe that
applications need a perfectly unambiguous idea of what constraint
they're dealing with. If applications have a constraint that has a
different domain-specific meaning depending on what schema it is in,
while a table in one schema uses that constraint in another, well,
that's a fairly awful design. Is it something that we really need to
care about? You mentioned the SQL standard, but as far as I know the
SQL standard has nothing to say about these fields within something
like errdata - their names are lifted from information_schema, but
being unambiguous is hardly so critical here. We want to identify an
object for the purposes of displaying an error message only. Caring
deeply about ambiguity seems wrong-headed to me in this context.+ routine_name text, + trigger_name text, + trigger_table text, + trigger_schema text,The whole point of an exception (which ereport() is very similar to)
is to decouple errors from error handling as much as possible - any
application that magically takes a different course of action based on
where that error occurred as reported by the error itself (and not the
location of where handling the error occurs) seems kind of
wrong-headed to me. Sure, a backtrace may be supplied, but that's only
for diagnostic purposes, and a human can just as easily get that
information already. If you can present an example of this information
actually being present in a way that is amenable to that, either in
any other RDBMS or any major programming language or framework, I
might reconsider.This just leaves:
+ column_name text, + table_name text, + constraint_name text, + schema_name text,
I agree so lot of mentioned fields are difficult defined in PostgreSQL
environment due different meaning between ANSI SQL and PostgreSQL and
because PostgreSQL doesn't support some ANSI SQL features -
assertions.
There are two basic aspects of design
* usability - we would to remove necessity of parsing error messages
for getting interesting data, it decrease dependency on current error
messages - then I am thinking so some informations about triggers or
functions where exception was born are practical (current
implementation is different task - we can find better solution than I
proposed highly probably)
* compatibility - personally, lot of diagnostics fields are very vague
defined, so here can be lot of issues - I have no experience with DB2
or Oracle, so I cannot to speak more, but we should to say, what we
expect.
This seems like enough information for any reasonable use of this
infrastructure, and I highly doubt anyone would avail of the other
fields if they remained. I guess an application might have done
something with "constraint_table", as with foreign keys for example,
but I just can't see that being used when constraint_name can be used.
I afraid so constraint name is not afraid
postgres=# create table a (a int primary key);
CREATE TABLE
postgres=# create table b (b int references a(a));
CREATE TABLE
postgres=# insert into b values (10);
ERROR: insert or update on table "b" violates foreign key constraint "b_b_fkey"
DETAIL: Key (b)=(10) is not present in table "a".
postgres=# \set VERBOSITY verbose
postgres=# insert into b values (10);
ERROR: 23503: insert or update on table "b" violates foreign key
constraint "b_b_fkey"
DETAIL: Key (b)=(10) is not present in table "a".
LOCATION: ri_ReportViolation, ri_triggers.c:3222
postgres=# insert into a values(10);
INSERT 0 1
postgres=# insert into b values (10);
INSERT 0 1
postgres=# delete from a;
ERROR: 23503: update or delete on table "a" violates foreign key
constraint "b_b_fkey" on table "b"
DETAIL: Key (a)=(10) is still referenced from table "b".
LOCATION: ri_ReportViolation, ri_triggers.c:3232
there are two different bugs - with same ERRCODE and constraint name -
once is problem on "b", second on "a"
so constraint_table is interesting information
Removing these fields has also allowed me to remove the "avoid setting
field at lower point in the callstack" logic, which was itself kind of
ugly. Since fields like routine_name only set themselves at the top of
the callstack, the potential for astonishing outcomes was pretty high
- what if you expect one routine_name, but then that routine follows a
slightly different code-path one day and you get another function
setting routine_name and undermining that expectation?
My implementation is probably too dumb - but a question - "where
exception is coming from?" is relative typical - but we can to clean
implementation. I see a issue in definition. Usually I am interesting
about most deep custom code - not most deep code.
so I am not against to removing some fields, but constraint_table and
routine_name is useful and we should to produce this information.
There were some bugs left in the patch eelog3.diff, mostly due to
things like setting table name to what is actually an index name. As I
mentioned, we now assert that:+ Assert(table->rd_rel->relkind == RELKIND_RELATION);
in the functions within relerror.c.
I have tightened up where these fields are available, and
appropriately documented that for the benefit of both application
developers and developers of client libraries. I went so far as to
hack the Perl scripts that generate .sgml and .h files from
errcodes.txt (i.e. the infrastrucutre that produces tables of errcodes
in various places from a single authoritative place) - I have
instituted a coding standard so that these fields are reliably
available and have documented that requirement at both the user and
hacker level.It would be nice if a Perl hacker could eyeball those changes - this
is my first time writing Perl, and I suspect it may be worth having
someone else to polish the Perl code a bit.I have emphasized the need for consistency and a sane contract for
application developers and third-party client driver authors - they
*need* to know that certain fields will always be available, or at
least won't be unavailable due to a change in the phase of the moon.errcodes.txt now says:
+ # 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_violationNow, there are one or two places where these fields are not actually
available even though they're formally required according to a literal
reading of the above. This is only because there is clearly no such
field sensibly available, even in principle - to my mind this cannot
be a problem, because the application developer cannot have any
reasonable expectation of a field being set. I'm really talking about
two cases in particular:* For ERRCODE_NOT_NULL_VIOLATION, we don't actually provide
schema_name and table_name in the event of domains. This was
previously identified as an issue. If it is judged better to not have
any requirements there at all, so be it.* For the validateDomainConstraint() ERRCODE_CHECK_VIOLATION ereport
call, we may not provide a constraint name iff a Constraint.connname
is NULL. Since there isn't a constraint name to give even in
principle, and this is an isolated case, this seems reasonable.
ok
To make logging less verbose, TABLE NAME isn't consistently split out
as a separate field - this seems fine to me, since application code
doesn't target logs:+ if (edata->column_name && edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"), + edata->table_name, edata->column_name); + } + else if (edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("TABLE NAME: %s\n"), + edata->table_name); + }
uff, no - I don't like it - it is not consistent and I don't think so
one row more is a issue. But is a question if we don't need a second
"VERBOSE" level for showing these fields - maybe VERBOSE_ENHANCED or
some similar
we don't need log these fields usually ??
I used pgindent to selectively indent parts of the codebase affected
by this patch. I am about ready to mark this one "ready for
committer", but it would be nice at this point to get some buy-in on
the basic view of how these things should work that informed this
revision. Does anyone disagree with my contention that there should be
a sane, well-defined contract, or any of the details of what that
should look like? Was I right to suggest that some of the set of
fields that appeared in Pavel's eelog3.diff revision are unnecessary?I'm sorry it took me as long as it did to get back to you on this.
It is ok, thank you very much
Regards
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2012/12/10 Peter Geoghegan <peter@2ndquadrant.com>:
On 10 December 2012 20:52, David Johnston <polobo@yahoo.com> wrote:
Just skimming this topic but if these enhanced error fields are going to be
used by software, and we have 99% adherence to a standard, then my first
reaction is why not just supply "<Not Applicable>" (or "<Not Available>" as
appropriate) instead of suppressing the field altogether in these (and
possibly other, future) cases and make adherence for these fields 100%?Well, this is an area that the standard doesn't have anything to say
about. The standard defines errcodes, but not what fields they make
available. I suppose you could say that the patch proposes that they
become a Postgres extension to the standard.
standard speaking relative cleanly about content of diagnostics fields
- it should to be empty string or value.
We don't know what and how to be filled from standards, but we know,
so "<Not Applicable>" or some similar is not compatible with ANSI SQL
Regards
Pavel
I probably could have found more places to set the fields. Certainly,
I've already set them in some places where they are not actually
required to be set by the new contract of errcodes.txt/errcodes.h. My
immediate concern is getting something minimal committed, though. I
think the latest revision handles all of the important cases (i.e.
cases where applications may want a well-principled way of detecting a
particular kind of error, like an error due to the violation of a
particular, known constraint).--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 11 December 2012 13:01, Pavel Stehule <pavel.stehule@gmail.com> wrote:
There are two basic aspects of design
* usability - we would to remove necessity of parsing error messages
for getting interesting data, it decrease dependency on current error
messages - then I am thinking so some informations about triggers or
functions where exception was born are practical (current
implementation is different task - we can find better solution than I
proposed highly probably)* compatibility - personally, lot of diagnostics fields are very vague
defined, so here can be lot of issues - I have no experience with DB2
or Oracle, so I cannot to speak more, but we should to say, what we
expect.
Me neither.
I afraid so constraint name is not afraid
I'm sorry, I don't follow you here.
postgres=# create table a (a int primary key);
CREATE TABLE
postgres=# create table b (b int references a(a));
CREATE TABLE
postgres=# insert into b values (10);
ERROR: insert or update on table "b" violates foreign key constraint "b_b_fkey"
DETAIL: Key (b)=(10) is not present in table "a".
postgres=# \set VERBOSITY verbose
postgres=# insert into b values (10);
ERROR: 23503: insert or update on table "b" violates foreign key
constraint "b_b_fkey"
DETAIL: Key (b)=(10) is not present in table "a".
LOCATION: ri_ReportViolation, ri_triggers.c:3222
postgres=# insert into a values(10);
INSERT 0 1
postgres=# insert into b values (10);
INSERT 0 1
postgres=# delete from a;
ERROR: 23503: update or delete on table "a" violates foreign key
constraint "b_b_fkey" on table "b"
DETAIL: Key (a)=(10) is still referenced from table "b".
LOCATION: ri_ReportViolation, ri_triggers.c:3232there are two different bugs - with same ERRCODE and constraint name -
once is problem on "b", second on "a"so constraint_table is interesting information
Really? I'm not so sure that that's a distinct that the user would
have to care about, or at least care much about. I defer to others
here. Certainly, I am generally conscious of the fact that we don't
want to create an excessive number of fields.
My implementation is probably too dumb - but a question - "where
exception is coming from?" is relative typical - but we can to clean
implementation. I see a issue in definition. Usually I am interesting
about most deep custom code - not most deep code.
I think that knowing where an exception comes from is not useful
information for end-users. Therefore, I am unconvinced of the need to
present information to end users that is based on that. That's my
difficulty.
so I am not against to removing some fields, but constraint_table and
routine_name is useful and we should to produce this information.
I'm afraid that you and I differ on that.
To make logging less verbose, TABLE NAME isn't consistently split out
as a separate field - this seems fine to me, since application code
doesn't target logs:
uff, no - I don't like it - it is not consistent and I don't think so
one row more is a issue. But is a question if we don't need a second
"VERBOSE" level for showing these fields - maybe VERBOSE_ENHANCED or
some similar
VERBOSE_ENHANCED? I'm not sure about that. If you think it's important
for them to be consistent, I concede the point.
we don't need log these fields usually ??
Well, I don't think we *usually* need to log *any* verbose fields.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello Peter
I rechecked your version eelog4 and I am thinking so it is ok. From my
view it can be enough for application developer and I am not against
to commit this (or little bit reduced version) as first step.
As plpgsql developer I really miss a fields "routine_name,
routine_schema and trigger_name". These fields can be simply
implemented without any impact on performance. Because routine_name
and routine_schema is not unique in postgres, I propose third field
"routine_oid". My patch eelog5 is based on your eelog4 with enhancing
for these mentioned fields - 5KB more - but only PL/pgSQL is supported
now. I would to find a acceptable design now.
Second - I don't see any value for forwarding these new fields
(column_name, table_name, constraint_name, schema_name, routine_name,
routine_schema, routine_id, trigger_name) to log or to csvlog and I
propose don't push it to log. We can drop logging in next iteration if
you agree.
What do you thinking about new version?
Regards
Pavel
2012/12/10 Peter Geoghegan <peter@2ndquadrant.com>:
Show quoted text
So, I took a look at this, and made some more changes.
I have a hard time seeing the utility of some fields that were in this
patch, and so they have been removed from this revision.Consider, for example:
+ constraint_table text,
+ constraint_schema text,While constraint_name remains, I find it really hard to believe that
applications need a perfectly unambiguous idea of what constraint
they're dealing with. If applications have a constraint that has a
different domain-specific meaning depending on what schema it is in,
while a table in one schema uses that constraint in another, well,
that's a fairly awful design. Is it something that we really need to
care about? You mentioned the SQL standard, but as far as I know the
SQL standard has nothing to say about these fields within something
like errdata - their names are lifted from information_schema, but
being unambiguous is hardly so critical here. We want to identify an
object for the purposes of displaying an error message only. Caring
deeply about ambiguity seems wrong-headed to me in this context.+ routine_name text, + trigger_name text, + trigger_table text, + trigger_schema text,The whole point of an exception (which ereport() is very similar to)
is to decouple errors from error handling as much as possible - any
application that magically takes a different course of action based on
where that error occurred as reported by the error itself (and not the
location of where handling the error occurs) seems kind of
wrong-headed to me. Sure, a backtrace may be supplied, but that's only
for diagnostic purposes, and a human can just as easily get that
information already. If you can present an example of this information
actually being present in a way that is amenable to that, either in
any other RDBMS or any major programming language or framework, I
might reconsider.This just leaves:
+ column_name text, + table_name text, + constraint_name text, + schema_name text,This seems like enough information for any reasonable use of this
infrastructure, and I highly doubt anyone would avail of the other
fields if they remained. I guess an application might have done
something with "constraint_table", as with foreign keys for example,
but I just can't see that being used when constraint_name can be used.Removing these fields has also allowed me to remove the "avoid setting
field at lower point in the callstack" logic, which was itself kind of
ugly. Since fields like routine_name only set themselves at the top of
the callstack, the potential for astonishing outcomes was pretty high
- what if you expect one routine_name, but then that routine follows a
slightly different code-path one day and you get another function
setting routine_name and undermining that expectation?There were some bugs left in the patch eelog3.diff, mostly due to
things like setting table name to what is actually an index name. As I
mentioned, we now assert that:+ Assert(table->rd_rel->relkind == RELKIND_RELATION);
in the functions within relerror.c.
I have tightened up where these fields are available, and
appropriately documented that for the benefit of both application
developers and developers of client libraries. I went so far as to
hack the Perl scripts that generate .sgml and .h files from
errcodes.txt (i.e. the infrastrucutre that produces tables of errcodes
in various places from a single authoritative place) - I have
instituted a coding standard so that these fields are reliably
available and have documented that requirement at both the user and
hacker level.It would be nice if a Perl hacker could eyeball those changes - this
is my first time writing Perl, and I suspect it may be worth having
someone else to polish the Perl code a bit.I have emphasized the need for consistency and a sane contract for
application developers and third-party client driver authors - they
*need* to know that certain fields will always be available, or at
least won't be unavailable due to a change in the phase of the moon.errcodes.txt now says:
+ # 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_violationNow, there are one or two places where these fields are not actually
available even though they're formally required according to a literal
reading of the above. This is only because there is clearly no such
field sensibly available, even in principle - to my mind this cannot
be a problem, because the application developer cannot have any
reasonable expectation of a field being set. I'm really talking about
two cases in particular:* For ERRCODE_NOT_NULL_VIOLATION, we don't actually provide
schema_name and table_name in the event of domains. This was
previously identified as an issue. If it is judged better to not have
any requirements there at all, so be it.* For the validateDomainConstraint() ERRCODE_CHECK_VIOLATION ereport
call, we may not provide a constraint name iff a Constraint.connname
is NULL. Since there isn't a constraint name to give even in
principle, and this is an isolated case, this seems reasonable.To make logging less verbose, TABLE NAME isn't consistently split out
as a separate field - this seems fine to me, since application code
doesn't target logs:+ if (edata->column_name && edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"), + edata->table_name, edata->column_name); + } + else if (edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("TABLE NAME: %s\n"), + edata->table_name); + }I used pgindent to selectively indent parts of the codebase affected
by this patch. I am about ready to mark this one "ready for
committer", but it would be nice at this point to get some buy-in on
the basic view of how these things should work that informed this
revision. Does anyone disagree with my contention that there should be
a sane, well-defined contract, or any of the details of what that
should look like? Was I right to suggest that some of the set of
fields that appeared in Pavel's eelog3.diff revision are unnecessary?I'm sorry it took me as long as it did to get back to you on this.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog5.patchapplication/octet-stream; name=eelog5.patchDownload
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
***************
*** 4246,4255 **** FROM pg_stat_activity;
error context,
user query that led to the error (if any and enabled by
<varname>log_min_error_statement</>),
! character count of the error position therein,
! location of the error in the PostgreSQL source code
! (if <varname>log_error_verbosity</> is set to <literal>verbose</>),
! and application name.
Here is a sample table definition for storing CSV-format log output:
<programlisting>
--- 4246,4256 ----
error context,
user query that led to the error (if any and enabled by
<varname>log_min_error_statement</>),
! character count of the error position therein, and, if
! <varname>log_error_verbosity</> is set to <literal>verbose</>,
! column name, table name, constraint name and location of the
! error in the PostgreSQL source code.
! Application name is also listed.
Here is a sample table definition for storing CSV-format log output:
<programlisting>
***************
*** 4277,4282 **** CREATE TABLE postgres_log
--- 4278,4291 ----
query text,
query_pos integer,
location text,
+ column_name text,
+ table_name text,
+ constraint_name text,
+ schema_name text,
+ routine_name text,
+ routine_schema text,
+ routine_oid oid,
+ trigger_name text,
application_name text,
PRIMARY KEY (session_id, session_line_num)
);
*** a/doc/src/sgml/errcodes.sgml
--- b/doc/src/sgml/errcodes.sgml
***************
*** 42,47 ****
--- 42,59 ----
</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). These additional fields are not
+ described by the SQL standard.
+ </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
*** a/doc/src/sgml/generate-errcodes-table.pl
--- b/doc/src/sgml/generate-errcodes-table.pl
***************
*** 41,46 **** while (<$errcodes>)
--- 41,71 ----
next;
}
+ # Emit requirement headers
+ if (/^Requirement:/)
+ {
+ # Replace the Requirement: string
+ s/^Requirement: /Provides: /;
+
+ # Replace "unused"
+ if (!s/unused/\(no fields\)/)
+ {
+ # Use literal for field names if appropriate
+ s/: (.*)/: <symbol>$1<\/symbol>/;
+ }
+
+ # Escape dashes for SGML
+ s/-/—/;
+
+ 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) =
*** a/doc/src/sgml/protocol.sgml
--- b/doc/src/sgml/protocol.sgml
***************
*** 4786,4791 **** message.
--- 4786,4881 ----
</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>r</>
+ </term>
+ <listitem>
+ <para>
+ Routine name: the name of routine that reports error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>u</>
+ </term>
+ <listitem>
+ <para>
+ Routine schema: the schema of routine that reports error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>O</>
+ </term>
+ <listitem>
+ <para>
+ Routine oid: the object id of routine that reports error.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <literal>T</>
+ </term>
+ <listitem>
+ <para>
+ Trigger name: the name of trigger that caused error.
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
<para>
*** a/src/backend/access/nbtree/nbtinsert.c
--- b/src/backend/access/nbtree/nbtinsert.c
***************
*** 393,399 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull))));
}
}
else if (all_dead)
--- 393,401 ----
RelationGetRelationName(rel)),
errdetail("Key %s already exists.",
BuildIndexValueDescription(rel,
! values, isnull)),
! errtable(heapRel),
! errconstraint(RelationGetRelationName(rel))));
}
}
else if (all_dead)
***************
*** 455,461 **** _bt_check_unique(Relation rel, IndexTuple itup, Relation heapRel,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("failed to re-find tuple within index \"%s\"",
RelationGetRelationName(rel)),
! errhint("This may be because of a non-immutable index expression.")));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
--- 457,464 ----
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("failed to re-find tuple within index \"%s\"",
RelationGetRelationName(rel)),
! errhint("This may be because of a non-immutable index expression."),
! errtable(heapRel)));
if (nbuf != InvalidBuffer)
_bt_relbuf(rel, nbuf);
***************
*** 533,539 **** _bt_findinsertloc(Relation rel,
RelationGetRelationName(rel)),
errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n"
"Consider a function index of an MD5 hash of the value, "
! "or use full text indexing.")));
/*----------
* If we will need to split the page to put the item on this page,
--- 536,543 ----
RelationGetRelationName(rel)),
errhint("Values larger than 1/3 of a buffer page cannot be indexed.\n"
"Consider a function index of an MD5 hash of the value, "
! "or use full text indexing."),
! errtable(heapRel)));
/*----------
* If we will need to split the page to put the item on this page,
*** a/src/backend/commands/tablecmds.c
--- b/src/backend/commands/tablecmds.c
***************
*** 3819,3828 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
int attn = lfirst_int(l);
if (heap_attisnull(tuple, attn + 1))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" contains null values",
! NameStr(newTupDesc->attrs[attn]->attname))));
}
foreach(l, tab->constraints)
--- 3819,3835 ----
int attn = lfirst_int(l);
if (heap_attisnull(tuple, attn + 1))
+ {
+ Form_pg_attribute att = newTupDesc->attrs[attn];
+
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" contains null values",
! NameStr(att->attname)),
! (newrel) ?
! errtablecol(newrel, NameStr(att->attname)) :
! errtablecol(oldrel, NameStr(att->attname))));
! }
}
foreach(l, tab->constraints)
***************
*** 3836,3842 **** ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
--- 3843,3853 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! con->name),
! (newrel) ?
! errtable(newrel) :
! errtable(oldrel),
! errconstraint(con->name)));
break;
case CONSTR_FOREIGN:
/* Nothing to do here */
***************
*** 6653,6659 **** validateCheckConstraint(Relation rel, HeapTuple constrtup)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
--- 6664,6672 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("check constraint \"%s\" is violated by some row",
! NameStr(constrForm->conname)),
! errtable(rel),
! errconstraint(NameStr(constrForm->conname))));
ResetExprContext(econtext);
}
*** a/src/backend/commands/typecmds.c
--- b/src/backend/commands/typecmds.c
***************
*** 98,104 **** static Oid findTypeAnalyzeFunction(List *procname, Oid typeOid);
static Oid findRangeSubOpclass(List *opcname, Oid subtype);
static Oid findRangeCanonicalFunction(List *procname, Oid typeOid);
static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype);
! static void validateDomainConstraint(Oid domainoid, char *ccbin);
static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
static void checkEnumOwner(HeapTuple tup);
static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
--- 98,104 ----
static Oid findRangeSubOpclass(List *opcname, Oid subtype);
static Oid findRangeCanonicalFunction(List *procname, Oid typeOid);
static Oid findRangeSubtypeDiffFunction(List *procname, Oid subtype);
! static void validateDomainConstraint(Oid domainoid, char *ccbin, char *conname);
static List *get_rels_with_domain(Oid domainOid, LOCKMODE lockmode);
static void checkEnumOwner(HeapTuple tup);
static char *domainAddConstraint(Oid domainOid, Oid domainNamespace,
***************
*** 2258,2270 **** AlterDomainNotNull(List *names, bool notNull)
for (i = 0; i < rtc->natts; i++)
{
int attnum = rtc->atts[i];
if (heap_attisnull(tuple, attnum))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains null values",
! NameStr(tupdesc->attrs[attnum - 1]->attname),
! RelationGetRelationName(testrel))));
}
}
heap_endscan(scan);
--- 2258,2271 ----
for (i = 0; i < rtc->natts; i++)
{
int attnum = rtc->atts[i];
+ Form_pg_attribute att = tupdesc->attrs[attnum - 1];
if (heap_attisnull(tuple, attnum))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains null values",
! NameStr(att->attname), RelationGetRelationName(testrel)),
! errtablecol(testrel, NameStr(att->attname))));
}
}
heap_endscan(scan);
***************
*** 2470,2476 **** AlterDomainAddConstraint(List *names, Node *newConstraint)
* attributes based on the domain the constraint is being added to.
*/
if (!constr->skip_validation)
! validateDomainConstraint(domainoid, ccbin);
/* Clean up */
heap_close(typrel, RowExclusiveLock);
--- 2471,2477 ----
* attributes based on the domain the constraint is being added to.
*/
if (!constr->skip_validation)
! validateDomainConstraint(domainoid, ccbin, constr->conname);
/* Clean up */
heap_close(typrel, RowExclusiveLock);
***************
*** 2555,2561 **** AlterDomainValidateConstraint(List *names, char *constrName)
HeapTupleGetOid(tuple));
conbin = TextDatumGetCString(val);
! validateDomainConstraint(domainoid, conbin);
/*
* Now update the catalog, while we have the door open.
--- 2556,2562 ----
HeapTupleGetOid(tuple));
conbin = TextDatumGetCString(val);
! validateDomainConstraint(domainoid, conbin, constrName);
/*
* Now update the catalog, while we have the door open.
***************
*** 2576,2582 **** AlterDomainValidateConstraint(List *names, char *constrName)
}
static void
! validateDomainConstraint(Oid domainoid, char *ccbin)
{
Expr *expr = (Expr *) stringToNode(ccbin);
List *rels;
--- 2577,2583 ----
}
static void
! validateDomainConstraint(Oid domainoid, char *ccbin, char *conname)
{
Expr *expr = (Expr *) stringToNode(ccbin);
List *rels;
***************
*** 2618,2623 **** validateDomainConstraint(Oid domainoid, char *ccbin)
--- 2619,2625 ----
Datum d;
bool isNull;
Datum conResult;
+ const char *col = NameStr(tupdesc->attrs[attnum - 1]->attname);
d = heap_getattr(tuple, attnum, tupdesc, &isNull);
***************
*** 2632,2639 **** validateDomainConstraint(Oid domainoid, char *ccbin)
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
! NameStr(tupdesc->attrs[attnum - 1]->attname),
! RelationGetRelationName(testrel))));
}
ResetExprContext(econtext);
--- 2634,2644 ----
ereport(ERROR,
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("column \"%s\" of table \"%s\" contains values that violate the new constraint",
! col,
! RelationGetRelationName(testrel)),
! errtablecol(testrel, col),
! conname?
! errconstraint(conname):0));
}
ResetExprContext(econtext);
*** a/src/backend/executor/execMain.c
--- b/src/backend/executor/execMain.c
***************
*** 1522,1535 **** ExecConstraints(ResultRelInfo *resultRelInfo,
for (attrChk = 1; attrChk <= natts; attrChk++)
{
if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
slot_attisnull(slot, attrChk))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" violates not-null constraint",
! NameStr(rel->rd_att->attrs[attrChk - 1]->attname)),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1522,1538 ----
for (attrChk = 1; attrChk <= natts; attrChk++)
{
+ Form_pg_attribute Chk = rel->rd_att->attrs[attrChk - 1];
+
if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
slot_attisnull(slot, attrChk))
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("null value in column \"%s\" violates not-null constraint",
! NameStr(Chk->attname)),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64)),
! errtablecol(rel, NameStr(Chk->attname))));
}
}
***************
*** 1543,1549 **** ExecConstraints(ResultRelInfo *resultRelInfo,
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64))));
}
}
--- 1546,1554 ----
errmsg("new row for relation \"%s\" violates check constraint \"%s\"",
RelationGetRelationName(rel), failed),
errdetail("Failing row contains %s.",
! ExecBuildSlotValueDescription(slot, 64)),
! errtable(rel),
! errconstraint(failed)));
}
}
*** a/src/backend/executor/execQual.c
--- b/src/backend/executor/execQual.c
***************
*** 54,59 ****
--- 54,60 ----
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+ #include "utils/rel.h"
#include "utils/typcache.h"
#include "utils/xml.h"
***************
*** 3864,3869 **** ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext,
--- 3865,3876 ----
CoerceToDomain *ctest = (CoerceToDomain *) cstate->xprstate.expr;
Datum result;
ListCell *l;
+ Relation heapRel = NULL;
+ struct EState *estate = econtext->ecxt_estate;
+
+ /* Build relation description, if possible */
+ if (estate && estate->es_result_relation_info)
+ heapRel = estate->es_result_relation_info->ri_RelationDesc;
result = ExecEvalExpr(cstate->arg, econtext, isNull, isDone);
***************
*** 3878,3887 **** ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext,
{
case DOM_CONSTRAINT_NOTNULL:
if (*isNull)
! ereport(ERROR,
! (errcode(ERRCODE_NOT_NULL_VIOLATION),
! errmsg("domain %s does not allow null values",
! format_type_be(ctest->resulttype))));
break;
case DOM_CONSTRAINT_CHECK:
{
--- 3885,3904 ----
{
case DOM_CONSTRAINT_NOTNULL:
if (*isNull)
! {
! if (heapRel)
! ereport(ERROR,
! (errcode(ERRCODE_NOT_NULL_VIOLATION),
! errmsg("domain %s does not allow null values",
! format_type_be(ctest->resulttype)),
! errtable(heapRel)));
!
! else
! ereport(ERROR,
! (errcode(ERRCODE_NOT_NULL_VIOLATION),
! errmsg("domain %s does not allow null values",
! format_type_be(ctest->resulttype))));
! }
break;
case DOM_CONSTRAINT_CHECK:
{
***************
*** 3907,3917 **** ExecEvalCoerceToDomain(CoerceToDomainState *cstate, ExprContext *econtext,
if (!conIsNull &&
!DatumGetBool(conResult))
! ereport(ERROR,
! (errcode(ERRCODE_CHECK_VIOLATION),
! errmsg("value for domain %s violates check constraint \"%s\"",
! format_type_be(ctest->resulttype),
! con->name)));
econtext->domainValue_datum = save_datum;
econtext->domainValue_isNull = save_isNull;
--- 3924,3946 ----
if (!conIsNull &&
!DatumGetBool(conResult))
! {
! if (heapRel)
! ereport(ERROR,
! (errcode(ERRCODE_CHECK_VIOLATION),
! errmsg("value for domain %s violates check constraint \"%s\"",
! format_type_be(ctest->resulttype),
! con->name),
! errtable(heapRel),
! errconstraint(con->name)));
! else
! ereport(ERROR,
! (errcode(ERRCODE_CHECK_VIOLATION),
! errmsg("value for domain %s violates check constraint \"%s\"",
! format_type_be(ctest->resulttype),
! con->name),
! errconstraint(con->name)));
! }
econtext->domainValue_datum = save_datum;
econtext->domainValue_isNull = save_isNull;
*** a/src/backend/executor/execUtils.c
--- b/src/backend/executor/execUtils.c
***************
*** 1307,1320 **** retry:
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing)));
else
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with existing key %s.",
! error_new, error_existing)));
}
index_endscan(index_scan);
--- 1307,1324 ----
errmsg("could not create exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with key %s.",
! error_new, error_existing),
! errtable(heap),
! errconstraint(RelationGetRelationName(index))));
else
ereport(ERROR,
(errcode(ERRCODE_EXCLUSION_VIOLATION),
errmsg("conflicting key value violates exclusion constraint \"%s\"",
RelationGetRelationName(index)),
errdetail("Key %s conflicts with existing key %s.",
! error_new, error_existing),
! errtable(heap),
! errconstraint(RelationGetRelationName(index))));
}
index_endscan(index_scan);
*** a/src/backend/utils/adt/domains.c
--- b/src/backend/utils/adt/domains.c
***************
*** 36,41 ****
--- 36,42 ----
#include "lib/stringinfo.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+ #include "utils/rel.h"
/*
***************
*** 123,132 **** domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
--- 124,140 ----
{
case DOM_CONSTRAINT_NOTNULL:
if (isnull)
+ {
+ /*
+ * We don't provide errtable here, because it is
+ * unavailable, though we require it anywhere where it is
+ * available in principle.
+ */
ereport(ERROR,
(errcode(ERRCODE_NOT_NULL_VIOLATION),
errmsg("domain %s does not allow null values",
format_type_be(my_extra->domain_type))));
+ }
break;
case DOM_CONSTRAINT_CHECK:
{
***************
*** 163,169 **** domain_check_input(Datum value, bool isnull, DomainIOData *my_extra)
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("value for domain %s violates check constraint \"%s\"",
format_type_be(my_extra->domain_type),
! con->name)));
break;
}
default:
--- 171,178 ----
(errcode(ERRCODE_CHECK_VIOLATION),
errmsg("value for domain %s violates check constraint \"%s\"",
format_type_be(my_extra->domain_type),
! con->name),
! errconstraint(con->name)));
break;
}
default:
*** a/src/backend/utils/adt/ri_triggers.c
--- b/src/backend/utils/adt/ri_triggers.c
***************
*** 339,345 **** RI_FKey_check(TriggerData *trigdata)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
--- 339,347 ----
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(trigdata->tg_relation),
NameStr(riinfo->conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
! errtable(trigdata->tg_relation),
! errconstraint(NameStr(riinfo->conname))));
heap_close(pk_rel, RowShareLock);
return PointerGetDatum(NULL);
***************
*** 2467,2473 **** RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
NameStr(fake_riinfo.conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values.")));
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
--- 2469,2478 ----
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
RelationGetRelationName(fk_rel),
NameStr(fake_riinfo.conname)),
! errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
! errtable(fk_rel),
! errconstraint(NameStr(fake_riinfo.conname))));
!
/*
* We tell ri_ReportViolation we were doing the RI_PLAN_CHECK_LOOKUPPK
***************
*** 3219,3225 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo,
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
--- 3224,3232 ----
NameStr(riinfo->conname)),
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(pk_rel)),
! errtable(fk_rel),
! errconstraint(NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
***************
*** 3229,3235 **** ri_ReportViolation(const RI_ConstraintInfo *riinfo,
RelationGetRelationName(fk_rel)),
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(fk_rel))));
}
--- 3236,3244 ----
RelationGetRelationName(fk_rel)),
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
! RelationGetRelationName(fk_rel)),
! errtable(pk_rel),
! errconstraint(NameStr(riinfo->conname))));
}
*** a/src/backend/utils/errcodes.txt
--- b/src/backend/utils/errcodes.txt
***************
*** 200,213 **** Section: Class 22 - Data Exception
2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment
2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction
Section: Class 23 - Integrity Constraint Violation
!
23000 E ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION integrity_constraint_violation
23001 E ERRCODE_RESTRICT_VIOLATION restrict_violation
23502 E ERRCODE_NOT_NULL_VIOLATION not_null_violation
23503 E ERRCODE_FOREIGN_KEY_VIOLATION foreign_key_violation
23505 E ERRCODE_UNIQUE_VIOLATION unique_violation
23514 E ERRCODE_CHECK_VIOLATION check_violation
23P01 E ERRCODE_EXCLUSION_VIOLATION exclusion_violation
Section: Class 24 - Invalid Cursor State
--- 200,227 ----
2200S E ERRCODE_INVALID_XML_COMMENT invalid_xml_comment
2200T E ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION invalid_xml_processing_instruction
+ # Postgres coding standards mandate that certain fields be available in all
+ # instances for some of the Class 23 errcodes, documented under "Requirement: "
+ # here. Some other errcode's ereport sites may, at their own discretion, make
+ # errcolumn, errtable, errconstraint and errschema fields available too.
+ # Furthermore, it is possible to make some fields available beyond those
+ # formally required at callsites involving these Class 23 errcodes with
+ # "Requirements: ".
Section: Class 23 - Integrity Constraint Violation
! Requirement: unused
23000 E ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION integrity_constraint_violation
+ Requirement: unused
23001 E ERRCODE_RESTRICT_VIOLATION restrict_violation
+ # Note that requirements for ERRCODE_NOT_NULL do not apply to domains:
+ Requirement: schema_name, table_name
23502 E ERRCODE_NOT_NULL_VIOLATION not_null_violation
+ Requirement: schema_name, table_name, constraint_name
23503 E ERRCODE_FOREIGN_KEY_VIOLATION foreign_key_violation
+ Requirement: schema_name, table_name, constraint_name
23505 E ERRCODE_UNIQUE_VIOLATION unique_violation
+ Requirement: constraint_name
23514 E ERRCODE_CHECK_VIOLATION check_violation
+ Requirement: schema_name, table_name, constraint_name
23P01 E ERRCODE_EXCLUSION_VIOLATION exclusion_violation
Section: Class 24 - Invalid Cursor State
*** a/src/backend/utils/error/Makefile
--- b/src/backend/utils/error/Makefile
***************
*** 12,17 **** subdir = src/backend/utils/error
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = assert.o elog.o
include $(top_srcdir)/src/backend/common.mk
--- 12,17 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = assert.o elog.o relerror.o
include $(top_srcdir)/src/backend/common.mk
*** a/src/backend/utils/error/elog.c
--- b/src/backend/utils/error/elog.c
***************
*** 130,135 **** static void write_syslog(int level, const char *line);
--- 130,136 ----
#endif
static void write_console(const char *line, int len);
+ static void set_errdata_field(char **ptr, const char *str);
#ifdef WIN32
extern char *event_source;
***************
*** 477,482 **** errfinish(int dummy,...)
--- 478,497 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
errordata_stack_depth--;
***************
*** 1101,1106 **** internalerrquery(const char *query)
--- 1116,1214 ----
}
/*
+ * errroutineid -- set routine_oid of top custom functions
+ */
+ int
+ errroutineid(Oid routineid)
+ {
+ ErrorData *edata = &errordata[errordata_stack_depth];
+
+ /* we don't bother incrementing recursion_depth */
+ CHECK_STACK_DEPTH();
+
+ edata->routine_oid = routineid;
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * err_generic_string -- used to set individual ErrorData string fields.
+ *
+ * Callers should prefer higher-level abstractions such as those within
+ * relerror.c.
+ */
+ int
+ err_generic_string(int field, const char *str)
+ {
+ ErrorData *edata = &errordata[errordata_stack_depth];
+
+ /* we don't bother incrementing recursion_depth */
+ CHECK_STACK_DEPTH();
+
+ switch (field)
+ {
+ case PG_DIAG_MESSAGE_PRIMARY:
+ set_errdata_field(&edata->message, str);
+ break;
+
+ case PG_DIAG_MESSAGE_DETAIL:
+ set_errdata_field(&edata->detail, str);
+ break;
+
+ case PG_DIAG_MESSAGE_HINT:
+ set_errdata_field(&edata->hint, str);
+ break;
+
+ case PG_DIAG_CONTEXT:
+ set_errdata_field(&edata->context, str);
+ break;
+
+ case PG_DIAG_COLUMN_NAME:
+ set_errdata_field(&edata->column_name, str);
+ break;
+
+ case PG_DIAG_TABLE_NAME:
+ set_errdata_field(&edata->table_name, str);
+ break;
+
+ case PG_DIAG_SCHEMA_NAME:
+ set_errdata_field(&edata->schema_name, str);
+ break;
+
+ case PG_DIAG_CONSTRAINT_NAME:
+ set_errdata_field(&edata->constraint_name, str);
+ break;
+
+ case PG_DIAG_ROUTINE_NAME:
+ set_errdata_field(&edata->routine_name, str);
+ break;
+
+ case PG_DIAG_ROUTINE_SCHEMA:
+ set_errdata_field(&edata->routine_schema, str);
+ break;
+
+ case PG_DIAG_TRIGGER_NAME:
+ set_errdata_field(&edata->trigger_name, str);
+ break;
+
+ default:
+ elog(ERROR, "unsupported or unknown ErrorData field id: %d", field);
+ }
+
+ return 0; /* return value does not matter */
+ }
+
+ /*
+ * set_errdata_field --- set an ErrorData string field
+ */
+ static void
+ set_errdata_field(char **ptr, const char *str)
+ {
+ Assert(*ptr == NULL);
+ *ptr = MemoryContextStrdup(ErrorContext, str);
+ }
+
+ /*
* geterrcode --- return the currently set SQLSTATE error code
*
* This is only intended for use in error callback subroutines, since there
***************
*** 1374,1379 **** CopyErrorData(void)
--- 1482,1501 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
return newedata;
}
***************
*** 1399,1404 **** FreeErrorData(ErrorData *edata)
--- 1521,1541 ----
pfree(edata->context);
if (edata->internalquery)
pfree(edata->internalquery);
+ if (edata->column_name)
+ pfree(edata->column_name);
+ if (edata->table_name)
+ pfree(edata->table_name);
+ if (edata->schema_name)
+ pfree(edata->schema_name);
+ if (edata->constraint_name)
+ pfree(edata->constraint_name);
+ if (edata->routine_name)
+ pfree(edata->routine_name);
+ if (edata->routine_schema)
+ pfree(edata->routine_schema);
+ if (edata->trigger_name)
+ pfree(edata->trigger_name);
+
pfree(edata);
}
***************
*** 1471,1476 **** ReThrowError(ErrorData *edata)
--- 1608,1627 ----
newedata->context = pstrdup(newedata->context);
if (newedata->internalquery)
newedata->internalquery = pstrdup(newedata->internalquery);
+ if (newedata->column_name)
+ newedata->column_name = pstrdup(newedata->column_name);
+ if (newedata->table_name)
+ newedata->table_name = pstrdup(newedata->table_name);
+ if (newedata->schema_name)
+ newedata->schema_name = pstrdup(newedata->schema_name);
+ if (newedata->constraint_name)
+ newedata->constraint_name = pstrdup(newedata->constraint_name);
+ if (newedata->routine_name)
+ newedata->routine_name = pstrdup(newedata->routine_name);
+ if (newedata->routine_schema)
+ newedata->routine_schema = pstrdup(newedata->routine_schema);
+ if (newedata->trigger_name)
+ newedata->trigger_name = pstrdup(newedata->trigger_name);
recursion_depth--;
PG_RE_THROW();
***************
*** 2275,2282 **** write_csvlog(ErrorData *edata)
edata->filename, edata->lineno);
appendCSVLiteral(&buf, msgbuf.data);
pfree(msgbuf.data);
}
- appendStringInfoChar(&buf, ',');
/* application name */
if (application_name)
--- 2426,2457 ----
edata->filename, edata->lineno);
appendCSVLiteral(&buf, msgbuf.data);
pfree(msgbuf.data);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->column_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->table_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->schema_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->constraint_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_name);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->routine_schema);
+ appendStringInfoChar(&buf, ',');
+
+ appendStringInfo(&buf, "%d", edata->routine_oid);
+ appendStringInfoChar(&buf, ',');
+
+ appendCSVLiteral(&buf, edata->trigger_name);
+ appendStringInfoChar(&buf, ',');
}
/* application name */
if (application_name)
***************
*** 2385,2391 **** send_message_to_server_log(ErrorData *edata)
}
if (Log_error_verbosity >= PGERROR_VERBOSE)
{
! /* assume no newlines in funcname or filename... */
if (edata->funcname && edata->filename)
{
log_line_prefix(&buf, edata);
--- 2560,2569 ----
}
if (Log_error_verbosity >= PGERROR_VERBOSE)
{
! /*
! * assume no newlines in funcname, filename, column_name,
! * table_name, constraint_name or schema_name...
! */
if (edata->funcname && edata->filename)
{
log_line_prefix(&buf, edata);
***************
*** 2399,2404 **** send_message_to_server_log(ErrorData *edata)
--- 2577,2630 ----
appendStringInfo(&buf, _("LOCATION: %s:%d\n"),
edata->filename, edata->lineno);
}
+ if (edata->column_name && edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"),
+ edata->table_name, edata->column_name);
+ }
+ else if (edata->table_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("TABLE NAME: %s\n"),
+ edata->table_name);
+ }
+ if (edata->constraint_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("CONSTRAINT NAME: %s\n"),
+ edata->constraint_name);
+ }
+ if (edata->schema_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("SCHEMA NAME: %s\n"),
+ edata->schema_name);
+ }
+ if (edata->routine_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("ROUTINE NAME: %s\n"),
+ edata->routine_name);
+ }
+ if (edata->routine_schema)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("ROUTINE SCHEMA: %s\n"),
+ edata->routine_schema);
+ }
+ if (edata->routine_oid != InvalidOid)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("ROUTINE OID: %d\n"),
+ edata->routine_oid);
+ }
+ if (edata->trigger_name)
+ {
+ log_line_prefix(&buf, edata);
+ appendStringInfo(&buf, _("TRIGGER NAME: %s\n"),
+ edata->trigger_name);
+ }
}
}
***************
*** 2695,2700 **** send_message_to_frontend(ErrorData *edata)
--- 2921,2975 ----
err_sendstring(&msgbuf, edata->funcname);
}
+ if (edata->column_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_COLUMN_NAME);
+ err_sendstring(&msgbuf, edata->column_name);
+ }
+
+ if (edata->table_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TABLE_NAME);
+ err_sendstring(&msgbuf, edata->table_name);
+ }
+
+ if (edata->constraint_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_CONSTRAINT_NAME);
+ err_sendstring(&msgbuf, edata->constraint_name);
+ }
+
+ if (edata->schema_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_SCHEMA_NAME);
+ err_sendstring(&msgbuf, edata->schema_name);
+ }
+
+ if (edata->routine_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_NAME);
+ err_sendstring(&msgbuf, edata->routine_name);
+ }
+
+ if (edata->routine_schema)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_SCHEMA);
+ err_sendstring(&msgbuf, edata->routine_schema);
+ }
+
+ if (edata->routine_oid != InvalidOid)
+ {
+ snprintf(tbuf, sizeof(tbuf), "%d", edata->routine_oid);
+ pq_sendbyte(&msgbuf, PG_DIAG_ROUTINE_OID);
+ err_sendstring(&msgbuf, tbuf);
+ }
+
+ if (edata->trigger_name)
+ {
+ pq_sendbyte(&msgbuf, PG_DIAG_TRIGGER_NAME);
+ err_sendstring(&msgbuf, edata->trigger_name);
+ }
+
pq_sendbyte(&msgbuf, '\0'); /* terminator */
}
else
*** a/src/backend/utils/generate-errcodes.pl
--- b/src/backend/utils/generate-errcodes.pl
***************
*** 28,33 **** while (<$errcodes>)
--- 28,39 ----
print "\n/* $header */\n";
next;
}
+ elsif (/(^Requirement:.*)/)
+ {
+ my $header = $1;
+ print "/* $header */\n";
+ next;
+ }
die "unable to parse errcodes.txt"
unless /^([^\s]{5})\s+[EWS]\s+([^\s]+)/;
*** a/src/backend/utils/sort/tuplesort.c
--- b/src/backend/utils/sort/tuplesort.c
***************
*** 3014,3019 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b,
--- 3014,3020 ----
* for equal keys at the end.
*/
ScanKey scanKey = state->indexScanKey;
+ Relation indexRel = state->indexRel;
IndexTuple tuple1;
IndexTuple tuple2;
int keysz;
***************
*** 3038,3044 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b,
tuple1 = (IndexTuple) a->tuple;
tuple2 = (IndexTuple) b->tuple;
keysz = state->nKeys;
! tupDes = RelationGetDescr(state->indexRel);
scanKey++;
for (nkey = 2; nkey <= keysz; nkey++, scanKey++)
{
--- 3039,3045 ----
tuple1 = (IndexTuple) a->tuple;
tuple2 = (IndexTuple) b->tuple;
keysz = state->nKeys;
! tupDes = RelationGetDescr(indexRel);
scanKey++;
for (nkey = 2; nkey <= keysz; nkey++, scanKey++)
{
***************
*** 3075,3080 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b,
--- 3076,3083 ----
{
Datum values[INDEX_MAX_KEYS];
bool isnull[INDEX_MAX_KEYS];
+ const char *indrelname = RelationGetRelationName(indexRel);
+ Relation heapRel = RelationIdGetRelation(indexRel->rd_index->indrelid);
/*
* Some rather brain-dead implementations of qsort (such as the one in
***************
*** 3088,3097 **** comparetup_index_btree(const SortTuple *a, const SortTuple *b,
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("could not create unique index \"%s\"",
! RelationGetRelationName(state->indexRel)),
errdetail("Key %s is duplicated.",
! BuildIndexValueDescription(state->indexRel,
! values, isnull))));
}
/*
--- 3091,3101 ----
ereport(ERROR,
(errcode(ERRCODE_UNIQUE_VIOLATION),
errmsg("could not create unique index \"%s\"",
! indrelname),
errdetail("Key %s is duplicated.",
! BuildIndexValueDescription(indexRel, values, isnull)),
! errtable(heapRel),
! errconstraint(indrelname)));
}
/*
*** a/src/include/postgres_ext.h
--- b/src/include/postgres_ext.h
***************
*** 60,64 **** typedef PG_INT64_TYPE pg_int64;
--- 60,72 ----
#define PG_DIAG_SOURCE_FILE 'F'
#define PG_DIAG_SOURCE_LINE 'L'
#define PG_DIAG_SOURCE_FUNCTION 'R'
+ #define PG_DIAG_COLUMN_NAME 'c'
+ #define PG_DIAG_TABLE_NAME 't'
+ #define PG_DIAG_CONSTRAINT_NAME 'n'
+ #define PG_DIAG_SCHEMA_NAME 's'
+ #define PG_DIAG_ROUTINE_NAME 'r'
+ #define PG_DIAG_ROUTINE_SCHEMA 'u'
+ #define PG_DIAG_ROUTINE_OID 'O'
+ #define PG_DIAG_TRIGGER_NAME 'T'
#endif /* POSTGRES_EXT_H */
*** a/src/include/utils/elog.h
--- b/src/include/utils/elog.h
***************
*** 206,211 **** extern int geterrcode(void);
--- 206,213 ----
extern int geterrposition(void);
extern int getinternalerrposition(void);
+ extern int errroutineid(Oid routineid);
+ extern int err_generic_string(int field, const char *str);
/*----------
* Old-style error reporting API: to be used in this way:
***************
*** 338,343 **** typedef struct ErrorData
--- 340,353 ----
char *detail_log; /* detail error message for server log only */
char *hint; /* hint message */
char *context; /* context message */
+ char *column_name; /* name of column */
+ char *table_name; /* name of table */
+ char *constraint_name; /* name of constraint */
+ char *schema_name; /* name of schema */
+ char *routine_name; /* name of routine */
+ char *routine_schema; /* schema of routine */
+ Oid routine_oid; /* oid of routine */
+ char *trigger_name; /* name of trigger */
int cursorpos; /* cursor index into query string */
int internalpos; /* cursor index into internalquery */
char *internalquery; /* text of internally-generated query */
*** a/src/include/utils/rel.h
--- b/src/include/utils/rel.h
***************
*** 398,401 **** typedef struct StdRdOptions
--- 398,406 ----
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+ /* routines in utils/error/relerror.c */
+ extern int errtablecol(Relation table, const char *colname);
+ extern int errtable(Relation table);
+ extern int errconstraint(const char *cname);
+
#endif /* REL_H */
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
***************
*** 936,941 **** pqGetErrorNotice3(PGconn *conn, bool isError)
--- 936,974 ----
valf, vall);
appendPQExpBufferChar(&workBuf, '\n');
}
+
+ val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("COLUMN NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TABLE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("CONSTRAINT NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("SCHEMA NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE NAME: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_SCHEMA);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE SHEMA: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_ROUTINE_OID);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("ROUTINE OID: %s\n"), val);
+ val = PQresultErrorField(res, PG_DIAG_TRIGGER_NAME);
+ if (val)
+ appendPQExpBuffer(&workBuf,
+ libpq_gettext("TRIGGER NAME: %s\n"), val);
}
/*
*** a/src/pl/plpgsql/src/generate-plerrcodes.pl
--- b/src/pl/plpgsql/src/generate-plerrcodes.pl
***************
*** 20,27 **** while (<$errcodes>)
next if /^#/;
next if /^\s*$/;
! # Skip section headers
! next if /^Section:/;
die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
--- 20,27 ----
next if /^#/;
next if /^\s*$/;
! # Skip section headers, and requirement notes
! next if /^Section:/ or /^Requirement:/;
die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 344,349 **** do_compile(FunctionCallInfo fcinfo,
--- 344,351 ----
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
+ function->fn_name = get_func_name(fcinfo->flinfo->fn_oid);
+ function->fn_schema = get_namespace_name(get_func_namespace(fcinfo->flinfo->fn_oid));
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 583,588 **** plpgsql_exec_trigger(PLpgSQL_function *func,
--- 583,590 ----
var->isnull = false;
var->freeval = true;
+ estate.trigger_name = trigdata->tg_trigger->tgname;
+
var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
var->value = CStringGetTextDatum("BEFORE");
***************
*** 917,922 **** plpgsql_exec_error_callback(void *arg)
--- 919,941 ----
else
errcontext("PL/pgSQL function %s",
estate->func->fn_signature);
+
+ /*
+ * if execution of this function is on top of error_context_stack,
+ * then set enhanced diagnostics fields related to function.
+ */
+ if (error_context_stack->callback == plpgsql_exec_error_callback &&
+ (PLpgSQL_execstate *) error_context_stack->arg == estate)
+ {
+ PLpgSQL_function *func = estate->func;
+
+ errroutineid(func->fn_oid);
+ err_generic_string(PG_DIAG_ROUTINE_NAME, func->fn_name);
+ err_generic_string(PG_DIAG_ROUTINE_SCHEMA, func->fn_schema);
+
+ if (func->fn_is_trigger != PLPGSQL_NOT_TRIGGER)
+ err_generic_string(PG_DIAG_TRIGGER_NAME, estate->trigger_name);
+ }
}
***************
*** 3053,3058 **** plpgsql_estate_setup(PLpgSQL_execstate *estate,
--- 3072,3078 ----
estate->err_stmt = NULL;
estate->err_text = NULL;
+ estate->trigger_name = NULL;
estate->plugin_info = NULL;
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 686,691 **** typedef enum PLpgSQL_trigtype
--- 686,693 ----
typedef struct PLpgSQL_function
{ /* Complete compiled function */
char *fn_signature;
+ char *fn_schema;
+ char *fn_name; /* fields for enhanced diagnostics */
Oid fn_oid;
TransactionId fn_xmin;
ItemPointerData fn_tid;
***************
*** 754,759 **** typedef struct PLpgSQL_execstate
--- 756,762 ----
char *exitlabel; /* the "target" label of the current EXIT or
* CONTINUE stmt, if any */
ErrorData *cur_error; /* current exception handler's error */
+ char *trigger_name; /* name of trigger for enhanced diagnostics */
Tuplestorestate *tuple_store; /* SRFs accumulate results here */
MemoryContext tuple_store_cxt;
Pavel, Peter,
To be honest, I haven't been following this thread at all, but I'm kind
of amazed that this patch seems to be progressing. I spent quite a bit
of time previously trying to change the CSV log structure to add a
single column and this patch is adding a whole slew of them. My prior
patch also allowed customization of the CSV log output, which seems like
it would be even more valuable if we're going to be adding a bunch more
fields. I know there are dissenting viewpoints on that, however, as
application authors would prefer to not have to deal with variable CSV
output formats, which I can understand.
As regards this specific patch, I trust that the protocol error response
changes won't have any impact on existing clients? Do we anticipate
something like the JDBC driver having any issues? If there's any chance
that we're going to break a client by sending it things which it won't
understand, it seems we'd need to bump the protocol (which hasn't been
done in quite some time and would likely needs its own discussion...).
Regarding qualifying what we're returning- I tend to agree with Pavel
that if we're going to provide this information, we should do it in a
fully qualified manner. I can certainly see situations where constraint
names are repeated in environments and it may not be clear what schema
it's in (think application development with a dev and a test schema in
the same database), and the same could hold for constraints against
tables (though having those repeated would certainly be more rare, since
you can't repeat a named constraint in a given schema if it has an index
behind it, though you could with simple CHECK constraints). Again, my
feeling is that if we're going to provide such information, it should be
fully-qualified.
There are some additional concerns regarding the patch itself that I
have (do we really want to modify ereport() in this way? How can we
implement something which scales better than just adding more and more
parameters?) but I think we need to figure out exactly what we're agreed
to be doing with this patch and get buy-in from everyone first.
Thanks,
Stephen
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
Show quoted text
I rechecked your version eelog4 and I am thinking so it is ok. From my
view it can be enough for application developer and I am not against
to commit this (or little bit reduced version) as first step.As plpgsql developer I really miss a fields "routine_name,
routine_schema and trigger_name". These fields can be simply
implemented without any impact on performance. Because routine_name
and routine_schema is not unique in postgres, I propose third field
"routine_oid". My patch eelog5 is based on your eelog4 with enhancing
for these mentioned fields - 5KB more - but only PL/pgSQL is supported
now. I would to find a acceptable design now.Second - I don't see any value for forwarding these new fields
(column_name, table_name, constraint_name, schema_name, routine_name,
routine_schema, routine_id, trigger_name) to log or to csvlog and I
propose don't push it to log. We can drop logging in next iteration if
you agree.What do you thinking about new version?
Regards
Pavel
2012/12/10 Peter Geoghegan <peter@2ndquadrant.com>:
So, I took a look at this, and made some more changes.
I have a hard time seeing the utility of some fields that were in this
patch, and so they have been removed from this revision.Consider, for example:
+ constraint_table text,
+ constraint_schema text,While constraint_name remains, I find it really hard to believe that
applications need a perfectly unambiguous idea of what constraint
they're dealing with. If applications have a constraint that has a
different domain-specific meaning depending on what schema it is in,
while a table in one schema uses that constraint in another, well,
that's a fairly awful design. Is it something that we really need to
care about? You mentioned the SQL standard, but as far as I know the
SQL standard has nothing to say about these fields within something
like errdata - their names are lifted from information_schema, but
being unambiguous is hardly so critical here. We want to identify an
object for the purposes of displaying an error message only. Caring
deeply about ambiguity seems wrong-headed to me in this context.+ routine_name text, + trigger_name text, + trigger_table text, + trigger_schema text,The whole point of an exception (which ereport() is very similar to)
is to decouple errors from error handling as much as possible - any
application that magically takes a different course of action based on
where that error occurred as reported by the error itself (and not the
location of where handling the error occurs) seems kind of
wrong-headed to me. Sure, a backtrace may be supplied, but that's only
for diagnostic purposes, and a human can just as easily get that
information already. If you can present an example of this information
actually being present in a way that is amenable to that, either in
any other RDBMS or any major programming language or framework, I
might reconsider.This just leaves:
+ column_name text, + table_name text, + constraint_name text, + schema_name text,This seems like enough information for any reasonable use of this
infrastructure, and I highly doubt anyone would avail of the other
fields if they remained. I guess an application might have done
something with "constraint_table", as with foreign keys for example,
but I just can't see that being used when constraint_name can be used.Removing these fields has also allowed me to remove the "avoid setting
field at lower point in the callstack" logic, which was itself kind of
ugly. Since fields like routine_name only set themselves at the top of
the callstack, the potential for astonishing outcomes was pretty high
- what if you expect one routine_name, but then that routine follows a
slightly different code-path one day and you get another function
setting routine_name and undermining that expectation?There were some bugs left in the patch eelog3.diff, mostly due to
things like setting table name to what is actually an index name. As I
mentioned, we now assert that:+ Assert(table->rd_rel->relkind == RELKIND_RELATION);
in the functions within relerror.c.
I have tightened up where these fields are available, and
appropriately documented that for the benefit of both application
developers and developers of client libraries. I went so far as to
hack the Perl scripts that generate .sgml and .h files from
errcodes.txt (i.e. the infrastrucutre that produces tables of errcodes
in various places from a single authoritative place) - I have
instituted a coding standard so that these fields are reliably
available and have documented that requirement at both the user and
hacker level.It would be nice if a Perl hacker could eyeball those changes - this
is my first time writing Perl, and I suspect it may be worth having
someone else to polish the Perl code a bit.I have emphasized the need for consistency and a sane contract for
application developers and third-party client driver authors - they
*need* to know that certain fields will always be available, or at
least won't be unavailable due to a change in the phase of the moon.errcodes.txt now says:
+ # 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_violationNow, there are one or two places where these fields are not actually
available even though they're formally required according to a literal
reading of the above. This is only because there is clearly no such
field sensibly available, even in principle - to my mind this cannot
be a problem, because the application developer cannot have any
reasonable expectation of a field being set. I'm really talking about
two cases in particular:* For ERRCODE_NOT_NULL_VIOLATION, we don't actually provide
schema_name and table_name in the event of domains. This was
previously identified as an issue. If it is judged better to not have
any requirements there at all, so be it.* For the validateDomainConstraint() ERRCODE_CHECK_VIOLATION ereport
call, we may not provide a constraint name iff a Constraint.connname
is NULL. Since there isn't a constraint name to give even in
principle, and this is an isolated case, this seems reasonable.To make logging less verbose, TABLE NAME isn't consistently split out
as a separate field - this seems fine to me, since application code
doesn't target logs:+ if (edata->column_name && edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"), + edata->table_name, edata->column_name); + } + else if (edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("TABLE NAME: %s\n"), + edata->table_name); + }I used pgindent to selectively indent parts of the codebase affected
by this patch. I am about ready to mark this one "ready for
committer", but it would be nice at this point to get some buy-in on
the basic view of how these things should work that informed this
revision. Does anyone disagree with my contention that there should be
a sane, well-defined contract, or any of the details of what that
should look like? Was I right to suggest that some of the set of
fields that appeared in Pavel's eelog3.diff revision are unnecessary?I'm sorry it took me as long as it did to get back to you on this.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2012/12/28 Stephen Frost <sfrost@snowman.net>:
Pavel, Peter,
To be honest, I haven't been following this thread at all, but I'm kind
of amazed that this patch seems to be progressing. I spent quite a bit
of time previously trying to change the CSV log structure to add a
single column and this patch is adding a whole slew of them. My prior
patch also allowed customization of the CSV log output, which seems like
it would be even more valuable if we're going to be adding a bunch more
fields. I know there are dissenting viewpoints on that, however, as
application authors would prefer to not have to deal with variable CSV
output formats, which I can understand.
Some smarter design of decision what will be stored to CSV and what
now can be great. I am not thinking so these enhanced fields has value
pro typical DBA and should be stored to CSV only when somebody need
it. But it is different story - although it can simplify our work
because then we don't need to solve this issue.
As regards this specific patch, I trust that the protocol error response
changes won't have any impact on existing clients? Do we anticipate
something like the JDBC driver having any issues? If there's any chance
that we're going to break a client by sending it things which it won't
understand, it seems we'd need to bump the protocol (which hasn't been
done in quite some time and would likely needs its own discussion...).
Depends on implementation. Implementations designed similar to libpq
should not have a problems.
Regarding qualifying what we're returning- I tend to agree with Pavel
that if we're going to provide this information, we should do it in a
fully qualified manner. I can certainly see situations where constraint
names are repeated in environments and it may not be clear what schema
it's in (think application development with a dev and a test schema in
the same database), and the same could hold for constraints against
tables (though having those repeated would certainly be more rare, since
you can't repeat a named constraint in a given schema if it has an index
behind it, though you could with simple CHECK constraints). Again, my
feeling is that if we're going to provide such information, it should be
fully-qualified.There are some additional concerns regarding the patch itself that I
have (do we really want to modify ereport() in this way? How can we
implement something which scales better than just adding more and more
parameters?) but I think we need to figure out exactly what we're agreed
to be doing with this patch and get buy-in from everyone first.
Related fields are fundamental - and I am thinking so is good to
optimize it - it has semantic tag, and tag has one char size. Some set
of fields is given by ANSI - we can select some subset because not all
fields described by ANSI has sense in pg. I don't would to enhance
range of this patch too much and I don't would to redesign current
concept of error handling. I am thinking so it works well and I have
no negative feedback from PostgreSQL users that I know.
But probably only reserved memory for ErrorData is limit for
"serialized custom fields" - I believe so we will have a associative
array - and one field can be of this type for any custom fields.
Regards
Pavel
Thanks,
Stephen
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
I rechecked your version eelog4 and I am thinking so it is ok. From my
view it can be enough for application developer and I am not against
to commit this (or little bit reduced version) as first step.As plpgsql developer I really miss a fields "routine_name,
routine_schema and trigger_name". These fields can be simply
implemented without any impact on performance. Because routine_name
and routine_schema is not unique in postgres, I propose third field
"routine_oid". My patch eelog5 is based on your eelog4 with enhancing
for these mentioned fields - 5KB more - but only PL/pgSQL is supported
now. I would to find a acceptable design now.Second - I don't see any value for forwarding these new fields
(column_name, table_name, constraint_name, schema_name, routine_name,
routine_schema, routine_id, trigger_name) to log or to csvlog and I
propose don't push it to log. We can drop logging in next iteration if
you agree.What do you thinking about new version?
Regards
Pavel
2012/12/10 Peter Geoghegan <peter@2ndquadrant.com>:
So, I took a look at this, and made some more changes.
I have a hard time seeing the utility of some fields that were in this
patch, and so they have been removed from this revision.Consider, for example:
+ constraint_table text,
+ constraint_schema text,While constraint_name remains, I find it really hard to believe that
applications need a perfectly unambiguous idea of what constraint
they're dealing with. If applications have a constraint that has a
different domain-specific meaning depending on what schema it is in,
while a table in one schema uses that constraint in another, well,
that's a fairly awful design. Is it something that we really need to
care about? You mentioned the SQL standard, but as far as I know the
SQL standard has nothing to say about these fields within something
like errdata - their names are lifted from information_schema, but
being unambiguous is hardly so critical here. We want to identify an
object for the purposes of displaying an error message only. Caring
deeply about ambiguity seems wrong-headed to me in this context.+ routine_name text, + trigger_name text, + trigger_table text, + trigger_schema text,The whole point of an exception (which ereport() is very similar to)
is to decouple errors from error handling as much as possible - any
application that magically takes a different course of action based on
where that error occurred as reported by the error itself (and not the
location of where handling the error occurs) seems kind of
wrong-headed to me. Sure, a backtrace may be supplied, but that's only
for diagnostic purposes, and a human can just as easily get that
information already. If you can present an example of this information
actually being present in a way that is amenable to that, either in
any other RDBMS or any major programming language or framework, I
might reconsider.This just leaves:
+ column_name text, + table_name text, + constraint_name text, + schema_name text,This seems like enough information for any reasonable use of this
infrastructure, and I highly doubt anyone would avail of the other
fields if they remained. I guess an application might have done
something with "constraint_table", as with foreign keys for example,
but I just can't see that being used when constraint_name can be used.Removing these fields has also allowed me to remove the "avoid setting
field at lower point in the callstack" logic, which was itself kind of
ugly. Since fields like routine_name only set themselves at the top of
the callstack, the potential for astonishing outcomes was pretty high
- what if you expect one routine_name, but then that routine follows a
slightly different code-path one day and you get another function
setting routine_name and undermining that expectation?There were some bugs left in the patch eelog3.diff, mostly due to
things like setting table name to what is actually an index name. As I
mentioned, we now assert that:+ Assert(table->rd_rel->relkind == RELKIND_RELATION);
in the functions within relerror.c.
I have tightened up where these fields are available, and
appropriately documented that for the benefit of both application
developers and developers of client libraries. I went so far as to
hack the Perl scripts that generate .sgml and .h files from
errcodes.txt (i.e. the infrastrucutre that produces tables of errcodes
in various places from a single authoritative place) - I have
instituted a coding standard so that these fields are reliably
available and have documented that requirement at both the user and
hacker level.It would be nice if a Perl hacker could eyeball those changes - this
is my first time writing Perl, and I suspect it may be worth having
someone else to polish the Perl code a bit.I have emphasized the need for consistency and a sane contract for
application developers and third-party client driver authors - they
*need* to know that certain fields will always be available, or at
least won't be unavailable due to a change in the phase of the moon.errcodes.txt now says:
+ # 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_violationNow, there are one or two places where these fields are not actually
available even though they're formally required according to a literal
reading of the above. This is only because there is clearly no such
field sensibly available, even in principle - to my mind this cannot
be a problem, because the application developer cannot have any
reasonable expectation of a field being set. I'm really talking about
two cases in particular:* For ERRCODE_NOT_NULL_VIOLATION, we don't actually provide
schema_name and table_name in the event of domains. This was
previously identified as an issue. If it is judged better to not have
any requirements there at all, so be it.* For the validateDomainConstraint() ERRCODE_CHECK_VIOLATION ereport
call, we may not provide a constraint name iff a Constraint.connname
is NULL. Since there isn't a constraint name to give even in
principle, and this is an isolated case, this seems reasonable.To make logging less verbose, TABLE NAME isn't consistently split out
as a separate field - this seems fine to me, since application code
doesn't target logs:+ if (edata->column_name && edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("COLUMN NAME: %s:%s\n"), + edata->table_name, edata->column_name); + } + else if (edata->table_name) + { + log_line_prefix(&buf, edata); + appendStringInfo(&buf, _("TABLE NAME: %s\n"), + edata->table_name); + }I used pgindent to selectively indent parts of the codebase affected
by this patch. I am about ready to mark this one "ready for
committer", but it would be nice at this point to get some buy-in on
the basic view of how these things should work that informed this
revision. Does anyone disagree with my contention that there should be
a sane, well-defined contract, or any of the details of what that
should look like? Was I right to suggest that some of the set of
fields that appeared in Pavel's eelog3.diff revision are unnecessary?I'm sorry it took me as long as it did to get back to you on this.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Stephen Frost <sfrost@snowman.net> writes:
To be honest, I haven't been following this thread at all, but I'm kind
of amazed that this patch seems to be progressing. I spent quite a bit
of time previously trying to change the CSV log structure to add a
single column and this patch is adding a whole slew of them.
I don't think that part's been agreed to at all; it seems entirely
possible that it'll get dropped if/when the patch gets committed.
I'm not convinced that having these fields in the log is worth
the compatibility hit of changing the CSV column set.
As regards this specific patch, I trust that the protocol error response
changes won't have any impact on existing clients? Do we anticipate
something like the JDBC driver having any issues? If there's any chance
that we're going to break a client by sending it things which it won't
understand, it seems we'd need to bump the protocol (which hasn't been
done in quite some time and would likely needs its own discussion...).
I think we are okay on this. The protocol definition has said from the
very beginning "Since more field types might be added in future,
frontends should silently ignore fields of unrecognized type". A client
that fails to do that is buggy, not grounds for bumping the protocol.
I haven't been following the patch so have no thoughts about your
other comments.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 14:00, Stephen Frost <sfrost@snowman.net> wrote:
There are some additional concerns regarding the patch itself that I
have (do we really want to modify ereport() in this way? How can we
implement something which scales better than just adding more and more
parameters?) but I think we need to figure out exactly what we're agreed
to be doing with this patch and get buy-in from everyone first.
I don't think that the need to scale beyond what we have in my
revision really exists. Some of the ereport sites are a bit unwieldy,
but I don't see that there is much that can be done about that - you
need to specify the information somewhere, and it makes sense to do it
at that point. The field names are frequently expanded in the error
message presented to the user anyway.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 15:57, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I don't think that part's been agreed to at all; it seems entirely
possible that it'll get dropped if/when the patch gets committed.
I'm not convinced that having these fields in the log is worth
the compatibility hit of changing the CSV column set.
I am willing to let that go, because it just doesn't seem important to
me. I just thought that if we were going to break compatibility, we
might as well not hold back on the inclusion of fields. I'm not sure
where this leaves the regular log. Pavel wants to remove that, too. I
defer to others.
Now, as to the question of whether we need to make sure that
everything is always fully qualified - I respectfully disagree with
Stephen, and maintain my position set out in the last round of
feedback [1]Post of revision "eelog4.diff": http://archives.postgresql.org/message-id/CAEYLb_UM9Z8vitJcKAOgG2shAB1N-71dozNhj2PJm2Ls96QVPg@mail.gmail.com -- Peter Geoghegan http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training and Services, which is that we don't need to fully qualify everything,
because *for the purposes of error handling*, which is what I think we
should care about, these fields are sufficient:
+ column_name text,
+ table_name text,
+ constraint_name text,
+ schema_name text,
If you use the same constraint name everywhere, you might get the same
error message. The situations in which this scheme might fall down are
too implausible for me to want to bloat up all those ereport sites any
further (something that Stephen also complained about).
I think that the major outstanding issues are concerning whether or
not I have the API here right. I make explicit guarantees as to the
availability of certain fields for certain errcodes (a small number of
"Class 23 - Integrity Constraint Violation" codes). No one has really
said anything about that, though I think it's important.
I also continue to think that we shouldn't have "routine_name",
"routine_schema" and "trigger_name" fields - I think it's wrong-headed
to have an exception handler magically act on knowledge about where
the exception came from that has been baked into the exception - is
there any sort of precedent for this? Pavel disagrees here. Again, I
defer to others.
[1]: Post of revision "eelog4.diff": http://archives.postgresql.org/message-id/CAEYLb_UM9Z8vitJcKAOgG2shAB1N-71dozNhj2PJm2Ls96QVPg@mail.gmail.com -- Peter Geoghegan http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training and Services
http://archives.postgresql.org/message-id/CAEYLb_UM9Z8vitJcKAOgG2shAB1N-71dozNhj2PJm2Ls96QVPg@mail.gmail.com
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I think that the major outstanding issues are concerning whether or
not I have the API here right. I make explicit guarantees as to the
availability of certain fields for certain errcodes (a small number of
"Class 23 - Integrity Constraint Violation" codes). No one has really
said anything about that, though I think it's important.I also continue to think that we shouldn't have "routine_name",
"routine_schema" and "trigger_name" fields - I think it's wrong-headed
to have an exception handler magically act on knowledge about where
the exception came from that has been baked into the exception - is
there any sort of precedent for this? Pavel disagrees here. Again, I
defer to others.
depends on perspective - lines of error context are taken by same
mechanism and some other fields from ErrorData too.
I have not strong opinion if last implementation is the best - but I
am sure about context. Developer usually should to know, where
exception was created but interesting is position in custom code - so
it usually different function than function that raises exception. If
I understand you, we have a fields that has behave that you expected -
filename and funcname. And I have not used these fields for
application programming.
Regards
Pavel
[1] Post of revision "eelog4.diff":
http://archives.postgresql.org/message-id/CAEYLb_UM9Z8vitJcKAOgG2shAB1N-71dozNhj2PJm2Ls96QVPg@mail.gmail.com
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 18:40, Pavel Stehule <pavel.stehule@gmail.com> wrote:
If
I understand you, we have a fields that has behave that you expected -
filename and funcname. And I have not used these fields for
application programming.
No, that's not what I mean. What I mean is that it seems questionable
to want to do anything *within* an exception handler on the basis of
where an exception was raised from. You should just place exception
handlers more carefully instead.
Are you aware of any popular programming language that provides this
kind of information? Can you tell in a well-principled way what
function a Python exception originated from, for example? These are
the built-in Python 2 exception classes:
http://docs.python.org/2/library/exceptions.html
None of the Python built-in exception types have this kind of
information available from fields or anything. Now, you might say that
Python isn't Postgres, and you'd be right. I'm fairly confident that
you'll find no precedent for a routine_name field in any code that is
even roughly analogous to the elog infrastructure, though, because
acting on the basis of what particular function the exception was
raised from seems quite hazardous - it breaks any kind of
encapsulation that might have existed.
If one person slightly refactors their code, it could break the code
of another person who has never even met or talked to the first.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/28 Peter Geoghegan <peter@2ndquadrant.com>:
On 28 December 2012 18:40, Pavel Stehule <pavel.stehule@gmail.com> wrote:
If
I understand you, we have a fields that has behave that you expected -
filename and funcname. And I have not used these fields for
application programming.No, that's not what I mean. What I mean is that it seems questionable
to want to do anything *within* an exception handler on the basis of
where an exception was raised from. You should just place exception
handlers more carefully instead.Are you aware of any popular programming language that provides this
kind of information? Can you tell in a well-principled way what
function a Python exception originated from, for example? These are
the built-in Python 2 exception classes:
for this subject ANSI SQL is more relevant source or manual for DB2 or
Oracle. Design of Python and native PL languages are different. Python
can use complex nested structures. PL - PL/pgSQL or PL/PSM is designed
for work with simply scalar types. So these environments are not
comparable.
None of the Python built-in exception types have this kind of
information available from fields or anything. Now, you might say that
Python isn't Postgres, and you'd be right. I'm fairly confident that
you'll find no precedent for a routine_name field in any code that is
even roughly analogous to the elog infrastructure, though, because
acting on the basis of what particular function the exception was
raised from seems quite hazardous - it breaks any kind of
encapsulation that might have existed.If one person slightly refactors their code, it could break the code
of another person who has never even met or talked to the first.
yes, it is not valid argument, I am sorry. Lot of error fields doesn't
work if developer doesn't respect some coding standards. It is not
just routine_name. Only when your implementation is correct, then code
works.
Best regards
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 19:23, Pavel Stehule <pavel.stehule@gmail.com> wrote:
for this subject ANSI SQL is more relevant source or manual for DB2 or
Oracle. Design of Python and native PL languages are different. Python
can use complex nested structures. PL - PL/pgSQL or PL/PSM is designed
for work with simply scalar types. So these environments are not
comparable.
I don't see how the fact that Python can use nested data structures
has any bearing (you could argue that plpgsql does too, fwiw).
Please direct me towards the manual of DB2 or Oracle where it says
that something like routine_name is exposed for error handling
purposes. Correct me if I'm mistaken, but I don't believe that ANSI
SQL has anything to say about any of these error fields. You've just
lifted the names of the fields from various information_schema
catalogs, which is hardly the same thing.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/28 Peter Geoghegan <peter@2ndquadrant.com>:
On 28 December 2012 19:23, Pavel Stehule <pavel.stehule@gmail.com> wrote:
for this subject ANSI SQL is more relevant source or manual for DB2 or
Oracle. Design of Python and native PL languages are different. Python
can use complex nested structures. PL - PL/pgSQL or PL/PSM is designed
for work with simply scalar types. So these environments are not
comparable.I don't see how the fact that Python can use nested data structures
has any bearing (you could argue that plpgsql does too, fwiw).Please direct me towards the manual of DB2 or Oracle where it says
that something like routine_name is exposed for error handling
purposes. Correct me if I'm mistaken, but I don't believe that ANSI
SQL has anything to say about any of these error fields. You've just
lifted the names of the fields from various information_schema
catalogs, which is hardly the same thing.
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Fdb2%2Frbafzmstgetdiag.htm
http://savage.net.au/SQL/sql-2003-2.bnf - GET DIAGNOSTICS statement
SQL: 1999, Jim Melton,Alan R. Simon - description of GET DIAGNOSTICS statement
ANSI SQL - Feature F122, "Enhanced diagnostics management" - table
identifier>s for use with <get diagnostics statement> - and related
description
Regards
Pavel
please, search
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/10/12 4:23 PM, Peter Geoghegan wrote:
Well, this is an area that the standard doesn't have anything to say
about. The standard defines errcodes, but not what fields they make
available. I suppose you could say that the patch proposes that they
become a Postgres extension to the standard.
The standard certainly does define extra fields for errors; see <get
diagnostics statement>. We have a GET DIAGNOSTICS statement in
PL/pgSQL, so there is certainly precedent for all of this.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/28/12 2:03 PM, Peter Geoghegan wrote:
No, that's not what I mean. What I mean is that it seems questionable
to want to do anything *within* an exception handler on the basis of
where an exception was raised from.
Isn't that the whole point of this patch? The only purpose of this
feature is to make the exception information available in a
"machine-readable" way. That functionality has been requested many
times over the years.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 19:55, Pavel Stehule <pavel.stehule@gmail.com> wrote:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Fdb2%2Frbafzmstgetdiag.htm
I'm unconvinced by this. First of all, it only applies to the GET
DIAGNOSTICS statement, and the only implementation that actually
currently uses that is DB2, AFAICT. Secondly, DB2 only provides it for
very specific errcode classes that relate to problems that are
peculiar to routines/functions, so in general you cannot rely on the
information being available in the same way as you intend. Clearly,
DB2 provides it for completeness, and not because you can rely on it
being available for error handling purposes. On the other hand, your
latest revision of the patch (eelog5.patch) sees plpgsql jury-rigged
to set the fields itself, which seems like a whole other proposition
entirely.
What's more, you're changing ErrorData to make this happen, rather
than having the user interrogate the server for this information as
GET DIAGNOSTICS does. So I don't see that this supports your case at
all. I maintain that having an exception handler's behaviour vary
based on a field that describes a routine that originally raised the
function is a really bad idea, and that we should not facilitate it.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 20:34, Peter Eisentraut <peter_e@gmx.net> wrote:
Isn't that the whole point of this patch? The only purpose of this
feature is to make the exception information available in a
"machine-readable" way. That functionality has been requested many
times over the years.
Right, and I agree that it's very useful for some fields (if you can
actually have a reasonable set of guarantees about where each becomes
available). I just don't think that it's worth including fields like
routine_name within ErrorData, and in fact it may be harmful to do so.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/28 Peter Geoghegan <peter@2ndquadrant.com>:
On 28 December 2012 19:55, Pavel Stehule <pavel.stehule@gmail.com> wrote:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Fdb2%2Frbafzmstgetdiag.htm
I'm unconvinced by this. First of all, it only applies to the GET
DIAGNOSTICS statement, and the only implementation that actually
currently uses that is DB2, AFAICT. Secondly, DB2 only provides it for
very specific errcode classes that relate to problems that are
peculiar to routines/functions, so in general you cannot rely on the
information being available in the same way as you intend. Clearly,
DB2 provides it for completeness, and not because you can rely on it
being available for error handling purposes. On the other hand, your
latest revision of the patch (eelog5.patch) sees plpgsql jury-rigged
to set the fields itself, which seems like a whole other proposition
entirely.What's more, you're changing ErrorData to make this happen, rather
than having the user interrogate the server for this information as
GET DIAGNOSTICS does. So I don't see that this supports your case at
all. I maintain that having an exception handler's behaviour vary
based on a field that describes a routine that originally raised the
function is a really bad idea, and that we should not facilitate it.
It cannot to wait to GET DIAGNOSTICS request - because when GET
DIAGNOSTICS is called, then all unsupported info about exception is
lost, all used memory will be released. So if we would to support
ROUTINE_NAME or similar fields like CONTEXT or MESSAGE, we have to
store these values to ErrorData without knowledge if this value will
be used or not.
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 12/28/12 2:03 PM, Peter Geoghegan wrote:
Are you aware of any popular programming language that provides this
kind of information? Can you tell in a well-principled way what
function a Python exception originated from, for example? These are
the built-in Python 2 exception classes:http://docs.python.org/2/library/exceptions.html
None of the Python built-in exception types have this kind of
information available from fields or anything.
Sure, OSError has a filename attribute (which I'm sure is qualified by a
directory name if necessary), SyntaxError has filename, lineno, etc.
OSError.filename is essentially the equivalent of what is being proposed
here.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 20:40, Pavel Stehule <pavel.stehule@gmail.com> wrote:
It cannot to wait to GET DIAGNOSTICS request - because when GET
DIAGNOSTICS is called, then all unsupported info about exception is
lost, all used memory will be released.
I'm not suggesting that you do. I'm only suggesting that the two are
not comparable.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/28 Peter Geoghegan <peter@2ndquadrant.com>:
On 28 December 2012 20:40, Pavel Stehule <pavel.stehule@gmail.com> wrote:
It cannot to wait to GET DIAGNOSTICS request - because when GET
DIAGNOSTICS is called, then all unsupported info about exception is
lost, all used memory will be released.I'm not suggesting that you do. I'm only suggesting that the two are
not comparable
MESSAGE and similar yes. But CONTEXT is comparable. And there is
interesting precedent. Some years PL/Python or PL/Perl doesn't support
CONTEXT and PL/pgSQL yes. And it was working.
Regards
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 December 2012 20:40, Peter Eisentraut <peter_e@gmx.net> wrote:
Sure, OSError has a filename attribute (which I'm sure is qualified by a
directory name if necessary), SyntaxError has filename, lineno, etc.OSError.filename is essentially the equivalent of what is being proposed
here.
I disagree. That isn't the same as what's being proposed here, because
that's something you're only going to get for those particular
exception classes, and I'm guessing that the fields only exist to
facilitate refactoring tools, IDEs and the like.
If BaseException, Exception or StandardError had a function_name
field, and it was idiomatic to change the behaviour of an exception
handler on the basis of the field's value, that would be equivalent.
Obviously that is not the case.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Eisentraut <peter_e@gmx.net> writes:
On 12/28/12 2:03 PM, Peter Geoghegan wrote:
None of the Python built-in exception types have this kind of
information available from fields or anything.
Sure, OSError has a filename attribute (which I'm sure is qualified by a
directory name if necessary), SyntaxError has filename, lineno, etc.
No no no ... that's completely unrelated. Those fields report
information about the user-visible object that the error is about.
The point Peter G is making is that reporting the source of the error is
like having the kernel report the name of the function inside the kernel
that threw the error. Or perhaps the name of the kernel source file
containing that function.
Now, these things are quite useful *to a kernel developer* who's trying
to debug a problem involving an error report. But they're pretty
useless to an application developer, and certainly any application
developer who relied on such information to control his error handling
code would receive no sympathy when (not if) a new kernel version broke
it.
In the same way, the filename/lineno stuff that we include in ereports
is sometimes useful to Postgres developers --- but it is very hard to
see a reason why application code would do anything else with it except
possibly print it for human reference.
And, in the same way, a CONTEXT traceback is sometimes useful for
debugging a collection of server-side functions --- but it's hard to see
any client-side code using that information in a mechanized way, at
least not while respecting a sane degree of separation between
server-side and client-side code.
So I'm with Peter G on this: the existing CONTEXT mechanism is good
enough, we do not need to split out selected sub-parts of that as
separate error fields. Moreover, doing so would provide only an utterly
arbitrary subset of the context stack: who's to say whether it would be
more important to report the most closely nested function, or the
outermost one?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/29 Tom Lane <tgl@sss.pgh.pa.us>:
Peter Eisentraut <peter_e@gmx.net> writes:
On 12/28/12 2:03 PM, Peter Geoghegan wrote:
None of the Python built-in exception types have this kind of
information available from fields or anything.Sure, OSError has a filename attribute (which I'm sure is qualified by a
directory name if necessary), SyntaxError has filename, lineno, etc.No no no ... that's completely unrelated. Those fields report
information about the user-visible object that the error is about.The point Peter G is making is that reporting the source of the error is
like having the kernel report the name of the function inside the kernel
that threw the error. Or perhaps the name of the kernel source file
containing that function.Now, these things are quite useful *to a kernel developer* who's trying
to debug a problem involving an error report. But they're pretty
useless to an application developer, and certainly any application
developer who relied on such information to control his error handling
code would receive no sympathy when (not if) a new kernel version broke
it.In the same way, the filename/lineno stuff that we include in ereports
is sometimes useful to Postgres developers --- but it is very hard to
see a reason why application code would do anything else with it except
possibly print it for human reference.
I wrote exactly this - our filename, funcname is not useful for PL
application developers
And, in the same way, a CONTEXT traceback is sometimes useful for
debugging a collection of server-side functions --- but it's hard to see
any client-side code using that information in a mechanized way, at
least not while respecting a sane degree of separation between
server-side and client-side code.
again - it is reason why I propose ROUTINE_NAME and TRIGGER_NAME - it
can be useful for some use cases, where developer should to handle
exception when they coming from known functions/triggers and he would
to raise exception, when it was raised elsewhere. Is possible working
with CONTEXT, but it needs little bit more work and situation is very
similar to fields COLUMN_NAME, where I can parse message and I can get
this information. But then it depends on message format.
So I'm with Peter G on this: the existing CONTEXT mechanism is good
enough, we do not need to split out selected sub-parts of that as
separate error fields. Moreover, doing so would provide only an utterly
arbitrary subset of the context stack: who's to say whether it would be
more important to report the most closely nested function, or the
outermost one?
I don't agree with this argument - you can say too "COLUMN_NAME,
TABLE_NAME" is not necessary, because MESSAGE is good enough. I don't
see any difference - and I would to use these variables for error
handling (not for logging) without dependency on current format of
MESSAGE or CONTEXT.
Second question - what should be context is important. I am thinking
so standard and implementation in other databases is relative clean -
it is name of custom routine, that doesn't handle exception. Moving to
pg - it is name of routine on the top on error callback stack, because
builtin functions doesn't set it - I don't need on top of CONTEX -
div_int or other name of builtin function - but I need there function
foo(b): a := 10/b.
other point - theoretically I can get ROUTINE_NAME from CONTEXT, but I
cannot get TRIGGER_NAME - this information is lost.
Regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 29 December 2012 05:04, Pavel Stehule <pavel.stehule@gmail.com> wrote:
again - it is reason why I propose ROUTINE_NAME and TRIGGER_NAME - it
can be useful for some use cases, where developer should to handle
exception when they coming from known functions/triggers and he would
to raise exception, when it was raised elsewhere. Is possible working
with CONTEXT, but it needs little bit more work and situation is very
similar to fields COLUMN_NAME, where I can parse message and I can get
this information. But then it depends on message format.
That seems very thin. Again, I have to point out that a precedent for
this doesn't really appear to exist in any major programming language,
even though ISTM it would be just as useful in a wide variety of
programming environments as it would be within Postgres. As I've said,
the DB2 GET DIAGNOSTIC stuff isn't anything like what you've proposed.
So I'm with Peter G on this: the existing CONTEXT mechanism is good
enough, we do not need to split out selected sub-parts of that as
separate error fields. Moreover, doing so would provide only an utterly
arbitrary subset of the context stack: who's to say whether it would be
more important to report the most closely nested function, or the
outermost one?I don't agree with this argument - you can say too "COLUMN_NAME,
TABLE_NAME" is not necessary, because MESSAGE is good enough. I don't
see any difference - and I would to use these variables for error
handling (not for logging) without dependency on current format of
MESSAGE or CONTEXT.
In my judgement, COLUMN_NAME and TABLE_NAME can be used without having
an unreasonable degree of coupling between client and server-side
code. They are at least easily understood, and not at all astonishing,
unlike ROUTINE_NAME. That said, CONSTRAINT_NAME clearly is by far the
most compelling new field, and I actually wouldn't mind losing the
other ones too much. I might have suggested losing COLUMN_NAME and
TABLE_NAME myself if we could reliably get something like a constraint
name for NOT NULL violations.
Second question - what should be context is important. I am thinking
so standard and implementation in other databases is relative clean -
it is name of custom routine, that doesn't handle exception. Moving to
pg - it is name of routine on the top on error callback stack, because
builtin functions doesn't set it - I don't need on top of CONTEX -
div_int or other name of builtin function - but I need there function
foo(b): a := 10/b.
I don't think the fact that built-in functions don't set ROUTINE_NAME
supports your position. In fact, it seems pretty broken to me that one
pl handler sets the value, while others may not. Furthermore, the
distinction between built-in and not built-in is fairly small within
Postgres - who is to say that even if a person thinks the proposed
semantics are useful, they'll continue to when they find out that
ROUTINE_NAME isn't set to the name of their C function?
other point - theoretically I can get ROUTINE_NAME from CONTEXT, but I
cannot get TRIGGER_NAME - this information is lost.
I don't think there should be a TRIGGER_NAME either - I think that we
should make interfaces easy to use correctly, and hard to use
incorrectly, and a mechanised response to a TRIGGER_NAME seems
incorrect. If you think that there should be a trigger name within
CONTEXT, there might be a case to be made for that, but I would prefer
to have that reviewed separately.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Geoghegan <peter@2ndquadrant.com> writes:
On 29 December 2012 05:04, Pavel Stehule <pavel.stehule@gmail.com> wrote:
So I'm with Peter G on this: the existing CONTEXT mechanism is good
enough, we do not need to split out selected sub-parts of that as
separate error fields. Moreover, doing so would provide only an utterly
arbitrary subset of the context stack: who's to say whether it would be
more important to report the most closely nested function, or the
outermost one?
I don't agree with this argument - you can say too "COLUMN_NAME,
TABLE_NAME" is not necessary, because MESSAGE is good enough. I don't
see any difference - and I would to use these variables for error
handling (not for logging) without dependency on current format of
MESSAGE or CONTEXT.
In my judgement, COLUMN_NAME and TABLE_NAME can be used without having
an unreasonable degree of coupling between client and server-side
code.
Yeah, I was about to reply in almost exactly those words. Table and
column names are, almost by definition, part of the shared understanding
of the client-side and server-side portions of any application, because
both sides have to manipulate those in order to do anything. However,
if client-side code were to rely on something like ROUTINE_NAME for
error processing, it would become closely tied to the *code structure*
of the server-side functions, which is a bad idea.
Basically the value proposition here is "let's contort the backend code
in order to support very bad programming practices in applications".
I'm not attracted by either part of that.
I don't think there should be a TRIGGER_NAME either - I think that we
should make interfaces easy to use correctly, and hard to use
incorrectly, and a mechanised response to a TRIGGER_NAME seems
incorrect. If you think that there should be a trigger name within
CONTEXT, there might be a case to be made for that, but I would prefer
to have that reviewed separately.
If the calling of a trigger isn't visible in the CONTEXT stack then
that's something we should address --- but in practice, wouldn't it be
visible anyway as an ordinary function call? At least if the trigger
is written in a reasonable PL. If the trigger is written in C, then
I'm outright against adding any such overhead. I don't think this patch
should be adding any cycles whatsoever to non-error code paths.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/29 Peter Geoghegan <peter@2ndquadrant.com>:
On 29 December 2012 05:04, Pavel Stehule <pavel.stehule@gmail.com> wrote:
again - it is reason why I propose ROUTINE_NAME and TRIGGER_NAME - it
can be useful for some use cases, where developer should to handle
exception when they coming from known functions/triggers and he would
to raise exception, when it was raised elsewhere. Is possible working
with CONTEXT, but it needs little bit more work and situation is very
similar to fields COLUMN_NAME, where I can parse message and I can get
this information. But then it depends on message format.That seems very thin. Again, I have to point out that a precedent for
this doesn't really appear to exist in any major programming language,
even though ISTM it would be just as useful in a wide variety of
programming environments as it would be within Postgres. As I've said,
the DB2 GET DIAGNOSTIC stuff isn't anything like what you've proposed.So I'm with Peter G on this: the existing CONTEXT mechanism is good
enough, we do not need to split out selected sub-parts of that as
separate error fields. Moreover, doing so would provide only an utterly
arbitrary subset of the context stack: who's to say whether it would be
more important to report the most closely nested function, or the
outermost one?I don't agree with this argument - you can say too "COLUMN_NAME,
TABLE_NAME" is not necessary, because MESSAGE is good enough. I don't
see any difference - and I would to use these variables for error
handling (not for logging) without dependency on current format of
MESSAGE or CONTEXT.In my judgement, COLUMN_NAME and TABLE_NAME can be used without having
an unreasonable degree of coupling between client and server-side
code. They are at least easily understood, and not at all astonishing,
unlike ROUTINE_NAME. That said, CONSTRAINT_NAME clearly is by far the
most compelling new field, and I actually wouldn't mind losing the
other ones too much. I might have suggested losing COLUMN_NAME and
TABLE_NAME myself if we could reliably get something like a constraint
name for NOT NULL violations.
Maybe this is main difference of our views. In my mind I never
thinking about using ROUTINE_NAME on client side part. My motivation
and reason why I push this feature is a using ROUTINE_NAME inside
PL/pgSQL - or other PL, where it can helps with more precious
exception processing - one real example - handling exception in
business process routines
BEGIN
...
... business logic
EXCEPTION WHEN OTHERS THEN
.. all is returned back
GET STACKED DIAGNOSTICS _message = MESSAGE, _routine_name =
ROUTINE_NAME, ...
IF verbosity <> 'verbose' THEN
INSERT INTO log(...) VALUES(_message, _routine_name)
ELSE
-- log CONTEXT too
INSERT INTO log(...) VALUES(_message, _routine_name, _context);
END IF;
END;
ROUTINE_NAME is information, that has different character from MESSAGE
--> and it is simply (not too detailed) information, where exception
coming from. Usually I don't know what check or builtin function
failed, because it is described by MESSAGE. And I don't would to use
CONTEXT everywhere, because it is too detailed.
Second question - what should be context is important. I am thinking
so standard and implementation in other databases is relative clean -
it is name of custom routine, that doesn't handle exception. Moving to
pg - it is name of routine on the top on error callback stack, because
builtin functions doesn't set it - I don't need on top of CONTEX -
div_int or other name of builtin function - but I need there function
foo(b): a := 10/b.I don't think the fact that built-in functions don't set ROUTINE_NAME
supports your position. In fact, it seems pretty broken to me that one
pl handler sets the value, while others may not. Furthermore, the
distinction between built-in and not built-in is fairly small within
Postgres - who is to say that even if a person thinks the proposed
semantics are useful, they'll continue to when they find out that
ROUTINE_NAME isn't set to the name of their C function?
If C function sets error callback then it can fill ROUTINE_NAME.
yes, now errors callback is used only for PL languages - probably it
is more intuitive design than explicit design, but it is working. It
is different design than in generic languages, where you can track
exception to really last point. But I don't think so it is necessary
in PL - typical customer is not interested in PostgreSQL internals.
other point - theoretically I can get ROUTINE_NAME from CONTEXT, but I
cannot get TRIGGER_NAME - this information is lost.I don't think there should be a TRIGGER_NAME either - I think that we
should make interfaces easy to use correctly, and hard to use
incorrectly, and a mechanised response to a TRIGGER_NAME seems
incorrect. If you think that there should be a trigger name within
CONTEXT, there might be a case to be made for that, but I would prefer
to have that reviewed separately.
We can do it safely for any PL language - because PL handlers
correctly use error callbacks.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/29 Tom Lane <tgl@sss.pgh.pa.us>:
Peter Geoghegan <peter@2ndquadrant.com> writes:
On 29 December 2012 05:04, Pavel Stehule <pavel.stehule@gmail.com> wrote:
So I'm with Peter G on this: the existing CONTEXT mechanism is good
enough, we do not need to split out selected sub-parts of that as
separate error fields. Moreover, doing so would provide only an utterly
arbitrary subset of the context stack: who's to say whether it would be
more important to report the most closely nested function, or the
outermost one?I don't agree with this argument - you can say too "COLUMN_NAME,
TABLE_NAME" is not necessary, because MESSAGE is good enough. I don't
see any difference - and I would to use these variables for error
handling (not for logging) without dependency on current format of
MESSAGE or CONTEXT.In my judgement, COLUMN_NAME and TABLE_NAME can be used without having
an unreasonable degree of coupling between client and server-side
code.Yeah, I was about to reply in almost exactly those words. Table and
column names are, almost by definition, part of the shared understanding
of the client-side and server-side portions of any application, because
both sides have to manipulate those in order to do anything. However,
if client-side code were to rely on something like ROUTINE_NAME for
error processing, it would become closely tied to the *code structure*
of the server-side functions, which is a bad idea.Basically the value proposition here is "let's contort the backend code
in order to support very bad programming practices in applications".
I'm not attracted by either part of that.
I don't think so it is necessary bad practice.
first - my motivation is related primary for usage in PL/pgSQL.
we can handle exceptions very near to place where exception was
raised. And then I can take same information like ROUTINE_NAME. Later
I can forward exception. A disadvantage of this design is higher
number of subtransactions.
Other design, I talked about it - is based on one relative high
positioned subtransaction, where I can handle lot of types of
exceptions or can raise final exception. I can do same work as in
previous mentioned design but just with one subtransaction. But for
design I need a data. And if is possible in simple form - because it
is better for PL/pgSQL.
I don't think there should be a TRIGGER_NAME either - I think that we
should make interfaces easy to use correctly, and hard to use
incorrectly, and a mechanised response to a TRIGGER_NAME seems
incorrect. If you think that there should be a trigger name within
CONTEXT, there might be a case to be made for that, but I would prefer
to have that reviewed separately.If the calling of a trigger isn't visible in the CONTEXT stack then
that's something we should address --- but in practice, wouldn't it be
visible anyway as an ordinary function call? At least if the trigger
is written in a reasonable PL. If the trigger is written in C, then
I'm outright against adding any such overhead. I don't think this patch
should be adding any cycles whatsoever to non-error code paths.
visibility in CONTEXT depends on creating and registering error
callbacks - I don't would to change it.
Regards
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
On 28 December 2012 15:57, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I don't think that part's been agreed to at all; it seems entirely
possible that it'll get dropped if/when the patch gets committed.
I'm not convinced that having these fields in the log is worth
the compatibility hit of changing the CSV column set.I am willing to let that go, because it just doesn't seem important to
me. I just thought that if we were going to break compatibility, we
might as well not hold back on the inclusion of fields. I'm not sure
where this leaves the regular log. Pavel wants to remove that, too. I
defer to others.
I'd like to see more options for what is logged, as I've mentioned in
the past, and I think all of these would be good candidates for logging
in some circumstances. The insistence on having one CSV format to rule
them all and which doesn't change (except that we've been regularly
changing it across major releases anyway..) doesn't strike me as the
right approach.
Now, as to the question of whether we need to make sure that
everything is always fully qualified - I respectfully disagree with
Stephen, and maintain my position set out in the last round of
feedback [1], which is that we don't need to fully qualify everything,
because *for the purposes of error handling*, which is what I think we
should care about, these fields are sufficient:
It's not entirely clear to me what distinction you're making here or if
we're simply in agreement about what the necessary fields are.
Having the schema name, table name, column name and constraint name
seems like it's sufficient to fully qualify a specific constraint..?
Where I see this being useful would be something along these lines:
COMMENT ON my_constraint ON my_schema.my_table
IS 'Please update XYZ first.';
Application runs, gets back a constraint violation, then:
SELECT
description,
consrc
FROM pg_description a
JOIN pg_constraint b ON (a.objid = b.oid)
JOIN pg_namespace c ON (b.connamespace = c.oid)
JOIN pg_class d ON (b.connrelid = d.oid)
WHERE classoid =
(select oid from pg_class x
join pg_namespace y on (x.relnamespace = y.oid)
where y.nspname = 'pg_catalog'
and x.relname = 'pg_constraint')
AND c.nspname = 'my_schema'
AND d.relname = 'my_table'
;
and have that information available to return to the client.
Thanks,
Stephen
On 29 December 2012 17:43, Stephen Frost <sfrost@snowman.net> wrote:
I'd like to see more options for what is logged, as I've mentioned in
the past, and I think all of these would be good candidates for logging
in some circumstances. The insistence on having one CSV format to rule
them all and which doesn't change (except that we've been regularly
changing it across major releases anyway..) doesn't strike me as the
right approach.
Yeah, you're probably right about that. I'm just not sure where it
leaves this patch.
It's not entirely clear to me what distinction you're making here or if
we're simply in agreement about what the necessary fields are.
I think we might be in agreement.
Having the schema name, table name, column name and constraint name
seems like it's sufficient to fully qualify a specific constraint..?
Not necessarily, strictly speaking. It's my position that we should
not care about the theoretical edge-cases that this presents. For
example, I don't have any sympathy for the idea that we need to fully
qualify that we're talking about a constraint in a particular schema,
in case there are two distinct constraints in different schemas *that
have the same name but don't do exactly the same thing anyway*. In
cases where there are two distinct constraints with the same name in
the same code path, it seems very likely that the custom error message
to be displayed (or whatever) should not need to differ for each (this
could come up if you were using schemas for multi-tenancy, for
example, and each schema contained the same objects).
So, in my latest revision of the patch, the only thing that isn't
fully-qualified is constraint_name (because routine_name and so on are
no longer included). It just seems unnecessary, given that only
ERRCODE_CHECK_VIOLATION errors will lack a schema name and table name
(and even then, only sometimes). Pavel originally included a
constraint_schema field, because he figured that the way constraints
are catalogued necessitated such a field.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/29 Peter Geoghegan <peter@2ndquadrant.com>:
On 29 December 2012 17:43, Stephen Frost <sfrost@snowman.net> wrote:
I'd like to see more options for what is logged, as I've mentioned in
the past, and I think all of these would be good candidates for logging
in some circumstances. The insistence on having one CSV format to rule
them all and which doesn't change (except that we've been regularly
changing it across major releases anyway..) doesn't strike me as the
right approach.Yeah, you're probably right about that. I'm just not sure where it
leaves this patch.It's not entirely clear to me what distinction you're making here or if
we're simply in agreement about what the necessary fields are.I think we might be in agreement.
Having the schema name, table name, column name and constraint name
seems like it's sufficient to fully qualify a specific constraint..?Not necessarily, strictly speaking. It's my position that we should
not care about the theoretical edge-cases that this presents. For
example, I don't have any sympathy for the idea that we need to fully
qualify that we're talking about a constraint in a particular schema,
in case there are two distinct constraints in different schemas *that
have the same name but don't do exactly the same thing anyway*. In
cases where there are two distinct constraints with the same name in
the same code path, it seems very likely that the custom error message
to be displayed (or whatever) should not need to differ for each (this
could come up if you were using schemas for multi-tenancy, for
example, and each schema contained the same objects).So, in my latest revision of the patch, the only thing that isn't
fully-qualified is constraint_name (because routine_name and so on are
no longer included). It just seems unnecessary, given that only
ERRCODE_CHECK_VIOLATION errors will lack a schema name and table name
(and even then, only sometimes). Pavel originally included a
constraint_schema field, because he figured that the way constraints
are catalogued necessitated such a field.
Design of constraints is little bit different between ANSI SQL and
PostgreSQL. And then some fields proposed by standard has no sense in
pg - TRIGGER_SCHEMA and probably CONSTRAINT_SCHEMA. Ours constraints
are related to some relation - everywhere. You cannot create
constraint without relation - so everywhere where CONSTRAINT_NAME is
not empty, then TABLE_NAME and TABLE_SCHEMA should be defined or
CONSTRAINT_NAME should be unique in database. In my first patch used
for these field some expected generated value, but I agree, so it is
not necessary and better to drop it, although it can help with
readability of some queries.
Regards
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter,
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
Pavel originally included a
constraint_schema field, because he figured that the way constraints
are catalogued necessitated such a field.
That's exactly what I was getting at also- in order to do a lookup in
the catalog, you need to know certain information to avoid potentially
getting multiple records back (which would be an error...).
If it isn't possible to uniquely identify a constraint using the
information returned, that's a problem. It's not a question about what
information is necessary- the catalog dictates what the definition of a
constraint is and it includes schema, table, and costraint name. In my
view, we shouldn't be returing a constraint name without returning the
other information to identify that constraint.
How is that different from the schema_name and table_name fields that
you mentioned were going to be included..? Are they not always filled
out for constraint violations and if not, why not? Is there a situation
where we could be getting an error back about a constraint where the
table returned isn't the table which the constraint is on?
If you review the use-case from my last email, I'm hopeful that you'll
see and understand where I think returning this information would be
useful and the information which would be necessary to make that query
work. Even without looking up the comment and instead just trying to
return the source of the constraint which was violated, you need the
information whcih will allow you to uniquely identify the constraint.
Thanks,
Stephen
On 29 December 2012 18:37, Stephen Frost <sfrost@snowman.net> wrote:
That's exactly what I was getting at also- in order to do a lookup in
the catalog, you need to know certain information to avoid potentially
getting multiple records back (which would be an error...).
Well, Pavel said that since a constraint is necessarily associated
with another object, the constraint name doesn't need to be separately
qualified. That isn't quite the truth, but I think it's close enough.
Note that I've documented a new set of requirements for various errcodes:
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
So, unless someone adds a constraint name outside of these errcodes (I
doubt that would make sense), there is exactly one case where a
constraint_name could not have a schema_name (which, as I've said, is
almost the same thing as constraint_schema, the exception being when
referencing FKs on *other* tables are involved) - that case is
ERRCODE_CHECK_VIOLATION.
That's because this SQL could cause ERRCODE_CHECK_VIOLATION:
select '123'::upc_barcode;
What should schema_name be set to now? Surely not the schema of the
type upc_barcode, since that would be inconsistent with a few other
ERRCODE_CHECK_VIOLATION sites where we do know schema_name +
table_name (those two are always either available together or not at
all).
The bottom line is that I'm not promising that you can reliably look
up the constraint, and I don't think that that should be a blocker, or
even that it's all that important. You could do it reliably with the
schema_name + table_name, though I'm not strongly encouraging that you
do.
So I guess we disagree on that, though I'm not *that* strongly opposed
to adding back in a constraint_schema field if the extra code is
deemed worth it.
Does anyone else have an opinion? Tom?
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2012/12/29 Peter Geoghegan <peter@2ndquadrant.com>:
On 29 December 2012 18:37, Stephen Frost <sfrost@snowman.net> wrote:
That's exactly what I was getting at also- in order to do a lookup in
the catalog, you need to know certain information to avoid potentially
getting multiple records back (which would be an error...).Well, Pavel said that since a constraint is necessarily associated
with another object, the constraint name doesn't need to be separately
qualified. That isn't quite the truth, but I think it's close enough.Note that I've documented a new set of requirements for various errcodes:
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_violationSo, unless someone adds a constraint name outside of these errcodes (I
doubt that would make sense), there is exactly one case where a
constraint_name could not have a schema_name (which, as I've said, is
almost the same thing as constraint_schema, the exception being when
referencing FKs on *other* tables are involved) - that case is
ERRCODE_CHECK_VIOLATION.That's because this SQL could cause ERRCODE_CHECK_VIOLATION:
select '123'::upc_barcode;
What should schema_name be set to now? Surely not the schema of the
type upc_barcode, since that would be inconsistent with a few other
ERRCODE_CHECK_VIOLATION sites where we do know schema_name +
table_name (those two are always either available together or not at
all).
I forgot on domain :(
this is use case, where CONSTRAINT_SCHEMA has sense
The bottom line is that I'm not promising that you can reliably look
up the constraint, and I don't think that that should be a blocker, or
even that it's all that important. You could do it reliably with the
schema_name + table_name, though I'm not strongly encouraging that you
do.
so then we probably need a CONSTRAINT_SCHEMA
So I guess we disagree on that, though I'm not *that* strongly opposed
to adding back in a constraint_schema field if the extra code is
deemed worth it.Does anyone else have an opinion? Tom?
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
So, unless someone adds a constraint name outside of these errcodes (I
doubt that would make sense), there is exactly one case where a
constraint_name could not have a schema_name (which, as I've said, is
almost the same thing as constraint_schema, the exception being when
referencing FKs on *other* tables are involved)
To be honest, I expected the concern to be about FKs and RESTRICT-type
relationships, which I think we do need to figure out an answer for. Is
there a distinction between the errors thrown for violating an FK on an
insert vs. violating a FK on a delete? Perhaps with that we could
identify referring table vs referred table and provide all of that
information to the application in a structured way?
- that case is
ERRCODE_CHECK_VIOLATION.That's because this SQL could cause ERRCODE_CHECK_VIOLATION:
select '123'::upc_barcode;
I'm surprised to see that as a constraint violation rather than a domain
violation..? ala:
=*> select '3000000000'::int;
ERROR: value "3000000000" is out of range for type integer
What should schema_name be set to now? Surely not the schema of the
type upc_barcode, since that would be inconsistent with a few other
ERRCODE_CHECK_VIOLATION sites where we do know schema_name +
table_name (those two are always either available together or not at
all).
I'm not sure that the schema of the type would be entirely wrong in that
specific case, along with the table name being set to the name of the
domain. I still think a domain violation-type error would be more
appropriate than calling it a constraint violation though.
The bottom line is that I'm not promising that you can reliably look
up the constraint, and I don't think that that should be a blocker, or
even that it's all that important. You could do it reliably with the
schema_name + table_name, though I'm not strongly encouraging that you
do.So I guess we disagree on that, though I'm not *that* strongly opposed
to adding back in a constraint_schema field if the extra code is
deemed worth it.Does anyone else have an opinion? Tom?
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.
Thanks,
Stephen
2012/12/29 Stephen Frost <sfrost@snowman.net>:
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
So, unless someone adds a constraint name outside of these errcodes (I
doubt that would make sense), there is exactly one case where a
constraint_name could not have a schema_name (which, as I've said, is
almost the same thing as constraint_schema, the exception being when
referencing FKs on *other* tables are involved)To be honest, I expected the concern to be about FKs and RESTRICT-type
relationships, which I think we do need to figure out an answer for. Is
there a distinction between the errors thrown for violating an FK on an
insert vs. violating a FK on a delete? Perhaps with that we could
identify referring table vs referred table and provide all of that
information to the application in a structured way?- that case is
ERRCODE_CHECK_VIOLATION.That's because this SQL could cause ERRCODE_CHECK_VIOLATION:
select '123'::upc_barcode;
I'm surprised to see that as a constraint violation rather than a domain
violation..? ala:=*> select '3000000000'::int;
ERROR: value "3000000000" is out of range for type integerWhat should schema_name be set to now? Surely not the schema of the
type upc_barcode, since that would be inconsistent with a few other
ERRCODE_CHECK_VIOLATION sites where we do know schema_name +
table_name (those two are always either available together or not at
all).I'm not sure that the schema of the type would be entirely wrong in that
specific case, along with the table name being set to the name of the
domain. I still think a domain violation-type error would be more
appropriate than calling it a constraint violation though.The bottom line is that I'm not promising that you can reliably look
up the constraint, and I don't think that that should be a blocker, or
even that it's all that important. You could do it reliably with the
schema_name + table_name, though I'm not strongly encouraging that you
do.So I guess we disagree on that, though I'm not *that* strongly opposed
to adding back in a constraint_schema field if the extra code is
deemed worth it.Does anyone else have an opinion? Tom?
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.
but then TABLE_NAME and TABLE_SCHEMA will be defined.
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.but then TABLE_NAME and TABLE_SCHEMA will be defined.
How are you going to look up the constraint? Using constraint_schema,
table_name, and constraint_name? Or table_schema, table_name and
constraint_name? When do you use constraint_schema instead of
table_schema?
None of those options is exactly clear or understandable...
Thanks,
Stephen
2012/12/29 Stephen Frost <sfrost@snowman.net>:
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.but then TABLE_NAME and TABLE_SCHEMA will be defined.
How are you going to look up the constraint? Using constraint_schema,
table_name, and constraint_name? Or table_schema, table_name and
constraint_name? When do you use constraint_schema instead of
table_schema?None of those options is exactly clear or understandable...
probably there will be situation when TABLE_SCHEMA and
CONSTRAINT_SCHEMA same values
Hypothetically - if we define CONSTRAINT_TABLE - what is difference
from TABLE_NAME ?
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 29 December 2012 19:56, Stephen Frost <sfrost@snowman.net> wrote:
- that case is
ERRCODE_CHECK_VIOLATION.That's because this SQL could cause ERRCODE_CHECK_VIOLATION:
select '123'::upc_barcode;
I'm surprised to see that as a constraint violation rather than a domain
violation..?
I was trying to convey that upc_barcode is a domain with a check
constraint (i.e. that the checkdigit on UPC barcode domains must be
correct). So yes, that would be an ERRCODE_CHECK_VIOLATION. See
ExecEvalCoerceToDomain().
What should schema_name be set to now? Surely not the schema of the
type upc_barcode, since that would be inconsistent with a few other
ERRCODE_CHECK_VIOLATION sites where we do know schema_name +
table_name (those two are always either available together or not at
all).I'm not sure that the schema of the type would be entirely wrong in that
specific case, along with the table name being set to the name of the
domain. I still think a domain violation-type error would be more
appropriate than calling it a constraint violation though.
Well, it is what it is. We can't change it now.
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.
I think that it's probably sufficient *for error handling purposes*.
Since it is non-trivial to get the schema of a constraint, and since
we have that jarring inconsistency (the schema of the type or the
schema of the table on which a check constraint is defined?) we might
well be better off just not addressing it.
It isn't as simple as you make out. Not all constraints appear within
pg_constraint (consider NOT NULL constraints), and besides,
pg_constraint.conrelid can be zero for non-table constraints. What's
more, pg_constraint actually has three pg_class oid columns; conrelid,
conindid and confrelid.
That is all.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.I think that it's probably sufficient *for error handling purposes*.
Since it is non-trivial to get the schema of a constraint, and since
we have that jarring inconsistency (the schema of the type or the
schema of the table on which a check constraint is defined?) we might
well be better off just not addressing it.
I actually really like the idea of being able to programatically figure
out what constraint caused a given error and then go look up that
constraint definition and the comment associated with it, to be able to
pass back a meaningful error to the user. Reducing the cases where
users end up implementing their own application-level error checking
before sending things to the DB is a worthwhile goal, as they often end
up implementing slightly different checking that what the DB does and
get upset when the DB throws an error that they can't do anything useful
with.
It isn't as simple as you make out. Not all constraints appear within
pg_constraint (consider NOT NULL constraints), and besides,
pg_constraint.conrelid can be zero for non-table constraints. What's
more, pg_constraint actually has three pg_class oid columns; conrelid,
conindid and confrelid.
Perhaps we can provide a bit more help to our application developers
then by coming up with something which will work consistently- eg: we
provide the data in a structured way to the client and then a function
(or a few of them) which the application can then use to get the
details. I understand that it's complicated and I'd hope that we can do
something better. In general, I wouldn't recommend developers to query
the catalogs directly, but I don't think there's really a better option
currently. If we fix that, great.
In the end, I may agree with you- the patch is a nice idea, but it needs
more to be a complete and working solution and providing something that
only gets people half-way there would result in developers hacking
things together which will quitely break when they least expect it.
Thanks,
Stephen
2012/12/29 Stephen Frost <sfrost@snowman.net>:
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
Having just constraint_schema and constraint_name feels horribly wrong
as the definition of a constraint also includes a pg_class oid.I think that it's probably sufficient *for error handling purposes*.
Since it is non-trivial to get the schema of a constraint, and since
we have that jarring inconsistency (the schema of the type or the
schema of the table on which a check constraint is defined?) we might
well be better off just not addressing it.I actually really like the idea of being able to programatically figure
out what constraint caused a given error and then go look up that
constraint definition and the comment associated with it, to be able to
pass back a meaningful error to the user. Reducing the cases where
users end up implementing their own application-level error checking
before sending things to the DB is a worthwhile goal, as they often end
up implementing slightly different checking that what the DB does and
get upset when the DB throws an error that they can't do anything useful
with.It isn't as simple as you make out. Not all constraints appear within
pg_constraint (consider NOT NULL constraints), and besides,
pg_constraint.conrelid can be zero for non-table constraints. What's
more, pg_constraint actually has three pg_class oid columns; conrelid,
conindid and confrelid.Perhaps we can provide a bit more help to our application developers
then by coming up with something which will work consistently- eg: we
provide the data in a structured way to the client and then a function
(or a few of them) which the application can then use to get the
details. I understand that it's complicated and I'd hope that we can do
something better. In general, I wouldn't recommend developers to query
the catalogs directly, but I don't think there's really a better option
currently. If we fix that, great.In the end, I may agree with you- the patch is a nice idea, but it needs
more to be a complete and working solution and providing something that
only gets people half-way there would result in developers hacking
things together which will quitely break when they least expect it.
it is a problem of this patch or not consistent constraints implementation ?
Regards
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
it is a problem of this patch or not consistent constraints implementation ?
Not sure, but I don't think it matters. You can blame the constraint
implementation, but that doesn't change my feelings about what we need
before we can accept a patch like this. Providing something which works
only part of the time and then doesn't work for very unclear reasons
isn't a good idea. Perhaps we need to fix the constraint implementation
and perhaps we need to fix the error information being returned, or most
likely we have to fix both, it doesn't change that we need to do
something more than just ignore this problem.
Thanks,
Stephen
2012/12/29 Stephen Frost <sfrost@snowman.net>:
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
it is a problem of this patch or not consistent constraints implementation ?
Not sure, but I don't think it matters. You can blame the constraint
implementation, but that doesn't change my feelings about what we need
before we can accept a patch like this. Providing something which works
only part of the time and then doesn't work for very unclear reasons
isn't a good idea. Perhaps we need to fix the constraint implementation
and perhaps we need to fix the error information being returned, or most
likely we have to fix both, it doesn't change that we need to do
something more than just ignore this problem.
can we remove CONSTRAINT_NAME from this patch? Minimally TABLE_SCHEMA,
TABLE_NAME and COLUMN_NAME works as expected.
CONSTRAINT_NAME can be implemented after constraints refactoring
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 29 December 2012 20:49, Stephen Frost <sfrost@snowman.net> wrote:
In the end, I may agree with you- the patch is a nice idea, but it needs
more to be a complete and working solution and providing something that
only gets people half-way there would result in developers hacking
things together which will quitely break when they least expect it.
I certainly wouldn't go that far. I think that 95% of the value that
could be delivered by this sort of thing will be realised by
committing something that is along the lines of what we have here
already. All I really want is to give users a well-principled way of
getting a constraint name within their error handler, so that they can
go do something in their application along the lines of displaying a
custom error message in the domain of the application and/or error
handler, not the domain of the database. That's it.
Ascertaining the identity of the object in question perfectly
unambiguously, so that you can safely do something like lookup a
comment on the object, seems like something way beyond what I'd
envisioned for this feature. Why should the comment be useful in an
error handler anyway? At best, that seems like a nice-to-have extra to
me. The vast majority are not even going to think about the ambiguity
that may exist. They'll just write:
if (constraint_name == "upc")
MessageBox("That is not a valid barcode.");
If it isn't the same constraint object as expected - if they have
multiple schemas with the same object definitions, are they likely to
care? We'll never promise that constraint_name is unambiguous, and in
fact will warn that it may be ambiguous.
It seems like what you're really looking for is a way of effectively
changing the error message to a user-defined error message using some
kind of DDL against a constraint object, without doing anything
special in a handler. That would be less flexible, but more automated,
and would probably entail using placeholders in our custom message
that get expanded (as with FK constraint violations - *what* key was
violated?). That has its appeal, but I don't see that it's something
that we need to do first.
Don't get me wrong - I think it's quite possible to pin down a way of
unambiguously identifying constraint objects as they appear here. I
just think that it isn't worth the effort to maintain the code in
Postgres, and in particular, I think people will not care about any
ambiguity, and even if they do, they could easily misunderstand the
exact rules. The right thing to do is not have multiple constraints
with the same name in flight within the same place that do different
things.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 29 December 2012 21:24, Pavel Stehule <pavel.stehule@gmail.com> wrote:
can we remove CONSTRAINT_NAME from this patch? Minimally TABLE_SCHEMA,
TABLE_NAME and COLUMN_NAME works as expected.CONSTRAINT_NAME can be implemented after constraints refactoring
This patch is almost completely pointless without a CONSTRAINT_NAME field.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter,
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
if (constraint_name == "upc")
MessageBox("That is not a valid barcode.");
So they'll quickly realize that a lookup-table based on constraint name
would be useful, create it, and then have a primary key on it to make
sure that they don't have any duplicates. Of course, this is all
duplicating what we're *already* keeping track of, except that method
won't be consistent with the catalog and they could end up with a single
entry in their table which corresponds to multiple actual constraints in
the system and begin subtly returning errors that don't make any sense
to the end user.
That's exactly the kind of subtly broken situation that I would hope
we'd try to keep them from getting into.
I'd almost rather return the OID and provide some lookup functions.
Thanks,
Stephen
On 29 December 2012 22:57, Stephen Frost <sfrost@snowman.net> wrote:
So they'll quickly realize that a lookup-table based on constraint name
would be useful, create it, and then have a primary key on it to make
sure that they don't have any duplicates.
I don't find that terribly likely. There is nothing broken about the
example. It's possible to misuse almost anything.
In order for the problem you describe to happen, the user would have
to ignore the warning in the documentation about constraint_name's
ability to uniquely identify something, and then have two constraints
in play at the same time with the same name but substantively
different. That seems incredibly unlikely.
Maybe you think that users cannot be trusted to take that warning on
board, but then the same user could not be trusted to heed another
warning about using a constraint_schema in the lookup table primary
key.
This whole lookup table idea presupposes that there'll only ever be
one error message per constraint in the entire application. That
usually isn't true for all sorts of reasons, in my experience.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter,
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
In order for the problem you describe to happen, the user would have
to ignore the warning in the documentation about constraint_name's
ability to uniquely identify something, and then have two constraints
in play at the same time with the same name but substantively
different. That seems incredibly unlikely.
I really don't think what I sketched out or something similar would
happen. I do think it's incredibly frustrating as a user who is trying
to develop an application which behaves correctly to be given only half
the information.
Thanks,
Stephen
On 30 December 2012 02:01, Stephen Frost <sfrost@snowman.net> wrote:
I really don't think what I sketched out or something similar would
happen. I do think it's incredibly frustrating as a user who is trying
to develop an application which behaves correctly to be given only half
the information.
I don't know what to say to that. Sometimes, when deciding how to
address problems like this, it's possible to arrive at slightly
surprising answers by process of elimination.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
On 30 December 2012 02:01, Stephen Frost <sfrost@snowman.net> wrote:
I really don't think what I sketched out or something similar would
happen. I do think it's incredibly frustrating as a user who is trying
to develop an application which behaves correctly to be given only half
the information.I don't know what to say to that. Sometimes, when deciding how to
address problems like this, it's possible to arrive at slightly
surprising answers by process of elimination.
Err. I intended to say "I really don't think what I sketched out, or
something similar, would be that unlikely to happen", or something along
those lines. Apologies for the confusion.
Thanks,
Stephen
On 30 December 2012 03:32, Stephen Frost <sfrost@snowman.net> wrote:
Err. I intended to say "I really don't think what I sketched out, or
something similar, would be that unlikely to happen", or something along
those lines. Apologies for the confusion.
Almost anything can be misused.
If you're going to insist that I hack a bunch of mechanism into this
patch so that the user can unambiguously identify each constraint
object, I'll do that. However, that's more code, and more complexity,
that will have to be documented, for just next to no practical benefit
that I can see.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
On 30 December 2012 03:32, Stephen Frost <sfrost@snowman.net> wrote:
Err. I intended to say "I really don't think what I sketched out, or
something similar, would be that unlikely to happen", or something along
those lines. Apologies for the confusion.Almost anything can be misused.
I agree, almost anything can be. The 'misuse' in this case, however, is
in expecting the information returned to be useful in a deterministic
and consistent manner, as it's being returned in a programmatic fashion.
If you're going to insist that I hack a bunch of mechanism into this
patch so that the user can unambiguously identify each constraint
object, I'll do that.
This is the part that I'm having trouble wrapping my head around- what's
the additional complexity here? If we have the OID of the constraint,
we should be able to unambiguoulsy return information which allows a
user to get back to that specific constraint and OID.
However, that's more code, and more complexity,
that will have to be documented, for just next to no practical benefit
that I can see.
I've certainly seen cases where constraints are duplicated by name
across schemas. I would imagine it could happen across tables in the
same schema also. I just don't like this notion of returning something
ambiguous to the user and worry that they'd accept it as deterministic
and dependable, regardless of the documentation. There's a certain
amount of "read the docs, but also look at what information you really
get back", which I think is, in general, a good thing, but it does mean
individuals might come to believe that they can depend on the constraint
name being unique in all cases.
As a side-note, I've recently run into more cases than I care to think
about where the 63-character limit on constraint names has been a
problem- because we're trying to ensure that we create an unambiguous
constraint name, and that's *with* various name shortening techniques
being used. The discussion about having longer values possible for the
'name' data type was on a different thread and should probably stay
there, but I bring it up because it has some impact on the possibility
of name collisions which is relevent to this discussion.
Thanks,
Stephen
2012/12/30 Stephen Frost <sfrost@snowman.net>:
* Peter Geoghegan (peter@2ndquadrant.com) wrote:
On 30 December 2012 03:32, Stephen Frost <sfrost@snowman.net> wrote:
Err. I intended to say "I really don't think what I sketched out, or
something similar, would be that unlikely to happen", or something along
those lines. Apologies for the confusion.Almost anything can be misused.
I agree, almost anything can be. The 'misuse' in this case, however, is
in expecting the information returned to be useful in a deterministic
and consistent manner, as it's being returned in a programmatic fashion.If you're going to insist that I hack a bunch of mechanism into this
patch so that the user can unambiguously identify each constraint
object, I'll do that.This is the part that I'm having trouble wrapping my head around- what's
the additional complexity here? If we have the OID of the constraint,
we should be able to unambiguoulsy return information which allows a
user to get back to that specific constraint and OID.However, that's more code, and more complexity,
that will have to be documented, for just next to no practical benefit
that I can see.I've certainly seen cases where constraints are duplicated by name
across schemas. I would imagine it could happen across tables in the
same schema also. I just don't like this notion of returning something
ambiguous to the user and worry that they'd accept it as deterministic
and dependable, regardless of the documentation. There's a certain
amount of "read the docs, but also look at what information you really
get back", which I think is, in general, a good thing, but it does mean
individuals might come to believe that they can depend on the constraint
name being unique in all cases.As a side-note, I've recently run into more cases than I care to think
about where the 63-character limit on constraint names has been a
problem- because we're trying to ensure that we create an unambiguous
constraint name, and that's *with* various name shortening techniques
being used. The discussion about having longer values possible for the
'name' data type was on a different thread and should probably stay
there, but I bring it up because it has some impact on the possibility
of name collisions which is relevent to this discussion.
so - cannot be a solution define CONSTRAINT_TABLE field - constaint
names in table are unique.
sure there is a problem with long names, but I am thinking so it has
solution - when constraint has no name, then we can try to generate
name, and when this name is longer than 63 chars, then CREATE
STATEMENT fails and users should be define name manually - this
feature should be disabled by guc due compatibility issues.
Regards
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
so - cannot be a solution define CONSTRAINT_TABLE field - constaint
names in table are unique.
Adding a table column, and a schema column, would be ideal. Those would
all be part of the PK and not null'able, but then we wouldn't
necessairly always return all that information- that's the situation
that we've been talking about.
sure there is a problem with long names, but I am thinking so it has
solution - when constraint has no name, then we can try to generate
name, and when this name is longer than 63 chars, then CREATE
STATEMENT fails and users should be define name manually - this
feature should be disabled by guc due compatibility issues.
CREATE doesn't fail if the name is too long today, it truncates it
instead. I continue to feel that's also the wrong thing to do.
Thanks,
Stephen
2012/12/30 Stephen Frost <sfrost@snowman.net>:
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
so - cannot be a solution define CONSTRAINT_TABLE field - constaint
names in table are unique.Adding a table column, and a schema column, would be ideal. Those would
all be part of the PK and not null'able, but then we wouldn't
necessairly always return all that information- that's the situation
that we've been talking about.sure there is a problem with long names, but I am thinking so it has
solution - when constraint has no name, then we can try to generate
name, and when this name is longer than 63 chars, then CREATE
STATEMENT fails and users should be define name manually - this
feature should be disabled by guc due compatibility issues.CREATE doesn't fail if the name is too long today, it truncates it
instead. I continue to feel that's also the wrong thing to do.
probably it is far to ideal - but I have not any feedback about
related problems in production.
Regards
Pavel Stehule
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Fri, Dec 28, 2012 at 1:21 PM, Peter Geoghegan <peter@2ndquadrant.com> wrote:
Now, as to the question of whether we need to make sure that
everything is always fully qualified - I respectfully disagree with
Stephen, and maintain my position set out in the last round of
feedback [1], which is that we don't need to fully qualify everything,
because *for the purposes of error handling*, which is what I think we
should care about, these fields are sufficient:+ column_name text, + table_name text, + constraint_name text, + schema_name text,If you use the same constraint name everywhere, you might get the same
error message. The situations in which this scheme might fall down are
too implausible for me to want to bloat up all those ereport sites any
further (something that Stephen also complained about).I think that the major outstanding issues are concerning whether or
not I have the API here right. I make explicit guarantees as to the
availability of certain fields for certain errcodes (a small number of
"Class 23 - Integrity Constraint Violation" codes). No one has really
said anything about that, though I think it's important.I also continue to think that we shouldn't have "routine_name",
"routine_schema" and "trigger_name" fields - I think it's wrong-headed
to have an exception handler magically act on knowledge about where
the exception came from that has been baked into the exception - is
there any sort of precedent for this? Pavel disagrees here. Again, I
defer to others.
I don't really agree with this. To be honest, my biggest concern
about this patch is that it will make it take longer to report an
error. At least in the cases where these additional fields are
included, it will take CPU time to populate those fields, and maybe
there will be some overhead even in the cases where they aren't even
used (although I'd expect that to be too little to measure). Now,
maybe that doesn't matter in the case where the error is being
reported back to the client, because the overhead of shipping packets
across even a local socket likely dwarfs the overhead, but I think it
matters a lot where you are running a big exception-catching loop.
That is already painfully slow, and -1 from me on anything that makes
it significantly slower. I have a feeling this isn't the first time
I'm ranting about this topic in relation to this patch, so apologies
if this is old news.
But if we decide that there is no performance issue or that we don't
care about the hit there, then I think Stephen and Pavel are right to
want a large amount of very precise detail. What's the use case for
this feature? Presumably, it's for people for whom parsing the error
message is not a practical option, so either they textual error
message doesn't identify the target object sufficiently precisely, and
they want to make sure they know what it applies to; or else it's for
people who want any error that applies to a table to be handled the
same way (without worrying about exactly which error they have got).
Imagine, for example, someone writing a framework that will be used as
a basis for many different applications. It might want to do
something, like, I don't know, update the comment on a table every
time an error involving that table occurs. Clearly, precise
identification of the table is a must. In a particular application
development environment, it's reasonable to rely on users to name
things sensibly, but if you're shipping a tool that might be dropped
into any arbitrary environment, that's significantly more dangerous.
Similarly, for a function-related error, you'd need something like
that looks like the output of pg_proc.oid::regprocedure, or individual
fields with the various components of that output. That sounds like
routine_name et. al.
I'm not happy about the idea of shipping OIDs back to the client.
OIDs are too user-visible as it is; we should try to make them less
not moreso.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Sat, Dec 29, 2012 at 4:30 PM, Peter Geoghegan <peter@2ndquadrant.com> wrote:
Ascertaining the identity of the object in question perfectly
unambiguously, so that you can safely do something like lookup a
comment on the object, seems like something way beyond what I'd
envisioned for this feature. Why should the comment be useful in an
error handler anyway? At best, that seems like a nice-to-have extra to
me. The vast majority are not even going to think about the ambiguity
that may exist. They'll just write:if (constraint_name == "upc")
MessageBox("That is not a valid barcode.");
The people who are content to do that don't need this patch at all.
They can just apply a regexp to the message that comes back from the
server and then set constraint_name based on what pops out of the
regex. And then do just what you did there.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> schrieb:
On Sat, Dec 29, 2012 at 4:30 PM, Peter Geoghegan
<peter@2ndquadrant.com> wrote:Ascertaining the identity of the object in question perfectly
unambiguously, so that you can safely do something like lookup a
comment on the object, seems like something way beyond what I'd
envisioned for this feature. Why should the comment be useful in an
error handler anyway? At best, that seems like a nice-to-have extrato
me. The vast majority are not even going to think about the ambiguity
that may exist. They'll just write:if (constraint_name == "upc")
MessageBox("That is not a valid barcode.");The people who are content to do that don't need this patch at all.
They can just apply a regexp to the message that comes back from the
server and then set constraint_name based on what pops out of the
regex. And then do just what you did there.
Easier said than done if you're dealing with pg installations with different lc_messages...
Andres
---
Please excuse the brevity and formatting - I am writing this on my mobile phone.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
"anarazel@anarazel.de" <andres@anarazel.de> writes:
Robert Haas <robertmhaas@gmail.com> schrieb:
The people who are content to do that don't need this patch at all.
They can just apply a regexp to the message that comes back from the
server and then set constraint_name based on what pops out of the
regex. And then do just what you did there.
Easier said than done if you're dealing with pg installations with different lc_messages...
Exactly. To my mind, the *entire* point of this patch is to remove the
need for people to try to dig information out of potentially-localized
message strings. It's not clear to me that we have to strain to provide
information that isn't in the currently-reported messages --- we are
only trying to make it easier for client-side code to extract the
information it's likely to need.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 4 January 2013 18:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Exactly. To my mind, the *entire* point of this patch is to remove the
need for people to try to dig information out of potentially-localized
message strings. It's not clear to me that we have to strain to provide
information that isn't in the currently-reported messages --- we are
only trying to make it easier for client-side code to extract the
information it's likely to need.
It seems that we're in agreement, then. I'll prepare a version of the
patch very similar to the one I previously posted, but with some
caveats about how reliably the values can be used. I think that that
should be fine.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 4 January 2013 17:10, Robert Haas <robertmhaas@gmail.com> wrote:
I don't really agree with this. To be honest, my biggest concern
about this patch is that it will make it take longer to report an
error. At least in the cases where these additional fields are
included, it will take CPU time to populate those fields, and maybe
there will be some overhead even in the cases where they aren't even
used (although I'd expect that to be too little to measure). Now,
maybe that doesn't matter in the case where the error is being
reported back to the client, because the overhead of shipping packets
across even a local socket likely dwarfs the overhead, but I think it
matters a lot where you are running a big exception-catching loop.
That is already painfully slow, and -1 from me on anything that makes
it significantly slower. I have a feeling this isn't the first time
I'm ranting about this topic in relation to this patch, so apologies
if this is old news.
You already brought it up. I measured the additional overhead, and
found it to be present, but inconsequential. That was with Pavel's
version of the patch, that had many more fields then what I've
proposed. Please take a look upthread.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
2013/1/4 Robert Haas <robertmhaas@gmail.com>:
On Fri, Dec 28, 2012 at 1:21 PM, Peter Geoghegan <peter@2ndquadrant.com> wrote:
Now, as to the question of whether we need to make sure that
everything is always fully qualified - I respectfully disagree with
Stephen, and maintain my position set out in the last round of
feedback [1], which is that we don't need to fully qualify everything,
because *for the purposes of error handling*, which is what I think we
should care about, these fields are sufficient:+ column_name text, + table_name text, + constraint_name text, + schema_name text,If you use the same constraint name everywhere, you might get the same
error message. The situations in which this scheme might fall down are
too implausible for me to want to bloat up all those ereport sites any
further (something that Stephen also complained about).I think that the major outstanding issues are concerning whether or
not I have the API here right. I make explicit guarantees as to the
availability of certain fields for certain errcodes (a small number of
"Class 23 - Integrity Constraint Violation" codes). No one has really
said anything about that, though I think it's important.I also continue to think that we shouldn't have "routine_name",
"routine_schema" and "trigger_name" fields - I think it's wrong-headed
to have an exception handler magically act on knowledge about where
the exception came from that has been baked into the exception - is
there any sort of precedent for this? Pavel disagrees here. Again, I
defer to others.I don't really agree with this. To be honest, my biggest concern
about this patch is that it will make it take longer to report an
error. At least in the cases where these additional fields are
included, it will take CPU time to populate those fields, and maybe
there will be some overhead even in the cases where they aren't even
used (although I'd expect that to be too little to measure). Now,
maybe that doesn't matter in the case where the error is being
reported back to the client, because the overhead of shipping packets
across even a local socket likely dwarfs the overhead, but I think it
matters a lot where you are running a big exception-catching loop.
That is already painfully slow, and -1 from me on anything that makes
it significantly slower. I have a feeling this isn't the first time
I'm ranting about this topic in relation to this patch, so apologies
if this is old news.
We did these tests independently - Peter and me with same result - in
use cases developed for highlighting impact of this patch you can see
some slowdown - if I remember well - less than 3% - and it raised
exceptions really intensively - and It can be visible only from stored
procedures environment - if somebody use it from client side, then
impact is zero.
But if we decide that there is no performance issue or that we don't
care about the hit there, then I think Stephen and Pavel are right to
want a large amount of very precise detail. What's the use case for
this feature? Presumably, it's for people for whom parsing the error
message is not a practical option, so either they textual error
message doesn't identify the target object sufficiently precisely, and
they want to make sure they know what it applies to; or else it's for
people who want any error that applies to a table to be handled the
same way (without worrying about exactly which error they have got).
Imagine, for example, someone writing a framework that will be used as
a basis for many different applications. It might want to do
something, like, I don't know, update the comment on a table every
time an error involving that table occurs. Clearly, precise
identification of the table is a must. In a particular application
development environment, it's reasonable to rely on users to name
things sensibly, but if you're shipping a tool that might be dropped
into any arbitrary environment, that's significantly more dangerous.Similarly, for a function-related error, you'd need something like
that looks like the output of pg_proc.oid::regprocedure, or individual
fields with the various components of that output. That sounds like
routine_name et. al.
Probably we don't need all fields mentioned in ANSI SQL, because some
from these fields has no sense in pg, but routine name and trigger
name is really basic for some little bit more sophisticate work with
exception on PL level.
Regards
Pavel
I'm not happy about the idea of shipping OIDs back to the client.
OIDs are too user-visible as it is; we should try to make them less
not moreso.--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/4 Peter Geoghegan <peter@2ndquadrant.com>:
On 4 January 2013 18:07, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Exactly. To my mind, the *entire* point of this patch is to remove the
need for people to try to dig information out of potentially-localized
message strings. It's not clear to me that we have to strain to provide
information that isn't in the currently-reported messages --- we are
only trying to make it easier for client-side code to extract the
information it's likely to need.It seems that we're in agreement, then. I'll prepare a version of the
patch very similar to the one I previously posted, but with some
caveats about how reliably the values can be used. I think that that
should be fine.
is there agreement of routine_name and trigger_name fields?
Regards
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5 January 2013 16:56, Pavel Stehule <pavel.stehule@gmail.com> wrote:
It seems that we're in agreement, then. I'll prepare a version of the
patch very similar to the one I previously posted, but with some
caveats about how reliably the values can be used. I think that that
should be fine.is there agreement of routine_name and trigger_name fields?
Well, Tom and I are both opposed to including those fields. Peter E
seemed to support it in some way, but didn't respond to Tom's
criticisms (which were just a restatement of my own). So, it seems to
me that we're not going to do that, assuming nothing changes.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/5 Peter Geoghegan <peter@2ndquadrant.com>:
On 5 January 2013 16:56, Pavel Stehule <pavel.stehule@gmail.com> wrote:
It seems that we're in agreement, then. I'll prepare a version of the
patch very similar to the one I previously posted, but with some
caveats about how reliably the values can be used. I think that that
should be fine.is there agreement of routine_name and trigger_name fields?
Well, Tom and I are both opposed to including those fields. Peter E
seemed to support it in some way, but didn't respond to Tom's
criticisms (which were just a restatement of my own). So, it seems to
me that we're not going to do that, assuming nothing changes.
if I understand well Robert Haas is for including these fields - so
score is still 2:2 - but this is not a match :)
I have no more new arguments for these fields - yes, there are no change
Pavel
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 5 January 2013 18:06, Pavel Stehule <pavel.stehule@gmail.com> wrote:
I have no more new arguments for these fields - yes, there are no change
Attached revision is similar to the eelog4.diff patch (i.e. it has
only a few fields, most of which are present within the original error
message). Note that "schema_name" remains, because it was so easy to
keep it around since we pass the new infrastructure a Relation (which
also serves to sanitize). There are a few differences:
* All logging infrastructure has been removed. If the sole purpose of
this patch is to facilitate client code, it doesn't make sense to
redundantly log what is indicated by an error message. My feelings on
log-scraping tools are well known (that they shouldn't need to exist,
even if they currently do), so it won't surprise anyone to learn that
I think it's totally wrong-headed to actively facilitate them.
* Miscellaneous minor tweaks.
I think it's probably fine that we don't go into detail about how
ERRCODE_NOT_NULL_VIOLATION errors won't have a schema_name and
table_name for domains - it's obvious that they don't, because a name
cannot exist in principle. This is documented here, for example:
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",
We also go to extra lengths to get a table_name for certain
domain-related ereport sites.
So, with the question of what fields to include and whether constraint
name needs to be unambiguously resolvable addressed (I think), it
appears that I've brought this one as far as I can. I'd still like to
get input from a Perl hacker, but I think a committer needs to pick
this up now.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
Attachments:
eelog6.patchapplication/octet-stream; name=eelog6.patchDownload
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/-/—/;
+
+ 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]+)?/;
Hello
2012/12/29 Stephen Frost <sfrost@snowman.net>:
* Pavel Stehule (pavel.stehule@gmail.com) wrote:
it is a problem of this patch or not consistent constraints implementation ?
Not sure, but I don't think it matters. You can blame the constraint
implementation, but that doesn't change my feelings about what we need
before we can accept a patch like this. Providing something which works
only part of the time and then doesn't work for very unclear reasons
isn't a good idea. Perhaps we need to fix the constraint implementation
and perhaps we need to fix the error information being returned, or most
likely we have to fix both, it doesn't change that we need to do
something more than just ignore this problem.
so we have to solve this issue first. Please, can you do resume, what
is and where is current constraint implementation raise
strange/unexpected messages?
one question
when we will fix constraints, maybe we can use some infrastructure for
enhanced error fields. What about partial commit now - just necessary
infrastructure without modification of other code - I am thinking so
there is agreement on new fields: column_name, table_name,
schema_name, constraint_name and constraint_schema?
Regards
Pavel
Thanks,
Stephen
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 13 January 2013 06:13, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Not sure, but I don't think it matters. You can blame the constraint
implementation, but that doesn't change my feelings about what we need
before we can accept a patch like this. Providing something which works
only part of the time and then doesn't work for very unclear reasons
isn't a good idea. Perhaps we need to fix the constraint implementation
and perhaps we need to fix the error information being returned, or most
likely we have to fix both, it doesn't change that we need to do
something more than just ignore this problem.so we have to solve this issue first. Please, can you do resume, what
is and where is current constraint implementation raise
strange/unexpected messages?
I felt that this was quite unnecessary because of the limited scope of
the patch, and because this raises thorny issues of both semantics and
implementation. Tom agreed with this general view - after all, this
patch exists for the express purpose of having a well-principled way
of obtaining the various fields across lc_messages settings. So I
don't see that we have to do anything about making a constraint_schema
available.
--
Peter Geoghegan http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training and Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Geoghegan <peter@2ndquadrant.com> writes:
I felt that this was quite unnecessary because of the limited scope of
the patch, and because this raises thorny issues of both semantics and
implementation. Tom agreed with this general view - after all, this
patch exists for the express purpose of having a well-principled way
of obtaining the various fields across lc_messages settings. So I
don't see that we have to do anything about making a constraint_schema
available.
Or in other words, there are two steps here: first, create
infrastructure to expose the fields that we already provide within the
regular message text; then two, consider adding new fields. The first
part of that is a good deal less controversial than the second, so let's
go ahead and get that part committed.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Geoghegan <peter@2ndquadrant.com> writes:
[ eelog6.patch ]
So, with the question of what fields to include and whether constraint
name needs to be unambiguously resolvable addressed (I think), it
appears that I've brought this one as far as I can. I'd still like to
get input from a Perl hacker, but I think a committer needs to pick
this up now.
I started to look this patch over. I think we can get to something
committable from here, but I'm having a problem with the concept that
we're going to "guarantee" anything about which additional fields might
be available for any given SQLSTATE. This is already quite broken for
the ERRCODE_NOT_NULL_VIOLATION case, and it's not hard to envision that
there will be other inconsistencies in future, even without the issue
that third-party code might use these SQLSTATEs without having gotten
the memo about additional fields.
If we were doing this from scratch we could perhaps fix that by using,
eg, two different SQLSTATEs for the column-not-null and
something-else-not-null cases. But it's probably too late to change the
SQLSTATEs for existing error cases; applications might be depending on
those. Anyway our choice of SQLSTATE is often constrained by the
standard.
I'm inclined to remove the "requirements" business altogether and just
document that these fields may be supplied, or words to that effect.
In practice, an application is going to know whether the particular
error case it's concerned about has the auxiliary fields, I should think.
We also go to extra lengths to get a table_name for certain
domain-related ereport sites.
A lot of that looks pretty broken to me, eg the changes in
ExecEvalCoerceToDomain are just hokum. (Even if the expression is
executing in a statement that has a result table, there's no very good
reason to think that the error has anything to do with the result
table.) It's possible we could restructure things so that coercions
performed as part of an assignment to a specific table column are
processed differently from other coercions, and have knowledge available
about what the target table/column is. But it would be a pretty
invasive change for limited benefit.
BTW, one thing that struck me in a quick look-through is that the
ERRCODE_FOREIGN_KEY_VIOLATION patches seem to inconsistently send
either the PK or FK rel as the "errtable". Is this really per spec?
I'd have sort of expected that the reported table ought to be the one
that the constraint belongs to, namely the FK table.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 26 January 2013 22:36, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I started to look this patch over. I think we can get to something
committable from here, but I'm having a problem with the concept that
we're going to "guarantee" anything about which additional fields might
be available for any given SQLSTATE. This is already quite broken for
the ERRCODE_NOT_NULL_VIOLATION case, and it's not hard to envision that
there will be other inconsistencies in future, even without the issue
that third-party code might use these SQLSTATEs without having gotten
the memo about additional fields.
I'm inclined to remove the "requirements" business altogether and just
document that these fields may be supplied, or words to that effect.
In practice, an application is going to know whether the particular
error case it's concerned about has the auxiliary fields, I should think.
I think we may be talking at cross purposes here. Guarantee may have
been too strong a word, or the wrong word entirely. All that I really
want here is for there to be a coding standard instituted, so that in
future client code will not be broken by a failure to include some
field in a new ereport site that related to what is effectively the
same error as an existing ereport site. I'm sure you'll agree that
subtly breaking client code when refactoring Postgres is unacceptable.
I thought that the best way to do that was at the errcode granularity,
and am perfectly willing to pull back on the set of fields where this
coding standard applies in respect of certain errcodes. I think we can
afford to be very conservative about limiting the scope of that
guarantee.
We also go to extra lengths to get a table_name for certain
domain-related ereport sites.A lot of that looks pretty broken to me, eg the changes in
ExecEvalCoerceToDomain are just hokum. (Even if the expression is
executing in a statement that has a result table, there's no very good
reason to think that the error has anything to do with the result
table.)
I must admit that that particular change wasn't very well thought out.
I was trying to appease Stephen, who seemed to think that having a
table_name where it was in principle available was particularly
important. I myself do not, and the current structure of the relevant
code (and difficulty in providing a table_name consistently) in a
sense reflects that. I'm inclined to agree that it is not worth
pursuing further.
BTW, one thing that struck me in a quick look-through is that the
ERRCODE_FOREIGN_KEY_VIOLATION patches seem to inconsistently send
either the PK or FK rel as the "errtable". Is this really per spec?
I'd have sort of expected that the reported table ought to be the one
that the constraint belongs to, namely the FK table.
Personally, on the face of it I'd expect the "inconsistency" to simply
reflect the fact that the error related to the referencing table or
referenced table. Pavel's original patch followed the same convention
(though it also had a constraint_table field). I'm having a hard time
figuring out the standards intent here, and I'm not sure that we
should even care, because that applies on to GET DIAGNOSTICS, which
isn't really the same thing as what we have here. I defer to you,
though - it's not as if I feel too strongly about it.
As I've said, the vast majority of the value likely to be delivered by
this patch comes from the constraint_name field. That's the really
compelling one, to my mind.
--
Regards,
Peter Geoghegan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
From my perspective - the core of this patch has two parts
a) necessary infrastructure
b) enhancing current ereport calls
@a point is important for me and plpgsql coders, because it allows
using these fields in custom PL/pgSQL exception - and errors
processing in this language can be more structurable and comfortable.
But now we are too late - and this part can be commited probably in
9.4 - although I have this patch prepared.
@b is important for application users - but there was agreement so we
will coverage exceptions step by step - so in this context we can drop
support for domains now.
2013/1/26 Tom Lane <tgl@sss.pgh.pa.us>:
Peter Geoghegan <peter@2ndquadrant.com> writes:
[ eelog6.patch ]
So, with the question of what fields to include and whether constraint
name needs to be unambiguously resolvable addressed (I think), it
appears that I've brought this one as far as I can. I'd still like to
get input from a Perl hacker, but I think a committer needs to pick
this up now.I started to look this patch over. I think we can get to something
committable from here, but I'm having a problem with the concept that
we're going to "guarantee" anything about which additional fields might
be available for any given SQLSTATE. This is already quite broken for
the ERRCODE_NOT_NULL_VIOLATION case, and it's not hard to envision that
there will be other inconsistencies in future, even without the issue
that third-party code might use these SQLSTATEs without having gotten
the memo about additional fields.
If we were doing this from scratch we could perhaps fix that by using,
eg, two different SQLSTATEs for the column-not-null and
something-else-not-null cases. But it's probably too late to change the
SQLSTATEs for existing error cases; applications might be depending on
those. Anyway our choice of SQLSTATE is often constrained by the
standard.I'm inclined to remove the "requirements" business altogether and just
document that these fields may be supplied, or words to that effect.
In practice, an application is going to know whether the particular
error case it's concerned about has the auxiliary fields, I should think.We also go to extra lengths to get a table_name for certain
domain-related ereport sites.A lot of that looks pretty broken to me, eg the changes in
ExecEvalCoerceToDomain are just hokum. (Even if the expression is
executing in a statement that has a result table, there's no very good
reason to think that the error has anything to do with the result
table.) It's possible we could restructure things so that coercions
performed as part of an assignment to a specific table column are
processed differently from other coercions, and have knowledge available
about what the target table/column is. But it would be a pretty
invasive change for limited benefit.
BTW, one thing that struck me in a quick look-through is that the
ERRCODE_FOREIGN_KEY_VIOLATION patches seem to inconsistently send
either the PK or FK rel as the "errtable". Is this really per spec?
I'd have sort of expected that the reported table ought to be the one
that the constraint belongs to, namely the FK table.
Today I'll to spec
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hello
Personally, on the face of it I'd expect the "inconsistency" to simply
reflect the fact that the error related to the referencing table or
referenced table. Pavel's original patch followed the same convention
(though it also had a constraint_table field). I'm having a hard time
figuring out the standards intent here, and I'm not sure that we
should even care, because that applies on to GET DIAGNOSTICS, which
isn't really the same thing as what we have here. I defer to you,
though - it's not as if I feel too strongly about it.
These fields will be reused in GET DIAGNOSTICS statement in PL/pgSQL.
It is was primary goal.
Regards
Pavel
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Geoghegan <peter.geoghegan86@gmail.com> writes:
On 26 January 2013 22:36, Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm inclined to remove the "requirements" business altogether and just
document that these fields may be supplied, or words to that effect.
I think we may be talking at cross purposes here. Guarantee may have
been too strong a word, or the wrong word entirely. All that I really
want here is for there to be a coding standard instituted, so that in
future client code will not be broken by a failure to include some
field in a new ereport site that related to what is effectively the
same error as an existing ereport site. I'm sure you'll agree that
subtly breaking client code when refactoring Postgres is unacceptable.
[ shrug... ] If you have a way of making a guarantee that future
versions introduce no new bugs, patent it and you'll soon have all the
money in the world.
It's conceivable that we could adapt some static checker to look for
ereport calls that mention particular ERRCODEs and lack particular
helper functions, but even that wouldn't be a cast-iron guarantee
because of the possibility of call sites using non-constant errcode
values. It'd probably be good enough in practice though. However,
this patch is not that, and mere documentation isn't going to buy a
thing here IMO. Especially not user-facing documentation, as opposed
to something that might be in a developers' face when he's
copying-and-pasting code somewhere. This patch didn't even touch the
one place in the documentation that might be somewhat useful from a
developer's standpoint, which is 49.2. Reporting Errors Within the
Server.
A lot of that looks pretty broken to me, eg the changes in
ExecEvalCoerceToDomain are just hokum. (Even if the expression is
executing in a statement that has a result table, there's no very good
reason to think that the error has anything to do with the result
table.)
I must admit that that particular change wasn't very well thought out.
I was trying to appease Stephen, who seemed to think that having a
table_name where it was in principle available was particularly
important.
Well, if we can get an accurate and relevant value then I'm all for
adding it. But field values that are misleading or even plain wrong
in corner cases are not an improvement.
At some point we might want to undertake a round of refactoring that
makes this type of information available; and once we do, we'd probably
want to expose it in the regular message texts not just the auxiliary
fields. I think that sort of work is out-of-scope for this patch
though. IMO what we should be doing here is getting the infrastructure
in place, and then decorating some basic set of messages with aux info
in places where not a lot of new code is needed to make that happen.
Extending the decoration beyond that is material for future work.
BTW, one thing that struck me in a quick look-through is that the
ERRCODE_FOREIGN_KEY_VIOLATION patches seem to inconsistently send
either the PK or FK rel as the "errtable". Is this really per spec?
I'd have sort of expected that the reported table ought to be the one
that the constraint belongs to, namely the FK table.
Personally, on the face of it I'd expect the "inconsistency" to simply
reflect the fact that the error related to the referencing table or
referenced table. Pavel's original patch followed the same convention
(though it also had a constraint_table field). I'm having a hard time
figuring out the standards intent here, and I'm not sure that we
should even care, because that applies on to GET DIAGNOSTICS, which
isn't really the same thing as what we have here. I defer to you,
though - it's not as if I feel too strongly about it.
A large part of the argument for doing this patch at all is to satisfy
the standard's requirements for information reported to a client.
(I believe that GET DIAGNOSTICS is in the end a client-side requirement,
ie in principle ecpg or similar library should be able to implement
it based on what the server reports.) So to the extent that the spec
defines what should be in the fields, we need to follow that.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 27 January 2013 18:57, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Geoghegan <peter.geoghegan86@gmail.com> writes:
I think we may be talking at cross purposes here. Guarantee may have
been too strong a word, or the wrong word entirely. All that I really
want here is for there to be a coding standard instituted, so that in
future client code will not be broken by a failure to include some
field in a new ereport site that related to what is effectively the
same error as an existing ereport site. I'm sure you'll agree that
subtly breaking client code when refactoring Postgres is unacceptable.[ shrug... ] If you have a way of making a guarantee that future
versions introduce no new bugs, patent it and you'll soon have all the
money in the world.
Is that kind of sarcasm really necessary?
Certain sets of ereport call sites within Postgres relate to either
very similar errors (i.e. ereports that have the same errcode) or
arguably identical errors (e.g. the various ERRCODE_CHECK_VIOLATION
sites within nbtinsert.c, that have identical error texts). It would
seem quite unfortunate to me if client code was to break based only on
an internal implementation detail that differed between Postgres
versions, or based on the current phase of the moon. This is the kind
of problem that I'd hoped to prevent by documenting a set of required
fields for a small number of errcodes going forward.
Now, you could take the view that all of this is only for the purposes
of error handling, and it isn't terribly critical that things work
very reliably. That isn't my view, though.
It's conceivable that we could adapt some static checker to look for
ereport calls that mention particular ERRCODEs and lack particular
helper functions, but even that wouldn't be a cast-iron guarantee
because of the possibility of call sites using non-constant errcode
values. It'd probably be good enough in practice though.
I thought about ways of doing that, but it didn't seem worth pursuing right now.
However, this patch is not that, and mere documentation isn't going to buy a
thing here IMO. Especially not user-facing documentation, as opposed
to something that might be in a developers' face when he's
copying-and-pasting code somewhere. This patch didn't even touch the
one place in the documentation that might be somewhat useful from a
developer's standpoint, which is 49.2. Reporting Errors Within the
Server.
Well, an entry should probably be added to 49.2 too, then. Why should
documentation (of whatever kind deemed appropriate) not buy a thing?
Don't we want to prevent the kind of problems that I describe above?
How are people supposed to know about something that isn't written
down anywhere? Surely documentation is better than nothing?
At some point we might want to undertake a round of refactoring that
makes this type of information available; and once we do, we'd probably
want to expose it in the regular message texts not just the auxiliary
fields. I think that sort of work is out-of-scope for this patch
though. IMO what we should be doing here is getting the infrastructure
in place, and then decorating some basic set of messages with aux info
in places where not a lot of new code is needed to make that happen.
Extending the decoration beyond that is material for future work.
Fair enough.
--
Regards,
Peter Geoghegan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Geoghegan <peter.geoghegan86@gmail.com> writes:
On 26 January 2013 22:36, Tom Lane <tgl@sss.pgh.pa.us> wrote:
BTW, one thing that struck me in a quick look-through is that the
ERRCODE_FOREIGN_KEY_VIOLATION patches seem to inconsistently send
either the PK or FK rel as the "errtable". Is this really per spec?
I'd have sort of expected that the reported table ought to be the one
that the constraint belongs to, namely the FK table.
Personally, on the face of it I'd expect the "inconsistency" to simply
reflect the fact that the error related to the referencing table or
referenced table.
I looked in the spec a bit, and what I found seems to support my
recollection about this. In SQL99, it's 19.1 <get diagnostics
statement> that defines the usage of these fields, and I see
f) If the value of RETURNED_SQLSTATE corresponds to integrity
constraint violation, transaction rollback - integrity
constraint violation, or a triggered data change violation
that was caused by a violation of a referential constraint,
then:
i) The values of CONSTRAINT_CATALOG and CONSTRAINT_SCHEMA are
the <catalog name> and the <unqualified schema name> of the
<schema name> of the schema containing the constraint or
assertion. The value of CONSTRAINT_NAME is the <qualified
identifier> of the constraint or assertion.
ii) Case:
1) If the violated integrity constraint is a table
constraint, then the values of CATALOG_NAME, SCHEMA_
NAME, and TABLE_NAME are the <catalog name>, the
<unqualified schema name> of the <schema name>, and
the <qualified identifier> or <local table name>,
respectively, of the table in which the table constraint
is contained.
The notion of a constraint being "contained" in a table is a bit weird;
I guess they mean contained in the table's schema description. Anyway
it seems fairly clear to me that it's supposed to be the table that the
constraint belongs to, and that has to be the FK table not the PK table.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Peter Geoghegan <peter.geoghegan86@gmail.com> writes:
On 27 January 2013 18:57, Tom Lane <tgl@sss.pgh.pa.us> wrote:
However, this patch is not that, and mere documentation isn't going to buy a
thing here IMO. Especially not user-facing documentation, as opposed
to something that might be in a developers' face when he's
copying-and-pasting code somewhere. This patch didn't even touch the
one place in the documentation that might be somewhat useful from a
developer's standpoint, which is 49.2. Reporting Errors Within the
Server.
Well, an entry should probably be added to 49.2 too, then. Why should
documentation (of whatever kind deemed appropriate) not buy a thing?
Don't we want to prevent the kind of problems that I describe above?
How are people supposed to know about something that isn't written
down anywhere? Surely documentation is better than nothing?
I don't think I said or implied that we should not have any
documentation about this. What I'm trying to say is that I find this
approach to documentation unhelpful, both to users and developers.
It's based on the notion that there's a rigorous connection between
particular SQLSTATEs and the auxiliary fields that should be provided;
an assumption already proven false within the very tiny set of SQLSTATEs
dealt with in this first patch. That's a connection that we probably
could have made valid if we'd been assigning SQLSTATEs with that idea
in mind from the beginning, but we didn't and now it's too late. Future
development will almost surely expose even more inconsistencies, not be
able to get rid of them.
I think we'd be better off providing docs that say "this is what this
auxiliary field means, if it's provided" and then encourage developers
to provide it wherever that meaning applies. We can at the same time
note something like "As of Postgres 9.3, only errors in Class 23 provide
this information", so that users (a) don't have unrealistic expectations
about what's provided, and (b) don't get the idea that the current set
of auxiliary fields is fixed. But a SQLSTATE-by-SQLSTATE listing
doesn't seem to me to be very helpful.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
A couple more things about this patch ...
I went back through the thread and reviewed all the angst about which
fields to provide, especially whether we need CONSTRAINT_SCHEMA.
I agree with the conclusion that we don't. It's in the spec because
the spec supposes that CONSTRAINT_SCHEMA+CONSTRAINT_NAME is a unique
identification for a constraint --- but it is not in Postgres, for
historical reasons that we aren't going to revisit in this patch.
Rather what we've got is that constraints are uniquely named among
those associated with a table, or with a domain. So the correct
unique key for a table constraint is table schema + table name +
constraint name, whereas for a domain constraint it's domain schema +
domain name + constraint name. The current patch provides sufficient
information to uniquely identify a table constraint, but not so much
domain constraints. Should we fix that? I think it'd be legitimate
to re-use SCHEMA_NAME for domain schema, but we'd need a new nonstandard
field DOMAIN_NAME (or maybe better DATATYPE_NAME) if we want to fix it.
Do we want to add that now?
If we do fix this, either now or later, it's going to require some small
support function very much like errtable() to perform the catalog
lookups for a datatype and set the ErrorData fields. The thought of
dropping such a thing into relerror.c exposes the fact that that
"module" isn't actually very modular. We could choose a different name
for the file but it'd still be pretty questionable as to what its
boundaries are. So I'm a bit inclined to drop relerror.c as such, and
go back to the early design that put errtable() and friends in
relcache.c. The errconstraint() function, not being specific to either
rels or datatypes, perhaps really does belong in elog.c.
Thoughts?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/28 Tom Lane <tgl@sss.pgh.pa.us>:
A couple more things about this patch ...
I went back through the thread and reviewed all the angst about which
fields to provide, especially whether we need CONSTRAINT_SCHEMA.
I agree with the conclusion that we don't. It's in the spec because
the spec supposes that CONSTRAINT_SCHEMA+CONSTRAINT_NAME is a unique
identification for a constraint --- but it is not in Postgres, for
historical reasons that we aren't going to revisit in this patch.
Rather what we've got is that constraints are uniquely named among
those associated with a table, or with a domain. So the correct
unique key for a table constraint is table schema + table name +
constraint name, whereas for a domain constraint it's domain schema +
domain name + constraint name. The current patch provides sufficient
information to uniquely identify a table constraint, but not so much
domain constraints. Should we fix that? I think it'd be legitimate
to re-use SCHEMA_NAME for domain schema, but we'd need a new nonstandard
field DOMAIN_NAME (or maybe better DATATYPE_NAME) if we want to fix it.
Do we want to add that now?
should be for me.
one question - what do you thing about marking proprietary field with
some prefix - like PG_DOMAIN_NAME ?
If we do fix this, either now or later, it's going to require some small
support function very much like errtable() to perform the catalog
lookups for a datatype and set the ErrorData fields. The thought of
dropping such a thing into relerror.c exposes the fact that that
"module" isn't actually very modular. We could choose a different name
for the file but it'd still be pretty questionable as to what its
boundaries are. So I'm a bit inclined to drop relerror.c as such, and
go back to the early design that put errtable() and friends in
relcache.c. The errconstraint() function, not being specific to either
rels or datatypes, perhaps really does belong in elog.c.
+1
Pavel
Thoughts?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Pavel Stehule <pavel.stehule@gmail.com> writes:
2013/1/28 Tom Lane <tgl@sss.pgh.pa.us>:
... The current patch provides sufficient
information to uniquely identify a table constraint, but not so much
domain constraints. Should we fix that? I think it'd be legitimate
to re-use SCHEMA_NAME for domain schema, but we'd need a new nonstandard
field DOMAIN_NAME (or maybe better DATATYPE_NAME) if we want to fix it.
Do we want to add that now?
should be for me.
one question - what do you thing about marking proprietary field with
some prefix - like PG_DOMAIN_NAME ?
Don't particularly see the point of that. It seems quite unlikely that
the ISO committee would invent a field with the same name and a
conflicting definition. Anyway, these names aren't going to be exposed
in any non "proprietary" interfaces AFAICS. Surely we don't, for
instance, need to call the postgres_ext.h macro PG_DIAG_PG_DOMAIN_NAME.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/28 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
2013/1/28 Tom Lane <tgl@sss.pgh.pa.us>:
... The current patch provides sufficient
information to uniquely identify a table constraint, but not so much
domain constraints. Should we fix that? I think it'd be legitimate
to re-use SCHEMA_NAME for domain schema, but we'd need a new nonstandard
field DOMAIN_NAME (or maybe better DATATYPE_NAME) if we want to fix it.
Do we want to add that now?should be for me.
one question - what do you thing about marking proprietary field with
some prefix - like PG_DOMAIN_NAME ?Don't particularly see the point of that. It seems quite unlikely that
the ISO committee would invent a field with the same name and a
conflicting definition. Anyway, these names aren't going to be exposed
in any non "proprietary" interfaces AFAICS. Surely we don't, for
instance, need to call the postgres_ext.h macro PG_DIAG_PG_DOMAIN_NAME.
ok
Pavel
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 1/5/13 12:48 PM, Peter Geoghegan wrote:
is there agreement of routine_name and trigger_name fields?
Well, Tom and I are both opposed to including those fields. Peter E
seemed to support it in some way, but didn't respond to Tom's
criticisms (which were just a restatement of my own). So, it seems to
me that we're not going to do that, assuming nothing changes.
Another point, in case someone wants to revisit this in the future, is
that these fields were applied in a way that is contrary to the SQL
standard, I think.
The presented patch interpreted ROUTINE_NAME as: the error happened
while executing this function. But according to the standard, the field
is only set when the error was directly related to the function itself,
for example when calling an INSERT statement in a non-volatile function.
This is consistent with how, for example, TABLE_NAME is set when the
error is about the table, not just happened while reading the table.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 28 January 2013 21:33, Peter Eisentraut <peter_e@gmx.net> wrote:
Another point, in case someone wants to revisit this in the future, is
that these fields were applied in a way that is contrary to the SQL
standard, I think.The presented patch interpreted ROUTINE_NAME as: the error happened
while executing this function. But according to the standard, the field
is only set when the error was directly related to the function itself,
for example when calling an INSERT statement in a non-volatile function.
Right. It seems to me that ROUTINE_NAME is vastly less compelling than
the fields that are likely to be present in the committed patch. GET
DIAGNOSTICS, as implemented by DB2, allows clients /to poll/ for a
large number of fields. I'm not really interested in that myelf, but
if we were to add something in the same spirit, I think that extending
errdata to support this would not be a sensible approach.
Perhaps I'm mistaken, but I can't imagine that it would be terribly
useful to anyone (including Pavel) to have a GET DIAGNOSTICS style
ROUTINE_NAME.
--
Regards,
Peter Geoghegan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I wrote:
Rather what we've got is that constraints are uniquely named among
those associated with a table, or with a domain. So the correct
unique key for a table constraint is table schema + table name +
constraint name, whereas for a domain constraint it's domain schema +
domain name + constraint name. The current patch provides sufficient
information to uniquely identify a table constraint, but not so much
domain constraints. Should we fix that? I think it'd be legitimate
to re-use SCHEMA_NAME for domain schema, but we'd need a new nonstandard
field DOMAIN_NAME (or maybe better DATATYPE_NAME) if we want to fix it.
I have hacked up the code (but not yet the documentation) to support
this, but I found out that there's at least one place where this
definition of the field semantics is a bit awkward. The issue is that
this definition presupposes that we want to complain about a table or
a domain, never both, because we're overloading both the SCHEMA_NAME
and CONSTRAINT_NAME fields for both purposes. This is annoying in
validateDomainConstraint(), where we know the domain constraint that
we're complaining about and also the table/column containing the bad
value. We can't fill in both TABLE_NAME and DATATYPE_NAME because
they both want to set SCHEMA_NAME, and perhaps not to the same value.
Since the error report is about a domain constraint, I don't have a big
problem deciding that the domain has to win this tug-of-war. And it
could easily be argued that if we had separate fields and filled in
both, an application could get confused about whether we meant a table
constraint or a domain constraint. (As submitted, the patch would
definitely have made it look like we were complaining about a table
constraint.) But it's still annoying that we can't represent all the
info that is in the human-readable message.
I'm not sure we should allow corner cases like this to drive the design;
but if anyone has an idea about a cleaner way, let's hear it.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 1/28/13 11:08 PM, Tom Lane wrote:
The issue is that
this definition presupposes that we want to complain about a table or
a domain, never both, because we're overloading both the SCHEMA_NAME
and CONSTRAINT_NAME fields for both purposes. This is annoying in
validateDomainConstraint(), where we know the domain constraint that
we're complaining about and also the table/column containing the bad
value. We can't fill in both TABLE_NAME and DATATYPE_NAME because
they both want to set SCHEMA_NAME, and perhaps not to the same value.
I think any error should only complain about one object, in this case
the domain. The table, in this case, is more like a context stack item.
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/27 Tom Lane <tgl@sss.pgh.pa.us>:
Peter Geoghegan <peter.geoghegan86@gmail.com> writes:
On 26 January 2013 22:36, Tom Lane <tgl@sss.pgh.pa.us> wrote:
BTW, one thing that struck me in a quick look-through is that the
ERRCODE_FOREIGN_KEY_VIOLATION patches seem to inconsistently send
either the PK or FK rel as the "errtable". Is this really per spec?
I'd have sort of expected that the reported table ought to be the one
that the constraint belongs to, namely the FK table.Personally, on the face of it I'd expect the "inconsistency" to simply
reflect the fact that the error related to the referencing table or
referenced table.I looked in the spec a bit, and what I found seems to support my
recollection about this. In SQL99, it's 19.1 <get diagnostics
statement> that defines the usage of these fields, and I seef) If the value of RETURNED_SQLSTATE corresponds to integrity
constraint violation, transaction rollback - integrity
constraint violation, or a triggered data change violation
that was caused by a violation of a referential constraint,
then:i) The values of CONSTRAINT_CATALOG and CONSTRAINT_SCHEMA are
the <catalog name> and the <unqualified schema name> of the
<schema name> of the schema containing the constraint or
assertion. The value of CONSTRAINT_NAME is the <qualified
identifier> of the constraint or assertion.ii) Case:
1) If the violated integrity constraint is a table
constraint, then the values of CATALOG_NAME, SCHEMA_
NAME, and TABLE_NAME are the <catalog name>, the
<unqualified schema name> of the <schema name>, and
the <qualified identifier> or <local table name>,
respectively, of the table in which the table constraint
is contained.The notion of a constraint being "contained" in a table is a bit weird;
I guess they mean contained in the table's schema description. Anyway
it seems fairly clear to me that it's supposed to be the table that the
constraint belongs to, and that has to be the FK table not the PK table.
+1
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/28 Peter Geoghegan <peter.geoghegan86@gmail.com>:
On 28 January 2013 21:33, Peter Eisentraut <peter_e@gmx.net> wrote:
Another point, in case someone wants to revisit this in the future, is
that these fields were applied in a way that is contrary to the SQL
standard, I think.The presented patch interpreted ROUTINE_NAME as: the error happened
while executing this function. But according to the standard, the field
is only set when the error was directly related to the function itself,
for example when calling an INSERT statement in a non-volatile function.Right. It seems to me that ROUTINE_NAME is vastly less compelling than
the fields that are likely to be present in the committed patch. GET
DIAGNOSTICS, as implemented by DB2, allows clients /to poll/ for a
large number of fields. I'm not really interested in that myelf, but
if we were to add something in the same spirit, I think that extending
errdata to support this would not be a sensible approach.Perhaps I'm mistaken, but I can't imagine that it would be terribly
useful to anyone (including Pavel) to have a GET DIAGNOSTICS style
ROUTINE_NAME.
I hoped so I can use it inside exception handler
Regards
Pavel
--
Regards,
Peter Geoghegan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 29 January 2013 17:05, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Perhaps I'm mistaken, but I can't imagine that it would be terribly
useful to anyone (including Pavel) to have a GET DIAGNOSTICS style
ROUTINE_NAME.I hoped so I can use it inside exception handler
Right, but is that really any use to you if it becomes available for a
small subset of errors, specifically, errors that directly relate to
the function? You're not going to be able to use it to trace the
function where an arbitrary error occurred, if we do something
consistent with GET DIAGNOSTICS as described by the SQL standard, it
seems.
I think that what the SQL standard intends here is actually consistent
with what we're going to do with CONSTRAINT_NAME and so on. I just
happen to think it's much less interesting, but am not opposed to it
in principle (though I may oppose it in practice, if writing the
feature means bloating up errdata).
--
Regards,
Peter Geoghegan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
2013/1/29 Peter Geoghegan <peter.geoghegan86@gmail.com>:
On 29 January 2013 17:05, Pavel Stehule <pavel.stehule@gmail.com> wrote:
Perhaps I'm mistaken, but I can't imagine that it would be terribly
useful to anyone (including Pavel) to have a GET DIAGNOSTICS style
ROUTINE_NAME.I hoped so I can use it inside exception handler
Right, but is that really any use to you if it becomes available for a
small subset of errors, specifically, errors that directly relate to
the function? You're not going to be able to use it to trace the
function where an arbitrary error occurred, if we do something
consistent with GET DIAGNOSTICS as described by the SQL standard, it
seems.
in this meaning is not too useful as I expected.
I think that what the SQL standard intends here is actually consistent
with what we're going to do with CONSTRAINT_NAME and so on. I just
happen to think it's much less interesting, but am not opposed to it
in principle (though I may oppose it in practice, if writing the
feature means bloating up errdata).
I checked performance and Robert too, and there is not significant slowdown.
if I do
DROP FUNCTION inner(int);
DROP FUNCTION middle(int);
DROP FUNCTION outer();
CREATE OR REPLACE FUNCTION inner(int)
RETURNS int AS $$
BEGIN
RETURN 10/$1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION middle(int)
RETURNS int AS $$
BEGIN
RETURN inner($1);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION outer()
RETURNS int AS $$
DECLARE
text_var1 text;
BEGIN
RETURN middle(0);
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_var1 = PG_EXCEPTION_CONTEXT;
RAISE NOTICE '>>%<<', text_var1;
RETURN -1;
END;
$$ LANGUAGE plpgsql;
SELECT outer();
then output is
psql:test.psql:34: NOTICE: >>PL/pgSQL function "inner"(integer) line
3 at RETURN
PL/pgSQL function middle(integer) line 3 at RETURN
PL/pgSQL function "outer"() line 5 at RETURN<<
I have not any possibility to take information about source of
exception without parsing context - and a string with context
information can be changed, so it is not immutable and not easy
accessible. Why I need this info - sometimes when I can log some info
about handled exception I don't would log a complete context due size
and due readability.
Another idea - some adjusted parser of context message can live in GET
STACKED DIAGNOSTICS implementation - so there can be some custom field
(just for GET STACKED DIAG.) that returns expected value. But this
value should be taken from context string with parsing - that is not
nice - but possible - I did similar game in orafce.
Regards
Pavel
--
Regards,
Peter Geoghegan
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
I wrote:
Rather what we've got is that constraints are uniquely named among
those associated with a table, or with a domain. So the correct
unique key for a table constraint is table schema + table name +
constraint name, whereas for a domain constraint it's domain schema +
domain name + constraint name. The current patch provides sufficient
information to uniquely identify a table constraint, but not so much
domain constraints. Should we fix that? I think it'd be legitimate
to re-use SCHEMA_NAME for domain schema, but we'd need a new nonstandard
field DOMAIN_NAME (or maybe better DATATYPE_NAME) if we want to fix it.
Here's an updated patch (code only, sans documentation) that fixes that
and adds some other refactoring that I thought made for improvements.
I think this is ready to commit except for the documentation.
I eventually concluded that the two ALTER DOMAIN call sites in
typecmds.c should report the table/column name (with errtablecol),
even though in principle the auxiliary info ought to be about the
domain. The reason is that the name of the domain is nearly useless
here --- you probably know it already, if you're issuing an ALTER for
it. In fact, we don't even bother to provide the name of the domain
at all in either human-readable error message. On the other hand,
the location of the offending value could be useful info. So I think
usefulness should trump principle there.
regards, tom lane
Attachments:
I wrote:
Here's an updated patch (code only, sans documentation) that fixes that
and adds some other refactoring that I thought made for improvements.
I think this is ready to commit except for the documentation.
Pushed with documentation.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers