From 2efaf3b582b15d62b3250535dfbc0840a7444dcb Mon Sep 17 00:00:00 2001
From: jian he <jian.universality@gmail.com>
Date: Sun, 10 Aug 2025 01:56:03 +0800
Subject: [PATCH v6 10/18] 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 is refactored in character type error safe patch.
---
 src/backend/catalog/namespace.c | 167 ++++++++++++++++++++++++++++++++
 src/backend/utils/adt/regproc.c |  22 ++++-
 src/backend/utils/adt/varlena.c |  42 ++++++++
 src/include/catalog/namespace.h |   4 +
 src/include/utils/varlena.h     |   1 +
 5 files changed, 233 insertions(+), 3 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d97d632a7ef..4a5506eeac5 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -641,6 +641,137 @@ RangeVarGetRelidExtended(const RangeVar *relation, LOCKMODE lockmode,
 	return relId;
 }
 
+/* see RangeVarGetRelidExtended also */
+Oid
+RangeVarGetRelidExtendedSafe(const RangeVar *relation, LOCKMODE lockmode,
+							 uint32 flags, RangeVarGetRelidCallback callback, void *callback_arg,
+							 Node *escontext)
+{
+	uint64		inval_count;
+	Oid			relId;
+	Oid			oldRelId = InvalidOid;
+	bool		retry = false;
+	bool		missing_ok = (flags & RVR_MISSING_OK) != 0;
+
+	/* verify that flags do no conflict */
+	Assert(!((flags & RVR_NOWAIT) && (flags & RVR_SKIP_LOCKED)));
+
+	/*
+	 * We check the catalog name and then ignore it.
+	 */
+	if (relation->catalogname)
+	{
+		if (strcmp(relation->catalogname, get_database_name(MyDatabaseId)) != 0)
+			ereturn(escontext, InvalidOid,
+					errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					errmsg("cross-database references are not implemented: \"%s.%s.%s\"",
+							relation->catalogname, relation->schemaname,
+							relation->relname));
+	}
+
+	for (;;)
+	{
+		inval_count = SharedInvalidMessageCounter;
+
+		if (relation->relpersistence == RELPERSISTENCE_TEMP)
+		{
+			if (!OidIsValid(myTempNamespace))
+				relId = InvalidOid;
+			else
+			{
+				if (relation->schemaname)
+				{
+					Oid			namespaceId;
+
+					namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+
+					/*
+					 * For missing_ok, allow a non-existent schema name to
+					 * return InvalidOid.
+					 */
+					if (namespaceId != myTempNamespace)
+						ereturn(escontext, InvalidOid,
+								errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+								errmsg("temporary tables cannot specify a schema name"));
+				}
+
+				relId = get_relname_relid(relation->relname, myTempNamespace);
+			}
+		}
+		else if (relation->schemaname)
+		{
+			Oid			namespaceId;
+
+			/* use exact schema given */
+			namespaceId = LookupExplicitNamespace(relation->schemaname, missing_ok);
+			if (missing_ok && !OidIsValid(namespaceId))
+				relId = InvalidOid;
+			else
+				relId = get_relname_relid(relation->relname, namespaceId);
+		}
+		else
+		{
+			/* search the namespace path */
+			relId = RelnameGetRelid(relation->relname);
+		}
+
+		if (callback)
+			callback(relation, relId, oldRelId, callback_arg);
+
+		if (lockmode == NoLock)
+			break;
+
+		if (retry)
+		{
+			if (relId == oldRelId)
+				break;
+			if (OidIsValid(oldRelId))
+				UnlockRelationOid(oldRelId, lockmode);
+		}
+
+		if (!OidIsValid(relId))
+			AcceptInvalidationMessages();
+		else if (!(flags & (RVR_NOWAIT | RVR_SKIP_LOCKED)))
+			LockRelationOid(relId, lockmode);
+		else if (!ConditionalLockRelationOid(relId, lockmode))
+		{
+			if (relation->schemaname)
+				ereport(DEBUG1,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s.%s\"",
+								relation->schemaname, relation->relname));
+			else
+				ereport(DEBUG1,
+						errcode(ERRCODE_LOCK_NOT_AVAILABLE),
+						errmsg("could not obtain lock on relation \"%s\"",
+							   relation->relname));
+
+			return InvalidOid;
+		}
+
+		if (inval_count == SharedInvalidMessageCounter)
+			break;
+
+		retry = true;
+		oldRelId = relId;
+	}
+
+	if (!OidIsValid(relId))
+	{
+		if (relation->schemaname)
+			ereport(DEBUG1,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s.%s\" does not exist",
+							relation->schemaname, relation->relname));
+		else
+			ereport(DEBUG1,
+					errcode(ERRCODE_UNDEFINED_TABLE),
+					errmsg("relation \"%s\" does not exist",
+							relation->relname));
+	}
+	return relId;
+}
+
 /*
  * RangeVarGetCreationNamespace
  *		Given a RangeVar describing a to-be-created relation,
@@ -3580,6 +3711,42 @@ makeRangeVarFromNameList(const List *names)
 	return rel;
 }
 
+/*
+ * makeRangeVarFromNameListSafe
+ *		Utility routine to convert a qualified-name list into RangeVar form.
+ * The result maybe NULL.
+ */
+RangeVar *
+makeRangeVarFromNameListSafe(const List *names, Node *escontext)
+{
+	RangeVar   *rel = makeRangeVar(NULL, NULL, -1);
+
+	switch (list_length(names))
+	{
+		case 1:
+			rel->relname = strVal(linitial(names));
+			break;
+		case 2:
+			rel->schemaname = strVal(linitial(names));
+			rel->relname = strVal(lsecond(names));
+			break;
+		case 3:
+			rel->catalogname = strVal(linitial(names));
+			rel->schemaname = strVal(lsecond(names));
+			rel->relname = strVal(lthird(names));
+			break;
+		default:
+			errsave(escontext,
+					errcode(ERRCODE_SYNTAX_ERROR),
+					errmsg("improper relation name (too many dotted names): %s",
+							NameListToString(names)));
+			rel = NULL;
+			break;
+	}
+
+	return rel;
+}
+
 /*
  * NameListToString
  *		Utility routine to convert a qualified-name list into a string.
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index b8bbe95e82e..e7f259e8a0a 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -1895,10 +1895,26 @@ text_regclass(PG_FUNCTION_ARGS)
 	Oid			result;
 	RangeVar   *rv;
 
-	rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
+	if (likely(!fcinfo->context))
+	{
+		rv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
 
-	/* We might not even have permissions on this relation; don't lock it. */
-	result = RangeVarGetRelid(rv, NoLock, false);
+		/* We might not even have permissions on this relation; don't lock it. */
+		result = RangeVarGetRelid(rv, NoLock, false);
+	}
+	else
+	{
+		List   		*rvnames;
+
+		rvnames = textToQualifiedNameListSafe(relname, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+
+		rv = makeRangeVarFromNameList(rvnames);
+		result = RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL, fcinfo->context);
+		if (SOFT_ERROR_OCCURRED(fcinfo->context))
+			PG_RETURN_NULL();
+	}
 
 	PG_RETURN_OID(result);
 }
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 11b442a5941..80852d5e922 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -2724,6 +2724,48 @@ textToQualifiedNameList(text *textval)
 	return result;
 }
 
+/* see textToQualifiedNameList also */
+List *
+textToQualifiedNameListSafe(text *textval, Node *escontext)
+{
+	char	   *rawname;
+	List	   *result = NIL;
+	List	   *namelist;
+	ListCell   *l;
+
+	/* Convert to C string (handles possible detoasting). */
+	/* Note we rely on being able to modify rawname below. */
+	rawname = text_to_cstring(textval);
+
+	if (!SplitIdentifierString(rawname, '.', &namelist))
+	{
+		errsave(escontext,
+				errcode(ERRCODE_INVALID_NAME),
+				errmsg("invalid name syntax"));
+		return NIL;
+	}
+
+	if (namelist == NIL)
+	{
+		errsave(escontext,
+				errcode(ERRCODE_INVALID_NAME),
+				errmsg("invalid name syntax"));
+		return NIL;
+	}
+
+	foreach(l, namelist)
+	{
+		char	   *curname = (char *) lfirst(l);
+
+		result = lappend(result, makeString(pstrdup(curname)));
+	}
+
+	pfree(rawname);
+	list_free(namelist);
+
+	return result;
+}
+
 /*
  * SplitIdentifierString --- parse a string containing identifiers
  *
diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h
index 8c7ccc69a3c..e095cbe70fa 100644
--- a/src/include/catalog/namespace.h
+++ b/src/include/catalog/namespace.h
@@ -85,6 +85,9 @@ 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,
@@ -148,6 +151,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

