Add schema-qualified relnames in constraint error messages.
Hackers,
At the moment schemas used as single-level namespaces.
It's quite convenient in large databases.
But error messages doesn’t contain schema names.
I have done a little patch with schema-qualified relation names in error
messages that produced by foreign key constraints and triggers.
Patch and usage example are attached.
Based on real bug hunting.
Pre-reviewed by Alexander Korotkov.
Attachments:
namespaced-relname.difftext/plain; charset=US-ASCII; name=namespaced-relname.diffDownload
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index b476500..997e959 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -343,7 +343,7 @@ RI_FKey_check(TriggerData *trigdata)
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
- RelationGetRelationName(fk_rel),
+ RelationGetNamespaceQualifiedRelationName(fk_rel),
NameStr(riinfo->conname)),
errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
errtableconstraint(fk_rel,
@@ -2490,7 +2490,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
- RelationGetRelationName(fk_rel),
+ RelationGetNamespaceQualifiedRelationName(fk_rel),
NameStr(fake_riinfo.conname)),
errdetail("MATCH FULL does not allow mixing of null and nonnull key values."),
errtableconstraint(fk_rel,
@@ -2767,7 +2767,7 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("no pg_constraint entry for trigger \"%s\" on table \"%s\"",
- trigger->tgname, RelationGetRelationName(trig_rel)),
+ trigger->tgname, RelationGetNamespaceQualifiedRelationName(trig_rel)),
errhint("Remove this referential integrity trigger and its mates, then do ALTER TABLE ADD CONSTRAINT.")));
/* Find or create a hashtable entry for the constraint */
@@ -2779,14 +2779,14 @@ ri_FetchConstraintInfo(Trigger *trigger, Relation trig_rel, bool rel_is_pk)
if (riinfo->fk_relid != trigger->tgconstrrelid ||
riinfo->pk_relid != RelationGetRelid(trig_rel))
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
- trigger->tgname, RelationGetRelationName(trig_rel));
+ trigger->tgname, RelationGetNamespaceQualifiedRelationName(trig_rel));
}
else
{
if (riinfo->fk_relid != RelationGetRelid(trig_rel) ||
riinfo->pk_relid != trigger->tgconstrrelid)
elog(ERROR, "wrong pg_constraint entry for trigger \"%s\" on table \"%s\"",
- trigger->tgname, RelationGetRelationName(trig_rel));
+ trigger->tgname, RelationGetNamespaceQualifiedRelationName(trig_rel));
}
return riinfo;
@@ -3225,9 +3225,9 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
ereport(ERROR,
(errcode(ERRCODE_INTERNAL_ERROR),
errmsg("referential integrity query on \"%s\" from constraint \"%s\" on \"%s\" gave unexpected result",
- RelationGetRelationName(pk_rel),
+ RelationGetNamespaceQualifiedRelationName(pk_rel),
NameStr(riinfo->conname),
- RelationGetRelationName(fk_rel)),
+ RelationGetNamespaceQualifiedRelationName(fk_rel)),
errhint("This is most likely due to a rule having rewritten the query.")));
/*
@@ -3315,28 +3315,28 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
- RelationGetRelationName(fk_rel),
+ RelationGetNamespaceQualifiedRelationName(fk_rel),
NameStr(riinfo->conname)),
has_perm ?
errdetail("Key (%s)=(%s) is not present in table \"%s\".",
key_names.data, key_values.data,
- RelationGetRelationName(pk_rel)) :
+ RelationGetNamespaceQualifiedRelationName(pk_rel)) :
errdetail("Key is not present in table \"%s\".",
- RelationGetRelationName(pk_rel)),
+ RelationGetNamespaceQualifiedRelationName(pk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname))));
else
ereport(ERROR,
(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
errmsg("update or delete on table \"%s\" violates foreign key constraint \"%s\" on table \"%s\"",
- RelationGetRelationName(pk_rel),
+ RelationGetNamespaceQualifiedRelationName(pk_rel),
NameStr(riinfo->conname),
- RelationGetRelationName(fk_rel)),
+ RelationGetNamespaceQualifiedRelationName(fk_rel)),
has_perm ?
errdetail("Key (%s)=(%s) is still referenced from table \"%s\".",
key_names.data, key_values.data,
- RelationGetRelationName(fk_rel)) :
+ RelationGetNamespaceQualifiedRelationName(fk_rel)) :
errdetail("Key is still referenced from table \"%s\".",
- RelationGetRelationName(fk_rel)),
+ RelationGetNamespaceQualifiedRelationName(fk_rel)),
errtableconstraint(fk_rel, NameStr(riinfo->conname))));
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index fc5b9d9..a7148dc 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1809,6 +1809,22 @@ RelationDecrementReferenceCount(Relation rel)
}
/*
+ * RelationGetNamespaceQualifiedRelationName
+ * Returns fully qualified relname including schema.
+ */
+const char *
+RelationGetNamespaceQualifiedRelationName(Relation rel)
+{
+ StringInfo name = makeStringInfo();
+
+ appendStringInfoString(name, get_namespace_name_or_temp(RelationGetNamespace(rel)));
+ appendStringInfoString(name, ".");
+ appendStringInfoString(name, RelationGetRelationName(rel));
+
+ return name->data;
+}
+
+/*
* RelationClose - close an open relation
*
* Actually, we just decrement the refcount.
diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h
index ff5672d..d45dd8e 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -509,5 +509,6 @@ typedef struct ViewOptions
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
+const char *RelationGetNamespaceQualifiedRelationName(Relation rel);
#endif /* REL_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 7c88ddc..da66974 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -315,8 +315,8 @@ ALTER TABLE tmp3 add constraint tmpconstr foreign key(a) references tmp2(b) matc
ERROR: column "b" referenced in foreign key constraint does not exist
-- Try (and fail) to add constraint due to invalid data
ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full;
-ERROR: insert or update on table "tmp3" violates foreign key constraint "tmpconstr"
-DETAIL: Key (a)=(5) is not present in table "tmp2".
+ERROR: insert or update on table "public.tmp3" violates foreign key constraint "tmpconstr"
+DETAIL: Key (a)=(5) is not present in table "public.tmp2".
-- Delete failing row
DELETE FROM tmp3 where a=5;
-- Try (and succeed)
@@ -326,8 +326,8 @@ INSERT INTO tmp3 values (5,50);
-- Try NOT VALID and then VALIDATE CONSTRAINT, but fails. Delete failure then re-validate
ALTER TABLE tmp3 add constraint tmpconstr foreign key (a) references tmp2 match full NOT VALID;
ALTER TABLE tmp3 validate constraint tmpconstr;
-ERROR: insert or update on table "tmp3" violates foreign key constraint "tmpconstr"
-DETAIL: Key (a)=(5) is not present in table "tmp2".
+ERROR: insert or update on table "public.tmp3" violates foreign key constraint "tmpconstr"
+DETAIL: Key (a)=(5) is not present in table "public.tmp2".
-- Delete failing row
DELETE FROM tmp3 where a=5;
-- Try (and succeed) and repeat to show it works on already valid constraint
@@ -483,8 +483,8 @@ ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
-- Check it actually works
INSERT INTO FKTABLE VALUES(42); -- should succeed
INSERT INTO FKTABLE VALUES(43); -- should fail
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pg_temp.pktable".
DROP TABLE FKTABLE;
-- This should fail, because we'd have to cast numeric to int which is
-- not an implicit coercion (or use numeric=numeric, but that's not part
@@ -504,8 +504,8 @@ ALTER TABLE FKTABLE ADD FOREIGN KEY(ftest1) references pktable;
-- Check it actually works
INSERT INTO FKTABLE VALUES(42); -- should succeed
INSERT INTO FKTABLE VALUES(43); -- should fail
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "pg_temp.pktable".
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
CREATE TEMP TABLE PKTABLE (ptest1 int, ptest2 inet,
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index bc26846..aa9b2ef 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -243,8 +243,8 @@ SELECT a,b,c,substring(d for 30), length(d) from clstr_tst;
-- Verify that foreign key link still works
INSERT INTO clstr_tst (b, c) VALUES (1111, 'this should fail');
-ERROR: insert or update on table "clstr_tst" violates foreign key constraint "clstr_tst_con"
-DETAIL: Key (b)=(1111) is not present in table "clstr_tst_s".
+ERROR: insert or update on table "public.clstr_tst" violates foreign key constraint "clstr_tst_con"
+DETAIL: Key (b)=(1111) is not present in table "public.clstr_tst_s".
SELECT conname FROM pg_constraint WHERE conrelid = 'clstr_tst'::regclass
ORDER BY 1;
conname
diff --git a/src/test/regress/expected/collate.out b/src/test/regress/expected/collate.out
index f076a4d..dc1d605 100644
--- a/src/test/regress/expected/collate.out
+++ b/src/test/regress/expected/collate.out
@@ -591,13 +591,13 @@ INSERT INTO collate_test20 VALUES ('foo'), ('bar');
CREATE TABLE collate_test21 (f2 text COLLATE "POSIX" REFERENCES collate_test20);
INSERT INTO collate_test21 VALUES ('foo'), ('bar');
INSERT INTO collate_test21 VALUES ('baz'); -- fail
-ERROR: insert or update on table "collate_test21" violates foreign key constraint "collate_test21_f2_fkey"
-DETAIL: Key (f2)=(baz) is not present in table "collate_test20".
+ERROR: insert or update on table "collate_tests.collate_test21" violates foreign key constraint "collate_test21_f2_fkey"
+DETAIL: Key (f2)=(baz) is not present in table "collate_tests.collate_test20".
CREATE TABLE collate_test22 (f2 text COLLATE "POSIX");
INSERT INTO collate_test22 VALUES ('foo'), ('bar'), ('baz');
ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20; -- fail
-ERROR: insert or update on table "collate_test22" violates foreign key constraint "collate_test22_f2_fkey"
-DETAIL: Key (f2)=(baz) is not present in table "collate_test20".
+ERROR: insert or update on table "collate_tests.collate_test22" violates foreign key constraint "collate_test22_f2_fkey"
+DETAIL: Key (f2)=(baz) is not present in table "collate_tests.collate_test20".
DELETE FROM collate_test22 WHERE f2 = 'baz';
ALTER TABLE collate_test22 ADD FOREIGN KEY (f2) REFERENCES collate_test20;
RESET enable_seqscan;
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 1a61a5b..69bb3c3 100644
--- a/src/test/regress/expected/enum.out
+++ b/src/test/regress/expected/enum.out
@@ -543,11 +543,11 @@ CREATE TABLE enumtest_child (parent rainbow REFERENCES enumtest_parent);
INSERT INTO enumtest_parent VALUES ('red');
INSERT INTO enumtest_child VALUES ('red');
INSERT INTO enumtest_child VALUES ('blue'); -- fail
-ERROR: insert or update on table "enumtest_child" violates foreign key constraint "enumtest_child_parent_fkey"
-DETAIL: Key (parent)=(blue) is not present in table "enumtest_parent".
+ERROR: insert or update on table "public.enumtest_child" violates foreign key constraint "enumtest_child_parent_fkey"
+DETAIL: Key (parent)=(blue) is not present in table "public.enumtest_parent".
DELETE FROM enumtest_parent; -- fail
-ERROR: update or delete on table "enumtest_parent" violates foreign key constraint "enumtest_child_parent_fkey" on table "enumtest_child"
-DETAIL: Key (id)=(red) is still referenced from table "enumtest_child".
+ERROR: update or delete on table "public.enumtest_parent" violates foreign key constraint "enumtest_child_parent_fkey" on table "public.enumtest_child"
+DETAIL: Key (id)=(red) is still referenced from table "public.enumtest_child".
--
-- cross-type RI should fail
--
diff --git a/src/test/regress/expected/foreign_key.out b/src/test/regress/expected/foreign_key.out
index 8c47bab..bf4693b 100644
--- a/src/test/regress/expected/foreign_key.out
+++ b/src/test/regress/expected/foreign_key.out
@@ -20,8 +20,8 @@ INSERT INTO FKTABLE VALUES (3, 4);
INSERT INTO FKTABLE VALUES (NULL, 1);
-- Insert a failed row into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(100) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(100) is not present in table "public.pktable".
-- Check FKTABLE
SELECT * FROM FKTABLE;
ftest1 | ftest2
@@ -82,16 +82,16 @@ INSERT INTO FKTABLE VALUES (3, 6, 12);
INSERT INTO FKTABLE VALUES (NULL, NULL, 0);
-- Insert failed rows into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
-DETAIL: Key (ftest1, ftest2)=(100, 2) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname"
+DETAIL: Key (ftest1, ftest2)=(100, 2) is not present in table "public.pktable".
INSERT INTO FKTABLE VALUES (2, 2, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
-DETAIL: Key (ftest1, ftest2)=(2, 2) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname"
+DETAIL: Key (ftest1, ftest2)=(2, 2) is not present in table "public.pktable".
INSERT INTO FKTABLE VALUES (NULL, 2, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname"
DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
INSERT INTO FKTABLE VALUES (1, NULL, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname"
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname"
DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
-- Check FKTABLE
SELECT * FROM FKTABLE;
@@ -191,16 +191,16 @@ INSERT INTO FKTABLE VALUES (3, 6, 12);
INSERT INTO FKTABLE VALUES (NULL, NULL, 0);
-- Insert failed rows into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
-DETAIL: Key (ftest1, ftest2)=(100, 2) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname2"
+DETAIL: Key (ftest1, ftest2)=(100, 2) is not present in table "public.pktable".
INSERT INTO FKTABLE VALUES (2, 2, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
-DETAIL: Key (ftest1, ftest2)=(2, 2) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname2"
+DETAIL: Key (ftest1, ftest2)=(2, 2) is not present in table "public.pktable".
INSERT INTO FKTABLE VALUES (NULL, 2, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname2"
DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
INSERT INTO FKTABLE VALUES (1, NULL, 4);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname2"
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname2"
DETAIL: MATCH FULL does not allow mixing of null and nonnull key values.
-- Check FKTABLE
SELECT * FROM FKTABLE;
@@ -278,8 +278,8 @@ INSERT INTO FKTABLE VALUES (3, 4);
INSERT INTO FKTABLE VALUES (NULL, 1);
-- Insert a failed row into FK TABLE
INSERT INTO FKTABLE VALUES (100, 2);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(100) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(100) is not present in table "public.pktable".
-- Check FKTABLE
SELECT * FROM FKTABLE;
ftest1 | ftest2
@@ -303,8 +303,8 @@ SELECT * FROM PKTABLE;
-- Delete a row from PK TABLE (should fail)
DELETE FROM PKTABLE WHERE ptest1=1;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
-DETAIL: Key (ptest1)=(1) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "public.fktable"
+DETAIL: Key (ptest1)=(1) is still referenced from table "public.fktable".
-- Delete a row from PK TABLE (should succeed)
DELETE FROM PKTABLE WHERE ptest1=5;
-- Check PKTABLE for deletes
@@ -319,8 +319,8 @@ SELECT * FROM PKTABLE;
-- Update a row from PK TABLE (should fail)
UPDATE PKTABLE SET ptest1=0 WHERE ptest1=2;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
-DETAIL: Key (ptest1)=(2) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "public.fktable"
+DETAIL: Key (ptest1)=(2) is still referenced from table "public.fktable".
-- Update a row from PK TABLE (should succeed)
UPDATE PKTABLE SET ptest1=0 WHERE ptest1=4;
-- Check PKTABLE for updates
@@ -353,8 +353,8 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
-DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "public.pktable".
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
@@ -368,14 +368,14 @@ SELECT * from FKTABLE;
-- Try to update something that should fail
UPDATE PKTABLE set ptest2=5 where ptest2=2;
-ERROR: update or delete on table "pktable" violates foreign key constraint "constrname3" on table "fktable"
-DETAIL: Key (ptest1, ptest2, ptest3)=(1, 2, 3) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "constrname3" on table "public.fktable"
+DETAIL: Key (ptest1, ptest2, ptest3)=(1, 2, 3) is still referenced from table "public.fktable".
-- Try to update something that should succeed
UPDATE PKTABLE set ptest1=1 WHERE ptest2=3;
-- Try to delete something that should fail
DELETE FROM PKTABLE where ptest1=1 and ptest2=2 and ptest3=3;
-ERROR: update or delete on table "pktable" violates foreign key constraint "constrname3" on table "fktable"
-DETAIL: Key (ptest1, ptest2, ptest3)=(1, 2, 3) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "constrname3" on table "public.fktable"
+DETAIL: Key (ptest1, ptest2, ptest3)=(1, 2, 3) is still referenced from table "public.fktable".
-- Try to delete something that should work
DELETE FROM PKTABLE where ptest1=2;
-- Show PKTABLE and FKTABLE
@@ -417,8 +417,8 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
-DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "public.pktable".
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
@@ -514,8 +514,8 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
-DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "public.pktable".
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
@@ -619,8 +619,8 @@ INSERT INTO FKTABLE VALUES (NULL, 2, 7, 4);
INSERT INTO FKTABLE VALUES (NULL, 3, 4, 5);
-- Insert a failed values
INSERT INTO FKTABLE VALUES (1, 2, 7, 6);
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
-DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(1, 2, 7) is not present in table "public.pktable".
-- Show FKTABLE
SELECT * from FKTABLE;
ftest1 | ftest2 | ftest3 | ftest4
@@ -636,8 +636,8 @@ SELECT * from FKTABLE;
-- Try to update something that will fail
UPDATE PKTABLE set ptest2=5 where ptest2=2;
-ERROR: insert or update on table "fktable" violates foreign key constraint "constrname3"
-DETAIL: Key (ftest1, ftest2, ftest3)=(0, -1, -2) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "constrname3"
+DETAIL: Key (ftest1, ftest2, ftest3)=(0, -1, -2) is not present in table "public.pktable".
-- Try to update something that will set default
UPDATE PKTABLE set ptest1=0, ptest2=-1, ptest3=-2 where ptest2=2;
UPDATE PKTABLE set ptest2=10 where ptest2=4;
@@ -753,12 +753,12 @@ CREATE TABLE FKTABLE (ftest1 int8 REFERENCES pktable);
-- Check it actually works
INSERT INTO FKTABLE VALUES(42); -- should succeed
INSERT INTO FKTABLE VALUES(43); -- should fail
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "public.pktable".
UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed
UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "public.pktable".
DROP TABLE FKTABLE;
-- This should fail, because we'd have to cast numeric to int which is
-- not an implicit coercion (or use numeric=numeric, but that's not part
@@ -775,12 +775,12 @@ CREATE TABLE FKTABLE (ftest1 int REFERENCES pktable);
-- Check it actually works
INSERT INTO FKTABLE VALUES(42); -- should succeed
INSERT INTO FKTABLE VALUES(43); -- should fail
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "public.pktable".
UPDATE FKTABLE SET ftest1 = ftest1; -- should succeed
UPDATE FKTABLE SET ftest1 = ftest1 + 1; -- should fail
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(43) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(43) is not present in table "public.pktable".
DROP TABLE FKTABLE;
DROP TABLE PKTABLE;
-- Two columns, two tables
@@ -847,20 +847,20 @@ insert into pktable(base1) values (1);
insert into pktable(base1) values (2);
-- let's insert a non-existent fktable value
insert into fktable(ftest1) values (3);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1)=(3) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1)=(3) is not present in table "public.pktable".
-- let's make a valid row for that
insert into pktable(base1) values (3);
insert into fktable(ftest1) values (3);
-- let's try removing a row that should fail from pktable
delete from pktable where base1>2;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
-DETAIL: Key (base1)=(3) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "public.fktable"
+DETAIL: Key (base1)=(3) is still referenced from table "public.fktable".
-- okay, let's try updating all of the base1 values to *4
-- which should fail.
update pktable set base1=base1*4;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
-DETAIL: Key (base1)=(3) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "public.fktable"
+DETAIL: Key (base1)=(3) is still referenced from table "public.fktable".
-- okay, let's try an update that should work.
update pktable set base1=base1*4 where base1<3;
-- and a delete that should work
@@ -875,20 +875,20 @@ insert into pktable(base1, ptest1) values (1, 1);
insert into pktable(base1, ptest1) values (2, 2);
-- let's insert a non-existent fktable value
insert into fktable(ftest1, ftest2) values (3, 1);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_ftest1_fkey"
-DETAIL: Key (ftest1, ftest2)=(3, 1) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_ftest1_fkey"
+DETAIL: Key (ftest1, ftest2)=(3, 1) is not present in table "public.pktable".
-- let's make a valid row for that
insert into pktable(base1,ptest1) values (3, 1);
insert into fktable(ftest1, ftest2) values (3, 1);
-- let's try removing a row that should fail from pktable
delete from pktable where base1>2;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
-DETAIL: Key (base1, ptest1)=(3, 1) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "public.fktable"
+DETAIL: Key (base1, ptest1)=(3, 1) is still referenced from table "public.fktable".
-- okay, let's try updating all of the base1 values to *4
-- which should fail.
update pktable set base1=base1*4;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "fktable"
-DETAIL: Key (base1, ptest1)=(3, 1) is still referenced from table "fktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "fktable_ftest1_fkey" on table "public.fktable"
+DETAIL: Key (base1, ptest1)=(3, 1) is still referenced from table "public.fktable".
-- okay, let's try an update that should work.
update pktable set base1=base1*4 where base1<3;
-- and a delete that should work
@@ -907,16 +907,16 @@ insert into pktable (base1, ptest1, base2, ptest2) values (2, 2, 2, 1);
insert into pktable (base1, ptest1, base2, ptest2) values (1, 3, 2, 2);
-- fails (3,2) isn't in base1, ptest1
insert into pktable (base1, ptest1, base2, ptest2) values (2, 3, 3, 2);
-ERROR: insert or update on table "pktable" violates foreign key constraint "pktable_base2_fkey"
-DETAIL: Key (base2, ptest2)=(3, 2) is not present in table "pktable".
+ERROR: insert or update on table "public.pktable" violates foreign key constraint "pktable_base2_fkey"
+DETAIL: Key (base2, ptest2)=(3, 2) is not present in table "public.pktable".
-- fails (2,2) is being referenced
delete from pktable where base1=2;
-ERROR: update or delete on table "pktable" violates foreign key constraint "pktable_base2_fkey" on table "pktable"
-DETAIL: Key (base1, ptest1)=(2, 2) is still referenced from table "pktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "pktable_base2_fkey" on table "public.pktable"
+DETAIL: Key (base1, ptest1)=(2, 2) is still referenced from table "public.pktable".
-- fails (1,1) is being referenced (twice)
update pktable set base1=3 where base1=1;
-ERROR: update or delete on table "pktable" violates foreign key constraint "pktable_base2_fkey" on table "pktable"
-DETAIL: Key (base1, ptest1)=(1, 1) is still referenced from table "pktable".
+ERROR: update or delete on table "public.pktable" violates foreign key constraint "pktable_base2_fkey" on table "public.pktable"
+DETAIL: Key (base1, ptest1)=(1, 1) is still referenced from table "public.pktable".
-- this sequence of two deletes will work, since after the first there will be no (2,*) references
delete from pktable where base2=2;
delete from pktable where base1=2;
@@ -980,8 +980,8 @@ CREATE TABLE fktable (
);
-- default to immediate: should fail
INSERT INTO fktable VALUES (5, 10);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(10) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(10) is not present in table "public.pktable".
-- explicitly defer the constraint
BEGIN;
SET CONSTRAINTS ALL DEFERRED;
@@ -1008,8 +1008,8 @@ BEGIN;
SET CONSTRAINTS ALL IMMEDIATE;
-- should fail
INSERT INTO fktable VALUES (500, 1000);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(1000) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(1000) is not present in table "public.pktable".
COMMIT;
DROP TABLE fktable, pktable;
-- tricky behavior: according to SQL99, if a deferred constraint is set
@@ -1030,8 +1030,8 @@ SET CONSTRAINTS ALL DEFERRED;
INSERT INTO fktable VALUES (1000, 2000);
-- should cause transaction abort, due to preceding error
SET CONSTRAINTS ALL IMMEDIATE;
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(2000) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(2000) is not present in table "public.pktable".
INSERT INTO pktable VALUES (2000, 3); -- too late
ERROR: current transaction is aborted, commands ignored until end of transaction block
COMMIT;
@@ -1050,8 +1050,8 @@ BEGIN;
INSERT INTO fktable VALUES (100, 200);
-- error here on commit
COMMIT;
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(200) is not present in table "pktable".
+ERROR: insert or update on table "public.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(200) is not present in table "public.pktable".
DROP TABLE pktable, fktable;
-- test notice about expensive referential integrity checks,
-- where the index cannot be used because of type incompatibilities.
@@ -1140,8 +1140,8 @@ INSERT INTO fktable VALUES (0, 20);
UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT
COMMIT;
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(20) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pg_temp.pktable".
-- check same case when insert is in a different subtransaction than update
BEGIN;
-- doesn't match PK, but no error yet
@@ -1152,8 +1152,8 @@ SAVEPOINT savept1;
UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT
COMMIT;
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(20) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pg_temp.pktable".
BEGIN;
-- INSERT will be in a subxact
SAVEPOINT savept1;
@@ -1164,8 +1164,8 @@ RELEASE SAVEPOINT savept1;
UPDATE fktable SET id = id + 1;
-- should catch error from initial INSERT
COMMIT;
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(20) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pg_temp.pktable".
BEGIN;
-- doesn't match PK, but no error yet
INSERT INTO fktable VALUES (0, 20);
@@ -1177,8 +1177,8 @@ UPDATE fktable SET id = id + 1;
ROLLBACK TO savept1;
-- should catch error from initial INSERT
COMMIT;
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(20) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pg_temp.pktable".
--
-- check ALTER CONSTRAINT
--
@@ -1187,14 +1187,14 @@ ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey DEFERRABLE INITIALLY IMMEDI
BEGIN;
-- doesn't match FK, should throw error now
UPDATE pktable SET id = 10 WHERE id = 5;
-ERROR: update or delete on table "pktable" violates foreign key constraint "fktable_fk_fkey" on table "fktable"
-DETAIL: Key (id)=(5) is still referenced from table "fktable".
+ERROR: update or delete on table "pg_temp.pktable" violates foreign key constraint "fktable_fk_fkey" on table "pg_temp.fktable"
+DETAIL: Key (id)=(5) is still referenced from table "pg_temp.fktable".
COMMIT;
BEGIN;
-- doesn't match PK, should throw error now
INSERT INTO fktable VALUES (0, 20);
-ERROR: insert or update on table "fktable" violates foreign key constraint "fktable_fk_fkey"
-DETAIL: Key (fk)=(20) is not present in table "pktable".
+ERROR: insert or update on table "pg_temp.fktable" violates foreign key constraint "fktable_fk_fkey"
+DETAIL: Key (fk)=(20) is not present in table "pg_temp.pktable".
COMMIT;
-- try additional syntax
ALTER TABLE fktable ALTER CONSTRAINT fktable_fk_fkey NOT DEFERRABLE;
@@ -1322,8 +1322,8 @@ select * from defc;
(1 row)
delete from defp where f1 = 0; -- fail
-ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc"
-DETAIL: Key (f1)=(0) is still referenced from table "defc".
+ERROR: update or delete on table "pg_temp.defp" violates foreign key constraint "defc_f1_fkey" on table "pg_temp.defc"
+DETAIL: Key (f1)=(0) is still referenced from table "pg_temp.defc".
alter table defc alter column f1 set default 1;
delete from defp where f1 = 0;
select * from defc;
@@ -1333,8 +1333,8 @@ select * from defc;
(1 row)
delete from defp where f1 = 1; -- fail
-ERROR: update or delete on table "defp" violates foreign key constraint "defc_f1_fkey" on table "defc"
-DETAIL: Key (f1)=(1) is still referenced from table "defc".
+ERROR: update or delete on table "pg_temp.defp" violates foreign key constraint "defc_f1_fkey" on table "pg_temp.defc"
+DETAIL: Key (f1)=(1) is still referenced from table "pg_temp.defc".
--
-- Test the difference between NO ACTION and RESTRICT
--
@@ -1346,8 +1346,8 @@ update pp set f1=f1+1;
insert into cc values(13);
update pp set f1=f1+1;
update pp set f1=f1+1; -- fail
-ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
-DETAIL: Key (f1)=(13) is still referenced from table "cc".
+ERROR: update or delete on table "pg_temp.pp" violates foreign key constraint "cc_f1_fkey" on table "pg_temp.cc"
+DETAIL: Key (f1)=(13) is still referenced from table "pg_temp.cc".
drop table pp, cc;
create temp table pp (f1 int primary key);
create temp table cc (f1 int references pp on update restrict);
@@ -1356,6 +1356,6 @@ insert into pp values(11);
update pp set f1=f1+1;
insert into cc values(13);
update pp set f1=f1+1; -- fail
-ERROR: update or delete on table "pp" violates foreign key constraint "cc_f1_fkey" on table "cc"
-DETAIL: Key (f1)=(13) is still referenced from table "cc".
+ERROR: update or delete on table "pg_temp.pp" violates foreign key constraint "cc_f1_fkey" on table "pg_temp.cc"
+DETAIL: Key (f1)=(13) is still referenced from table "pg_temp.cc".
drop table pp, cc;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index e30c579..24a36ef 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2105,8 +2105,8 @@ create temp table slave(f1 int references master deferrable);
insert into master values(1);
insert into slave values(1);
insert into slave values(2); -- fails
-ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
-DETAIL: Key (f1)=(2) is not present in table "master".
+ERROR: insert or update on table "pg_temp.slave" violates foreign key constraint "slave_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "pg_temp.master".
create function trap_foreign_key(int) returns int as $$
begin
begin -- start a subtransaction
@@ -2152,8 +2152,8 @@ begin;
savepoint x;
set constraints all immediate; -- fails
-ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
-DETAIL: Key (f1)=(2) is not present in table "master".
+ERROR: insert or update on table "pg_temp.slave" violates foreign key constraint "slave_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "pg_temp.master".
rollback to x;
select trap_foreign_key_2(); -- detects FK violation
NOTICE: caught foreign_key_violation
@@ -2163,8 +2163,8 @@ NOTICE: caught foreign_key_violation
(1 row)
commit; -- still fails
-ERROR: insert or update on table "slave" violates foreign key constraint "slave_f1_fkey"
-DETAIL: Key (f1)=(2) is not present in table "master".
+ERROR: insert or update on table "pg_temp.slave" violates foreign key constraint "slave_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "pg_temp.master".
drop function trap_foreign_key(int);
drop function trap_foreign_key_2();
--
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index 4aaa88f..63c0031 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -306,8 +306,8 @@ SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
(6 rows)
DELETE FROM category WHERE cid = 33; -- fails with FK violation
-ERROR: update or delete on table "category" violates foreign key constraint "document_cid_fkey" on table "document"
-DETAIL: Key is still referenced from table "document".
+ERROR: update or delete on table "rls_regress_schema.category" violates foreign key constraint "document_cid_fkey" on table "rls_regress_schema.document"
+DETAIL: Key is still referenced from table "rls_regress_schema.document".
-- can insert FK referencing invisible PK
SET SESSION AUTHORIZATION rls_regress_user2;
SELECT * FROM document d FULL OUTER JOIN category c on d.cid = c.cid;
@@ -3242,8 +3242,8 @@ ALTER TABLE r2 ENABLE ROW LEVEL SECURITY;
ALTER TABLE r2 FORCE ROW LEVEL SECURITY;
-- Errors due to rows in r2
DELETE FROM r1;
-ERROR: update or delete on table "r1" violates foreign key constraint "r2_a_fkey" on table "r2"
-DETAIL: Key (a)=(10) is still referenced from table "r2".
+ERROR: update or delete on table "rls_regress_schema.r1" violates foreign key constraint "r2_a_fkey" on table "rls_regress_schema.r2"
+DETAIL: Key (a)=(10) is still referenced from table "rls_regress_schema.r2".
-- Reset r2 to no-RLS
DROP POLICY p1 ON r2;
ALTER TABLE r2 NO FORCE ROW LEVEL SECURITY;
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 80374e4..6b5b23f 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2375,22 +2375,22 @@ insert into rule_and_refint_t3 values (1, 11, 12, 'row2');
insert into rule_and_refint_t3 values (1, 12, 11, 'row3');
insert into rule_and_refint_t3 values (1, 12, 12, 'row4');
insert into rule_and_refint_t3 values (1, 11, 13, 'row5');
-ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey1"
-DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "rule_and_refint_t2".
+ERROR: insert or update on table "public.rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey1"
+DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "public.rule_and_refint_t2".
insert into rule_and_refint_t3 values (1, 13, 11, 'row6');
-ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
-DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+ERROR: insert or update on table "public.rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "public.rule_and_refint_t1".
-- Ordinary table
insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
on conflict do nothing;
-ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
-DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+ERROR: insert or update on table "public.rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "public.rule_and_refint_t1".
-- rule not fired, so fk violation
insert into rule_and_refint_t3 values (1, 13, 11, 'row6')
on conflict (id3a, id3b, id3c) do update
set id3b = excluded.id3b;
-ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
-DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+ERROR: insert or update on table "public.rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "public.rule_and_refint_t1".
-- rule fired, so unsupported
insert into shoelace values ('sl9', 0, 'pink', 35.0, 'inch', 0.0)
on conflict (sl_name) do update
@@ -2406,11 +2406,11 @@ create rule rule_and_refint_t3_ins as on insert to rule_and_refint_t3
and (rule_and_refint_t3.id3b = new.id3b))
and (rule_and_refint_t3.id3c = new.id3c));
insert into rule_and_refint_t3 values (1, 11, 13, 'row7');
-ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey1"
-DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "rule_and_refint_t2".
+ERROR: insert or update on table "public.rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey1"
+DETAIL: Key (id3a, id3c)=(1, 13) is not present in table "public.rule_and_refint_t2".
insert into rule_and_refint_t3 values (1, 13, 11, 'row8');
-ERROR: insert or update on table "rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
-DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "rule_and_refint_t1".
+ERROR: insert or update on table "public.rule_and_refint_t3" violates foreign key constraint "rule_and_refint_t3_id3a_fkey"
+DETAIL: Key (id3a, id3b)=(1, 13) is not present in table "public.rule_and_refint_t1".
--
-- disallow dropping a view's rule (bug #5072)
--
Petr Korobeinikov <pkorobeinikov@gmail.com> writes:
At the moment schemas used as single-level namespaces.
It's quite convenient in large databases.
But error messages doesn’t contain schema names.
I have done a little patch with schema-qualified relation names in error
messages that produced by foreign key constraints and triggers.
We have gone around on this before and pretty much concluded we didn't
want to do it; the demand is too small and the risk of breaking things
too large. In particular, your patch as presented *would* break every
application that is still parsing primary error messages to extract
object names from them.
What seems more likely to be useful and to not break anything is to push
forward on adding PG_DIAG_SCHEMA_NAME and similar auxiliary fields to more
error messages (see errtable() and siblings). That would allow
applications to extract this information automatically and reliably.
Only after that work is complete and there's been a reasonable amount of
time for clients to start relying on it can we really start thinking about
whacking the message texts around.
Aside from those points, it's quite unacceptable to format qualified names
as you propose here; they would really have to look more like "foo"."bar"
to prevent confusion.
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/16 7:21 PM, Tom Lane wrote:
What seems more likely to be useful and to not break anything is to push
forward on adding PG_DIAG_SCHEMA_NAME and similar auxiliary fields to more
error messages (see errtable() and siblings). That would allow
applications to extract this information automatically and reliably.
Only after that work is complete and there's been a reasonable amount of
time for clients to start relying on it can we really start thinking about
whacking the message texts around.
+1, but...
does psql do anything with those fields? ISTM the biggest use for this
info is someone sitting at psql or pgAdmin.
Maybe schema info could be presented in HINT or DETAIL messages as well?
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Jim Nasby <Jim.Nasby@BlueTreble.com> writes:
does psql do anything with those fields? ISTM the biggest use for this
info is someone sitting at psql or pgAdmin.
Sure, if you turn up the error verbosity.
regression=# create table foo (f1 int primary key);
CREATE TABLE
regression=# create table bar (f1 int references foo);
CREATE TABLE
regression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \set VERBOSITY verbose
regression=# insert into bar values(1);
ERROR: 23503: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
I can't speak to pgAdmin, but if it doesn't make this info available
the answer is to fix pgAdmin ...
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/16 8:41 PM, Tom Lane wrote:
Jim Nasby<Jim.Nasby@BlueTreble.com> writes:
does psql do anything with those fields? ISTM the biggest use for this
info is someone sitting at psql or pgAdmin.Sure, if you turn up the error verbosity.
FWIW, I suspect very few people know about the verbosity setting (I
didn't until a few months ago...) Maybe psql should hint about it the
first time an error is reported in a session.
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Jim Nasby <Jim.Nasby@bluetreble.com> writes:
FWIW, I suspect very few people know about the verbosity setting (I
didn't until a few months ago...) Maybe psql should hint about it the
first time an error is reported in a session.
Actually, what'd be really handy IMO is something to regurgitate the
most recent error in verbose mode, without making a permanent session
state change. Something like
regression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
ERROR: 23503: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
regression=#
Not sure how hard that would be to do within psql's current structure.
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/16 9:16 PM, Tom Lane wrote:
Jim Nasby <Jim.Nasby@bluetreble.com> writes:
FWIW, I suspect very few people know about the verbosity setting (I
didn't until a few months ago...) Maybe psql should hint about it the
first time an error is reported in a session.Actually, what'd be really handy IMO is something to regurgitate the
most recent error in verbose mode, without making a permanent session
state change. Something likeregression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
ERROR: 23503: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
regression=#Not sure how hard that would be to do within psql's current structure.
At first glance, it looks like it just means changing AcceptResult() to
use PQresultErrorField in addition to PQresultErrorMessage, and stuffing
the results somewhere. And of course adding \saywhat to the psql parser,
but maybe someone more versed in psql code can verify that.
If it is that simple, looks like another good beginner hacker task. :)
--
Jim Nasby, Data Architect, Blue Treble Consulting, Austin TX
Experts in Analytics, Data Architecture and PostgreSQL
Data in Trouble? Get it in Treble! http://BlueTreble.com
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Jan 6, 2016 at 5:06 AM, Jim Nasby <Jim.Nasby@bluetreble.com> wrote:
On 1/5/16 9:16 PM, Tom Lane wrote:
Jim Nasby <Jim.Nasby@bluetreble.com> writes:
FWIW, I suspect very few people know about the verbosity setting (I
didn't until a few months ago...) Maybe psql should hint about it the
first time an error is reported in a session.Actually, what'd be really handy IMO is something to regurgitate the
most recent error in verbose mode, without making a permanent session
state change. Something likeregression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint
"bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
ERROR: 23503: insert or update on table "bar" violates foreign key
constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
regression=#Not sure how hard that would be to do within psql's current structure.
At first glance, it looks like it just means changing AcceptResult() to
use PQresultErrorField in addition to PQresultErrorMessage, and stuffing
the results somewhere. And of course adding \saywhat to the psql parser,
but maybe someone more versed in psql code can verify that.If it is that simple, looks like another good beginner hacker task. :)
Sorry, I couldn't resist it: I was too excited to learn such option
existed. :-)
Please find attached a POC patch, using \errverbose for the command name.
Unfortunately, I didn't see a good way to contain the change in psql only
and had to change libpq, adding new interface PQresultBuildErrorMessage().
Also, what I don't like very much is that we need to keep track of the last
result from last SendQuery() in psql's pset. So in my view this is sort of
a dirty hack that works nonetheless.
Cheers!
--
Alex
Attachments:
0001-POC-errverbose-in-psql.patchtext/x-patch; charset=US-ASCII; name=0001-POC-errverbose-in-psql.patchDownload
From 402866a6899791e7f410ed747e3b0018eed717e0 Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Wed, 6 Jan 2016 14:58:17 +0100
Subject: [PATCH] POC: \errverbose in psql
---
src/bin/psql/command.c | 20 ++++++
src/bin/psql/common.c | 6 +-
src/bin/psql/settings.h | 1 +
src/bin/psql/startup.c | 1 +
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-exec.c | 21 ++++++
src/interfaces/libpq/fe-protocol3.c | 131 +++++++++++++++++++-----------------
src/interfaces/libpq/libpq-fe.h | 1 +
src/interfaces/libpq/libpq-int.h | 2 +
9 files changed, 122 insertions(+), 62 deletions(-)
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 9750a5b..9ae5ae5 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -822,6 +822,24 @@ exec_command(const char *cmd,
}
}
+ /* \errverbose -- display verbose error message from last result */
+ else if (strcmp(cmd, "errverbose") == 0)
+ {
+ char *errmsg;
+
+ if (pset.last_result && PQresultStatus(pset.last_result) == PGRES_FATAL_ERROR)
+ {
+ /* increase error verbosity for a moment */
+ PQsetErrorVerbosity(pset.db, PQERRORS_VERBOSE);
+ errmsg = PQresultBuildErrorMessage(pset.db, pset.last_result);
+ PQsetErrorVerbosity(pset.db, pset.verbosity);
+
+ psql_error("%s", errmsg);
+
+ PQfreemem(errmsg);
+ }
+ }
+
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
{
@@ -1865,6 +1883,8 @@ do_connect(char *dbname, char *user, char *host, char *port)
{
PQfinish(o_conn);
pset.db = NULL;
+ PQclear(pset.last_result);
+ pset.last_result = NULL;
}
}
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 2cb2e9b..85658e6 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -315,6 +315,8 @@ CheckConnection(void)
psql_error("Failed.\n");
PQfinish(pset.db);
pset.db = NULL;
+ PQclear(pset.last_result);
+ pset.last_result = NULL;
ResetCancelConn();
UnsyncVariables();
}
@@ -1154,7 +1156,9 @@ SendQuery(const char *query)
}
}
- PQclear(results);
+ /* XXX: can we have PQclear that would free everything except errfields? */
+ PQclear(pset.last_result);
+ pset.last_result = results;
/* Possible microtiming output */
if (pset.timing)
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 20a6470..8b88fbb 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -80,6 +80,7 @@ enum trivalue
typedef struct _psqlSettings
{
PGconn *db; /* connection to backend */
+ PGresult *last_result; /* last result from running a query, if any */
int encoding; /* client_encoding */
FILE *queryFout; /* where to send the query results */
bool queryFoutPipe; /* queryFout is from a popen() */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 6916f6f..7586ee81 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -130,6 +130,7 @@ main(int argc, char *argv[])
pset.progname = get_progname(argv[0]);
pset.db = NULL;
+ pset.last_result = NULL;
setDecimalLocale();
pset.encoding = PQenv2encoding();
pset.queryFout = stdout;
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index c69a4d5..042bdd1 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -170,3 +170,4 @@ PQsslStruct 167
PQsslAttributeNames 168
PQsslAttribute 169
PQsetErrorContextVisibility 170
+PQresultBuildErrorMessage 171
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 41937c0..ca2fb50 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2591,6 +2591,27 @@ PQresStatus(ExecStatusType status)
}
char *
+PQresultBuildErrorMessage(const PGconn *conn, const PGresult *res)
+{
+ char *errmsg;
+ PQExpBufferData errbuf;
+
+ if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
+ {
+ initPQExpBuffer(&errbuf);
+ pqBuildErrorNoticeMessage3(conn, res, &errbuf);
+
+ errmsg = strdup(errbuf.data);
+
+ termPQExpBuffer(&errbuf);
+ }
+ else
+ errmsg = strdup("requires libpq protocol version 3");
+
+ return errmsg;
+}
+
+char *
PQresultErrorMessage(const PGresult *res)
{
if (!res || !res->errMsg)
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 43898a4..7e674c7 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -865,9 +865,6 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
PGresult *res = NULL;
PQExpBufferData workBuf;
char id;
- const char *val;
- const char *querytext = NULL;
- int querypos = 0;
/*
* Since the fields might be pretty long, we create a temporary
@@ -910,20 +907,67 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
* Also, save the SQLSTATE in conn->last_sqlstate.
*/
resetPQExpBuffer(&workBuf);
+ pqBuildErrorNoticeMessage3(conn, res, &workBuf);
+
+ /*
+ * Either save error as current async result, or just emit the notice.
+ */
+ if (isError)
+ {
+ if (res)
+ res->errMsg = pqResultStrdup(res, workBuf.data);
+ pqClearAsyncResult(conn);
+ conn->result = res;
+ if (PQExpBufferDataBroken(workBuf))
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ else
+ appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
+ }
+ else
+ {
+ /* if we couldn't allocate the result set, just discard the NOTICE */
+ if (res)
+ {
+ /* We can cheat a little here and not copy the message. */
+ res->errMsg = workBuf.data;
+ if (res->noticeHooks.noticeRec != NULL)
+ (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
+ PQclear(res);
+ }
+ }
+
+ termPQExpBuffer(&workBuf);
+ return 0;
+
+fail:
+ PQclear(res);
+ termPQExpBuffer(&workBuf);
+ return EOF;
+}
+
+void
+pqBuildErrorNoticeMessage3(const PGconn *conn, const PGresult *res, PQExpBuffer workBuf)
+{
+ const char *val;
+ const char *querytext = NULL;
+ int querypos = 0;
+
val = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (val)
- appendPQExpBuffer(&workBuf, "%s: ", val);
+ appendPQExpBuffer(workBuf, "%s: ", val);
val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (val)
{
+ /* XXX: multiple calls seems OK, but we violate the constness of conn */
if (strlen(val) < sizeof(conn->last_sqlstate))
- strcpy(conn->last_sqlstate, val);
+ strcpy(((PGconn *) conn)->last_sqlstate, val);
if (conn->verbosity == PQERRORS_VERBOSE)
- appendPQExpBuffer(&workBuf, "%s: ", val);
+ appendPQExpBuffer(workBuf, "%s: ", val);
}
val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
if (val)
- appendPQExpBufferStr(&workBuf, val);
+ appendPQExpBufferStr(workBuf, val);
val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
if (val)
{
@@ -937,7 +981,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
- appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
+ appendPQExpBuffer(workBuf, libpq_gettext(" at character %s"),
val);
}
}
@@ -956,32 +1000,33 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
- appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
+ appendPQExpBuffer(workBuf, libpq_gettext(" at character %s"),
val);
}
}
}
- appendPQExpBufferChar(&workBuf, '\n');
+ appendPQExpBufferChar(workBuf, '\n');
if (conn->verbosity != PQERRORS_TERSE)
{
if (querytext && querypos > 0)
- reportErrorPosition(&workBuf, querytext, querypos,
+ reportErrorPosition(workBuf, querytext, querypos,
conn->client_encoding);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("DETAIL: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("HINT: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("QUERY: %s\n"), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("QUERY: %s\n"), val);
if (conn->show_context == PQSHOW_CONTEXT_ALWAYS ||
- (conn->show_context == PQSHOW_CONTEXT_ERRORS && isError))
+ (conn->show_context == PQSHOW_CONTEXT_ERRORS &&
+ res->resultStatus == PGRES_FATAL_ERROR))
{
val = PQresultErrorField(res, PG_DIAG_CONTEXT);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"),
+ appendPQExpBuffer(workBuf, libpq_gettext("CONTEXT: %s\n"),
val);
}
}
@@ -989,23 +1034,23 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
{
val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("SCHEMA NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("TABLE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("COLUMN NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("DATATYPE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("CONSTRAINT NAME: %s\n"), val);
}
if (conn->verbosity == PQERRORS_VERBOSE)
@@ -1018,51 +1063,15 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION);
if (val || valf || vall)
{
- appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: "));
+ appendPQExpBufferStr(workBuf, libpq_gettext("LOCATION: "));
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("%s, "), val);
if (valf && vall) /* unlikely we'd have just one */
- appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"),
+ appendPQExpBuffer(workBuf, libpq_gettext("%s:%s"),
valf, vall);
- appendPQExpBufferChar(&workBuf, '\n');
+ appendPQExpBufferChar(workBuf, '\n');
}
}
-
- /*
- * Either save error as current async result, or just emit the notice.
- */
- if (isError)
- {
- if (res)
- res->errMsg = pqResultStrdup(res, workBuf.data);
- pqClearAsyncResult(conn);
- conn->result = res;
- if (PQExpBufferDataBroken(workBuf))
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory"));
- else
- appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
- }
- else
- {
- /* if we couldn't allocate the result set, just discard the NOTICE */
- if (res)
- {
- /* We can cheat a little here and not copy the message. */
- res->errMsg = workBuf.data;
- if (res->noticeHooks.noticeRec != NULL)
- (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
- PQclear(res);
- }
- }
-
- termPQExpBuffer(&workBuf);
- return 0;
-
-fail:
- PQclear(res);
- termPQExpBuffer(&workBuf);
- return EOF;
}
/*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 6bf34b3..5d89767 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -462,6 +462,7 @@ extern PGresult *PQfn(PGconn *conn,
/* Accessor functions for PGresult objects */
extern ExecStatusType PQresultStatus(const PGresult *res);
extern char *PQresStatus(ExecStatusType status);
+extern char *PQresultBuildErrorMessage(const PGconn *conn, const PGresult *res);
extern char *PQresultErrorMessage(const PGresult *res);
extern char *PQresultErrorField(const PGresult *res, int fieldcode);
extern int PQntuples(const PGresult *res);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6c9bbf7..c7a9e96 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -575,6 +575,8 @@ extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen,
const PQEnvironmentOption *options);
extern void pqParseInput3(PGconn *conn);
extern int pqGetErrorNotice3(PGconn *conn, bool isError);
+extern void pqBuildErrorNoticeMessage3(const PGconn *conn, const PGresult *res,
+ PQExpBuffer workBuf);
extern int pqGetCopyData3(PGconn *conn, char **buffer, int async);
extern int pqGetline3(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
--
2.5.0
On Wed, Jan 6, 2016 at 3:02 PM, Shulgin, Oleksandr <
oleksandr.shulgin@zalando.de> wrote:
Please find attached a POC patch, using \errverbose for the command name.
Unfortunately, I didn't see a good way to contain the change in psql only
and had to change libpq, adding new interface PQresultBuildErrorMessage().
Also, what I don't like very much is that we need to keep track of the last
result from last SendQuery() in psql's pset. So in my view this is sort of
a dirty hack that works nonetheless.
Added to the Open commitfest: https://commitfest.postgresql.org/9/475/
Shulgin, Oleksandr wrote:
Added to the Open commitfest: https://commitfest.postgresql.org/9/475/
Here's a review. Note that the patch tested and submitted
is not the initial one in the thread, so it doesn't exactly
match $subject now.
What's tested here is a client-side approach, suggested by Tom
upthread as an alternative, and implemented by Oleksandr in
0001-POC-errverbose-in-psql.patch
The patch has two parts: one part in libpq exposing a new function
named PQresultBuildErrorMessage, and a second part implementing an
\errverbose command in psql, essentially displaying the result of the
function.
The separation makes sense if we consider that other clients might benefit
from the function, or that libpq is a better place than psql to maintain
this in the future, as the list of error fields available in a PGresult
might grow.
OTOH maybe adding a new libpq function just for that is overkill, but this
is subjective.
psql part
=========
Compiles and works as intended.
For instance with \set VERBOSITY default, an FK violation produces:
# insert into table2 values(10);
ERROR: insert or update on table "table2" violates foreign key constraint
"table2_id_tbl1_fkey"
DETAIL: Key (id_tbl1)=(10) is not present in table "table1".
Then \errverbose just displays the verbose form of the error:
# \errverbose
ERROR: 23503: insert or update on table "table2" violates foreign
key constraint "table2_id_tbl1_fkey"
DETAIL: Key (id_tbl1)=(10) is not present in table "table1".
SCHEMA NAME: public
TABLE NAME: table2
CONSTRAINT NAME: table2_id_tbl1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
- make check passes
- valgrind test is OK (no obvious leak after using the command).
Missing bits:
- the command is not mentioned in the help (\? command, see help.c)
- it should be added to tab completions (see tab-complete.c)
- it needs to be described in the SGML documentation
libpq part
==========
The patch implements and exports a new PQresultBuildErrorMessage()
function.
AFAICS its purpose is to produce a result similar to what
PQresultErrorMessage() would have produced, if PQERRORS_VERBOSE
was the verbosity in effect at execution time.
My comments:
- the name of the function does not really hint at what it does.
Maybe something like PQresultVerboseErrorMessage() would be more
explicit?
I would expect the new function to have the same interface than
PQresultErrorMessage(), but it's not the case.
- it takes a PGconn* and PGresult* as input parameters, but
PQresultErrorMessage takes only a <const PGresult*> as input.
It's not clear why PGconn* is necessary.
- it returns a pointer to a strdup'ed() buffer, which
has to be freed separately by the caller. That differs from
PQresultErrorMessage() which keeps this managed inside the
PGresult struct.
- if protocol version < 3, an error message is returned. It's not
clear to the caller that this error is emitted by the function itself
rather than the query. Besides, are we sure it's necessary?
Maybe the function could just produce an output with whatever
error fields it has, even minimally with older protocol versions,
rather than failing?
- if the fixed error message is kept, it should pass through
libpq_gettext() for translation.
- a description of the function should be added to the SGML doc.
There's a sentence in PQsetErrorVerbosity() that says, currently:
"Changing the verbosity does not affect the messages available from
already-existing PGresult objects, only subsequently-created ones."
As it's precisely the point of that new function, that bit could
be altered to refer to it.
Best regards,
--
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Mon, Feb 8, 2016 at 5:24 PM, Daniel Verite <daniel@manitou-mail.org>
wrote:
Shulgin, Oleksandr wrote:
Added to the Open commitfest: https://commitfest.postgresql.org/9/475/
Here's a review. Note that the patch tested and submitted
is not the initial one in the thread, so it doesn't exactly
match $subject now.
I've edited the CF entry title to read: Add \errverbose to psql (and
support in libpq)
What's tested here is a client-side approach, suggested by Tom
upthread as an alternative, and implemented by Oleksandr in
0001-POC-errverbose-in-psql.patchThe patch has two parts: one part in libpq exposing a new function
named PQresultBuildErrorMessage, and a second part implementing an
\errverbose command in psql, essentially displaying the result of the
function.
The separation makes sense if we consider that other clients might benefit
from the function, or that libpq is a better place than psql to maintain
this in the future, as the list of error fields available in a PGresult
might grow.
OTOH maybe adding a new libpq function just for that is overkill, but this
is subjective.psql part
=========
Compiles and works as intended.
For instance with \set VERBOSITY default, an FK violation produces:# insert into table2 values(10);
ERROR: insert or update on table "table2" violates foreign key
constraint
"table2_id_tbl1_fkey"
DETAIL: Key (id_tbl1)=(10) is not present in table "table1".Then \errverbose just displays the verbose form of the error:
# \errverbose
ERROR: 23503: insert or update on table "table2" violates foreign
key constraint "table2_id_tbl1_fkey"
DETAIL: Key (id_tbl1)=(10) is not present in table "table1".
SCHEMA NAME: public
TABLE NAME: table2
CONSTRAINT NAME: table2_id_tbl1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326- make check passes
- valgrind test is OK (no obvious leak after using the command).Missing bits:
- the command is not mentioned in the help (\? command, see help.c)
- it should be added to tab completions (see tab-complete.c)
- it needs to be described in the SGML documentationlibpq part
==========
The patch implements and exports a new PQresultBuildErrorMessage()
function.AFAICS its purpose is to produce a result similar to what
PQresultErrorMessage() would have produced, if PQERRORS_VERBOSE
was the verbosity in effect at execution time.My comments:
- the name of the function does not really hint at what it does.
Maybe something like PQresultVerboseErrorMessage() would be more
explicit?
Not exactly, the verbosity setting might or might not be in effect when
this function is called. Another external part of the state that might
affect the result is conn->show_context field.
I would expect the new function to have the same interface than
PQresultErrorMessage(), but it's not the case.
- it takes a PGconn* and PGresult* as input parameters, but
PQresultErrorMessage takes only a <const PGresult*> as input.
It's not clear why PGconn* is necessary.
This is because PQresultErrorMessage() returns a stored error message:
res->errMsg (or "").
- it returns a pointer to a strdup'ed() buffer, which
has to be freed separately by the caller. That differs from
PQresultErrorMessage() which keeps this managed inside the
PGresult struct.
For the same reason: this function actually produces a new error message,
as opposed to returning a stored one.
- if protocol version < 3, an error message is returned. It's not
clear to the caller that this error is emitted by the function itself
rather than the query. Besides, are we sure it's necessary?
Maybe the function could just produce an output with whatever
error fields it has, even minimally with older protocol versions,
rather than failing?
Hm, probably we could just copy the message from conn->errorMessage, in
case of protocol v2, but I don't see a point in passing PGresult to the
func or naming it PQresult<Something> in the case of v2.
- if the fixed error message is kept, it should pass through
libpq_gettext() for translation.
Good point.
- a description of the function should be added to the SGML doc.
There's a sentence in PQsetErrorVerbosity() that says, currently:
"Changing the verbosity does not affect the messages available from
already-existing PGresult objects, only subsequently-created ones."As it's precisely the point of that new function, that bit could
be altered to refer to it.
I didn't touch the documentation specifically, because this is just a
proof-of-concept, but it's good that you notice this detail. Most
importantly, I'd like to learn of better options than storing the whole
last_result in psql's pset structure.
Thanks for the review!
--
Alex
Shulgin, Oleksandr wrote:
Most importantly, I'd like to learn of better options than storing the
whole last_result in psql's pset structure.
I guess that you could, each time a query fails, gather silently the
result of \errverbose, store it in a buffer, discard the PGresult,
and in case the user does \errverbose before running another query,
output what was in that buffer.
Best regards,
--
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Tue, Jan 5, 2016 at 10:16 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Jim Nasby <Jim.Nasby@bluetreble.com> writes:
FWIW, I suspect very few people know about the verbosity setting (I
didn't until a few months ago...) Maybe psql should hint about it the
first time an error is reported in a session.Actually, what'd be really handy IMO is something to regurgitate the
most recent error in verbose mode, without making a permanent session
state change. Something likeregression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
ERROR: 23503: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
regression=#
Wow, that's a fabulous idea. I see Oleksandr has tried to implement
it, although I haven't looked at the patch. But I think this would be
REALLY helpful.
--
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
most recent error in verbose mode, without making a permanent session
state change. Something like
regression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint"bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
ERROR: 23503: insert or update on table "bar" violates foreign keyconstraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
regression=#Wow, that's a fabulous idea. I see Oleksandr has tried to implement
it, although I haven't looked at the patch. But I think this would be
REALLY helpful.
yes
+1
Pavel
Show quoted text
--
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 Thu, Feb 11, 2016 at 10:53 AM, Pavel Stehule <pavel.stehule@gmail.com>
wrote:
most recent error in verbose mode, without making a permanent session
state change. Something like
regression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint"bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
Maybe its too cute but I'll give a +1 for this alone.
David J.
On Wed, Feb 10, 2016 at 12:33 AM, Daniel Verite <daniel@manitou-mail.org>
wrote:
Shulgin, Oleksandr wrote:
Most importantly, I'd like to learn of better options than storing the
whole last_result in psql's pset structure.I guess that you could, each time a query fails, gather silently the
result of \errverbose, store it in a buffer, discard the PGresult,
and in case the user does \errverbose before running another query,
output what was in that buffer.
That's a neat idea. I also think that we could only store last PGresult
when the query fails actually and discard it otherwise: the PGresult
holding only the error doesn't occupy too much space.
What I dislike about this POC is all the disruption in libpq, to be
honest. It would be much neater if we could form the verbose message every
time and let the client decide where to cut it. Maybe a bit "too clever"
would be to put a \0 char between short message and it's verbose
continuation. The client could then reach the verbose part like this
(assuming that libpq did put a verbose part there): msg + strlen(msg) + 1.
--
Alex
"Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de> writes:
What I dislike about this POC is all the disruption in libpq, to be
honest.
Yeah, I don't much like that either. But I don't think we can avoid
some refactoring there; as designed, conversion of an error message into
user-visible form is too tightly tied to receipt of the message.
It would be much neater if we could form the verbose message every
time and let the client decide where to cut it. Maybe a bit "too clever"
would be to put a \0 char between short message and it's verbose
continuation. The client could then reach the verbose part like this
(assuming that libpq did put a verbose part there): msg + strlen(msg) + 1.
Blech :-(
Thinking about it, though, it seems to me that we could get away with
always performing both styles of conversion and sticking both strings
into the PGresult. That would eat a little more space but not much.
Then we just need to add API to let the application get at both forms.
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 Mon, Mar 14, 2016 at 7:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de> writes:
What I dislike about this POC is all the disruption in libpq, to be
honest.Yeah, I don't much like that either. But I don't think we can avoid
some refactoring there; as designed, conversion of an error message into
user-visible form is too tightly tied to receipt of the message.
True. Attached is a v2 which addresses all of the points raised earlier I
believe.
We still extract the message building part of code from
pqGetErrorNotice3(), but the user-facing change is much more sane now: just
added a new function PQresultVerboseErrorMessage().
It would be much neater if we could form the verbose message every
time and let the client decide where to cut it. Maybe a bit "too clever"
would be to put a \0 char between short message and it's verbose
continuation. The client could then reach the verbose part like this
(assuming that libpq did put a verbose part there): msg + strlen(msg) +1.
Blech :-(
:-) That would not work either way, I've just noticed that SQLLEVEL is put
at the start of the message in verbose mode, but not by default.
Thinking about it, though, it seems to me that we could get away with
always performing both styles of conversion and sticking both strings
into the PGresult. That would eat a little more space but not much.
Then we just need to add API to let the application get at both forms.
This is what the v2 basically implements, now complete with help,
tab-complete and documentation changes. I don't think we can add a usual
simple regression test here reliably, due to LOCATION field which might be
subject to unexpected line number changes. But do we really need one?
--
Regards,
Alex
Attachments:
errverbose-psql-v2.patchtext/x-patch; charset=US-ASCII; name=errverbose-psql-v2.patchDownload
From d8d6a65da57d8e14eac4f614d31b19d52f8176d9 Mon Sep 17 00:00:00 2001
From: Oleksandr Shulgin <oleksandr.shulgin@zalando.de>
Date: Wed, 6 Jan 2016 14:58:17 +0100
Subject: [PATCH] Add \errverbose psql command and support in libpq
For every error we build both the default and verbose error message,
then store both in PGresult. The client can then retrieve the verbose
message using the new exported function PGresultVerboseErrorMessage().
In psql we store the verbose error message in pset (if any) for display
when requested by the user with \errverbose.
---
doc/src/sgml/libpq.sgml | 40 ++++++++-
src/bin/psql/command.c | 9 ++
src/bin/psql/common.c | 5 ++
src/bin/psql/help.c | 1 +
src/bin/psql/settings.h | 2 +
src/bin/psql/startup.c | 1 +
src/bin/psql/tab-complete.c | 2 +-
src/interfaces/libpq/exports.txt | 1 +
src/interfaces/libpq/fe-exec.c | 29 ++++++-
src/interfaces/libpq/fe-protocol3.c | 166 +++++++++++++++++++++---------------
src/interfaces/libpq/libpq-fe.h | 1 +
src/interfaces/libpq/libpq-int.h | 3 +-
12 files changed, 181 insertions(+), 79 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2328d8f..8b9544f 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2691,6 +2691,38 @@ char *PQresultErrorMessage(const PGresult *res);
</listitem>
</varlistentry>
+ <varlistentry id="libpq-pqresultverboseerrormessage">
+ <term>
+ <function>PQresultVerboseErrorMessage</function>
+ <indexterm>
+ <primary>PQresultVerboseErrorMessage</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ Returns the most verbose error message associated with the
+ command, or an empty string if there was no error.
+<synopsis>
+char *PQresultVerboseErrorMessage(const PGresult *res);
+</synopsis>
+ If there was an error, the returned string will include a trailing
+ newline. The caller should not free the result directly. It will
+ be freed when the associated <structname>PGresult</> handle is
+ passed to <function>PQclear</function>.
+ </para>
+
+ <para>
+ If error verbosity is already set to the maximum available level,
+ this will return exactly the same error message
+ as <function>PQresultErrorMessage</function>. Otherwise it can be
+ useful to retrieve a more detailed error message without running
+ the failed query again (while increasing the error verbosity
+ level).
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-pqresulterrorfield">
<term><function>PQresultErrorField</function><indexterm><primary>PQresultErrorField</></></term>
<listitem>
@@ -5579,9 +5611,11 @@ PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);
this will normally fit on a single line. The default mode produces
messages that include the above plus any detail, hint, or context
fields (these might span multiple lines). The <firstterm>VERBOSE</>
- mode includes all available fields. Changing the verbosity does not
- affect the messages available from already-existing
- <structname>PGresult</> objects, only subsequently-created ones.
+ mode includes all available fields. It is still possible to
+ retrieve the most verbose error message from an
+ existing <structname>PGresult</> object (without changing the
+ current verbosity level)
+ using <function>PQresultVerboseErrorMessage</>.
</para>
</listitem>
</varlistentry>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 9750a5b..8ec2bbd 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -822,6 +822,13 @@ exec_command(const char *cmd,
}
}
+ /* \errverbose -- display verbose error message from last result */
+ else if (strcmp(cmd, "errverbose") == 0)
+ {
+ if (pset.verbose_error && *pset.verbose_error)
+ psql_error("%s", pset.verbose_error);
+ }
+
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
{
@@ -1865,6 +1872,8 @@ do_connect(char *dbname, char *user, char *host, char *port)
{
PQfinish(o_conn);
pset.db = NULL;
+ free(pset.verbose_error);
+ pset.verbose_error = NULL;
}
}
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 2cb2e9b..331addb 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -315,6 +315,8 @@ CheckConnection(void)
psql_error("Failed.\n");
PQfinish(pset.db);
pset.db = NULL;
+ free(pset.verbose_error);
+ pset.verbose_error = NULL;
ResetCancelConn();
UnsyncVariables();
}
@@ -1154,6 +1156,9 @@ SendQuery(const char *query)
}
}
+ /* Save the verbose error message (if any) and clear the results. */
+ free(pset.verbose_error);
+ pset.verbose_error = pg_strdup(PQresultVerboseErrorMessage(results));
PQclear(results);
/* Possible microtiming output */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 59f6f25..abfa40d 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -172,6 +172,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
+ fprintf(output, _(" \\errverbose show verbose error message from the last query\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
fprintf(output, _(" \\q quit psql\n"));
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 20a6470..bcb16ce 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -130,6 +130,8 @@ typedef struct _psqlSettings
const char *prompt3;
PGVerbosity verbosity; /* current error verbosity level */
PGContextVisibility show_context; /* current context display level */
+ char *verbose_error; /* set if last query resulted in an error
+ and a verbose message was available */
} PsqlSettings;
extern PsqlSettings pset;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 6916f6f..695a84a 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -130,6 +130,7 @@ main(int argc, char *argv[])
pset.progname = get_progname(argv[0]);
pset.db = NULL;
+ pset.verbose_error = NULL;
setDecimalLocale();
pset.encoding = PQenv2encoding();
pset.queryFout = stdout;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 6a81416..061da8d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1279,7 +1279,7 @@ psql_completion(const char *text, int start, int end)
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS",
"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
- "\\e", "\\echo", "\\ef", "\\encoding", "\\ev",
+ "\\e", "\\echo", "\\ef", "\\encoding", "\\errverbose", "\\ev",
"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index c69a4d5..21dd772 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -170,3 +170,4 @@ PQsslStruct 167
PQsslAttributeNames 168
PQsslAttribute 169
PQsetErrorContextVisibility 170
+PQresultVerboseErrorMessage 171
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 41937c0..72f31a0 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -158,6 +158,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
result->events = NULL;
result->nEvents = 0;
result->errMsg = NULL;
+ result->errMsgVerbose = NULL;
result->errFields = NULL;
result->null_field[0] = '\0';
result->curBlock = NULL;
@@ -627,17 +628,29 @@ pqSetResultError(PGresult *res, const char *msg)
* concatenate a new error message to the one already in a PGresult
*/
void
-pqCatenateResultError(PGresult *res, const char *msg)
+pqCatenateResultError(PGresult *res, const char *msg, const char *msgVerbose)
{
PQExpBufferData errorBuf;
if (!res || !msg)
return;
+
initPQExpBuffer(&errorBuf);
if (res->errMsg)
appendPQExpBufferStr(&errorBuf, res->errMsg);
appendPQExpBufferStr(&errorBuf, msg);
pqSetResultError(res, errorBuf.data);
+
+ if (msgVerbose)
+ {
+ resetPQExpBuffer(&errorBuf);
+ if (res->errMsgVerbose)
+ appendPQExpBufferStr(&errorBuf, res->errMsgVerbose);
+ appendPQExpBufferStr(&errorBuf, msgVerbose);
+
+ res->errMsgVerbose = pqResultStrdup(res, errorBuf.data);
+ }
+
termPQExpBuffer(&errorBuf);
}
@@ -740,7 +753,9 @@ pqSaveErrorResult(PGconn *conn)
else
{
/* Else, concatenate error message to existing async result. */
- pqCatenateResultError(conn->result, conn->errorMessage.data);
+
+ /* XXX do we need to store verbose error message in conn? */
+ pqCatenateResultError(conn->result, conn->errorMessage.data, NULL);
}
}
@@ -2004,7 +2019,7 @@ PQexecFinish(PGconn *conn)
if (lastResult->resultStatus == PGRES_FATAL_ERROR &&
result->resultStatus == PGRES_FATAL_ERROR)
{
- pqCatenateResultError(lastResult, result->errMsg);
+ pqCatenateResultError(lastResult, result->errMsg, result->errMsgVerbose);
PQclear(result);
result = lastResult;
@@ -2599,6 +2614,14 @@ PQresultErrorMessage(const PGresult *res)
}
char *
+PQresultVerboseErrorMessage(const PGresult *res)
+{
+ if (!res || !res->errMsgVerbose)
+ return "";
+ return res->errMsgVerbose;
+}
+
+char *
PQresultErrorField(const PGresult *res, int fieldcode)
{
PGMessageField *pfield;
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 43898a4..a3365ac 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -51,6 +51,8 @@ static int getParameterStatus(PGconn *conn);
static int getNotify(PGconn *conn);
static int getCopyStart(PGconn *conn, ExecStatusType copytype);
static int getReadyForQuery(PGconn *conn);
+static void buildErrorMessage(const PGconn *conn, const PGresult *res, bool isError,
+ PGVerbosity verbosity, PQExpBuffer workBuf);
static void reportErrorPosition(PQExpBuffer msg, const char *query,
int loc, int encoding);
static int build_startup_packet(const PGconn *conn, char *packet,
@@ -865,9 +867,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
PGresult *res = NULL;
PQExpBufferData workBuf;
char id;
- const char *val;
- const char *querytext = NULL;
- int querypos = 0;
+ const char *sqlstate;
/*
* Since the fields might be pretty long, we create a temporary
@@ -904,30 +904,90 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
pqSaveMessageField(res, id, workBuf.data);
}
+ /* Save the last SQLSTATE in connection. */
+ sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE);
+ if (sqlstate && strlen(sqlstate) < sizeof(conn->last_sqlstate))
+ strcpy(conn->last_sqlstate, sqlstate);
+
+ /* Now build the "overall" error message for PQresultErrorMessage. */
+ resetPQExpBuffer(&workBuf);
+ buildErrorMessage(conn, res, isError, conn->verbosity, &workBuf);
+
/*
- * Now build the "overall" error message for PQresultErrorMessage.
- *
- * Also, save the SQLSTATE in conn->last_sqlstate.
+ * Either save error as current async result, or just emit the notice.
*/
- resetPQExpBuffer(&workBuf);
+ if (isError)
+ {
+ if (res)
+ res->errMsg = pqResultStrdup(res, workBuf.data);
+ pqClearAsyncResult(conn);
+ conn->result = res;
+ if (PQExpBufferDataBroken(workBuf))
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ else
+ {
+ appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
+
+ if (res)
+ {
+ /* Also build the verbose error message. */
+ resetPQExpBuffer(&workBuf);
+ buildErrorMessage(conn, res, isError, PQERRORS_VERBOSE, &workBuf);
+ if (PQExpBufferDataBroken(workBuf))
+ res->errMsgVerbose = pqResultStrdup(res,
+ libpq_gettext("out of memory"));
+ else
+ res->errMsgVerbose = pqResultStrdup(res, workBuf.data);
+ }
+ }
+ }
+ else
+ {
+ /* if we couldn't allocate the result set, just discard the NOTICE */
+ if (res)
+ {
+ /* We can cheat a little here and not copy the message. */
+ res->errMsg = workBuf.data;
+ if (res->noticeHooks.noticeRec != NULL)
+ (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
+ PQclear(res);
+ }
+ }
+
+ termPQExpBuffer(&workBuf);
+ return 0;
+
+fail:
+ PQclear(res);
+ termPQExpBuffer(&workBuf);
+ return EOF;
+}
+
+static void
+buildErrorMessage(const PGconn *conn, const PGresult *res, bool isError,
+ PGVerbosity verbosity, PQExpBuffer workBuf)
+{
+ const char *val;
+ const char *querytext = NULL;
+ int querypos = 0;
+
val = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (val)
- appendPQExpBuffer(&workBuf, "%s: ", val);
+ appendPQExpBuffer(workBuf, "%s: ", val);
val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (val)
{
- if (strlen(val) < sizeof(conn->last_sqlstate))
- strcpy(conn->last_sqlstate, val);
- if (conn->verbosity == PQERRORS_VERBOSE)
- appendPQExpBuffer(&workBuf, "%s: ", val);
+ if (verbosity == PQERRORS_VERBOSE)
+ appendPQExpBuffer(workBuf, "%s: ", val);
}
val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
if (val)
- appendPQExpBufferStr(&workBuf, val);
+ appendPQExpBufferStr(workBuf, val);
val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
if (val)
{
- if (conn->verbosity != PQERRORS_TERSE && conn->last_query != NULL)
+ if (verbosity != PQERRORS_TERSE && conn->last_query != NULL)
{
/* emit position as a syntax cursor display */
querytext = conn->last_query;
@@ -937,7 +997,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
- appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
+ appendPQExpBuffer(workBuf, libpq_gettext(" at character %s"),
val);
}
}
@@ -947,7 +1007,7 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
if (val)
{
querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
- if (conn->verbosity != PQERRORS_TERSE && querytext != NULL)
+ if (verbosity != PQERRORS_TERSE && querytext != NULL)
{
/* emit position as a syntax cursor display */
querypos = atoi(val);
@@ -956,59 +1016,59 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
- appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
+ appendPQExpBuffer(workBuf, libpq_gettext(" at character %s"),
val);
}
}
}
- appendPQExpBufferChar(&workBuf, '\n');
- if (conn->verbosity != PQERRORS_TERSE)
+ appendPQExpBufferChar(workBuf, '\n');
+ if (verbosity != PQERRORS_TERSE)
{
if (querytext && querypos > 0)
- reportErrorPosition(&workBuf, querytext, querypos,
+ reportErrorPosition(workBuf, querytext, querypos,
conn->client_encoding);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("DETAIL: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("HINT: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("QUERY: %s\n"), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("QUERY: %s\n"), val);
if (conn->show_context == PQSHOW_CONTEXT_ALWAYS ||
(conn->show_context == PQSHOW_CONTEXT_ERRORS && isError))
{
val = PQresultErrorField(res, PG_DIAG_CONTEXT);
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"),
+ appendPQExpBuffer(workBuf, libpq_gettext("CONTEXT: %s\n"),
val);
}
}
- if (conn->verbosity == PQERRORS_VERBOSE)
+ if (verbosity == PQERRORS_VERBOSE)
{
val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("SCHEMA NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("TABLE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("COLUMN NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("DATATYPE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
if (val)
- appendPQExpBuffer(&workBuf,
+ appendPQExpBuffer(workBuf,
libpq_gettext("CONSTRAINT NAME: %s\n"), val);
}
- if (conn->verbosity == PQERRORS_VERBOSE)
+ if (verbosity == PQERRORS_VERBOSE)
{
const char *valf;
const char *vall;
@@ -1018,51 +1078,15 @@ pqGetErrorNotice3(PGconn *conn, bool isError)
val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION);
if (val || valf || vall)
{
- appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: "));
+ appendPQExpBufferStr(workBuf, libpq_gettext("LOCATION: "));
if (val)
- appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val);
+ appendPQExpBuffer(workBuf, libpq_gettext("%s, "), val);
if (valf && vall) /* unlikely we'd have just one */
- appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"),
+ appendPQExpBuffer(workBuf, libpq_gettext("%s:%s"),
valf, vall);
- appendPQExpBufferChar(&workBuf, '\n');
+ appendPQExpBufferChar(workBuf, '\n');
}
}
-
- /*
- * Either save error as current async result, or just emit the notice.
- */
- if (isError)
- {
- if (res)
- res->errMsg = pqResultStrdup(res, workBuf.data);
- pqClearAsyncResult(conn);
- conn->result = res;
- if (PQExpBufferDataBroken(workBuf))
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("out of memory"));
- else
- appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
- }
- else
- {
- /* if we couldn't allocate the result set, just discard the NOTICE */
- if (res)
- {
- /* We can cheat a little here and not copy the message. */
- res->errMsg = workBuf.data;
- if (res->noticeHooks.noticeRec != NULL)
- (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
- PQclear(res);
- }
- }
-
- termPQExpBuffer(&workBuf);
- return 0;
-
-fail:
- PQclear(res);
- termPQExpBuffer(&workBuf);
- return EOF;
}
/*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 6bf34b3..ad177cc 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -463,6 +463,7 @@ extern PGresult *PQfn(PGconn *conn,
extern ExecStatusType PQresultStatus(const PGresult *res);
extern char *PQresStatus(ExecStatusType status);
extern char *PQresultErrorMessage(const PGresult *res);
+extern char *PQresultVerboseErrorMessage(const PGresult *res);
extern char *PQresultErrorField(const PGresult *res, int fieldcode);
extern int PQntuples(const PGresult *res);
extern int PQnfields(const PGresult *res);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6c9bbf7..1574b7b 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -196,6 +196,7 @@ struct pg_result
* per-field info then it is stored in a linked list.
*/
char *errMsg; /* error message, or NULL if no error */
+ char *errMsgVerbose; /* verbose error message, or NULL if no error */
PGMessageField *errFields; /* message broken into fields */
/* All NULL attributes in the query result point to this null string */
@@ -539,7 +540,7 @@ extern pgthreadlock_t pg_g_threadlock;
/* === in fe-exec.c === */
extern void pqSetResultError(PGresult *res, const char *msg);
-extern void pqCatenateResultError(PGresult *res, const char *msg);
+extern void pqCatenateResultError(PGresult *res, const char *msg, const char *msgVerbose);
extern void *pqResultAlloc(PGresult *res, size_t nBytes, bool isBinary);
extern char *pqResultStrdup(PGresult *res, const char *str);
extern void pqClearAsyncResult(PGconn *conn);
--
2.5.0
On Tue, Mar 15, 2016 at 4:44 PM, Shulgin, Oleksandr <
oleksandr.shulgin@zalando.de> wrote:
On Mon, Mar 14, 2016 at 7:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de> writes:
What I dislike about this POC is all the disruption in libpq, to be
honest.Yeah, I don't much like that either. But I don't think we can avoid
some refactoring there; as designed, conversion of an error message into
user-visible form is too tightly tied to receipt of the message.True. Attached is a v2 which addresses all of the points raised earlier I
believe.We still extract the message building part of code from
pqGetErrorNotice3(), but the user-facing change is much more sane now: just
added a new function PQresultVerboseErrorMessage().
I wonder if this sort of change has any chance to be back-patched? If not,
it would be really nice to have any sort of review soonish.
Thank you.
--
Alex
"Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de> writes:
On Mon, Mar 14, 2016 at 7:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Yeah, I don't much like that either. But I don't think we can avoid
some refactoring there; as designed, conversion of an error message into
user-visible form is too tightly tied to receipt of the message.
True. Attached is a v2 which addresses all of the points raised earlier I
believe.
I took a closer look at what's going on here and realized that actually
it's not that hard to decouple the message-building routine from the
PGconn state, because mostly it works with fields it's extracting out
of the PGresult anyway. The only piece of information that's lacking
is conn->last_query. I propose therefore that instead of doing it like
this, we copy last_query into error PGresults. This is strictly less
added storage requirement than storing the whole verbose message would be,
and it saves time compared to the v2 patch in the typical case where
the application never does ask for an alternately-formatted error message.
Plus we can actually support requests for any variant format, not only
VERBOSE.
Attached is a libpq-portion-only version of a patch doing it this way.
I've not yet looked at the psql part of your patch.
Comments?
regards, tom lane
Attachments:
errverbose-v3.patchtext/x-diff; charset=us-ascii; name=errverbose-v3.patchDownload
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 2328d8f..80f7014 100644
*** a/doc/src/sgml/libpq.sgml
--- b/doc/src/sgml/libpq.sgml
*************** char *PQresultErrorMessage(const PGresul
*** 2691,2696 ****
--- 2691,2738 ----
</listitem>
</varlistentry>
+ <varlistentry id="libpq-pqresultverboseerrormessage">
+ <term>
+ <function>PQresultVerboseErrorMessage</function>
+ <indexterm>
+ <primary>PQresultVerboseErrorMessage</primary>
+ </indexterm>
+ </term>
+
+ <listitem>
+ <para>
+ Returns a reformatted version of the error message associated with
+ a <structname>PGresult</> object.
+ <synopsis>
+ char *PQresultVerboseErrorMessage(const PGresult *res,
+ PGVerbosity verbosity,
+ PGContextVisibility show_context);
+ </synopsis>
+ In some situations a client might wish to obtain a more detailed
+ version of a previously-reported error.
+ <function>PQresultVerboseErrorMessage</function> addresses this need
+ by computing the message that would have been produced
+ by <function>PQresultErrorMessage</function> if the specified
+ verbosity settings had been in effect for the connection when the
+ given <structname>PGresult</> was generated. If
+ the <structname>PGresult</> is not an error result,
+ <quote>PGresult is not an error result</> is reported instead.
+ The returned string includes a trailing newline.
+ </para>
+
+ <para>
+ Unlike most other functions for extracting data from
+ a <structname>PGresult</>, the result of this function is a freshly
+ allocated string. The caller must free it
+ using <function>PQfreemem()</> when the string is no longer needed.
+ </para>
+
+ <para>
+ A NULL return is possible if there is insufficient memory.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-pqresulterrorfield">
<term><function>PQresultErrorField</function><indexterm><primary>PQresultErrorField</></></term>
<listitem>
*************** PGVerbosity PQsetErrorVerbosity(PGconn *
*** 5582,5587 ****
--- 5624,5631 ----
mode includes all available fields. Changing the verbosity does not
affect the messages available from already-existing
<structname>PGresult</> objects, only subsequently-created ones.
+ (But see <function>PQresultVerboseErrorMessage</function> if you
+ want to print a previous error with a different verbosity.)
</para>
</listitem>
</varlistentry>
*************** PGContextVisibility PQsetErrorContextVis
*** 5622,5627 ****
--- 5666,5673 ----
affect the messages available from
already-existing <structname>PGresult</> objects, only
subsequently-created ones.
+ (But see <function>PQresultVerboseErrorMessage</function> if you
+ want to print a previous error with a different display mode.)
</para>
</listitem>
</varlistentry>
*************** PQsetNoticeProcessor(PGconn *conn,
*** 6089,6096 ****
receiver function is called. It is passed the message in the form of
a <symbol>PGRES_NONFATAL_ERROR</symbol>
<structname>PGresult</structname>. (This allows the receiver to extract
! individual fields using <function>PQresultErrorField</>, or the complete
! preformatted message using <function>PQresultErrorMessage</>.) The same
void pointer passed to <function>PQsetNoticeReceiver</function> is also
passed. (This pointer can be used to access application-specific state
if needed.)
--- 6135,6143 ----
receiver function is called. It is passed the message in the form of
a <symbol>PGRES_NONFATAL_ERROR</symbol>
<structname>PGresult</structname>. (This allows the receiver to extract
! individual fields using <function>PQresultErrorField</>, or complete
! preformatted messages using <function>PQresultErrorMessage</> or
! <function>PQresultVerboseErrorMessage</>.) The same
void pointer passed to <function>PQsetNoticeReceiver</function> is also
passed. (This pointer can be used to access application-specific state
if needed.)
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index c69a4d5..21dd772 100644
*** a/src/interfaces/libpq/exports.txt
--- b/src/interfaces/libpq/exports.txt
*************** PQsslStruct 167
*** 170,172 ****
--- 170,173 ----
PQsslAttributeNames 168
PQsslAttribute 169
PQsetErrorContextVisibility 170
+ PQresultVerboseErrorMessage 171
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 41937c0..2621767 100644
*** a/src/interfaces/libpq/fe-exec.c
--- b/src/interfaces/libpq/fe-exec.c
*************** PQmakeEmptyPGresult(PGconn *conn, ExecSt
*** 159,164 ****
--- 159,165 ----
result->nEvents = 0;
result->errMsg = NULL;
result->errFields = NULL;
+ result->errQuery = NULL;
result->null_field[0] = '\0';
result->curBlock = NULL;
result->curOffset = 0;
*************** PQresultErrorMessage(const PGresult *res
*** 2599,2604 ****
--- 2600,2643 ----
}
char *
+ PQresultVerboseErrorMessage(const PGresult *res,
+ PGVerbosity verbosity,
+ PGContextVisibility show_context)
+ {
+ PQExpBufferData workBuf;
+
+ /*
+ * Because the caller is expected to free the result string, we must
+ * strdup any constant result. We use plain strdup and document that
+ * callers should expect NULL if out-of-memory.
+ */
+ if (!res ||
+ (res->resultStatus != PGRES_FATAL_ERROR &&
+ res->resultStatus != PGRES_NONFATAL_ERROR))
+ return strdup(libpq_gettext("PGresult is not an error result\n"));
+
+ initPQExpBuffer(&workBuf);
+
+ /*
+ * Currently, we pass this off to fe-protocol3.c in all cases; it will
+ * behave reasonably sanely with an error reported by fe-protocol2.c as
+ * well. If necessary, we could record the protocol version in PGresults
+ * so as to be able to invoke a version-specific message formatter, but
+ * for now there's no need.
+ */
+ pqBuildErrorMessage3(&workBuf, res, verbosity, show_context);
+
+ /* If insufficient memory to format the message, fail cleanly */
+ if (PQExpBufferDataBroken(workBuf))
+ {
+ termPQExpBuffer(&workBuf);
+ return strdup(libpq_gettext("out of memory\n"));
+ }
+
+ return workBuf.data;
+ }
+
+ char *
PQresultErrorField(const PGresult *res, int fieldcode)
{
PGMessageField *pfield;
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 3034773..481ee9b 100644
*** a/src/interfaces/libpq/fe-protocol3.c
--- b/src/interfaces/libpq/fe-protocol3.c
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 878,886 ****
PGresult *res = NULL;
PQExpBufferData workBuf;
char id;
- const char *val;
- const char *querytext = NULL;
- int querypos = 0;
/*
* Since the fields might be pretty long, we create a temporary
--- 878,883 ----
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 905,910 ****
--- 902,909 ----
/*
* Read the fields and save into res.
+ *
+ * Also, save the SQLSTATE in conn->last_sqlstate.
*/
for (;;)
{
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 915,956 ****
if (pqGets(&workBuf, conn))
goto fail;
pqSaveMessageField(res, id, workBuf.data);
}
/*
* Now build the "overall" error message for PQresultErrorMessage.
- *
- * Also, save the SQLSTATE in conn->last_sqlstate.
*/
resetPQExpBuffer(&workBuf);
val = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (val)
! appendPQExpBuffer(&workBuf, "%s: ", val);
! val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
! if (val)
{
! if (strlen(val) < sizeof(conn->last_sqlstate))
! strcpy(conn->last_sqlstate, val);
! if (conn->verbosity == PQERRORS_VERBOSE)
! appendPQExpBuffer(&workBuf, "%s: ", val);
}
val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
if (val)
! appendPQExpBufferStr(&workBuf, val);
val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
if (val)
{
! if (conn->verbosity != PQERRORS_TERSE && conn->last_query != NULL)
{
/* emit position as a syntax cursor display */
! querytext = conn->last_query;
querypos = atoi(val);
}
else
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
! appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
val);
}
}
--- 914,1032 ----
if (pqGets(&workBuf, conn))
goto fail;
pqSaveMessageField(res, id, workBuf.data);
+ if (id == PG_DIAG_SQLSTATE)
+ strlcpy(conn->last_sqlstate, workBuf.data,
+ sizeof(conn->last_sqlstate));
}
/*
+ * Save the active query text, if any, into res as well.
+ */
+ if (res && conn->last_query)
+ res->errQuery = pqResultStrdup(res, conn->last_query);
+
+ /*
* Now build the "overall" error message for PQresultErrorMessage.
*/
resetPQExpBuffer(&workBuf);
+ pqBuildErrorMessage3(&workBuf, res, conn->verbosity, conn->show_context);
+
+ /*
+ * Either save error as current async result, or just emit the notice.
+ */
+ if (isError)
+ {
+ if (res)
+ res->errMsg = pqResultStrdup(res, workBuf.data);
+ pqClearAsyncResult(conn);
+ conn->result = res;
+ if (PQExpBufferDataBroken(workBuf))
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("out of memory"));
+ else
+ appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
+ }
+ else
+ {
+ /* if we couldn't allocate the result set, just discard the NOTICE */
+ if (res)
+ {
+ /* We can cheat a little here and not copy the message. */
+ res->errMsg = workBuf.data;
+ if (res->noticeHooks.noticeRec != NULL)
+ (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
+ PQclear(res);
+ }
+ }
+
+ termPQExpBuffer(&workBuf);
+ return 0;
+
+ fail:
+ PQclear(res);
+ termPQExpBuffer(&workBuf);
+ return EOF;
+ }
+
+ /*
+ * Construct an error message from the fields in the given PGresult,
+ * appending it to the contents of "msg".
+ */
+ void
+ pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res,
+ PGVerbosity verbosity, PGContextVisibility show_context)
+ {
+ const char *val;
+ const char *querytext = NULL;
+ int querypos = 0;
+
+ /* If we couldn't allocate a PGresult, just say "out of memory" */
+ if (res == NULL)
+ {
+ appendPQExpBuffer(msg, libpq_gettext("out of memory\n"));
+ return;
+ }
+
+ /*
+ * If we don't have any broken-down fields, just return the base message.
+ * This mainly applies if we're given a libpq-generated error result.
+ */
+ if (res->errFields == NULL)
+ {
+ if (res->errMsg && res->errMsg[0])
+ appendPQExpBufferStr(msg, res->errMsg);
+ else
+ appendPQExpBuffer(msg, libpq_gettext("no error message available\n"));
+ return;
+ }
+
+ /* Else build error message from relevant fields */
val = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (val)
! appendPQExpBuffer(msg, "%s: ", val);
! if (verbosity == PQERRORS_VERBOSE)
{
! val = PQresultErrorField(res, PG_DIAG_SQLSTATE);
! if (val)
! appendPQExpBuffer(msg, "%s: ", val);
}
val = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
if (val)
! appendPQExpBufferStr(msg, val);
val = PQresultErrorField(res, PG_DIAG_STATEMENT_POSITION);
if (val)
{
! if (verbosity != PQERRORS_TERSE && res->errQuery != NULL)
{
/* emit position as a syntax cursor display */
! querytext = res->errQuery;
querypos = atoi(val);
}
else
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
! appendPQExpBuffer(msg, libpq_gettext(" at character %s"),
val);
}
}
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 960,966 ****
if (val)
{
querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
! if (conn->verbosity != PQERRORS_TERSE && querytext != NULL)
{
/* emit position as a syntax cursor display */
querypos = atoi(val);
--- 1036,1042 ----
if (val)
{
querytext = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
! if (verbosity != PQERRORS_TERSE && querytext != NULL)
{
/* emit position as a syntax cursor display */
querypos = atoi(val);
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 969,1027 ****
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
! appendPQExpBuffer(&workBuf, libpq_gettext(" at character %s"),
val);
}
}
}
! appendPQExpBufferChar(&workBuf, '\n');
! if (conn->verbosity != PQERRORS_TERSE)
{
if (querytext && querypos > 0)
! reportErrorPosition(&workBuf, querytext, querypos,
! conn->client_encoding);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
if (val)
! appendPQExpBuffer(&workBuf, libpq_gettext("DETAIL: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
if (val)
! appendPQExpBuffer(&workBuf, libpq_gettext("HINT: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (val)
! appendPQExpBuffer(&workBuf, libpq_gettext("QUERY: %s\n"), val);
! if (conn->show_context == PQSHOW_CONTEXT_ALWAYS ||
! (conn->show_context == PQSHOW_CONTEXT_ERRORS && isError))
{
val = PQresultErrorField(res, PG_DIAG_CONTEXT);
if (val)
! appendPQExpBuffer(&workBuf, libpq_gettext("CONTEXT: %s\n"),
val);
}
}
! if (conn->verbosity == PQERRORS_VERBOSE)
{
val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
if (val)
! appendPQExpBuffer(&workBuf,
libpq_gettext("SCHEMA NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
if (val)
! appendPQExpBuffer(&workBuf,
libpq_gettext("TABLE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
if (val)
! appendPQExpBuffer(&workBuf,
libpq_gettext("COLUMN NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME);
if (val)
! appendPQExpBuffer(&workBuf,
libpq_gettext("DATATYPE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
if (val)
! appendPQExpBuffer(&workBuf,
libpq_gettext("CONSTRAINT NAME: %s\n"), val);
}
! if (conn->verbosity == PQERRORS_VERBOSE)
{
const char *valf;
const char *vall;
--- 1045,1104 ----
{
/* emit position as text addition to primary message */
/* translator: %s represents a digit string */
! appendPQExpBuffer(msg, libpq_gettext(" at character %s"),
val);
}
}
}
! appendPQExpBufferChar(msg, '\n');
! if (verbosity != PQERRORS_TERSE)
{
if (querytext && querypos > 0)
! reportErrorPosition(msg, querytext, querypos,
! res->client_encoding);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_DETAIL);
if (val)
! appendPQExpBuffer(msg, libpq_gettext("DETAIL: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_MESSAGE_HINT);
if (val)
! appendPQExpBuffer(msg, libpq_gettext("HINT: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_INTERNAL_QUERY);
if (val)
! appendPQExpBuffer(msg, libpq_gettext("QUERY: %s\n"), val);
! if (show_context == PQSHOW_CONTEXT_ALWAYS ||
! (show_context == PQSHOW_CONTEXT_ERRORS &&
! res->resultStatus == PGRES_FATAL_ERROR))
{
val = PQresultErrorField(res, PG_DIAG_CONTEXT);
if (val)
! appendPQExpBuffer(msg, libpq_gettext("CONTEXT: %s\n"),
val);
}
}
! if (verbosity == PQERRORS_VERBOSE)
{
val = PQresultErrorField(res, PG_DIAG_SCHEMA_NAME);
if (val)
! appendPQExpBuffer(msg,
libpq_gettext("SCHEMA NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_TABLE_NAME);
if (val)
! appendPQExpBuffer(msg,
libpq_gettext("TABLE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_COLUMN_NAME);
if (val)
! appendPQExpBuffer(msg,
libpq_gettext("COLUMN NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_DATATYPE_NAME);
if (val)
! appendPQExpBuffer(msg,
libpq_gettext("DATATYPE NAME: %s\n"), val);
val = PQresultErrorField(res, PG_DIAG_CONSTRAINT_NAME);
if (val)
! appendPQExpBuffer(msg,
libpq_gettext("CONSTRAINT NAME: %s\n"), val);
}
! if (verbosity == PQERRORS_VERBOSE)
{
const char *valf;
const char *vall;
*************** pqGetErrorNotice3(PGconn *conn, bool isE
*** 1031,1081 ****
val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION);
if (val || valf || vall)
{
! appendPQExpBufferStr(&workBuf, libpq_gettext("LOCATION: "));
if (val)
! appendPQExpBuffer(&workBuf, libpq_gettext("%s, "), val);
if (valf && vall) /* unlikely we'd have just one */
! appendPQExpBuffer(&workBuf, libpq_gettext("%s:%s"),
valf, vall);
! appendPQExpBufferChar(&workBuf, '\n');
! }
! }
!
! /*
! * Either save error as current async result, or just emit the notice.
! */
! if (isError)
! {
! if (res)
! res->errMsg = pqResultStrdup(res, workBuf.data);
! pqClearAsyncResult(conn);
! conn->result = res;
! if (PQExpBufferDataBroken(workBuf))
! printfPQExpBuffer(&conn->errorMessage,
! libpq_gettext("out of memory"));
! else
! appendPQExpBufferStr(&conn->errorMessage, workBuf.data);
! }
! else
! {
! /* if we couldn't allocate the result set, just discard the NOTICE */
! if (res)
! {
! /* We can cheat a little here and not copy the message. */
! res->errMsg = workBuf.data;
! if (res->noticeHooks.noticeRec != NULL)
! (*res->noticeHooks.noticeRec) (res->noticeHooks.noticeRecArg, res);
! PQclear(res);
}
}
-
- termPQExpBuffer(&workBuf);
- return 0;
-
- fail:
- PQclear(res);
- termPQExpBuffer(&workBuf);
- return EOF;
}
/*
--- 1108,1122 ----
val = PQresultErrorField(res, PG_DIAG_SOURCE_FUNCTION);
if (val || valf || vall)
{
! appendPQExpBufferStr(msg, libpq_gettext("LOCATION: "));
if (val)
! appendPQExpBuffer(msg, libpq_gettext("%s, "), val);
if (valf && vall) /* unlikely we'd have just one */
! appendPQExpBuffer(msg, libpq_gettext("%s:%s"),
valf, vall);
! appendPQExpBufferChar(msg, '\n');
}
}
}
/*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 6bf34b3..9ca0756 100644
*** a/src/interfaces/libpq/libpq-fe.h
--- b/src/interfaces/libpq/libpq-fe.h
*************** extern PGresult *PQfn(PGconn *conn,
*** 463,468 ****
--- 463,471 ----
extern ExecStatusType PQresultStatus(const PGresult *res);
extern char *PQresStatus(ExecStatusType status);
extern char *PQresultErrorMessage(const PGresult *res);
+ extern char *PQresultVerboseErrorMessage(const PGresult *res,
+ PGVerbosity verbosity,
+ PGContextVisibility show_context);
extern char *PQresultErrorField(const PGresult *res, int fieldcode);
extern int PQntuples(const PGresult *res);
extern int PQnfields(const PGresult *res);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6c9bbf7..1183323 100644
*** a/src/interfaces/libpq/libpq-int.h
--- b/src/interfaces/libpq/libpq-int.h
*************** struct pg_result
*** 197,202 ****
--- 197,203 ----
*/
char *errMsg; /* error message, or NULL if no error */
PGMessageField *errFields; /* message broken into fields */
+ char *errQuery; /* text of triggering query, if available */
/* All NULL attributes in the query result point to this null string */
char null_field[1];
*************** extern char *pqBuildStartupPacket3(PGcon
*** 575,580 ****
--- 576,583 ----
const PQEnvironmentOption *options);
extern void pqParseInput3(PGconn *conn);
extern int pqGetErrorNotice3(PGconn *conn, bool isError);
+ extern void pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res,
+ PGVerbosity verbosity, PGContextVisibility show_context);
extern int pqGetCopyData3(PGconn *conn, char **buffer, int async);
extern int pqGetline3(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
I wrote:
Attached is a libpq-portion-only version of a patch doing it this way.
I've not yet looked at the psql part of your patch.
Here's an update for the psql side.
regards, tom lane
Attachments:
errverbose-psql-v3.patchtext/x-diff; charset=us-ascii; name=errverbose-psql-v3.patchDownload
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 385cb59..330127a 100644
*** a/doc/src/sgml/ref/psql-ref.sgml
--- b/doc/src/sgml/ref/psql-ref.sgml
*************** Tue Oct 26 21:40:57 CEST 1999
*** 1718,1723 ****
--- 1718,1737 ----
<varlistentry>
+ <term><literal>\errverbose</literal></term>
+
+ <listitem>
+ <para>
+ Repeats the most recent server error or notice message at maximum
+ verbosity, as though <varname>VERBOSITY</varname> were set
+ to <literal>verbose</> and <varname>SHOW_CONTEXT</varname> were
+ set to <literal>always</>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+
+ <varlistentry>
<term><literal>\f [ <replaceable class="parameter">string</replaceable> ]</literal></term>
<listitem>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 50dc43b..3401b51 100644
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
*************** exec_command(const char *cmd,
*** 822,827 ****
--- 822,849 ----
}
}
+ /* \errverbose -- display verbose message from last failed query */
+ else if (strcmp(cmd, "errverbose") == 0)
+ {
+ if (pset.last_error_result)
+ {
+ char *msg;
+
+ msg = PQresultVerboseErrorMessage(pset.last_error_result,
+ PQERRORS_VERBOSE,
+ PQSHOW_CONTEXT_ALWAYS);
+ if (msg)
+ {
+ psql_error("%s", msg);
+ PQfreemem(msg);
+ }
+ else
+ puts(_("out of memory"));
+ }
+ else
+ puts(_("There was no previous error."));
+ }
+
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
{
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 892058e..a2a07fb 100644
*** a/src/bin/psql/common.c
--- b/src/bin/psql/common.c
*************** AcceptResult(const PGresult *result)
*** 497,502 ****
--- 497,529 ----
}
+ /*
+ * ClearOrSaveResult
+ *
+ * If the result represents an error, remember it for possible display by
+ * \errverbose. Otherwise, just PQclear() it.
+ */
+ static void
+ ClearOrSaveResult(PGresult *result)
+ {
+ if (result)
+ {
+ switch (PQresultStatus(result))
+ {
+ case PGRES_NONFATAL_ERROR:
+ case PGRES_FATAL_ERROR:
+ if (pset.last_error_result)
+ PQclear(pset.last_error_result);
+ pset.last_error_result = result;
+ break;
+
+ default:
+ PQclear(result);
+ break;
+ }
+ }
+ }
+
/*
* PSQLexec
*************** PSQLexec(const char *query)
*** 548,554 ****
if (!AcceptResult(res))
{
! PQclear(res);
res = NULL;
}
--- 575,581 ----
if (!AcceptResult(res))
{
! ClearOrSaveResult(res);
res = NULL;
}
*************** PSQLexecWatch(const char *query, const p
*** 590,596 ****
if (!AcceptResult(res))
{
! PQclear(res);
return 0;
}
--- 617,623 ----
if (!AcceptResult(res))
{
! ClearOrSaveResult(res);
return 0;
}
*************** SendQuery(const char *query)
*** 1077,1087 ****
if (PQresultStatus(results) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
! PQclear(results);
ResetCancelConn();
goto sendquery_cleanup;
}
! PQclear(results);
transaction_status = PQtransactionStatus(pset.db);
}
--- 1104,1114 ----
if (PQresultStatus(results) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
! ClearOrSaveResult(results);
ResetCancelConn();
goto sendquery_cleanup;
}
! ClearOrSaveResult(results);
transaction_status = PQtransactionStatus(pset.db);
}
*************** SendQuery(const char *query)
*** 1102,1112 ****
if (PQresultStatus(results) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
! PQclear(results);
ResetCancelConn();
goto sendquery_cleanup;
}
! PQclear(results);
on_error_rollback_savepoint = true;
}
}
--- 1129,1139 ----
if (PQresultStatus(results) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
! ClearOrSaveResult(results);
ResetCancelConn();
goto sendquery_cleanup;
}
! ClearOrSaveResult(results);
on_error_rollback_savepoint = true;
}
}
*************** SendQuery(const char *query)
*** 1202,1208 ****
if (PQresultStatus(svptres) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
! PQclear(svptres);
OK = false;
PQclear(results);
--- 1229,1235 ----
if (PQresultStatus(svptres) != PGRES_COMMAND_OK)
{
psql_error("%s", PQerrorMessage(pset.db));
! ClearOrSaveResult(svptres);
OK = false;
PQclear(results);
*************** SendQuery(const char *query)
*** 1213,1219 ****
}
}
! PQclear(results);
/* Possible microtiming output */
if (pset.timing)
--- 1240,1246 ----
}
}
! ClearOrSaveResult(results);
/* Possible microtiming output */
if (pset.timing)
*************** ExecQueryUsingCursor(const char *query,
*** 1299,1305 ****
results = PQexec(pset.db, "BEGIN");
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
! PQclear(results);
if (!OK)
return false;
started_txn = true;
--- 1326,1332 ----
results = PQexec(pset.db, "BEGIN");
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
! ClearOrSaveResult(results);
if (!OK)
return false;
started_txn = true;
*************** ExecQueryUsingCursor(const char *query,
*** 1313,1319 ****
results = PQexec(pset.db, buf.data);
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
! PQclear(results);
termPQExpBuffer(&buf);
if (!OK)
goto cleanup;
--- 1340,1346 ----
results = PQexec(pset.db, buf.data);
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
! ClearOrSaveResult(results);
termPQExpBuffer(&buf);
if (!OK)
goto cleanup;
*************** ExecQueryUsingCursor(const char *query,
*** 1384,1390 ****
OK = AcceptResult(results);
Assert(!OK);
! PQclear(results);
break;
}
--- 1411,1417 ----
OK = AcceptResult(results);
Assert(!OK);
! ClearOrSaveResult(results);
break;
}
*************** ExecQueryUsingCursor(const char *query,
*** 1392,1398 ****
{
/* StoreQueryTuple will complain if not exactly one row */
OK = StoreQueryTuple(results);
! PQclear(results);
break;
}
--- 1419,1425 ----
{
/* StoreQueryTuple will complain if not exactly one row */
OK = StoreQueryTuple(results);
! ClearOrSaveResult(results);
break;
}
*************** ExecQueryUsingCursor(const char *query,
*** 1415,1421 ****
printQuery(results, &my_popt, fout, is_pager, pset.logfile);
! PQclear(results);
/* after the first result set, disallow header decoration */
my_popt.topt.start_table = false;
--- 1442,1448 ----
printQuery(results, &my_popt, fout, is_pager, pset.logfile);
! ClearOrSaveResult(results);
/* after the first result set, disallow header decoration */
my_popt.topt.start_table = false;
*************** cleanup:
*** 1473,1486 ****
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
}
! PQclear(results);
if (started_txn)
{
results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
OK &= AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
! PQclear(results);
}
if (pset.timing)
--- 1500,1513 ----
OK = AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
}
! ClearOrSaveResult(results);
if (started_txn)
{
results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
OK &= AcceptResult(results) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
! ClearOrSaveResult(results);
}
if (pset.timing)
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 59f6f25..c6f0993 100644
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
*************** slashUsage(unsigned short int pager)
*** 168,177 ****
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
! output = PageOutput(109, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
fprintf(output, _(" \\q quit psql\n"));
--- 168,178 ----
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
! output = PageOutput(110, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
+ fprintf(output, _(" \\errverbose show most recent error message at maximum verbosity\n"));
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\n"));
fprintf(output, _(" \\gset [PREFIX] execute query and store results in psql variables\n"));
fprintf(output, _(" \\q quit psql\n"));
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 159a7a5..ae30b2e 100644
*** a/src/bin/psql/settings.h
--- b/src/bin/psql/settings.h
*************** typedef struct _psqlSettings
*** 86,91 ****
--- 86,93 ----
FILE *copyStream; /* Stream to read/write for \copy command */
+ PGresult *last_error_result; /* most recent error result, if any */
+
printQueryOpt popt;
char *gfname; /* one-shot file output argument for \g */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 07e94d0..b96cdc4 100644
*** a/src/bin/psql/startup.c
--- b/src/bin/psql/startup.c
*************** main(int argc, char *argv[])
*** 135,140 ****
--- 135,141 ----
pset.queryFout = stdout;
pset.queryFoutPipe = false;
pset.copyStream = NULL;
+ pset.last_error_result = NULL;
pset.cur_cmd_source = stdin;
pset.cur_cmd_interactive = false;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index eb592bb..688d92a 100644
*** a/src/bin/psql/tab-complete.c
--- b/src/bin/psql/tab-complete.c
*************** psql_completion(const char *text, int st
*** 1280,1286 ****
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS",
"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
! "\\e", "\\echo", "\\ef", "\\encoding", "\\ev",
"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
--- 1280,1286 ----
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS",
"\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
! "\\e", "\\echo", "\\ef", "\\encoding", "\\errverbose", "\\ev",
"\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
"\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
"\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
On Thu, Feb 11, 2016 at 9:50 AM, Robert Haas <robertmhaas@gmail.com> wrote:
Actually, what'd be really handy IMO is something to regurgitate the
most recent error in verbose mode, without making a permanent session
state change. Something likeregression=# insert into bar values(1);
ERROR: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
regression=# \saywhat
ERROR: 23503: insert or update on table "bar" violates foreign key constraint "bar_f1_fkey"
DETAIL: Key (f1)=(1) is not present in table "foo".
SCHEMA NAME: public
TABLE NAME: bar
CONSTRAINT NAME: bar_f1_fkey
LOCATION: ri_ReportViolation, ri_triggers.c:3326
regression=#Wow, that's a fabulous idea. I see Oleksandr has tried to implement
it, although I haven't looked at the patch. But I think this would be
REALLY helpful.
+1
--
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 Sat, Apr 2, 2016 at 11:41 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
"Shulgin, Oleksandr" <oleksandr.shulgin@zalando.de> writes:
On Mon, Mar 14, 2016 at 7:55 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Yeah, I don't much like that either. But I don't think we can avoid
some refactoring there; as designed, conversion of an error message into
user-visible form is too tightly tied to receipt of the message.True. Attached is a v2 which addresses all of the points raised earlier
I
believe.
I took a closer look at what's going on here and realized that actually
it's not that hard to decouple the message-building routine from the
PGconn state, because mostly it works with fields it's extracting out
of the PGresult anyway. The only piece of information that's lacking
is conn->last_query. I propose therefore that instead of doing it like
this, we copy last_query into error PGresults. This is strictly less
added storage requirement than storing the whole verbose message would be,
and it saves time compared to the v2 patch in the typical case where
the application never does ask for an alternately-formatted error message.
Plus we can actually support requests for any variant format, not only
VERBOSE.Attached is a libpq-portion-only version of a patch doing it this way.
I've not yet looked at the psql part of your patch.Comments?
Ah, neat, that's even better. :-)
What about regression tests? My assumption was that we won't be able to
add them with the usual expected file approach, but that we also don't need
it that hard. Everyone's in favor?
--
Alex
Alex Shulgin <alex.shulgin@gmail.com> writes:
What about regression tests? My assumption was that we won't be able to
add them with the usual expected file approach, but that we also don't need
it that hard. Everyone's in favor?
It'd be nice to have a regression test, but I concur that the LOCATION
information is too unstable for that to be practical.
We could imagine testing some variant behavior that omits printing
LOCATION, but that doesn't really seem worth the trouble; I think
we'd be inventing the variant behavior only for testing purposes.
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 <pg@heroku.com> writes:
On Thu, Feb 11, 2016 at 9:50 AM, Robert Haas <robertmhaas@gmail.com> wrote:
Wow, that's a fabulous idea. I see Oleksandr has tried to implement
it, although I haven't looked at the patch. But I think this would be
REALLY helpful.
+1
Pushed.
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 Sun, Apr 3, 2016, 18:32 Tom Lane <tgl@sss.pgh.pa.us> wrote:
Peter Geoghegan <pg@heroku.com> writes:
On Thu, Feb 11, 2016 at 9:50 AM, Robert Haas <robertmhaas@gmail.com>
wrote:
Wow, that's a fabulous idea. I see Oleksandr has tried to implement
it, although I haven't looked at the patch. But I think this would be
REALLY helpful.+1
Pushed.
Yay!