diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 109bdfb33f6..a1df8b1ddcd 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 f7ad6894596..8f3850aa4e8 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 b8c08bcf7ba..3df29e3345b 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 7808fbd4486..40ac4d4c342 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -724,7 +724,7 @@ 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); + (void) parseTypeString(typnamestr, &typoid, &my_extra->typmod, NULL); /* 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 a6d695d6cbf..14d76c856d7 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, false); + result = get_collation_oid(names, true); + + 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, false); + result = get_ts_dict_oid(names, true); + + 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 caeb85b4ca1..66ce710598a 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 450ea34336a..043abd341df 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 87cbb1d3e38..51e58934040 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 4e5624d721c..c6c92a00097 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 0e2965ff934..4c3311d8e2d 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 8f21e0d7011..8143ae40a01 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 f7cf2b4b899..fe63766e5d5 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 6b9f8d5b438..ff87b27de09 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 4185fb12210..185d5bed99f 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 e45ff5483fb..0c5e1d4be63 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 faab0c15ce8..aa1f1bb17a2 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');