From 4210ab408627c60160fae4c55b2b31f874af9518 Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Mon, 30 Mar 2026 22:28:58 +0800
Subject: [PATCH v28 1/2] error safe for casting text 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 = 'text'::regtype
order by castsource::regtype;

 castsource | casttarget | castfunc | castcontext | castmethod |    prosrc     | proname
------------+------------+----------+-------------+------------+---------------+----------
 text       | regclass   |     1079 | i           | f          | text_regclass | regclass
 text       | "char"     |      944 | a           | f          | text_char     | char
 text       | name       |      407 | i           | f          | text_name     | name
 text       | xml        |     2896 | e           | f          | texttoxml     | xml
(4 rows)

Already error safe: text_name, text_char, texttoxml.

Author: jian he <jian.universality@gmail.com>
Reviewed-by: Andrew Dunstan <andrew@dunslane.net>
Reviewed-by: Amul Sul <sulamul@gmail.com>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://postgr.es/m/CADkLM=fv1JfY4Ufa-jcwwNbjQixNViskQ8jZu3Tz_p656i_4hQ@mail.gmail.com
Commitfest: https://commitfest.postgresql.org/patch/5941
---
 src/backend/catalog/namespace.c | 46 ++++++++++++++++++++++++++-------
 src/backend/utils/adt/regproc.c | 13 +++++++---
 src/backend/utils/adt/varlena.c | 10 +++++--
 src/include/catalog/namespace.h |  6 +++++
 src/include/utils/varlena.h     |  1 +
 5 files changed, 62 insertions(+), 14 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 56b87d878e8..0f63c826e80 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -442,6 +442,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;
@@ -458,7 +468,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,
@@ -515,7 +525,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")));
 				}
@@ -595,14 +605,20 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 		{
 			int			elevel = (flags & RVR_SKIP_LOCKED) ? DEBUG1 : ERROR;
 
-			if (relation->schemaname)
+			if (escontext == NULL)
 				ereport(elevel,
 						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 relation->schemaname ?
 						 errmsg("could not obtain lock on relation \"%s.%s\"",
-								relation->schemaname, relation->relname)));
+								relation->schemaname, relation->relname) :
+						 errmsg("could not obtain lock on relation \"%s\"",
+								relation->relname)));
 			else
-				ereport(elevel,
+				ereturn(escontext, InvalidOid,
 						(errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						 relation->schemaname ?
+						 errmsg("could not obtain lock on relation \"%s.%s\"",
+								relation->schemaname, relation->relname) :
 						 errmsg("could not obtain lock on relation \"%s\"",
 								relation->relname)));
 
@@ -628,14 +644,20 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	{
 		int			elevel = missing_ok ? DEBUG1 : ERROR;
 
-		if (relation->schemaname)
+		if (escontext == NULL)
 			ereport(elevel,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 relation->schemaname ?
 					 errmsg("relation \"%s.%s\" does not exist",
-							relation->schemaname, relation->relname)));
+							relation->schemaname, relation->relname) :
+					 errmsg("relation \"%s\" does not exist",
+							relation->relname)));
 		else
-			ereport(elevel,
+			ereturn(escontext, InvalidOid,
 					(errcode(ERRCODE_UNDEFINED_TABLE),
+					 relation->schemaname ?
+					 errmsg("relation \"%s.%s\" does not exist",
+							relation->schemaname, relation->relname) :
 					 errmsg("relation \"%s\" does not exist",
 							relation->relname)));
 	}
@@ -3624,6 +3646,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);
 
@@ -3642,7 +3670,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 ee34d1d85f8..604e19a1cb9 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 7b1ee61bde6..56da3d7b440 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2717,6 +2717,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;
@@ -2728,12 +2734,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/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 9453a3e4932..7f3141ba27d 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 4b32574a075..5bc78aa02c0 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

