From 55106063589c356276a6c5b9475b516ce4b09cc0 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Tue, 25 Nov 2025 20:07:28 +0800
Subject: [PATCH v13 04/20] error safe for casting character to other types per
 pg_cast

select castsource::regtype, casttarget::regtype, castfunc,
castcontext,castmethod, pp.prosrc, pp.proname from pg_cast pc join pg_proc pp on
pp.oid = pc.castfunc and pc.castfunc > 0
and castsource::regtype ='character'::regtype
order by castsource::regtype;

 castsource |    casttarget     | castfunc | castcontext | castmethod |   prosrc    | proname
------------+-------------------+----------+-------------+------------+-------------+---------
 character  | text              |      401 | i           | f          | rtrim1      | text
 character  | character varying |      401 | i           | f          | rtrim1      | text
 character  | "char"            |      944 | a           | f          | text_char   | char
 character  | name              |      409 | i           | f          | bpchar_name | name
 character  | xml               |     2896 | e           | f          | texttoxml   | xml
 character  | character         |      668 | i           | f          | bpchar      | bpchar
(6 rows)

only texttoxml, bpchar(PG_FUNCTION_ARGS) need take care of error handling.
other functions already error safe.

discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
---
 src/backend/catalog/namespace.c | 58 ++++++++++++++++++++++++++-------
 src/backend/utils/adt/regproc.c | 13 ++++++--
 src/backend/utils/adt/varlena.c | 10 ++++--
 src/backend/utils/adt/xml.c     |  2 +-
 src/include/catalog/namespace.h |  6 ++++
 src/include/utils/varlena.h     |  1 +
 6 files changed, 73 insertions(+), 17 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..bef2de5dd39 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -440,6 +440,16 @@ Oid
 RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 						 uint32 flags,
 						 RangeVarGetRelidCallback callback, void *callback_arg)
+{
+	return RangeVarGetRelidExtendedSafe(relation, lockmode, flags,
+										callback, callback_arg,
+										NULL);
+}
+
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode, uint32 flags,
+							 RangeVarGetRelidCallback callback, void *callback_arg,
+							 Node *escontext)
 {
 	uint64		inval_count;
 	Oid			relId;
@@ -456,7 +466,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	if (relation->catalogname)
 	{
 		if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
-			ereport(ERROR,
+			ereturn(escontext, InvalidOid,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
 							relation->catalogname, relation->schemaname,
@@ -513,7 +523,7 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 					 * return InvalidOid.
 					 */
 					if (namespaceId != myTempNamespace)
-						ereport(ERROR,
+						ereturn(escontext, InvalidOid,
 								(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 								 errmsg("temporary tables cannot specify a schema name")));
 				}
@@ -593,13 +603,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 		{
 			int			elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
 
-			if (relation->schemaname)
-				ereport(elevel,
+			if (relation->schemaname && elevel == DEBUG1)
+				ereport(DEBUG1,
 						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
 						 errmsg("could not obtain lock on relation \"%s.%s\"",
 								relation->schemaname, relation->relname)));
-			else
-				ereport(elevel,
+			else if (relation->schemaname && elevel == ERROR)
+				ereturn(escontext, InvalidOid,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s.%s\"",
+							   relation->schemaname, relation->relname));
+			else if (elevel == DEBUG1)
+				ereport(DEBUG1,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+							   relation->relname));
+			else if (elevel == ERROR)
+				ereturn(escontext, InvalidOid,
 						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
 						 errmsg("could not obtain lock on relation \"%s\"",
 								relation->relname)));
@@ -626,13 +646,23 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	{
 		int			elevel = missing_ok ? DEBUG1 : ERROR;
 
-		if (relation->schemaname)
-			ereport(elevel,
+		if (relation->schemaname && elevel == DEBUG1)
+			ereport(DEBUG1,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s.%s\" does not exist",
 							relation->schemaname, relation->relname)));
-		else
-			ereport(elevel,
+		else if (relation->schemaname && elevel == ERROR)
+			ereturn(escontext, InvalidOid,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s.%s\" does not exist",
+						   relation->schemaname, relation->relname));
+		else if (elevel == DEBUG1)
+			ereport(DEBUG1,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" does not exist",
+						   relation->relname));
+		else if (elevel == ERROR)
+			ereturn(escontext, InvalidOid,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
 					 errmsg("relation \"%s\" does not exist",
 							relation->relname)));
@@ -3622,6 +3652,12 @@ get_namespace_oid(const char *nspname, bool missing_ok)
  */
 RangeVar *
 makeRangeVarFromNameList(const List *names)
+{
+	return makeRangeVarFromNameListSafe(names, NULL);
+}
+
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
 {
 	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
 
@@ -3640,7 +3676,7 @@ makeRangeVarFromNameList(const List *names)
 			rel->relname = strVal(lthird(names));
 			break;
 		default:
-			ereport(ERROR,
+			ereturn(escontext, NULL,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("improper relation name (too many dotted names): %s",
 							NameListToString(names))));
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index e5c2246f2c9..59cc508f805 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1901,12 +1901,19 @@ text_regclass(PG_FUNCTION_ARGS)
 	text	   *relname = PG_GETARG_TEXT_PP(0);
 	Oid			result;
 	RangeVar   *rv;
+	List	   *namelist;
 
-	rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+	namelist = textToQualifiedNameListSafe(relname, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
+
+	rv = makeRangeVarFromNameListSafe(namelist, fcinfo->context);
+	if (SOFT_ERROR_OCCURRED(fcinfo->context))
+		PG_RETURN_NULL();
 
 	/* We might not even have permissions on this relation; don't lock it. */
-	result = RangeVarGetRelid(rv, NoLock, false);
-
+	result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL,
+										  fcinfo->context);
 	PG_RETURN_OID(result);
 }
 
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 3894457ab40..f8becbcde51 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2684,6 +2684,12 @@ name_text(PG_FUNCTION_ARGS)
  */
 List *
 textToQualifiedNameList(text *textval)
+{
+	return textToQualifiedNameListSafe(textval, NULL);
+}
+
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
 {
 	char	   *rawname;
 	List	   *result = NIL;
@@ -2695,12 +2701,12 @@ textToQualifiedNameList(text *textval)
 	rawname = text_to_cstring(textval);
 
 	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")));
 
diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c
index 9e8016456ce..8de1f1fc741 100644
--- a/src/backend/utils/adt/xml.c
+++ b/src/backend/utils/adt/xml.c
@@ -1043,7 +1043,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node
 
 	return (xmltype *) data;
 #else
-	ereturn(escontext, NULL
+	ereturn(escontext, NULL,
 			errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			errmsg("unsupported XML feature"),
 			errdetail("This functionality requires the server to be built with libxml support."));
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index f1423f28c32..ab61af55ddc 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -103,6 +103,11 @@ extern Oid	RangeVarGetRelidExtended(const RangeVar *relation,
 									 LOCKMODE lockmode, uint32 flags,
 									 RangeVarGetRelidCallback callback,
 									 void *callback_arg);
+extern Oid RangeVarGetRelidExtendedSafe(const RangeVar *relation,
+										LOCKMODE lockmode, uint32 flags,
+										RangeVarGetRelidCallback callback,
+										void *callback_arg,
+										Node *escontext);
 extern Oid	RangeVarGetCreationNamespace(const RangeVar *newRelation);
 extern Oid	RangeVarGetAndCheckCreationNamespace(RangeVar *relation,
 												 LOCKMODE lockmode,
@@ -168,6 +173,7 @@ extern Oid	LookupCreationNamespace(const char *nspname);
 extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid);
 extern Oid	QualifiedNameGetCreationNamespace(const List *names, char **objname_p);
 extern RangeVar *makeRangeVarFromNameList(const List *names);
+extern RangeVar *makeRangeVarFromNameListSafe(const List *names, Node *escontext);
 extern char *NameListToString(const List *names);
 extern char *NameListToQuotedString(const List *names);
 
diff --git a/src/include/utils/varlena.h b/src/include/utils/varlena.h
index db9fdf72941..0cf01ae5281 100644
--- a/src/include/utils/varlena.h
+++ b/src/include/utils/varlena.h
@@ -27,6 +27,7 @@ extern int	varstr_levenshtein_less_equal(const char *source, int slen,
 										  int ins_c, int del_c, int sub_c,
 										  int max_d, bool trusted);
 extern List *textToQualifiedNameList(text *textval);
+extern List *textToQualifiedNameListSafe(text *textval, Node *escontext);
 extern bool SplitIdentifierString(char *rawstring, char separator,
 								  List **namelist);
 extern bool SplitDirectoriesString(char *rawstring, char separator,
-- 
2.34.1

