Re: Error-safe user functions - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Error-safe user functions
Date
Msg-id 3342239.1671988406@sss.pgh.pa.us
Whole thread Raw
In response to Re: Error-safe user functions  (Robert Haas <robertmhaas@gmail.com>)
Responses Re: Error-safe user functions  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: Error-safe user functions  (Andrew Dunstan <andrew@dunslane.net>)
Re: Error-safe user functions  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: Error-safe user functions  (Robert Haas <robertmhaas@gmail.com>)
List pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Fri, Dec 16, 2022 at 1:31 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> The reg* functions probably need a unified plan as to how far
>> down we want to push non-error behavior.

> I would be in favor of an aggressive approach.

Here's a proposed patch for converting regprocin and friends
to soft error reporting.  I'll say at the outset that it's an
engineering compromise, and it may be worth going further in
future.  But I doubt it's worth doing more than this for v16,
because the next steps would be pretty invasive.

I've converted all the errors thrown directly within regproc.c,
and also converted parseTypeString, typeStringToTypeName, and
stringToQualifiedNameList to report their own errors softly.
This affected some outside callers, but not so many of them
that I think it's worth inventing compatibility wrappers.

I dealt with lookup failures by just changing the input functions
to call the respective lookup functions with missing_ok = true,
and then throw their own error softly on failure.

Also, I've changed to_regproc() and friends to return NULL
in exactly the same cases that are now soft errors for the
input functions.  Previously they were a bit inconsistent
about what triggered hard errors vs. returning NULL.
(Perhaps we should go further than this, and convert all these
functions to just be DirectInputFunctionCallSafe wrappers
around the corresponding input functions?  That would save
some duplicative code, but I've not done it here.)

What's not fixed here:

1. As previously discussed, parse errors in type names are
thrown by the main grammar, so getting those to not be
hard errors seems like too big a lift for today.

2. Errors about invalid type modifiers (reported by
typenameTypeMod or type-specific typmodin routines) are not
trapped either.  Fixing this would require extending the
soft-error conventions to typmodin routines, which maybe will
be worth doing someday but it seems pretty far down the
priority list.  Specifying a typmod is surely not main-line
usage for regtypein.

3. Throwing our own error has the demerit that it might be
different from what the underlying lookup function would have
reported.  This is visible in some changes in existing
regression test cases, such as

-ERROR:  schema "ng_catalog" does not exist
+ERROR:  relation "ng_catalog.pg_class" does not exist

This isn't wrong, exactly, but the loss of specificity is
a bit annoying.

4. This still fails to trap errors about "too many dotted names"
and "cross-database references are not implemented", which are
thrown in DeconstructQualifiedName, LookupTypeName,
RangeVarGetRelid, and maybe some other places.

5. We also don't trap errors about "the schema exists but
you don't have USAGE permission to do a lookup in it",
because LookupExplicitNamespace still throws that even
when passed missing_ok = true.

The obvious way to fix #3,#4,#5 is to change pretty much all
of the catalog lookup infrastructure to deal in escontext
arguments instead of "missing_ok" booleans.  That might be
worth doing --- it'd have benefits beyond the immediate
problem, I think --- but I feel it's a bigger lift than we
want to undertake for v16.  It'd be better to spend the time
we have left for v16 on building features that use soft error
reporting than on refining corner cases in the reg* functions.

So I think we should stop more or less here, possibly after
changing the to_regfoo functions to be simple wrappers
around the soft input functions.

            regards, tom lane

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 109bdfb33f..a1df8b1ddc 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2182,7 +2182,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
             ereport(ERROR,
                     (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                      errmsg("name or argument lists may not contain nulls")));
-        typename = typeStringToTypeName(TextDatumGetCString(elems[0]));
+        typename = typeStringToTypeName(TextDatumGetCString(elems[0]), NULL);
     }
     else if (type == OBJECT_LARGEOBJECT)
     {
@@ -2238,7 +2238,8 @@ pg_get_object_address(PG_FUNCTION_ARGS)
                         (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                          errmsg("name or argument lists may not contain nulls")));
             args = lappend(args,
-                           typeStringToTypeName(TextDatumGetCString(elems[i])));
+                           typeStringToTypeName(TextDatumGetCString(elems[i]),
+                                                NULL));
         }
     }
     else
diff --git a/src/backend/parser/parse_type.c b/src/backend/parser/parse_type.c
index f7ad689459..8f3850aa4e 100644
--- a/src/backend/parser/parse_type.c
+++ b/src/backend/parser/parse_type.c
@@ -727,10 +727,15 @@ pts_error_callback(void *arg)
  * Given a string that is supposed to be a SQL-compatible type declaration,
  * such as "int4" or "integer" or "character varying(32)", parse
  * the string and return the result as a TypeName.
- * If the string cannot be parsed as a type, an error is raised.
+ *
+ * If the string cannot be parsed as a type, an error is raised,
+ * unless escontext is an ErrorSaveContext node, in which case we may
+ * fill that and return NULL.  But note that the ErrorSaveContext option
+ * is mostly aspirational at present: errors detected by the main
+ * grammar, rather than here, will still be thrown.
  */
 TypeName *
-typeStringToTypeName(const char *str)
+typeStringToTypeName(const char *str, Node *escontext)
 {
     List       *raw_parsetree_list;
     TypeName   *typeName;
@@ -763,49 +768,54 @@ typeStringToTypeName(const char *str)
     return typeName;

 fail:
-    ereport(ERROR,
+    ereturn(escontext, NULL,
             (errcode(ERRCODE_SYNTAX_ERROR),
              errmsg("invalid type name \"%s\"", str)));
-    return NULL;                /* keep compiler quiet */
 }

 /*
  * Given a string that is supposed to be a SQL-compatible type declaration,
  * such as "int4" or "integer" or "character varying(32)", parse
  * the string and convert it to a type OID and type modifier.
- * If missing_ok is true, InvalidOid is returned rather than raising an error
- * when the type name is not found.
+ *
+ * If escontext is an ErrorSaveContext node, then errors are reported by
+ * filling escontext and returning false, instead of throwing them.
  */
-void
-parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok)
+bool
+parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
+                Node *escontext)
 {
     TypeName   *typeName;
     Type        tup;

-    typeName = typeStringToTypeName(str);
+    typeName = typeStringToTypeName(str, escontext);
+    if (typeName == NULL)
+        return false;

-    tup = LookupTypeName(NULL, typeName, typmod_p, missing_ok);
+    tup = LookupTypeName(NULL, typeName, typmod_p,
+                         (escontext && IsA(escontext, ErrorSaveContext)));
     if (tup == NULL)
     {
-        if (!missing_ok)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("type \"%s\" does not exist",
-                            TypeNameToString(typeName)),
-                     parser_errposition(NULL, typeName->location)));
-        *typeid_p = InvalidOid;
+        ereturn(escontext, false,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("type \"%s\" does not exist",
+                        TypeNameToString(typeName))));
     }
     else
     {
         Form_pg_type typ = (Form_pg_type) GETSTRUCT(tup);

         if (!typ->typisdefined)
-            ereport(ERROR,
+        {
+            ReleaseSysCache(tup);
+            ereturn(escontext, false,
                     (errcode(ERRCODE_UNDEFINED_OBJECT),
                      errmsg("type \"%s\" is only a shell",
-                            TypeNameToString(typeName)),
-                     parser_errposition(NULL, typeName->location)));
+                            TypeNameToString(typeName))));
+        }
         *typeid_p = typ->oid;
         ReleaseSysCache(tup);
     }
+
+    return true;
 }
diff --git a/src/backend/tsearch/dict_thesaurus.c b/src/backend/tsearch/dict_thesaurus.c
index b8c08bcf7b..3df29e3345 100644
--- a/src/backend/tsearch/dict_thesaurus.c
+++ b/src/backend/tsearch/dict_thesaurus.c
@@ -599,6 +599,7 @@ thesaurus_init(PG_FUNCTION_ARGS)
     DictThesaurus *d;
     char       *subdictname = NULL;
     bool        fileloaded = false;
+    List       *namelist;
     ListCell   *l;

     d = (DictThesaurus *) palloc0(sizeof(DictThesaurus));
@@ -642,7 +643,8 @@ thesaurus_init(PG_FUNCTION_ARGS)
                 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                  errmsg("missing Dictionary parameter")));

-    d->subdictOid = get_ts_dict_oid(stringToQualifiedNameList(subdictname), false);
+    namelist = stringToQualifiedNameList(subdictname, NULL);
+    d->subdictOid = get_ts_dict_oid(namelist, false);
     d->subdict = lookup_ts_dictionary_cache(d->subdictOid);

     compileTheLexeme(d);
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 7808fbd448..cc25acb656 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -724,7 +724,9 @@ pg_input_is_valid_common(FunctionCallInfo fcinfo,
         Oid            typoid;

         /* Parse type-name argument to obtain type OID and encoded typmod. */
-        parseTypeString(typnamestr, &typoid, &my_extra->typmod, false);
+        if (!parseTypeString(typnamestr, &typoid, &my_extra->typmod,
+                             (Node *) escontext))
+            return false;

         /* Update type-specific info if typoid changed. */
         if (my_extra->typoid != typoid)
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index a6d695d6cb..14d76c856d 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -31,7 +31,9 @@
 #include "catalog/pg_ts_dict.h"
 #include "catalog/pg_type.h"
 #include "lib/stringinfo.h"
+#include "mb/pg_wchar.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/scansup.h"
 #include "utils/acl.h"
@@ -43,8 +45,9 @@

 static bool parseNumericOid(char *string, Oid *result, Node *escontext);
 static bool parseDashOrOid(char *string, Oid *result, Node *escontext);
-static void parseNameAndArgTypes(const char *string, bool allowNone,
-                                 List **names, int *nargs, Oid *argtypes);
+static bool parseNameAndArgTypes(const char *string, bool allowNone,
+                                 List **names, int *nargs, Oid *argtypes,
+                                 Node *escontext);


 /*****************************************************************************
@@ -63,12 +66,13 @@ Datum
 regprocin(PG_FUNCTION_ARGS)
 {
     char       *pro_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     RegProcedure result;
     List       *names;
     FuncCandidateList clist;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(pro_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(pro_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* Else it's a name, possibly schema-qualified */
@@ -84,15 +88,18 @@ regprocin(PG_FUNCTION_ARGS)
      * Normal case: parse the name into components and see if it matches any
      * pg_proc entries in the current search path.
      */
-    names = stringToQualifiedNameList(pro_name_or_oid);
-    clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, false);
+    names = stringToQualifiedNameList(pro_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();
+
+    clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true);

     if (clist == NULL)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
                  errmsg("function \"%s\" does not exist", pro_name_or_oid)));
     else if (clist->next != NULL)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_AMBIGUOUS_FUNCTION),
                  errmsg("more than one function named \"%s\"",
                         pro_name_or_oid)));
@@ -113,12 +120,16 @@ to_regproc(PG_FUNCTION_ARGS)
     char       *pro_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     List       *names;
     FuncCandidateList clist;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Parse the name into components and see if it matches any pg_proc
      * entries in the current search path.
      */
-    names = stringToQualifiedNameList(pro_name);
+    names = stringToQualifiedNameList(pro_name, (Node *) &escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();
+
     clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, true);

     if (clist == NULL || clist->next != NULL)
@@ -222,6 +233,7 @@ Datum
 regprocedurein(PG_FUNCTION_ARGS)
 {
     char       *pro_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     RegProcedure result;
     List       *names;
     int            nargs;
@@ -229,7 +241,7 @@ regprocedurein(PG_FUNCTION_ARGS)
     FuncCandidateList clist;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(pro_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(pro_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* The rest of this wouldn't work in bootstrap mode */
@@ -242,10 +254,13 @@ regprocedurein(PG_FUNCTION_ARGS)
      * which one exactly matches the given argument types.  (There will not be
      * more than one match.)
      */
-    parseNameAndArgTypes(pro_name_or_oid, false, &names, &nargs, argtypes);
+    if (!parseNameAndArgTypes(pro_name_or_oid, false,
+                              &names, &nargs, argtypes,
+                              escontext))
+        PG_RETURN_NULL();

     clist = FuncnameGetCandidates(names, nargs, NIL, false, false,
-                                  false, false);
+                                  false, true);

     for (; clist; clist = clist->next)
     {
@@ -254,7 +269,7 @@ regprocedurein(PG_FUNCTION_ARGS)
     }

     if (clist == NULL)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
                  errmsg("function \"%s\" does not exist", pro_name_or_oid)));

@@ -276,13 +291,17 @@ to_regprocedure(PG_FUNCTION_ARGS)
     int            nargs;
     Oid            argtypes[FUNC_MAX_ARGS];
     FuncCandidateList clist;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Parse the name and arguments, look up potential matches in the current
      * namespace search list, and scan to see which one exactly matches the
      * given argument types.    (There will not be more than one match.)
      */
-    parseNameAndArgTypes(pro_name, false, &names, &nargs, argtypes);
+    if (!parseNameAndArgTypes(pro_name, false,
+                              &names, &nargs, argtypes,
+                              (Node *) &escontext))
+        PG_RETURN_NULL();

     clist = FuncnameGetCandidates(names, nargs, NIL, false, false, false, true);

@@ -484,12 +503,13 @@ Datum
 regoperin(PG_FUNCTION_ARGS)
 {
     char       *opr_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;
     FuncCandidateList clist;

     /* Handle "0" or numeric OID */
-    if (parseNumericOid(opr_name_or_oid, &result, fcinfo->context))
+    if (parseNumericOid(opr_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* Else it's a name, possibly schema-qualified */
@@ -502,15 +522,18 @@ regoperin(PG_FUNCTION_ARGS)
      * Normal case: parse the name into components and see if it matches any
      * pg_operator entries in the current search path.
      */
-    names = stringToQualifiedNameList(opr_name_or_oid);
-    clist = OpernameGetCandidates(names, '\0', false);
+    names = stringToQualifiedNameList(opr_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();
+
+    clist = OpernameGetCandidates(names, '\0', true);

     if (clist == NULL)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
                  errmsg("operator does not exist: %s", opr_name_or_oid)));
     else if (clist->next != NULL)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_AMBIGUOUS_FUNCTION),
                  errmsg("more than one operator named %s",
                         opr_name_or_oid)));
@@ -531,12 +554,16 @@ to_regoper(PG_FUNCTION_ARGS)
     char       *opr_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     List       *names;
     FuncCandidateList clist;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Parse the name into components and see if it matches any pg_operator
      * entries in the current search path.
      */
-    names = stringToQualifiedNameList(opr_name);
+    names = stringToQualifiedNameList(opr_name, (Node *) &escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();
+
     clist = OpernameGetCandidates(names, '\0', true);

     if (clist == NULL || clist->next != NULL)
@@ -646,13 +673,14 @@ Datum
 regoperatorin(PG_FUNCTION_ARGS)
 {
     char       *opr_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;
     int            nargs;
     Oid            argtypes[FUNC_MAX_ARGS];

     /* Handle "0" or numeric OID */
-    if (parseNumericOid(opr_name_or_oid, &result, fcinfo->context))
+    if (parseNumericOid(opr_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* The rest of this wouldn't work in bootstrap mode */
@@ -665,14 +693,18 @@ regoperatorin(PG_FUNCTION_ARGS)
      * which one exactly matches the given argument types.  (There will not be
      * more than one match.)
      */
-    parseNameAndArgTypes(opr_name_or_oid, true, &names, &nargs, argtypes);
+    if (!parseNameAndArgTypes(opr_name_or_oid, true,
+                              &names, &nargs, argtypes,
+                              escontext))
+        PG_RETURN_NULL();
+
     if (nargs == 1)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_UNDEFINED_PARAMETER),
                  errmsg("missing argument"),
                  errhint("Use NONE to denote the missing argument of a unary operator.")));
     if (nargs != 2)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
                  errmsg("too many arguments"),
                  errhint("Provide two argument types for operator.")));
@@ -680,7 +712,7 @@ regoperatorin(PG_FUNCTION_ARGS)
     result = OpernameGetOprid(names, argtypes[0], argtypes[1]);

     if (!OidIsValid(result))
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_UNDEFINED_FUNCTION),
                  errmsg("operator does not exist: %s", opr_name_or_oid)));

@@ -700,23 +732,20 @@ to_regoperator(PG_FUNCTION_ARGS)
     List       *names;
     int            nargs;
     Oid            argtypes[FUNC_MAX_ARGS];
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Parse the name and arguments, look up potential matches in the current
      * namespace search list, and scan to see which one exactly matches the
      * given argument types.    (There will not be more than one match.)
      */
-    parseNameAndArgTypes(opr_name_or_oid, true, &names, &nargs, argtypes);
-    if (nargs == 1)
-        ereport(ERROR,
-                (errcode(ERRCODE_UNDEFINED_PARAMETER),
-                 errmsg("missing argument"),
-                 errhint("Use NONE to denote the missing argument of a unary operator.")));
+    if (!parseNameAndArgTypes(opr_name_or_oid, true,
+                              &names, &nargs, argtypes,
+                              (Node *) &escontext))
+        PG_RETURN_NULL();
+
     if (nargs != 2)
-        ereport(ERROR,
-                (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
-                 errmsg("too many arguments"),
-                 errhint("Provide two argument types for operator.")));
+        PG_RETURN_NULL();

     result = OpernameGetOprid(names, argtypes[0], argtypes[1]);

@@ -903,11 +932,12 @@ Datum
 regclassin(PG_FUNCTION_ARGS)
 {
     char       *class_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(class_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(class_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* Else it's a name, possibly schema-qualified */
@@ -920,10 +950,18 @@ regclassin(PG_FUNCTION_ARGS)
      * Normal case: parse the name into components and see if it matches any
      * pg_class entries in the current search path.
      */
-    names = stringToQualifiedNameList(class_name_or_oid);
+    names = stringToQualifiedNameList(class_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     /* We might not even have permissions on this relation; don't lock it. */
-    result = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, false);
+    result = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, true);
+
+    if (!OidIsValid(result))
+        ereturn(escontext, (Datum) 0,
+                (errcode(ERRCODE_UNDEFINED_TABLE),
+                 errmsg("relation \"%s\" does not exist",
+                        NameListToString(names))));

     PG_RETURN_OID(result);
 }
@@ -939,12 +977,15 @@ to_regclass(PG_FUNCTION_ARGS)
     char       *class_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     Oid            result;
     List       *names;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Parse the name into components and see if it matches any pg_class
      * entries in the current search path.
      */
-    names = stringToQualifiedNameList(class_name);
+    names = stringToQualifiedNameList(class_name, (Node *) &escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     /* We might not even have permissions on this relation; don't lock it. */
     result = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, true);
@@ -1045,11 +1086,12 @@ Datum
 regcollationin(PG_FUNCTION_ARGS)
 {
     char       *collation_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(collation_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(collation_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* Else it's a name, possibly schema-qualified */
@@ -1062,9 +1104,17 @@ regcollationin(PG_FUNCTION_ARGS)
      * Normal case: parse the name into components and see if it matches any
      * pg_collation entries in the current search path.
      */
-    names = stringToQualifiedNameList(collation_name_or_oid);
+    names = stringToQualifiedNameList(collation_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();
+
+    result = get_collation_oid(names, true);

-    result = get_collation_oid(names, false);
+    if (!OidIsValid(result))
+        ereturn(escontext, (Datum) 0,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("collation \"%s\" for encoding \"%s\" does not exist",
+                        NameListToString(names), GetDatabaseEncodingName())));

     PG_RETURN_OID(result);
 }
@@ -1080,12 +1130,15 @@ to_regcollation(PG_FUNCTION_ARGS)
     char       *collation_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     Oid            result;
     List       *names;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Parse the name into components and see if it matches any pg_collation
      * entries in the current search path.
      */
-    names = stringToQualifiedNameList(collation_name);
+    names = stringToQualifiedNameList(collation_name, (Node *) &escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     result = get_collation_oid(names, true);

@@ -1192,11 +1245,12 @@ Datum
 regtypein(PG_FUNCTION_ARGS)
 {
     char       *typ_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     int32        typmod;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(typ_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(typ_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* Else it's a type name, possibly schema-qualified or decorated */
@@ -1207,9 +1261,10 @@ regtypein(PG_FUNCTION_ARGS)

     /*
      * Normal case: invoke the full parser to deal with special cases such as
-     * array syntax.
+     * array syntax.  We don't need to check for parseTypeString failure,
+     * since we'll just return anyway.
      */
-    parseTypeString(typ_name_or_oid, &result, &typmod, false);
+    (void) parseTypeString(typ_name_or_oid, &result, &typmod, escontext);

     PG_RETURN_OID(result);
 }
@@ -1225,13 +1280,12 @@ to_regtype(PG_FUNCTION_ARGS)
     char       *typ_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     Oid            result;
     int32        typmod;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

     /*
      * Invoke the full parser to deal with special cases such as array syntax.
      */
-    parseTypeString(typ_name, &result, &typmod, true);
-
-    if (OidIsValid(result))
+    if (parseTypeString(typ_name, &result, &typmod, (Node *) &escontext))
         PG_RETURN_OID(result);
     else
         PG_RETURN_NULL();
@@ -1318,11 +1372,12 @@ Datum
 regconfigin(PG_FUNCTION_ARGS)
 {
     char       *cfg_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(cfg_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(cfg_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* The rest of this wouldn't work in bootstrap mode */
@@ -1333,9 +1388,17 @@ regconfigin(PG_FUNCTION_ARGS)
      * Normal case: parse the name into components and see if it matches any
      * pg_ts_config entries in the current search path.
      */
-    names = stringToQualifiedNameList(cfg_name_or_oid);
+    names = stringToQualifiedNameList(cfg_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

-    result = get_ts_config_oid(names, false);
+    result = get_ts_config_oid(names, true);
+
+    if (!OidIsValid(result))
+        ereturn(escontext, (Datum) 0,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("text search configuration \"%s\" does not exist",
+                        NameListToString(names))));

     PG_RETURN_OID(result);
 }
@@ -1419,11 +1482,12 @@ Datum
 regdictionaryin(PG_FUNCTION_ARGS)
 {
     char       *dict_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(dict_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(dict_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* The rest of this wouldn't work in bootstrap mode */
@@ -1434,9 +1498,17 @@ regdictionaryin(PG_FUNCTION_ARGS)
      * Normal case: parse the name into components and see if it matches any
      * pg_ts_dict entries in the current search path.
      */
-    names = stringToQualifiedNameList(dict_name_or_oid);
+    names = stringToQualifiedNameList(dict_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();
+
+    result = get_ts_dict_oid(names, true);

-    result = get_ts_dict_oid(names, false);
+    if (!OidIsValid(result))
+        ereturn(escontext, (Datum) 0,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("text search dictionary \"%s\" does not exist",
+                        NameListToString(names))));

     PG_RETURN_OID(result);
 }
@@ -1520,11 +1592,12 @@ Datum
 regrolein(PG_FUNCTION_ARGS)
 {
     char       *role_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(role_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(role_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* The rest of this wouldn't work in bootstrap mode */
@@ -1532,14 +1605,22 @@ regrolein(PG_FUNCTION_ARGS)
         elog(ERROR, "regrole values must be OIDs in bootstrap mode");

     /* Normal case: see if the name matches any pg_authid entry. */
-    names = stringToQualifiedNameList(role_name_or_oid);
+    names = stringToQualifiedNameList(role_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     if (list_length(names) != 1)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_INVALID_NAME),
                  errmsg("invalid name syntax")));

-    result = get_role_oid(strVal(linitial(names)), false);
+    result = get_role_oid(strVal(linitial(names)), true);
+
+    if (!OidIsValid(result))
+        ereturn(escontext, (Datum) 0,
+                (errcode(ERRCODE_UNDEFINED_OBJECT),
+                 errmsg("role \"%s\" does not exist",
+                        strVal(linitial(names)))));

     PG_RETURN_OID(result);
 }
@@ -1555,13 +1636,14 @@ to_regrole(PG_FUNCTION_ARGS)
     char       *role_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     Oid            result;
     List       *names;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

-    names = stringToQualifiedNameList(role_name);
+    names = stringToQualifiedNameList(role_name, (Node *) &escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     if (list_length(names) != 1)
-        ereport(ERROR,
-                (errcode(ERRCODE_INVALID_NAME),
-                 errmsg("invalid name syntax")));
+        PG_RETURN_NULL();

     result = get_role_oid(strVal(linitial(names)), true);

@@ -1635,11 +1717,12 @@ Datum
 regnamespacein(PG_FUNCTION_ARGS)
 {
     char       *nsp_name_or_oid = PG_GETARG_CSTRING(0);
+    Node       *escontext = fcinfo->context;
     Oid            result;
     List       *names;

     /* Handle "-" or numeric OID */
-    if (parseDashOrOid(nsp_name_or_oid, &result, fcinfo->context))
+    if (parseDashOrOid(nsp_name_or_oid, &result, escontext))
         PG_RETURN_OID(result);

     /* The rest of this wouldn't work in bootstrap mode */
@@ -1647,14 +1730,22 @@ regnamespacein(PG_FUNCTION_ARGS)
         elog(ERROR, "regnamespace values must be OIDs in bootstrap mode");

     /* Normal case: see if the name matches any pg_namespace entry. */
-    names = stringToQualifiedNameList(nsp_name_or_oid);
+    names = stringToQualifiedNameList(nsp_name_or_oid, escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     if (list_length(names) != 1)
-        ereport(ERROR,
+        ereturn(escontext, (Datum) 0,
                 (errcode(ERRCODE_INVALID_NAME),
                  errmsg("invalid name syntax")));

-    result = get_namespace_oid(strVal(linitial(names)), false);
+    result = get_namespace_oid(strVal(linitial(names)), true);
+
+    if (!OidIsValid(result))
+        ereturn(escontext, (Datum) 0,
+                (errcode(ERRCODE_UNDEFINED_SCHEMA),
+                 errmsg("schema \"%s\" does not exist",
+                        strVal(linitial(names)))));

     PG_RETURN_OID(result);
 }
@@ -1670,13 +1761,14 @@ to_regnamespace(PG_FUNCTION_ARGS)
     char       *nsp_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
     Oid            result;
     List       *names;
+    ErrorSaveContext escontext = {T_ErrorSaveContext};

-    names = stringToQualifiedNameList(nsp_name);
+    names = stringToQualifiedNameList(nsp_name, (Node *) &escontext);
+    if (names == NIL)
+        PG_RETURN_NULL();

     if (list_length(names) != 1)
-        ereport(ERROR,
-                (errcode(ERRCODE_INVALID_NAME),
-                 errmsg("invalid name syntax")));
+        PG_RETURN_NULL();

     result = get_namespace_oid(strVal(linitial(names)), true);

@@ -1763,9 +1855,13 @@ text_regclass(PG_FUNCTION_ARGS)

 /*
  * Given a C string, parse it into a qualified-name list.
+ *
+ * If escontext is an ErrorSaveContext node, invalid input will be
+ * reported there instead of being thrown, and we return NIL.
+ * (NIL is not possible as a success return, since empty-input is an error.)
  */
 List *
-stringToQualifiedNameList(const char *string)
+stringToQualifiedNameList(const char *string, Node *escontext)
 {
     char       *rawname;
     List       *result = NIL;
@@ -1776,12 +1872,12 @@ stringToQualifiedNameList(const char *string)
     rawname = pstrdup(string);

     if (!SplitIdentifierString(rawname, '.', &namelist))
-        ereport(ERROR,
+        ereturn(escontext, NIL,
                 (errcode(ERRCODE_INVALID_NAME),
                  errmsg("invalid name syntax")));

     if (namelist == NIL)
-        ereport(ERROR,
+        ereturn(escontext, NIL,
                 (errcode(ERRCODE_INVALID_NAME),
                  errmsg("invalid name syntax")));

@@ -1858,10 +1954,14 @@ parseDashOrOid(char *string, Oid *result, Node *escontext)
  *
  * If allowNone is true, accept "NONE" and return it as InvalidOid (this is
  * for unary operators).
+ *
+ * Returns true on success, false on failure (the latter only possible
+ * if escontext is an ErrorSaveContext node).
  */
-static void
+static bool
 parseNameAndArgTypes(const char *string, bool allowNone, List **names,
-                     int *nargs, Oid *argtypes)
+                     int *nargs, Oid *argtypes,
+                     Node *escontext)
 {
     char       *rawname;
     char       *ptr;
@@ -1886,13 +1986,15 @@ parseNameAndArgTypes(const char *string, bool allowNone, List **names,
             break;
     }
     if (*ptr == '\0')
-        ereport(ERROR,
+        ereturn(escontext, false,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("expected a left parenthesis")));

     /* Separate the name and parse it into a list */
     *ptr++ = '\0';
-    *names = stringToQualifiedNameList(rawname);
+    *names = stringToQualifiedNameList(rawname, escontext);
+    if (*names == NIL)
+        return false;

     /* Check for the trailing right parenthesis and remove it */
     ptr2 = ptr + strlen(ptr);
@@ -1902,7 +2004,7 @@ parseNameAndArgTypes(const char *string, bool allowNone, List **names,
             break;
     }
     if (*ptr2 != ')')
-        ereport(ERROR,
+        ereturn(escontext, false,
                 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                  errmsg("expected a right parenthesis")));

@@ -1921,7 +2023,7 @@ parseNameAndArgTypes(const char *string, bool allowNone, List **names,
         {
             /* End of string.  Okay unless we had a comma before. */
             if (had_comma)
-                ereport(ERROR,
+                ereturn(escontext, false,
                         (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                          errmsg("expected a type name")));
             break;
@@ -1953,7 +2055,7 @@ parseNameAndArgTypes(const char *string, bool allowNone, List **names,
             }
         }
         if (in_quote || paren_count != 0)
-            ereport(ERROR,
+            ereturn(escontext, false,
                     (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
                      errmsg("improper type name")));

@@ -1985,10 +2087,11 @@ parseNameAndArgTypes(const char *string, bool allowNone, List **names,
         else
         {
             /* Use full parser to resolve the type name */
-            parseTypeString(typename, &typeid, &typmod, false);
+            if (!parseTypeString(typename, &typeid, &typmod, escontext))
+                return false;
         }
         if (*nargs >= FUNC_MAX_ARGS)
-            ereport(ERROR,
+            ereturn(escontext, false,
                     (errcode(ERRCODE_TOO_MANY_ARGUMENTS),
                      errmsg("too many arguments")));

@@ -1997,4 +2100,6 @@ parseNameAndArgTypes(const char *string, bool allowNone, List **names,
     }

     pfree(rawname);
+
+    return true;
 }
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index caeb85b4ca..66ce710598 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -2652,7 +2652,7 @@ tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
     {
         List       *names;

-        names = stringToQualifiedNameList(trigger->tgargs[1]);
+        names = stringToQualifiedNameList(trigger->tgargs[1], NULL);
         /* require a schema so that results are not search path dependent */
         if (list_length(names) < 2)
             ereport(ERROR,
diff --git a/src/backend/utils/cache/ts_cache.c b/src/backend/utils/cache/ts_cache.c
index 450ea34336..043abd341d 100644
--- a/src/backend/utils/cache/ts_cache.c
+++ b/src/backend/utils/cache/ts_cache.c
@@ -38,6 +38,7 @@
 #include "catalog/pg_ts_template.h"
 #include "commands/defrem.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
 #include "tsearch/ts_cache.h"
 #include "utils/builtins.h"
 #include "utils/catcache.h"
@@ -556,6 +557,8 @@ lookup_ts_config_cache(Oid cfgId)
 Oid
 getTSCurrentConfig(bool emitError)
 {
+    List       *namelist;
+
     /* if we have a cached value, return it */
     if (OidIsValid(TSCurrentConfigCache))
         return TSCurrentConfigCache;
@@ -576,9 +579,22 @@ getTSCurrentConfig(bool emitError)
     }

     /* Look up the config */
-    TSCurrentConfigCache =
-        get_ts_config_oid(stringToQualifiedNameList(TSCurrentConfig),
-                          !emitError);
+    if (emitError)
+    {
+        namelist = stringToQualifiedNameList(TSCurrentConfig, NULL);
+        TSCurrentConfigCache = get_ts_config_oid(namelist, false);
+    }
+    else
+    {
+        ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+        namelist = stringToQualifiedNameList(TSCurrentConfig,
+                                             (Node *) &escontext);
+        if (namelist != NIL)
+            TSCurrentConfigCache = get_ts_config_oid(namelist, true);
+        else
+            TSCurrentConfigCache = InvalidOid;    /* bad name list syntax */
+    }

     return TSCurrentConfigCache;
 }
@@ -594,12 +610,19 @@ check_default_text_search_config(char **newval, void **extra, GucSource source)
      */
     if (IsTransactionState() && MyDatabaseId != InvalidOid)
     {
+        ErrorSaveContext escontext = {T_ErrorSaveContext};
+        List       *namelist;
         Oid            cfgId;
         HeapTuple    tuple;
         Form_pg_ts_config cfg;
         char       *buf;

-        cfgId = get_ts_config_oid(stringToQualifiedNameList(*newval), true);
+        namelist = stringToQualifiedNameList(*newval,
+                                             (Node *) &escontext);
+        if (namelist != NIL)
+            cfgId = get_ts_config_oid(namelist, true);
+        else
+            cfgId = InvalidOid; /* bad name list syntax */

         /*
          * When source == PGC_S_TEST, don't throw a hard error for a
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 87cbb1d3e3..51e5893404 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -1876,7 +1876,7 @@ RelationNameGetTupleDesc(const char *relname)
     List       *relname_list;

     /* Open relation and copy the tuple description */
-    relname_list = stringToQualifiedNameList(relname);
+    relname_list = stringToQualifiedNameList(relname, NULL);
     relvar = makeRangeVarFromNameList(relname_list);
     rel = relation_openrv(relvar, AccessShareLock);
     tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
diff --git a/src/include/parser/parse_type.h b/src/include/parser/parse_type.h
index 4e5624d721..c6c92a0009 100644
--- a/src/include/parser/parse_type.h
+++ b/src/include/parser/parse_type.h
@@ -51,8 +51,9 @@ extern Datum stringTypeDatum(Type tp, char *string, int32 atttypmod);
 extern Oid    typeidTypeRelid(Oid type_id);
 extern Oid    typeOrDomainTypeRelid(Oid type_id);

-extern TypeName *typeStringToTypeName(const char *str);
-extern void parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p, bool missing_ok);
+extern TypeName *typeStringToTypeName(const char *str, Node *escontext);
+extern bool parseTypeString(const char *str, Oid *typeid_p, int32 *typmod_p,
+                            Node *escontext);

 /* true if typeid is composite, or domain over composite, but not RECORD */
 #define ISCOMPLEX(typeid) (typeOrDomainTypeRelid(typeid) != InvalidOid)
diff --git a/src/include/utils/regproc.h b/src/include/utils/regproc.h
index 0e2965ff93..4c3311d8e2 100644
--- a/src/include/utils/regproc.h
+++ b/src/include/utils/regproc.h
@@ -25,7 +25,7 @@ extern char *format_procedure_extended(Oid procedure_oid, bits16 flags);
 #define FORMAT_OPERATOR_FORCE_QUALIFY    0x02    /* force qualification */
 extern char *format_operator_extended(Oid operator_oid, bits16 flags);

-extern List *stringToQualifiedNameList(const char *string);
+extern List *stringToQualifiedNameList(const char *string, Node *escontext);
 extern char *format_procedure(Oid procedure_oid);
 extern char *format_procedure_qualified(Oid procedure_oid);
 extern void format_procedure_parts(Oid procedure_oid, List **objnames,
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 8f21e0d701..8143ae40a0 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -3613,7 +3613,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
             char       *typstr;

             typstr = sv2cstr(argv[i]);
-            parseTypeString(typstr, &typId, &typmod, false);
+            (void) parseTypeString(typstr, &typId, &typmod, NULL);
             pfree(typstr);

             getTypeInputInfo(typId, &typInput, &typIOParam);
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index f7cf2b4b89..fe63766e5d 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -3725,7 +3725,7 @@ parse_datatype(const char *string, int location)
     error_context_stack = &syntax_errcontext;

     /* Let the main parser try to parse it under standard SQL rules */
-    typeName = typeStringToTypeName(string);
+    typeName = typeStringToTypeName(string, NULL);
     typenameTypeIdAndMod(NULL, typeName, &type_id, &typmod);

     /* Restore former ereport callback */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 6b9f8d5b43..ff87b27de0 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -105,7 +105,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
              *information for input conversion.
              ********************************************************/

-            parseTypeString(sptr, &typeId, &typmod, false);
+            (void) parseTypeString(sptr, &typeId, &typmod, NULL);

             Py_DECREF(optr);

diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 4185fb1221..185d5bed99 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -615,7 +615,7 @@ call_pltcl_start_proc(Oid prolang, bool pltrusted)
     error_context_stack = &errcallback;

     /* Parse possibly-qualified identifier and look up the function */
-    namelist = stringToQualifiedNameList(start_proc);
+    namelist = stringToQualifiedNameList(start_proc, NULL);
     procOid = LookupFuncName(namelist, 0, NULL, false);

     /* Current user must have permission to call function */
@@ -2603,7 +2603,8 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
                         typIOParam;
             int32        typmod;

-            parseTypeString(Tcl_GetString(argsObj[i]), &typId, &typmod, false);
+            (void) parseTypeString(Tcl_GetString(argsObj[i]),
+                                   &typId, &typmod, NULL);

             getTypeInputInfo(typId, &typInput, &typIOParam);

diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
index e45ff5483f..0c5e1d4be6 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -245,7 +245,7 @@ LINE 1: SELECT regtype('int3');
                        ^
 -- with schemaname
 SELECT regoper('ng_catalog.||/');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  operator does not exist: ng_catalog.||/
 LINE 1: SELECT regoper('ng_catalog.||/');
                        ^
 SELECT regoperator('ng_catalog.+(int4,int4)');
@@ -253,15 +253,15 @@ ERROR:  operator does not exist: ng_catalog.+(int4,int4)
 LINE 1: SELECT regoperator('ng_catalog.+(int4,int4)');
                            ^
 SELECT regproc('ng_catalog.now');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  function "ng_catalog.now" does not exist
 LINE 1: SELECT regproc('ng_catalog.now');
                        ^
 SELECT regprocedure('ng_catalog.abs(numeric)');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  function "ng_catalog.abs(numeric)" does not exist
 LINE 1: SELECT regprocedure('ng_catalog.abs(numeric)');
                             ^
 SELECT regclass('ng_catalog.pg_class');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  relation "ng_catalog.pg_class" does not exist
 LINE 1: SELECT regclass('ng_catalog.pg_class');
                         ^
 SELECT regtype('ng_catalog.int4');
@@ -269,7 +269,7 @@ ERROR:  schema "ng_catalog" does not exist
 LINE 1: SELECT regtype('ng_catalog.int4');
                        ^
 SELECT regcollation('ng_catalog."POSIX"');
-ERROR:  schema "ng_catalog" does not exist
+ERROR:  collation "ng_catalog.POSIX" for encoding "SQL_ASCII" does not exist
 LINE 1: SELECT regcollation('ng_catalog."POSIX"');
                             ^
 -- schemaname not applicable
@@ -406,7 +406,11 @@ SELECT to_regrole('"regress_regrole_test"');
 (1 row)

 SELECT to_regrole('foo.bar');
-ERROR:  invalid name syntax
+ to_regrole
+------------
+
+(1 row)
+
 SELECT to_regrole('Nonexistent');
  to_regrole
 ------------
@@ -420,7 +424,11 @@ SELECT to_regrole('"Nonexistent"');
 (1 row)

 SELECT to_regrole('foo.bar');
-ERROR:  invalid name syntax
+ to_regrole
+------------
+
+(1 row)
+
 SELECT to_regnamespace('Nonexistent');
  to_regnamespace
 -----------------
@@ -434,4 +442,105 @@ SELECT to_regnamespace('"Nonexistent"');
 (1 row)

 SELECT to_regnamespace('foo.bar');
-ERROR:  invalid name syntax
+ to_regnamespace
+-----------------
+
+(1 row)
+
+-- Test soft-error API
+SELECT pg_input_error_message('ng_catalog.pg_class', 'regclass');
+            pg_input_error_message
+-----------------------------------------------
+ relation "ng_catalog.pg_class" does not exist
+(1 row)
+
+SELECT pg_input_error_message('ng_catalog."POSIX"', 'regcollation');
+                        pg_input_error_message
+----------------------------------------------------------------------
+ collation "ng_catalog.POSIX" for encoding "SQL_ASCII" does not exist
+(1 row)
+
+SELECT pg_input_error_message('no_such_config', 'regconfig');
+                  pg_input_error_message
+-----------------------------------------------------------
+ text search configuration "no_such_config" does not exist
+(1 row)
+
+SELECT pg_input_error_message('no_such_dictionary', 'regdictionary');
+                   pg_input_error_message
+------------------------------------------------------------
+ text search dictionary "no_such_dictionary" does not exist
+(1 row)
+
+SELECT pg_input_error_message('Nonexistent', 'regnamespace');
+       pg_input_error_message
+-------------------------------------
+ schema "nonexistent" does not exist
+(1 row)
+
+SELECT pg_input_error_message('ng_catalog.||/', 'regoper');
+         pg_input_error_message
+-----------------------------------------
+ operator does not exist: ng_catalog.||/
+(1 row)
+
+SELECT pg_input_error_message('-', 'regoper');
+     pg_input_error_message
+--------------------------------
+ more than one operator named -
+(1 row)
+
+SELECT pg_input_error_message('ng_catalog.+(int4,int4)', 'regoperator');
+              pg_input_error_message
+--------------------------------------------------
+ operator does not exist: ng_catalog.+(int4,int4)
+(1 row)
+
+SELECT pg_input_error_message('-', 'regoperator');
+   pg_input_error_message
+-----------------------------
+ expected a left parenthesis
+(1 row)
+
+SELECT pg_input_error_message('ng_catalog.now', 'regproc');
+          pg_input_error_message
+------------------------------------------
+ function "ng_catalog.now" does not exist
+(1 row)
+
+SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
+              pg_input_error_message
+---------------------------------------------------
+ function "ng_catalog.abs(numeric)" does not exist
+(1 row)
+
+SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
+    pg_input_error_message
+------------------------------
+ expected a right parenthesis
+(1 row)
+
+SELECT pg_input_error_message('regress_regrole_test', 'regrole');
+           pg_input_error_message
+--------------------------------------------
+ role "regress_regrole_test" does not exist
+(1 row)
+
+SELECT pg_input_error_message('no_such_type', 'regtype');
+       pg_input_error_message
+------------------------------------
+ type "no_such_type" does not exist
+(1 row)
+
+-- Some cases that should be soft errors, but are not yet
+SELECT pg_input_error_message('incorrect type name syntax', 'regtype');
+ERROR:  syntax error at or near "type"
+LINE 1: SELECT pg_input_error_message('incorrect type name syntax', ...
+                  ^
+CONTEXT:  invalid type name "incorrect type name syntax"
+SELECT pg_input_error_message('numeric(1,2,3)', 'regtype');  -- bogus typmod
+ERROR:  invalid NUMERIC type modifier
+SELECT pg_input_error_message('way.too.many.names', 'regtype');
+ERROR:  improper qualified name (too many dotted names): way.too.many.names
+SELECT pg_input_error_message('no_such_catalog.schema.name', 'regtype');
+ERROR:  cross-database references are not implemented: no_such_catalog.schema.name
diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql
index faab0c15ce..aa1f1bb17a 100644
--- a/src/test/regress/sql/regproc.sql
+++ b/src/test/regress/sql/regproc.sql
@@ -120,3 +120,26 @@ SELECT to_regrole('foo.bar');
 SELECT to_regnamespace('Nonexistent');
 SELECT to_regnamespace('"Nonexistent"');
 SELECT to_regnamespace('foo.bar');
+
+-- Test soft-error API
+
+SELECT pg_input_error_message('ng_catalog.pg_class', 'regclass');
+SELECT pg_input_error_message('ng_catalog."POSIX"', 'regcollation');
+SELECT pg_input_error_message('no_such_config', 'regconfig');
+SELECT pg_input_error_message('no_such_dictionary', 'regdictionary');
+SELECT pg_input_error_message('Nonexistent', 'regnamespace');
+SELECT pg_input_error_message('ng_catalog.||/', 'regoper');
+SELECT pg_input_error_message('-', 'regoper');
+SELECT pg_input_error_message('ng_catalog.+(int4,int4)', 'regoperator');
+SELECT pg_input_error_message('-', 'regoperator');
+SELECT pg_input_error_message('ng_catalog.now', 'regproc');
+SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
+SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
+SELECT pg_input_error_message('regress_regrole_test', 'regrole');
+SELECT pg_input_error_message('no_such_type', 'regtype');
+
+-- Some cases that should be soft errors, but are not yet
+SELECT pg_input_error_message('incorrect type name syntax', 'regtype');
+SELECT pg_input_error_message('numeric(1,2,3)', 'regtype');  -- bogus typmod
+SELECT pg_input_error_message('way.too.many.names', 'regtype');
+SELECT pg_input_error_message('no_such_catalog.schema.name', 'regtype');

pgsql-hackers by date:

Previous
From: Ankit Kumar Pandey
Date:
Subject: [PATCH] Improve ability to display optimizer analysis using OPTIMIZER_DEBUG
Next
From: Pavel Stehule
Date:
Subject: Re: [RFC] Add jit deform_counter