public inbox for [email protected]  
help / color / mirror / Atom feed
 quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
8+ messages / 3 participants
[nested] [flat]

*  quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2025-11-17 09:48  Chao Li <[email protected]>
  0 siblings, 2 replies; 8+ messages in thread

From: Chao Li @ 2025-11-17 09:48 UTC (permalink / raw)
  To: Postgres hackers <[email protected]>

Hi Hackers,

== Background ==

While reviewing a patch, I noticed that quoteOneName() duplicates the logic
of quote_identifier() for adding double quotes to identifiers.  The main
difference is that quoteOneName() does *not* honor the GUC
`quote_all_identifiers`.

The typical usage pattern of quoteOneName() looks like this:

```
    // Define a local buffer with size MAX_QUOTED_NAME_LEN
    // MAX_QUOTED_NAME_LEN = MAX_NAMELEN * 2 + 3 to ensure no overflow
    char attname[MAX_QUOTED_NAME_LEN];

    // Add quotes and copy into the stack buffer
    quoteOneName(attname, RIAttName(pk_rel,
riinfo->pk_attnums[riinfo->nkeys - 1]));

    // Copy the quoted identifier into a StringInfo
    appendStringInfoString(&querybuf, attname);
```

This pattern is expensive because:

* it allocates a larger-than-necessary buffer on the stack
* it incurs two pallocs and two data copies

Looking further, the common pattern around quote_identifier() is similar:

```
    appendStringInfoString(&relations, quote_identifier(relname));
```

This also incurs two pallocs and two copies: quote_identifier() allocates a
temporary buffer and copies the quoted identifier into it, and then
appendStringInfoString() may allocate and copy again.

== Idea ==

I'd like to propose introducing `appendStringInfoIdentifier()`, which adds
the quoting logic directly to StringInfo.  The goals are:

* eventually deprecate quoteOneName() — I'm not aware of a reason why it
should ignore the GUC `quote_all_identifiers`
* simplify the usage patterns for quoting identifiers
* avoid unnecessary intermediate buffers, pallocs, and data copies

== About the attached patch ==

Attached v1 is not intended to be the final version — it is mainly to
demonstrate the idea and get feedback on the design direction.

* 0001 implements `appendStringInfoIdentifier()` and uses it in a few places
* 0002 switches ri_triggers.c to use it, resolving a complicated usage
pattern and showing a path toward removing quoteOneName()

Comments and suggestions on the overall direction would be very welcome.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/


Attachments:

  [application/octet-stream] v1-0001-Add-appendStringInfoIdentifier-and-use-it-in-a-fe.patch (10.5K, 3-v1-0001-Add-appendStringInfoIdentifier-and-use-it-in-a-fe.patch)
  download | inline diff:
From c9e86872d98f4bc948fb370f582941d946887156 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 14 Nov 2025 21:44:29 +0800
Subject: [PATCH v1 1/2] Add appendStringInfoIdentifier and use it in a few
 places

Author: Chao Li <[email protected]>
---
 src/backend/catalog/objectaddress.c |  20 ++--
 src/backend/commands/explain.c      |   3 +-
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 142 +++++++++++++++++++++++-----
 src/include/utils/builtins.h        |   6 ++
 5 files changed, 136 insertions(+), 43 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c75b7131ed7..fbb0b4f30f3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3611,9 +3611,9 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				else
 					nspname = get_namespace_name(cfgForm->cfgnamespace);
 
-				appendStringInfo(&buffer, _("text search configuration %s"),
-								 quote_qualified_identifier(nspname,
-															NameStr(cfgForm->cfgname)));
+				appendStringInfoQualifiedIdentifier(&buffer,
+													_("text search configuration "),
+													nspname, NameStr(cfgForm->cfgname), NULL);
 				ReleaseSysCache(tup);
 				break;
 			}
@@ -4883,8 +4883,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (attr)
 				{
-					appendStringInfo(&buffer, ".%s",
-									 quote_identifier(attr));
+					appendStringInfoIdentifier(&buffer, ".", attr, NULL);
 					if (objname)
 						*objname = lappend(*objname, attr);
 				}
@@ -5395,8 +5394,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer,
-									   quote_identifier(nspname));
+				appendStringInfoIdentifier(&buffer, NULL, nspname, NULL);
 				if (objname)
 					*objname = list_make1(nspname);
 				break;
@@ -5739,16 +5737,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
 				username = GetUserNameFromId(defacl->defaclrole, false);
-				appendStringInfo(&buffer,
-								 "for role %s",
-								 quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, "for role ", username, NULL);
 
 				if (OidIsValid(defacl->defaclnamespace))
 				{
 					schema = get_namespace_name_or_temp(defacl->defaclnamespace);
-					appendStringInfo(&buffer,
-									 " in schema %s",
-									 quote_identifier(schema));
+					appendStringInfoIdentifier(&buffer, " in schema ", schema, NULL);
 				}
 				else
 					schema = NULL;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7e699f8595e..cc979737845 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1705,8 +1705,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					explain_get_index_name(bitmapindexscan->indexid);
 
 				if (es->format == EXPLAIN_FORMAT_TEXT)
-					appendStringInfo(es->str, " on %s",
-									 quote_identifier(indexname));
+					appendStringInfoIdentifier(es->str,	" on ", indexname, NULL);
 				else
 					ExplainPropertyText("Index Name", indexname, es);
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 059fc5ebf60..9e13f526994 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -395,7 +395,6 @@ RI_FKey_check(TriggerData *trigdata)
 		{
 			quoteOneName(attname,
 						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
 			appendStringInfo(&querybuf,
 							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
 							 attname, pk_only, pkrelname);
@@ -2095,7 +2094,6 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	HeapTuple	tp;
 	Form_pg_collation colltup;
 	char	   *collname;
-	char		onename[MAX_QUOTED_NAME_LEN];
 
 	/* Nothing to do if it's a noncollatable data type */
 	if (!OidIsValid(collation))
@@ -2111,10 +2109,8 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	 * We qualify the name always, for simplicity and to ensure the query is
 	 * not search-path-dependent.
 	 */
-	quoteOneName(onename, get_namespace_name(colltup->collnamespace));
-	appendStringInfo(buf, " COLLATE %s", onename);
-	quoteOneName(onename, collname);
-	appendStringInfo(buf, ".%s", onename);
+	appendStringInfoIdentifier(buf, " COLLATE ", get_namespace_name(colltup->collnamespace), NULL);
+	appendStringInfoIdentifier(buf, ".", collname, NULL);
 
 	ReleaseSysCache(tp);
 }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 556ab057e5a..1d767deb6c4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13052,25 +13052,17 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	}
 }
 
-/*
- * quote_identifier			- Quote an identifier only if needed
- *
- * When quotes are needed, we palloc the required space; slightly
- * space-wasteful but well worth it for notational simplicity.
- */
-const char *
-quote_identifier(const char *ident)
+static inline bool
+is_identifier_safe(const char *ident, int *nquotes)
 {
 	/*
 	 * Can avoid quoting if ident starts with a lowercase letter or underscore
 	 * and contains only lowercase letters, digits, and underscores, *and* is
 	 * not any SQL keyword.  Otherwise, supply quotes.
 	 */
-	int			nquotes = 0;
 	bool		safe;
-	const char *ptr;
-	char	   *result;
-	char	   *optr;
+
+	*nquotes = 0;
 
 	/*
 	 * would like to use <ctype.h> macros here, but they might yield unwanted
@@ -13078,7 +13070,7 @@ quote_identifier(const char *ident)
 	 */
 	safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
 
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13092,7 +13084,7 @@ quote_identifier(const char *ident)
 		{
 			safe = false;
 			if (ch == '"')
-				nquotes++;
+				(*nquotes)++;
 		}
 	}
 
@@ -13115,14 +13107,20 @@ quote_identifier(const char *ident)
 			safe = false;
 	}
 
-	if (safe)
-		return ident;			/* no change needed */
+	return safe;
+}
 
-	result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+static inline const char *
+quote_identifier_internal(const char *ident, int nquotes, char **result)
+{
+	char	   *optr;
 
-	optr = result;
+	if (*result == NULL)
+		*result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+
+	optr = *result;
 	*optr++ = '"';
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13133,7 +13131,107 @@ quote_identifier(const char *ident)
 	*optr++ = '"';
 	*optr = '\0';
 
-	return result;
+	return *result;
+}
+
+/*
+ * quote_identifier			- Quote an identifier only if needed
+ *
+ * When quotes are needed, we palloc the required space; slightly
+ * space-wasteful but well worth it for notational simplicity.
+ */
+const char *
+quote_identifier(const char *ident)
+{
+	int			nquotes;
+	bool		safe;
+	char	   *result = NULL;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		return ident;			/* no change needed */
+
+	return quote_identifier_internal(ident, nquotes, &result);
+}
+
+/*
+ * appendStringInfoIdentifier
+ *      Append an SQL identifier to a StringInfo, quoting if required.
+ *
+ * This behaves like writing prefix + quote_identifier(ident) + suffix into
+ * the StringInfo, but emits the identifier directly into the buffer to avoid
+ * an intermediate palloc.  prefix and suffix may be NULL.
+ *
+ * The identifier is copied verbatim if it is safe to leave unquoted; otherwise
+ * it is written with double quotes and embedded double quotes are doubled.
+ * Quoting rules are local to ruleutils, so this helper is defined here rather
+ * than in stringinfo.c.
+ */
+
+void
+appendStringInfoIdentifier(StringInfo str, const char *prefix,
+						   const char *ident, const char *suffix)
+{
+	int			nquotes;
+	bool		safe;
+	int			ident_len;
+	int			prefix_len = 0;
+	int			suffix_len = 0;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		ident_len = strlen(ident);
+	else
+		ident_len = strlen(ident) + nquotes + 2;	/* quotes + possible
+													 * escapes */
+
+	if (prefix != NULL)
+		prefix_len = strlen(prefix);
+
+	if (suffix != NULL)
+		suffix_len = strlen(suffix);
+
+	enlargeStringInfo(str, ident_len + prefix_len + suffix_len + 1);	/* +1 for trailing null */
+
+	if (prefix != NULL)
+		appendBinaryStringInfo(str, prefix, prefix_len);
+
+	if (safe)
+		appendBinaryStringInfo(str, ident, ident_len);
+	else
+	{
+		char	   *result = str->data + str->len;
+
+		(void) quote_identifier_internal(ident, nquotes, &result);
+		str->len += ident_len;
+		str->data[str->len] = '\0';
+	}
+
+	if (suffix != NULL)
+		appendBinaryStringInfo(str, suffix, suffix_len);
+}
+
+/*
+ * appendStringInfoQualifiedIdentifier
+ *      Append a (possibly) qualified SQL identifier to a StringInfo.
+ *
+ * Writes prefix + qualifier + '.' + ident + suffix, quoting each identifier
+ * component if needed.  If no qualifier is given, only ident (plus optional
+ * prefix/suffix) is appended.  prefix and suffix may be NULL.
+ *
+ * This is a convenience wrapper around appendStringInfoIdentifier().
+ */
+void
+appendStringInfoQualifiedIdentifier(StringInfo str, const char *prefix,
+									const char *qualifier, const char *ident,
+									const char *suffix)
+{
+	if (qualifier)
+	{
+		appendStringInfoIdentifier(str, prefix, qualifier, ".");
+		prefix = NULL;
+	}
+	appendStringInfoIdentifier(str, prefix, ident, suffix);
 }
 
 /*
@@ -13150,8 +13248,8 @@ quote_qualified_identifier(const char *qualifier,
 
 	initStringInfo(&buf);
 	if (qualifier)
-		appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
-	appendStringInfoString(&buf, quote_identifier(ident));
+		appendStringInfoIdentifier(&buf, NULL, qualifier, ".");
+	appendStringInfoIdentifier(&buf, NULL, ident, NULL);
 	return buf.data;
 }
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce6285a2c03..ff4ba7265c2 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -84,6 +84,12 @@ extern void generate_operator_clause(StringInfo buf,
 									 const char *leftop, Oid leftoptype,
 									 Oid opoid,
 									 const char *rightop, Oid rightoptype);
+extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
+extern void appendStringInfoQualifiedIdentifier(StringInfo str,
+												const char *prefix,
+												const char *qualifier,
+												const char *ident,
+												const char *suffix);
 
 /* varchar.c */
 extern int	bpchartruelen(char *s, int len);
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v1-0002-Apply-appendStringInfoIdentifier-in-ri_triggers.c.patch (21.3K, 4-v1-0002-Apply-appendStringInfoIdentifier-in-ri_triggers.c.patch)
  download | inline diff:
From 05b67f72a8fc0b4578fed6fb7f82b8a423a7dd1d Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Mon, 17 Nov 2025 12:09:12 +0800
Subject: [PATCH v1 2/2] Apply appendStringInfoIdentifier in ri_triggers.c

Author: Chao Li <[email protected]>
---
 src/backend/commands/matview.c      |   4 +-
 src/backend/utils/adt/ri_triggers.c | 188 +++++++++++++---------------
 src/backend/utils/adt/ruleutils.c   |  28 ++++-
 src/include/utils/builtins.h        |   9 +-
 4 files changed, 119 insertions(+), 110 deletions(-)

diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index ef7c0d624f1..f1ad5a8e059 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -797,9 +797,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 													 NameStr(attr->attname));
 
 				generate_operator_clause(&querybuf,
-										 leftop, attrtype,
+										 NULL, leftop, attrtype, false,
 										 op,
-										 rightop, attrtype);
+										 NULL, rightop, attrtype, false);
 
 				foundUniqueIndex = true;
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 9e13f526994..3cc4dc2b7c8 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -196,9 +196,9 @@ static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
-							const char *leftop, Oid leftoptype,
+							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 							Oid opoid,
-							const char *rightop, Oid rightoptype);
+							const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
 static int	ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
@@ -358,7 +358,7 @@ RI_FKey_check(TriggerData *trigdata)
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -393,11 +393,11 @@ RI_FKey_check(TriggerData *trigdata)
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -410,13 +410,12 @@ RI_FKey_check(TriggerData *trigdata)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pf_eq_oprs[i],
-							paramname, fk_type);
+							NULL, paramname, fk_type, false);
 			querysep = "AND";
 			queryoids[i] = fk_type;
 		}
@@ -428,9 +427,9 @@ RI_FKey_check(TriggerData *trigdata)
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -528,7 +527,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *pk_only;
@@ -563,11 +562,11 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -579,13 +578,12 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		{
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pp_eq_oprs[i],
-							paramname, pk_type);
+							NULL, paramname, pk_type, false);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -597,9 +595,9 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -757,7 +755,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
@@ -784,13 +782,12 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -828,7 +825,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			char	   *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 				"" : "ONLY ";
 
-			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
 			sprintf(paramname, "$%d", riinfo->nkeys);
 
 			appendStringInfoString(&querybuf, " AND NOT coalesce(");
@@ -837,9 +834,9 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&intersectbuf);
 			appendStringInfoChar(&intersectbuf, '(');
 			ri_GenerateQual(&intersectbuf, "",
-							attname, fk_period_type,
+							NULL, attname, fk_period_type, true,
 							riinfo->period_intersect_oper,
-							paramname, pk_period_type);
+							NULL, paramname, pk_period_type, false);
 			appendStringInfoChar(&intersectbuf, ')');
 
 			/* Find the remaining history */
@@ -857,22 +854,21 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			{
 				Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-				quoteOneName(attname,
-							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 				sprintf(paramname, "$%d", i + 1);
 				ri_GenerateQual(&replacementsbuf, querysep,
-								paramname, pk_type,
+								NULL, paramname, pk_type, false,
 								riinfo->pp_eq_oprs[i],
-								attname, pk_type);
+								NULL, attname, pk_type, true);
 				querysep = "AND";
 				queryoids[i] = pk_type;
 			}
 			appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
 
 			ri_GenerateQual(&querybuf, "",
-							intersectbuf.data, fk_period_type,
+							NULL, intersectbuf.data, fk_period_type, false,
 							riinfo->agged_period_contained_by_oper,
-							replacementsbuf.data, ANYMULTIRANGEOID);
+							NULL, replacementsbuf.data, ANYMULTIRANGEOID, false);
 			/* end of coalesce: */
 			appendStringInfoString(&querybuf, ", false)");
 		}
@@ -944,7 +940,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -969,13 +965,12 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1050,7 +1045,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1081,16 +1076,15 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = $%d",
-							 querysep, attname, i + 1);
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfo(&querybuf, "$%d", i + 1);
 			sprintf(paramname, "$%d", j + 1);
 			ri_GenerateQual(&qualbuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = ",";
 			qualsep = "AND";
 			queryoids[i] = pk_type;
@@ -1239,7 +1233,7 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1297,11 +1291,10 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		querysep = "";
 		for (int i = 0; i < num_cols_to_set; i++)
 		{
-			quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = %s",
-							 querysep, attname,
-							 is_set_null ? "NULL" : "DEFAULT");
+			attname = RIAttName(fk_rel, set_cols[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfoString(&querybuf, is_set_null ? "NULL" : "DEFAULT");
 			querysep = ",";
 		}
 
@@ -1314,14 +1307,13 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			qualsep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1519,8 +1511,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	StringInfoData querybuf;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *pk_perminfo;
 	RTEPermissionInfo *fk_perminfo;
@@ -1615,9 +1607,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1631,8 +1623,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
 					 fk_only, fkrelname, pk_only, pkrelname);
 
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1641,14 +1631,12 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1658,16 +1646,15 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * It's sufficient to test any one pk attribute for null to detect a join
 	 * failure.
 	 */
-	quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
-	appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
+	pkattname = RIAttName(pk_rel, riinfo->pk_attnums[0]);
+	appendStringInfoIdentifier(&querybuf, ") WHERE pk.", pkattname, " IS NULL AND (");
 
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -1814,8 +1801,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char	   *constraintDef;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	const char *sep;
 	const char *fk_only;
 	int			save_nestlevel;
@@ -1852,9 +1839,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1865,8 +1852,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendStringInfo(&querybuf,
 					 " FROM %s%s fk JOIN %s pk ON",
 					 fk_only, fkrelname, pkrelname);
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
+	/* strcpy(pkattname, "pk."); */
+	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1875,14 +1862,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1903,10 +1888,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -2064,13 +2048,15 @@ quoteRelationName(char *buffer, Relation rel)
 static void
 ri_GenerateQual(StringInfo buf,
 				const char *sep,
-				const char *leftop, Oid leftoptype,
+				const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 				Oid opoid,
-				const char *rightop, Oid rightoptype)
+				const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	appendStringInfo(buf, " %s ", sep);
-	generate_operator_clause(buf, leftop, leftoptype, opoid,
-							 rightop, rightoptype);
+	generate_operator_clause(buf,
+							 leftopprefix, leftop, leftoptype, quoteleftop,
+							 opoid,
+							 rightopprefix, rightop, rightoptype, quoterightop);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1d767deb6c4..035e8441e61 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13570,9 +13570,9 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
  */
 void
 generate_operator_clause(StringInfo buf,
-						 const char *leftop, Oid leftoptype,
+						 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 						 Oid opoid,
-						 const char *rightop, Oid rightoptype)
+						 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	HeapTuple	opertup;
 	Form_pg_operator operform;
@@ -13588,12 +13588,30 @@ generate_operator_clause(StringInfo buf,
 
 	nspname = get_namespace_name(operform->oprnamespace);
 
-	appendStringInfoString(buf, leftop);
+	if (quoteleftop)
+		appendStringInfoIdentifier(buf, leftopprefix, leftop, NULL);
+	else
+	{
+		if (leftopprefix)
+			appendStringInfoString(buf, leftopprefix);
+		appendStringInfoString(buf, leftop);
+	}
+	/* appendStringInfoString(buf, leftop); */
 	if (leftoptype != operform->oprleft)
 		add_cast_to(buf, operform->oprleft);
-	appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
+	/* appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname)); */
+	appendStringInfoIdentifier(buf, " OPERATOR(", nspname, ".");
 	appendStringInfoString(buf, oprname);
-	appendStringInfo(buf, ") %s", rightop);
+	appendStringInfoString(buf, ") ");
+	if (quoterightop)
+		appendStringInfoIdentifier(buf, rightopprefix, rightop, NULL);
+	else
+	{
+		if (rightopprefix)
+			appendStringInfoString(buf, rightopprefix);
+		appendStringInfoString(buf, rightop);
+	}
+	/* appendStringInfo(buf, ") %s", rightop); */
 	if (rightoptype != operform->oprright)
 		add_cast_to(buf, operform->oprright);
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ff4ba7265c2..fc4de4446ac 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -80,10 +80,15 @@ extern PGDLLIMPORT bool quote_all_identifiers;
 extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 										const char *ident);
+
+/* extern void generate_operator_clause(StringInfo buf, */
+/* 									 const char *leftop, Oid leftoptype, */
+/* 									 Oid opoid, */
+/* 									 const char *rightop, Oid rightoptype); */
 extern void generate_operator_clause(StringInfo buf,
-									 const char *leftop, Oid leftoptype,
+									 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 									 Oid opoid,
-									 const char *rightop, Oid rightoptype);
+									 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
 extern void appendStringInfoQualifiedIdentifier(StringInfo str,
 												const char *prefix,
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

*  Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2025-11-20 08:19  Chao Li <[email protected]>
  parent: Chao Li <[email protected]>
  1 sibling, 1 reply; 8+ messages in thread

From: Chao Li @ 2025-11-20 08:19 UTC (permalink / raw)
  To: Postgres hackers <[email protected]>

On Mon, Nov 17, 2025 at 5:48 PM Chao Li <[email protected]> wrote:

> Hi Hackers,
>
> == Background ==
>
> While reviewing a patch, I noticed that quoteOneName() duplicates the
> logic of quote_identifier() for adding double quotes to identifiers.  The
> main difference is that quoteOneName() does *not* honor the GUC
> `quote_all_identifiers`.
>
> The typical usage pattern of quoteOneName() looks like this:
>
> ```
>     // Define a local buffer with size MAX_QUOTED_NAME_LEN
>     // MAX_QUOTED_NAME_LEN = MAX_NAMELEN * 2 + 3 to ensure no overflow
>     char attname[MAX_QUOTED_NAME_LEN];
>
>     // Add quotes and copy into the stack buffer
>     quoteOneName(attname, RIAttName(pk_rel,
> riinfo->pk_attnums[riinfo->nkeys - 1]));
>
>     // Copy the quoted identifier into a StringInfo
>     appendStringInfoString(&querybuf, attname);
> ```
>
> This pattern is expensive because:
>
> * it allocates a larger-than-necessary buffer on the stack
> * it incurs two pallocs and two data copies
>
> Looking further, the common pattern around quote_identifier() is similar:
>
> ```
>     appendStringInfoString(&relations, quote_identifier(relname));
> ```
>
> This also incurs two pallocs and two copies: quote_identifier() allocates
> a temporary buffer and copies the quoted identifier into it, and then
> appendStringInfoString() may allocate and copy again.
>
> == Idea ==
>
> I'd like to propose introducing `appendStringInfoIdentifier()`, which adds
> the quoting logic directly to StringInfo.  The goals are:
>
> * eventually deprecate quoteOneName() — I'm not aware of a reason why it
> should ignore the GUC `quote_all_identifiers`
> * simplify the usage patterns for quoting identifiers
> * avoid unnecessary intermediate buffers, pallocs, and data copies
>
> == About the attached patch ==
>
> Attached v1 is not intended to be the final version — it is mainly to
> demonstrate the idea and get feedback on the design direction.
>
> * 0001 implements `appendStringInfoIdentifier()` and uses it in a few
> places
> * 0002 switches ri_triggers.c to use it, resolving a complicated usage
> pattern and showing a path toward removing quoteOneName()
>
> Comments and suggestions on the overall direction would be very welcome.
>
>
CF entry added: https://commitfest.postgresql.org/patch/6240/. And here
comes the v2 patch set.

Best regards,
Chao Li (Evan)
---------------------
HighGo Software Co., Ltd.
https://www.highgo.com/


Attachments:

  [application/octet-stream] v2-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch (11.3K, 3-v2-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch)
  download | inline diff:
From 70971d0e75ab61436d0328a88e5441cf2751a9b6 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 14 Nov 2025 21:44:29 +0800
Subject: [PATCH v2 1/4] Add appendStringInfoIdentifier() to avoid intermediate
 quoting buffers

Introduce appendStringInfoIdentifier() and
appendStringInfoQualifiedIdentifier(), helper functions that append an SQL
identifier directly to a StringInfo while applying quoting rules when
necessary.  This avoids allocating and copying through temporary palloc
buffers, as currently happens with quote_identifier() when used together
with appendStringInfoString().

The new functions improve both readability and efficiency of call sites that
construct SQL fragments, especially those that need to build qualified
names such as schema.table.

Convert several existing callers in objectaddress.c, explain.c and
ruleutils.c to use appendStringInfoIdentifier() /
appendStringInfoQualifiedIdentifier() as examples.

No functional behavior change is intended.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/objectaddress.c |  20 ++--
 src/backend/commands/explain.c      |   3 +-
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 142 +++++++++++++++++++++++-----
 src/include/utils/builtins.h        |   6 ++
 5 files changed, 136 insertions(+), 43 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c75b7131ed7..fbb0b4f30f3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -3611,9 +3611,9 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok)
 				else
 					nspname = get_namespace_name(cfgForm->cfgnamespace);
 
-				appendStringInfo(&buffer, _("text search configuration %s"),
-								 quote_qualified_identifier(nspname,
-															NameStr(cfgForm->cfgname)));
+				appendStringInfoQualifiedIdentifier(&buffer,
+													_("text search configuration "),
+													nspname, NameStr(cfgForm->cfgname), NULL);
 				ReleaseSysCache(tup);
 				break;
 			}
@@ -4883,8 +4883,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (attr)
 				{
-					appendStringInfo(&buffer, ".%s",
-									 quote_identifier(attr));
+					appendStringInfoIdentifier(&buffer, ".", attr, NULL);
 					if (objname)
 						*objname = lappend(*objname, attr);
 				}
@@ -5395,8 +5394,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer,
-									   quote_identifier(nspname));
+				appendStringInfoIdentifier(&buffer, NULL, nspname, NULL);
 				if (objname)
 					*objname = list_make1(nspname);
 				break;
@@ -5739,16 +5737,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
 				username = GetUserNameFromId(defacl->defaclrole, false);
-				appendStringInfo(&buffer,
-								 "for role %s",
-								 quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, "for role ", username, NULL);
 
 				if (OidIsValid(defacl->defaclnamespace))
 				{
 					schema = get_namespace_name_or_temp(defacl->defaclnamespace);
-					appendStringInfo(&buffer,
-									 " in schema %s",
-									 quote_identifier(schema));
+					appendStringInfoIdentifier(&buffer, " in schema ", schema, NULL);
 				}
 				else
 					schema = NULL;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7e699f8595e..cc979737845 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1705,8 +1705,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					explain_get_index_name(bitmapindexscan->indexid);
 
 				if (es->format == EXPLAIN_FORMAT_TEXT)
-					appendStringInfo(es->str, " on %s",
-									 quote_identifier(indexname));
+					appendStringInfoIdentifier(es->str,	" on ", indexname, NULL);
 				else
 					ExplainPropertyText("Index Name", indexname, es);
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 059fc5ebf60..9e13f526994 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -395,7 +395,6 @@ RI_FKey_check(TriggerData *trigdata)
 		{
 			quoteOneName(attname,
 						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
 			appendStringInfo(&querybuf,
 							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
 							 attname, pk_only, pkrelname);
@@ -2095,7 +2094,6 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	HeapTuple	tp;
 	Form_pg_collation colltup;
 	char	   *collname;
-	char		onename[MAX_QUOTED_NAME_LEN];
 
 	/* Nothing to do if it's a noncollatable data type */
 	if (!OidIsValid(collation))
@@ -2111,10 +2109,8 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	 * We qualify the name always, for simplicity and to ensure the query is
 	 * not search-path-dependent.
 	 */
-	quoteOneName(onename, get_namespace_name(colltup->collnamespace));
-	appendStringInfo(buf, " COLLATE %s", onename);
-	quoteOneName(onename, collname);
-	appendStringInfo(buf, ".%s", onename);
+	appendStringInfoIdentifier(buf, " COLLATE ", get_namespace_name(colltup->collnamespace), NULL);
+	appendStringInfoIdentifier(buf, ".", collname, NULL);
 
 	ReleaseSysCache(tp);
 }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 556ab057e5a..1d767deb6c4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13052,25 +13052,17 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	}
 }
 
-/*
- * quote_identifier			- Quote an identifier only if needed
- *
- * When quotes are needed, we palloc the required space; slightly
- * space-wasteful but well worth it for notational simplicity.
- */
-const char *
-quote_identifier(const char *ident)
+static inline bool
+is_identifier_safe(const char *ident, int *nquotes)
 {
 	/*
 	 * Can avoid quoting if ident starts with a lowercase letter or underscore
 	 * and contains only lowercase letters, digits, and underscores, *and* is
 	 * not any SQL keyword.  Otherwise, supply quotes.
 	 */
-	int			nquotes = 0;
 	bool		safe;
-	const char *ptr;
-	char	   *result;
-	char	   *optr;
+
+	*nquotes = 0;
 
 	/*
 	 * would like to use <ctype.h> macros here, but they might yield unwanted
@@ -13078,7 +13070,7 @@ quote_identifier(const char *ident)
 	 */
 	safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
 
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13092,7 +13084,7 @@ quote_identifier(const char *ident)
 		{
 			safe = false;
 			if (ch == '"')
-				nquotes++;
+				(*nquotes)++;
 		}
 	}
 
@@ -13115,14 +13107,20 @@ quote_identifier(const char *ident)
 			safe = false;
 	}
 
-	if (safe)
-		return ident;			/* no change needed */
+	return safe;
+}
 
-	result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+static inline const char *
+quote_identifier_internal(const char *ident, int nquotes, char **result)
+{
+	char	   *optr;
 
-	optr = result;
+	if (*result == NULL)
+		*result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+
+	optr = *result;
 	*optr++ = '"';
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13133,7 +13131,107 @@ quote_identifier(const char *ident)
 	*optr++ = '"';
 	*optr = '\0';
 
-	return result;
+	return *result;
+}
+
+/*
+ * quote_identifier			- Quote an identifier only if needed
+ *
+ * When quotes are needed, we palloc the required space; slightly
+ * space-wasteful but well worth it for notational simplicity.
+ */
+const char *
+quote_identifier(const char *ident)
+{
+	int			nquotes;
+	bool		safe;
+	char	   *result = NULL;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		return ident;			/* no change needed */
+
+	return quote_identifier_internal(ident, nquotes, &result);
+}
+
+/*
+ * appendStringInfoIdentifier
+ *      Append an SQL identifier to a StringInfo, quoting if required.
+ *
+ * This behaves like writing prefix + quote_identifier(ident) + suffix into
+ * the StringInfo, but emits the identifier directly into the buffer to avoid
+ * an intermediate palloc.  prefix and suffix may be NULL.
+ *
+ * The identifier is copied verbatim if it is safe to leave unquoted; otherwise
+ * it is written with double quotes and embedded double quotes are doubled.
+ * Quoting rules are local to ruleutils, so this helper is defined here rather
+ * than in stringinfo.c.
+ */
+
+void
+appendStringInfoIdentifier(StringInfo str, const char *prefix,
+						   const char *ident, const char *suffix)
+{
+	int			nquotes;
+	bool		safe;
+	int			ident_len;
+	int			prefix_len = 0;
+	int			suffix_len = 0;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		ident_len = strlen(ident);
+	else
+		ident_len = strlen(ident) + nquotes + 2;	/* quotes + possible
+													 * escapes */
+
+	if (prefix != NULL)
+		prefix_len = strlen(prefix);
+
+	if (suffix != NULL)
+		suffix_len = strlen(suffix);
+
+	enlargeStringInfo(str, ident_len + prefix_len + suffix_len + 1);	/* +1 for trailing null */
+
+	if (prefix != NULL)
+		appendBinaryStringInfo(str, prefix, prefix_len);
+
+	if (safe)
+		appendBinaryStringInfo(str, ident, ident_len);
+	else
+	{
+		char	   *result = str->data + str->len;
+
+		(void) quote_identifier_internal(ident, nquotes, &result);
+		str->len += ident_len;
+		str->data[str->len] = '\0';
+	}
+
+	if (suffix != NULL)
+		appendBinaryStringInfo(str, suffix, suffix_len);
+}
+
+/*
+ * appendStringInfoQualifiedIdentifier
+ *      Append a (possibly) qualified SQL identifier to a StringInfo.
+ *
+ * Writes prefix + qualifier + '.' + ident + suffix, quoting each identifier
+ * component if needed.  If no qualifier is given, only ident (plus optional
+ * prefix/suffix) is appended.  prefix and suffix may be NULL.
+ *
+ * This is a convenience wrapper around appendStringInfoIdentifier().
+ */
+void
+appendStringInfoQualifiedIdentifier(StringInfo str, const char *prefix,
+									const char *qualifier, const char *ident,
+									const char *suffix)
+{
+	if (qualifier)
+	{
+		appendStringInfoIdentifier(str, prefix, qualifier, ".");
+		prefix = NULL;
+	}
+	appendStringInfoIdentifier(str, prefix, ident, suffix);
 }
 
 /*
@@ -13150,8 +13248,8 @@ quote_qualified_identifier(const char *qualifier,
 
 	initStringInfo(&buf);
 	if (qualifier)
-		appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
-	appendStringInfoString(&buf, quote_identifier(ident));
+		appendStringInfoIdentifier(&buf, NULL, qualifier, ".");
+	appendStringInfoIdentifier(&buf, NULL, ident, NULL);
 	return buf.data;
 }
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce6285a2c03..ff4ba7265c2 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -84,6 +84,12 @@ extern void generate_operator_clause(StringInfo buf,
 									 const char *leftop, Oid leftoptype,
 									 Oid opoid,
 									 const char *rightop, Oid rightoptype);
+extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
+extern void appendStringInfoQualifiedIdentifier(StringInfo str,
+												const char *prefix,
+												const char *qualifier,
+												const char *ident,
+												const char *suffix);
 
 /* varchar.c */
 extern int	bpchartruelen(char *s, int len);
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v2-0004-Use-appendStringInfoIdentifier-in-more-places.patch (35.6K, 4-v2-0004-Use-appendStringInfoIdentifier-in-more-places.patch)
  download | inline diff:
From 12cc4e6619f7cb273a1f02dc1706cac842c89ac3 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 19 Nov 2025 15:55:23 +0800
Subject: [PATCH v2 4/4] Use appendStringInfoIdentifier() in more places.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/namespace.c     |   6 +-
 src/backend/catalog/objectaddress.c |  64 +++----
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 260 ++++++++++++----------------
 4 files changed, 147 insertions(+), 191 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..7c3b0bb7853 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3699,14 +3699,14 @@ NameListToQuotedString(const List *names)
 {
 	StringInfoData string;
 	ListCell   *l;
+	const char *sep = "";
 
 	initStringInfo(&string);
 
 	foreach(l, names)
 	{
-		if (l != list_head(names))
-			appendStringInfoChar(&string, '.');
-		appendStringInfoString(&string, quote_identifier(strVal(lfirst(l))));
+		appendStringInfoIdentifier(&string, sep, strVal(lfirst(l)), NULL);
+		sep = ".";
 	}
 
 	return string.data;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index fbb0b4f30f3..ee495fc5155 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -5004,8 +5004,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (OidIsValid(con->conrelid))
 				{
-					appendStringInfo(&buffer, "%s on ",
-									 quote_identifier(NameStr(con->conname)));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
 					getRelationIdentity(&buffer, con->conrelid, objname,
 										false);
 					if (objname)
@@ -5020,10 +5019,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 					domain.objectId = con->contypid;
 					domain.objectSubId = 0;
 
-					appendStringInfo(&buffer, "%s on %s",
-									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname,
-															objargs, false));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
+					appendStringInfoString(&buffer,
+										   getObjectIdentityParts(&domain, objname,
+																  objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -5096,8 +5095,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
-				appendStringInfoString(&buffer,
-									   quote_identifier(NameStr(langForm->lanname)));
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(langForm->lanname), NULL);
 				if (objname)
 					*objname = list_make1(pstrdup(NameStr(langForm->lanname)));
 				ReleaseSysCache(langTup);
@@ -5155,10 +5154,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 opcForm->opcmethod);
 				amForm = (Form_pg_am) GETSTRUCT(amTup);
 
-				appendStringInfo(&buffer, "%s USING %s",
-								 quote_qualified_identifier(schema,
-															NameStr(opcForm->opcname)),
-								 quote_identifier(NameStr(amForm->amname)));
+				appendStringInfoQualifiedIdentifier(&buffer, NULL,
+													schema, NameStr(opcForm->opcname),
+													" USING ");
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(amForm->amname), NULL);
 				if (objname)
 					*objname = list_make3(pstrdup(NameStr(amForm->amname)),
 										  schema,
@@ -5186,7 +5186,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(amname));
+				appendStringInfoIdentifier(&buffer, NULL, amname, NULL);
 				if (objname)
 					*objname = list_make1(amname);
 			}
@@ -5339,8 +5339,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(rule->rulename)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(rule->rulename), " on ");
 				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
@@ -5372,8 +5371,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(trig->tgname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(trig->tgname), " on ");
 				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
@@ -5544,8 +5542,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				if (objname)
 					*objname = list_make1(username);
-				appendStringInfoString(&buffer,
-									   quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, NULL, username, NULL);
 				break;
 			}
 
@@ -5606,8 +5603,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(datname);
-				appendStringInfoString(&buffer,
-									   quote_identifier(datname));
+				appendStringInfoIdentifier(&buffer, NULL, datname, NULL);
 				break;
 			}
 
@@ -5625,8 +5621,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(tblspace);
-				appendStringInfoString(&buffer,
-									   quote_identifier(tblspace));
+				appendStringInfoIdentifier(&buffer, NULL, tblspace, NULL);
 				break;
 			}
 
@@ -5638,7 +5633,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 													missing_ok);
 				if (fdw)
 				{
-					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					appendStringInfoIdentifier(&buffer, NULL, fdw->fdwname, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(fdw->fdwname));
 				}
@@ -5653,8 +5648,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 											   missing_ok);
 				if (srv)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(srv->servername));
+					appendStringInfoIdentifier(&buffer, NULL, srv->servername, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(srv->servername));
 				}
@@ -5695,9 +5689,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					*objargs = list_make1(pstrdup(srv->servername));
 				}
 
-				appendStringInfo(&buffer, "%s on server %s",
-								 quote_identifier(usename),
-								 srv->servername);
+				appendStringInfoIdentifier(&buffer, NULL, usename, " on server ");
+				appendStringInfoString(&buffer, srv->servername);
 				break;
 			}
 
@@ -5800,7 +5793,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(extname));
+				appendStringInfoIdentifier(&buffer, NULL, extname, NULL);
 				if (objname)
 					*objname = list_make1(extname);
 				break;
@@ -5823,7 +5816,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				evtname = pstrdup(NameStr(trigForm->evtname));
-				appendStringInfoString(&buffer, quote_identifier(evtname));
+				appendStringInfoIdentifier(&buffer, NULL, evtname, NULL);
 				if (objname)
 					*objname = list_make1(evtname);
 				ReleaseSysCache(tup);
@@ -5878,8 +5871,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(policy->polname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(policy->polname), " on ");
 				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
@@ -5895,8 +5887,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				pubname = get_publication_name(object->objectId, missing_ok);
 				if (pubname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(pubname));
+					appendStringInfoIdentifier(&buffer, NULL, pubname, NULL);
 					if (objname)
 						*objname = list_make1(pubname);
 				}
@@ -5963,8 +5954,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				subname = get_subscription_name(object->objectId, missing_ok);
 				if (subname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(subname));
+					appendStringInfoIdentifier(&buffer, NULL, subname, NULL);
 					if (objname)
 						*objname = list_make1(subname);
 				}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index d676930aca2..5b1c6547519 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -825,9 +825,11 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 
 			/* Find the remaining history */
 			initStringInfo(&replacementsbuf);
-			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendStringInfoIdentifier(&replacementsbuf,
+									   "(SELECT pg_catalog.range_agg(r) FROM (SELECT y.",
+									   RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]),
+									   " r FROM ");
 			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
@@ -1821,8 +1823,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
 	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
 
-	/* strcpy(pkattname, "pk."); */
-	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b212a28d9be..a83cac7f03a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -979,18 +979,17 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		/* tgattr is first var-width field, so OK to access directly */
 		if (trigrec->tgattr.dim1 > 0)
 		{
-			int			i;
+			const char *sep = "";
 
 			appendStringInfoString(&buf, " OF ");
-			for (i = 0; i < trigrec->tgattr.dim1; i++)
+			for (int i = 0; i < trigrec->tgattr.dim1; i++)
 			{
 				char	   *attname;
 
-				if (i > 0)
-					appendStringInfoString(&buf, ", ");
 				attname = get_attname(trigrec->tgrelid,
 									  trigrec->tgattr.values[i], false);
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, sep, attname, NULL);
+				sep = ", ";
 			}
 		}
 	}
@@ -1042,11 +1041,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	{
 		appendStringInfoString(&buf, "REFERENCING ");
 		if (tgoldtable != NULL)
-			appendStringInfo(&buf, "OLD TABLE AS %s ",
-							 quote_identifier(tgoldtable));
+			appendStringInfoIdentifier(&buf, "OLD TABLE AS ", tgoldtable, " ");
 		if (tgnewtable != NULL)
-			appendStringInfo(&buf, "NEW TABLE AS %s ",
-							 quote_identifier(tgnewtable));
+			appendStringInfoIdentifier(&buf, "NEW TABLE AS ", tgnewtable, " ");
 	}
 
 	if (TRIGGER_FOR_ROW(trigrec->tgtype))
@@ -1387,8 +1384,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 							 generate_qualified_relation_name(indrelid),
 							 quote_identifier(NameStr(amrec->amname)));
 		else					/* currently, must be EXCLUDE constraint */
-			appendStringInfo(&buf, "EXCLUDE USING %s (",
-							 quote_identifier(NameStr(amrec->amname)));
+			appendStringInfoIdentifier(&buf, "EXCLUDE USING ", NameStr(amrec->amname), " (");
 	}
 
 	/*
@@ -1426,7 +1422,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 
 			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(indrelid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -1536,8 +1532,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			{
 				if (isConstraint)
 					appendStringInfoString(&buf, " USING INDEX");
-				appendStringInfo(&buf, " TABLESPACE %s",
-								 quote_identifier(get_tablespace_name(tblspc)));
+				appendStringInfoIdentifier(&buf, " TABLESPACE ",
+										   get_tablespace_name(tblspc), NULL);
 			}
 		}
 
@@ -1794,7 +1790,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
 
 		attname = get_attname(statextrec->stxrelid, attnum, false);
 
-		appendStringInfoString(&buf, quote_identifier(attname));
+		appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 	}
 
 	context = deparse_context_for(get_relation_name(statextrec->stxrelid),
@@ -2038,7 +2034,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			int32		keycoltypmod;
 
 			attname = get_attname(relid, attnum, false);
-			appendStringInfoString(&buf, quote_identifier(attname));
+			appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -2246,17 +2242,19 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			 * we might need to let callers specify whether to put ONLY in the
 			 * command.
 			 */
-			appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
-							 generate_qualified_relation_name(conForm->conrelid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER TABLE %s ",
+							 generate_qualified_relation_name(conForm->conrelid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 		else
 		{
 			/* Must be a domain constraint */
 			Assert(OidIsValid(conForm->contypid));
-			appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
-							 generate_qualified_type_name(conForm->contypid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER DOMAIN %s ",
+							 generate_qualified_type_name(conForm->contypid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 	}
 
@@ -2423,6 +2421,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					Datum	   *keys;
 					int			nKeys;
 					int			j;
+					const char *sep = "";
 
 					appendStringInfoString(&buf, " INCLUDE (");
 
@@ -2438,9 +2437,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 						colName = get_attname(conForm->conrelid,
 											  DatumGetInt16(keys[j]), false);
-						if (j > keyatts)
-							appendStringInfoString(&buf, ", ");
-						appendStringInfoString(&buf, quote_identifier(colName));
+						appendStringInfoIdentifier(&buf, sep, colName, NULL);
+						sep = ", ";
 					}
 
 					appendStringInfoChar(&buf, ')');
@@ -2467,8 +2465,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					 */
 					tblspc = get_rel_tablespace(indexId);
 					if (OidIsValid(tblspc))
-						appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
-										 quote_identifier(get_tablespace_name(tblspc)));
+						appendStringInfoIdentifier(&buf, " USING INDEX TABLESPACE ",
+												   get_tablespace_name(tblspc), NULL);
 				}
 
 				break;
@@ -2529,9 +2527,9 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 					attnum = extractNotNullColumn(tup);
 
-					appendStringInfo(&buf, "NOT NULL %s",
-									 quote_identifier(get_attname(conForm->conrelid,
-																  attnum, false)));
+					appendStringInfoIdentifier(&buf, "NOT NULL ",
+											   get_attname(conForm->conrelid,
+														   attnum, false), NULL);
 					if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
 						appendStringInfoString(&buf, " NO INHERIT");
 				}
@@ -2635,11 +2633,14 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
-			appendStringInfoString(buf, quote_identifier(colName));
+			appendStringInfoIdentifier(buf, NULL, colName, NULL);
 		else
-			appendStringInfo(buf, ", %s%s",
-							 (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
-							 quote_identifier(colName));
+		{
+			appendStringInfoString(buf, ", ");
+			appendStringInfoIdentifier(buf,
+									   (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
+									   colName, NULL);
+		}
 	}
 
 	return nKeys;
@@ -2975,8 +2976,8 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 
 	print_function_trftypes(&buf, proctup);
 
-	appendStringInfo(&buf, " LANGUAGE %s\n",
-					 quote_identifier(get_language_name(proc->prolang, false)));
+	appendStringInfoIdentifier(&buf, " LANGUAGE ",
+							   get_language_name(proc->prolang, false), "\n");
 
 	/* Emit some miscellaneous options on one line */
 	oldlen = buf.len;
@@ -3075,8 +3076,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 					continue;
 				*pos++ = '\0';
 
-				appendStringInfo(&buf, " SET %s TO ",
-								 quote_identifier(configitem));
+				appendStringInfoIdentifier(&buf, " SET ", configitem, " TO ");
 
 				/*
 				 * Variables that are marked GUC_LIST_QUOTE were already fully
@@ -3418,7 +3418,7 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
 
 		appendStringInfoString(buf, modename);
 		if (argname && argname[0])
-			appendStringInfo(buf, "%s ", quote_identifier(argname));
+			appendStringInfoIdentifier(buf, NULL, argname, " ");
 		appendStringInfoString(buf, format_type_be(argtype));
 		if (print_defaults && isinput && inputargno > nlackdefaults)
 		{
@@ -5402,8 +5402,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 	/*
 	 * Build the rules definition text
 	 */
-	appendStringInfo(buf, "CREATE RULE %s AS",
-					 quote_identifier(rulename));
+	appendStringInfoIdentifier(buf, "CREATE RULE ", rulename, " AS");
 
 	if (prettyFlags & PRETTYFLAG_INDENT)
 		appendStringInfoString(buf, "\n    ON ");
@@ -5791,21 +5790,18 @@ get_with_clause(Query *query, deparse_context *context)
 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
 
 		appendStringInfoString(buf, sep);
-		appendStringInfoString(buf, quote_identifier(cte->ctename));
+		appendStringInfoIdentifier(buf, NULL, cte->ctename, NULL);
 		if (cte->aliascolnames)
 		{
-			bool		first = true;
 			ListCell   *col;
+			const char *colsep = "";
 
 			appendStringInfoChar(buf, '(');
 			foreach(col, cte->aliascolnames)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(col))));
+				appendStringInfoIdentifier(buf, colsep,
+										   strVal(lfirst(col)), NULL);
+				colsep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 		}
@@ -5834,43 +5830,35 @@ get_with_clause(Query *query, deparse_context *context)
 
 		if (cte->search_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *colsep = "";
 
 			appendStringInfo(buf, " SEARCH %s FIRST BY ",
 							 cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
 
 			foreach(lc, cte->search_clause->search_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, colsep, strVal(lfirst(lc)), NULL);
+				colsep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->search_clause->search_seq_column, NULL);
 		}
 
 		if (cte->cycle_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *sep = "";
 
 			appendStringInfoString(buf, " CYCLE ");
 
 			foreach(lc, cte->cycle_clause->cycle_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, sep, strVal(lfirst(lc)), NULL);
+				sep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->cycle_clause->cycle_mark_column, NULL);
 
 			{
 				Const	   *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
@@ -5886,7 +5874,7 @@ get_with_clause(Query *query, deparse_context *context)
 				}
 			}
 
-			appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
+			appendStringInfoIdentifier(buf, " USING ", cte->cycle_clause->cycle_path_column, NULL);
 		}
 
 		sep = ", ";
@@ -6022,9 +6010,7 @@ get_select_query_def(Query *query, deparse_context *context)
 					break;
 			}
 
-			appendStringInfo(buf, " OF %s",
-							 quote_identifier(get_rtable_name(rc->rti,
-															  context)));
+			appendStringInfoIdentifier(buf, " OF ", get_rtable_name(rc->rti, context), NULL);
 			if (rc->waitPolicy == LockWaitError)
 				appendStringInfoString(buf, " NOWAIT");
 			else if (rc->waitPolicy == LockWaitSkip)
@@ -6317,7 +6303,7 @@ get_target_list(List *targetList, deparse_context *context)
 		if (colname)			/* resname could be NULL */
 		{
 			if (attname == NULL || strcmp(attname, colname) != 0)
-				appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
+				appendStringInfoIdentifier(&targetbuf, " AS ", colname, NULL);
 		}
 
 		/* Restore context's output buffer */
@@ -6391,19 +6377,16 @@ get_returning_clause(Query *query, deparse_context *context)
 		/* Add WITH (OLD/NEW) options, if they're not the defaults */
 		if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
 		{
-			appendStringInfo(buf, " WITH (OLD AS %s",
-							 quote_identifier(query->returningOldAlias));
+			appendStringInfoIdentifier(buf, " WITH (OLD AS ", query->returningOldAlias, NULL);
 			have_with = true;
 		}
 		if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
 		{
 			if (have_with)
-				appendStringInfo(buf, ", NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, ", NEW AS ", query->returningNewAlias, NULL);
 			else
 			{
-				appendStringInfo(buf, " WITH (NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, " WITH (NEW AS ", query->returningNewAlias, NULL);
 				have_with = true;
 			}
 		}
@@ -6771,7 +6754,7 @@ get_rule_windowclause(Query *query, deparse_context *context)
 		else
 			appendStringInfoString(buf, sep);
 
-		appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
+		appendStringInfoIdentifier(buf, NULL, wc->name, " AS ");
 
 		get_rule_windowspec(wc, query->targetList, context);
 
@@ -6794,7 +6777,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 	appendStringInfoChar(buf, '(');
 	if (wc->refname)
 	{
-		appendStringInfoString(buf, quote_identifier(wc->refname));
+		appendStringInfoIdentifier(buf, NULL, wc->refname, NULL);
 		needspace = true;
 	}
 	/* partition clauses are always inherited, so only print if no refname */
@@ -7021,10 +7004,8 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL,
+								   get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7117,8 +7098,7 @@ get_insert_query_def(Query *query, deparse_context *context)
 			if (!constraint)
 				elog(ERROR, "cache lookup failed for constraint %u",
 					 confl->constraint);
-			appendStringInfo(buf, " ON CONSTRAINT %s",
-							 quote_identifier(constraint));
+			appendStringInfoIdentifier(buf, " ON CONSTRAINT ", constraint, NULL);
 		}
 
 		if (confl->action == ONCONFLICT_NOTHING)
@@ -7320,10 +7300,7 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL, get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7511,10 +7488,10 @@ get_merge_query_def(Query *query, deparse_context *context)
 				appendStringInfoString(buf, sep);
 				sep = ", ";
 
-				appendStringInfoString(buf,
-									   quote_identifier(get_attname(rte->relid,
-																	tle->resno,
-																	false)));
+				appendStringInfoIdentifier(buf, NULL,
+										   get_attname(rte->relid,
+													   tle->resno,
+													   false), NULL);
 				strippedexprs = lappend(strippedexprs,
 										processIndirection((Node *) tle->expr,
 														   context));
@@ -7573,8 +7550,8 @@ get_utility_query_def(Query *query, deparse_context *context)
 
 		appendContextKeyword(context, "",
 							 0, PRETTYINDENT_STD, 1);
-		appendStringInfo(buf, "NOTIFY %s",
-						 quote_identifier(stmt->conditionname));
+		appendStringInfoIdentifier(buf, "NOTIFY ",
+								   stmt->conditionname, NULL);
 		if (stmt->payload)
 		{
 			appendStringInfoString(buf, ", ");
@@ -7865,11 +7842,11 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 	if (refname && need_prefix)
 	{
-		appendStringInfoString(buf, quote_identifier(refname));
+		appendStringInfoIdentifier(buf, NULL, refname, NULL);
 		appendStringInfoChar(buf, '.');
 	}
 	if (attname)
-		appendStringInfoString(buf, quote_identifier(attname));
+		appendStringInfoIdentifier(buf, NULL, attname, NULL);
 	else
 	{
 		appendStringInfoChar(buf, '*');
@@ -8806,11 +8783,10 @@ get_parameter(Param *param, deparse_context *context)
 				}
 				if (should_qualify)
 				{
-					appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
-					appendStringInfoChar(context->buf, '.');
+					appendStringInfoIdentifier(context->buf, NULL, dpns->funcname, ".");
 				}
 
-				appendStringInfoString(context->buf, quote_identifier(argname));
+				appendStringInfoIdentifier(context->buf, NULL, argname, NULL);
 				return;
 			}
 		}
@@ -9391,7 +9367,7 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				NamedArgExpr *na = (NamedArgExpr *) node;
 
-				appendStringInfo(buf, "%s => ", quote_identifier(na->name));
+				appendStringInfoIdentifier(buf, NULL, na->name, " => ");
 				get_rule_expr((Node *) na->arg, context, showimplicit);
 			}
 			break;
@@ -9679,7 +9655,7 @@ get_rule_expr(Node *node, deparse_context *context,
 				 */
 				fieldname = get_name_for_var_field((Var *) arg, fno,
 												   0, context);
-				appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+				appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 			}
 			break;
 
@@ -10131,8 +10107,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				}
 				if (xexpr->name)
 				{
-					appendStringInfo(buf, "NAME %s",
-									 quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
+					appendStringInfoIdentifier(buf, "NAME ",
+											   map_xml_name_to_sql_identifier(xexpr->name), NULL);
 					needcomma = true;
 				}
 				if (xexpr->named_args)
@@ -10152,8 +10128,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						if (needcomma)
 							appendStringInfoString(buf, ", ");
 						get_rule_expr((Node *) e, context, true);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(map_xml_name_to_sql_identifier(argname)));
+						appendStringInfoIdentifier(buf, " AS ",
+												   map_xml_name_to_sql_identifier(argname), NULL);
 						needcomma = true;
 					}
 					if (xexpr->op != IS_XMLFOREST)
@@ -10369,8 +10345,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
 
 				if (cexpr->cursor_name)
-					appendStringInfo(buf, "CURRENT OF %s",
-									 quote_identifier(cexpr->cursor_name));
+					appendStringInfoIdentifier(buf, "CURRENT OF ",
+											   cexpr->cursor_name, NULL);
 				else
 					appendStringInfo(buf, "CURRENT OF $%d",
 									 cexpr->cursor_param);
@@ -10604,8 +10580,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						needcomma = true;
 
 						get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(lfirst_node(String, lc1)->sval));
+						appendStringInfoIdentifier(buf, " AS ",
+												   lfirst_node(String, lc1)->sval, NULL);
 					}
 				}
 
@@ -11135,7 +11111,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 			if (wc->winref == wfunc->winref)
 			{
 				if (wc->name)
-					appendStringInfoString(buf, quote_identifier(wc->name));
+					appendStringInfoIdentifier(buf, NULL, wc->name, NULL);
 				else
 					get_rule_windowspec(wc, context->targetList, context);
 				break;
@@ -11161,7 +11137,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 
 				if (wagg->winref == wfunc->winref)
 				{
-					appendStringInfoString(buf, quote_identifier(wagg->winname));
+					appendStringInfoIdentifier(buf, NULL, wagg->winname, NULL);
 					break;
 				}
 			}
@@ -12001,8 +11977,8 @@ get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
 			if (ns_node != NULL)
 			{
 				get_rule_expr(expr, context, showimplicit);
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(strVal(ns_node)));
+				appendStringInfoIdentifier(buf, " AS ",
+										   strVal(ns_node), NULL);
 			}
 			else
 			{
@@ -12088,7 +12064,7 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
 		appendStringInfoChar(context->buf, ' ');
 		appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
 		get_const_expr(scan->path->value, context, -1);
-		appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
+		appendStringInfoIdentifier(context->buf, " AS ", scan->path->name, NULL);
 		get_json_table_columns(tf, scan, context, showimplicit);
 	}
 	else if (IsA(plan, JsonTableSiblingJoin))
@@ -12231,7 +12207,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 
 	get_const_expr(root->path->value, context, -1);
 
-	appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
+	appendStringInfoIdentifier(buf, " AS ", root->path->name, NULL);
 
 	if (jexpr->passing_values)
 	{
@@ -12255,9 +12231,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 			appendContextKeyword(context, "", 0, 0, 0);
 
 			get_rule_expr((Node *) lfirst(lc2), context, false);
-			appendStringInfo(buf, " AS %s",
-							 quote_identifier((lfirst_node(String, lc1))->sval)
-				);
+			appendStringInfoIdentifier(buf, " AS ", lfirst_node(String, lc1)->sval, NULL);
 		}
 
 		if (PRETTY_INDENT(context))
@@ -12533,7 +12507,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 				appendStringInfoChar(buf, ')');
 				break;
 			case RTE_CTE:
-				appendStringInfoString(buf, quote_identifier(rte->ctename));
+				appendStringInfoIdentifier(buf, NULL, rte->ctename, NULL);
 				break;
 			default:
 				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
@@ -12620,7 +12594,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		if (j->usingClause)
 		{
 			ListCell   *lc;
-			bool		first = true;
+			const char *sep = "";
 
 			appendStringInfoString(buf, " USING (");
 			/* Use the assigned names, not what's in usingClause */
@@ -12628,17 +12602,13 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			{
 				char	   *colname = (char *) lfirst(lc);
 
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf, quote_identifier(colname));
+				appendStringInfoIdentifier(buf, sep, colname, NULL);
+				sep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 
 			if (j->join_using_alias)
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(j->join_using_alias->aliasname));
+				appendStringInfoIdentifier(buf, " AS ", j->join_using_alias->aliasname, NULL);
 		}
 		else if (j->quals)
 		{
@@ -12668,9 +12638,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			 * subtleties we don't want.  However, we might print a different
 			 * alias name than was there originally.
 			 */
-			appendStringInfo(buf, " %s",
-							 quote_identifier(get_rtable_name(j->rtindex,
-															  context)));
+			appendStringInfoIdentifier(buf, " ",
+									   get_rtable_name(j->rtindex,
+													   context), NULL);
 			get_column_alias_list(colinfo, context);
 		}
 	}
@@ -12745,9 +12715,9 @@ get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
 	}
 
 	if (printalias)
-		appendStringInfo(context->buf, "%s%s",
-						 use_as ? " AS " : " ",
-						 quote_identifier(refname));
+		appendStringInfoIdentifier(context->buf,
+								   use_as ? " AS " : " ",
+								   refname, NULL);
 }
 
 /*
@@ -12777,7 +12747,7 @@ get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
 		}
 		else
 			appendStringInfoString(buf, ", ");
-		appendStringInfoString(buf, quote_identifier(colname));
+		appendStringInfoIdentifier(buf, NULL, colname, NULL);
 	}
 	if (!first)
 		appendStringInfoChar(buf, ')');
@@ -12910,13 +12880,11 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
 		/* Okay, we need the opclass name.  Do we need to qualify it? */
 		opcname = NameStr(opcrec->opcname);
 		if (OpclassIsVisible(opclass))
-			appendStringInfo(buf, " %s", quote_identifier(opcname));
+			appendStringInfoIdentifier(buf, " ", opcname, NULL);
 		else
 		{
 			nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
-			appendStringInfo(buf, " %s.%s",
-							 quote_identifier(nspname),
-							 quote_identifier(opcname));
+			appendStringInfoQualifiedIdentifier(buf, NULL, nspname, opcname, NULL);
 		}
 	}
 	ReleaseSysCache(ht_opc);
@@ -12980,7 +12948,7 @@ processIndirection(Node *node, deparse_context *context)
 			Assert(list_length(fstore->fieldnums) == 1);
 			fieldname = get_attname(typrelid,
 									linitial_int(fstore->fieldnums), false);
-			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+			appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 
 			/*
 			 * We ignore arg since it should be an uninteresting reference to
@@ -13535,7 +13503,7 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
 	else
 	{
 		nspname = get_namespace_name_or_temp(operform->oprnamespace);
-		appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
+		appendStringInfoIdentifier(&buf, "OPERATOR(", nspname, ".");
 	}
 
 	appendStringInfoString(&buf, oprname);
@@ -13641,8 +13609,7 @@ add_cast_to(StringInfo buf, Oid typid)
 	typname = NameStr(typform->typname);
 	nspname = get_namespace_name_or_temp(typform->typnamespace);
 
-	appendStringInfo(buf, "::%s.%s",
-					 quote_identifier(nspname), quote_identifier(typname));
+	appendStringInfoQualifiedIdentifier(buf, "::", nspname, typname, NULL);
 
 	ReleaseSysCache(typetup);
 }
@@ -13739,12 +13706,12 @@ get_reloptions(StringInfo buf, Datum reloptions)
 {
 	Datum	   *options;
 	int			noptions;
-	int			i;
+	const char *sep = "";
 
 	deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
 							  &options, NULL, &noptions);
 
-	for (i = 0; i < noptions; i++)
+	for (int i = 0; i < noptions; i++)
 	{
 		char	   *option = TextDatumGetCString(options[i]);
 		char	   *name;
@@ -13765,9 +13732,8 @@ get_reloptions(StringInfo buf, Datum reloptions)
 		else
 			value = "";
 
-		if (i > 0)
-			appendStringInfoString(buf, ", ");
-		appendStringInfo(buf, "%s=", quote_identifier(name));
+		appendStringInfoIdentifier(buf, sep, name, "=");
+		sep = ", ";
 
 		/*
 		 * In general we need to quote the value; but to avoid unnecessary
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v2-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch (11.5K, 5-v2-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch)
  download | inline diff:
From a26c32c5a2f9b538a2db266245790d29907b42c5 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Tue, 18 Nov 2025 17:13:07 +0800
Subject: [PATCH v2 3/4] Remove quoteOneName() and related buffer-sizing macros
 from ri_triggers.c

After the previous refactoring, quoteOneName() and its callers are no longer
needed.  Remove the function along with MAX_QUOTED_NAME_LEN,
MAX_QUOTED_REL_NAME_LEN, and quoteRelationName(), and introduce
appendRelationName() as the remaining helper for writing qualified relation
names using appendStringInfoQualifiedIdentifier().

This reduces redundant quoting code and centralizes identifier handling in
appendStringInfoIdentifier() / appendStringInfoQualifiedIdentifier(), making
RI triggers consistent with other code that generates SQL fragments.

No functional behavior change is expected.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/utils/adt/ri_triggers.c | 112 ++++++++--------------------
 1 file changed, 30 insertions(+), 82 deletions(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3cc4dc2b7c8..d676930aca2 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -80,9 +80,6 @@
 #define RI_PLAN_SETDEFAULT_ONDELETE		9
 #define RI_PLAN_SETDEFAULT_ONUPDATE		10
 
-#define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
-#define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
-
 #define RIAttName(rel, attnum)	NameStr(*attnumAttName(rel, attnum))
 #define RIAttType(rel, attnum)	attnumTypeId(rel, attnum)
 #define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
@@ -192,8 +189,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 							  const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
 static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
-static void quoteOneName(char *buffer, const char *name);
-static void quoteRelationName(char *buffer, Relation rel);
+static void appendRelationName(StringInfo buffer, Relation rel, const char *prefix, const char *suffix);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
 							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
@@ -357,7 +353,6 @@ RI_FKey_check(TriggerData *trigdata)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -390,19 +385,16 @@ RI_FKey_check(TriggerData *trigdata)
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -526,7 +518,6 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -559,19 +550,16 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -753,10 +741,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
-		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -773,9 +758,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " x");
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -843,10 +827,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&replacementsbuf);
 			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-			quoteRelationName(pkrelname, pk_rel);
-			appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y",
-							 periodattname, pk_only, pkrelname);
+			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
 			querysep = "WHERE";
@@ -939,7 +921,6 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -956,9 +937,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "DELETE FROM %s%s",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "DELETE FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, NULL);
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -1044,7 +1024,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1066,9 +1045,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		initStringInfo(&qualbuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 		querysep = "";
 		qualsep = "WHERE";
 		for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
@@ -1232,7 +1210,6 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1281,9 +1258,8 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfo(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 
 		/*
 		 * Add assignment clauses
@@ -1509,8 +1485,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 {
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	RangeTblEntry *rte;
@@ -1613,15 +1587,13 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
 	pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
-					 fk_only, fkrelname, pk_only, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk LEFT OUTER JOIN ");
+	appendRelationName(&querybuf, pk_rel, pk_only, " pk ON");
 
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
@@ -1799,8 +1771,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
 	char	   *constraintDef;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	const char *sep;
@@ -1845,13 +1815,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk JOIN %s pk ON",
-					 fk_only, fkrelname, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
+	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
+
 	/* strcpy(pkattname, "pk."); */
 	/* strcpy(fkattname, "fk."); */
 	sep = "(";
@@ -2004,37 +1973,16 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 
 
 /*
- * quoteOneName --- safely quote a single SQL name
- *
- * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
- */
-static void
-quoteOneName(char *buffer, const char *name)
-{
-	/* Rather than trying to be smart, just always quote it. */
-	*buffer++ = '"';
-	while (*name)
-	{
-		if (*name == '"')
-			*buffer++ = '"';
-		*buffer++ = *name++;
-	}
-	*buffer++ = '"';
-	*buffer = '\0';
-}
+ * appendRelationName --- safely append a quoted fully qualified relation name
 
-/*
- * quoteRelationName --- safely quote a fully qualified relation name
- *
- * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
  */
 static void
-quoteRelationName(char *buffer, Relation rel)
+appendRelationName(StringInfo buffer, Relation rel,
+				   const char *prefix, const char *suffix)
 {
-	quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
-	buffer += strlen(buffer);
-	*buffer++ = '.';
-	quoteOneName(buffer, RelationGetRelationName(rel));
+	appendStringInfoQualifiedIdentifier(buffer, prefix,
+										get_namespace_name(RelationGetNamespace(rel)),
+										RelationGetRelationName(rel), suffix);
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v2-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch (22.2K, 6-v2-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch)
  download | inline diff:
From a6075d489778b3d8e5f70de38ef5b5b97226d905 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Mon, 17 Nov 2025 12:09:12 +0800
Subject: [PATCH v2 2/4] Use appendStringInfoIdentifier() throughout
 ri_triggers.c

Replace most uses of quoteOneName() and manual stack buffers in
ri_triggers.c with appendStringInfoIdentifier() and related infrastructure.

This simplifies the construction of SQL queries generated by RI triggers and
eliminates the need for MAX_QUOTED_NAME_LEN / stack-allocated intermediate
buffers.  It also removes several code paths where identifiers were quoted
manually, making the quoting rules consistent with ruleutils.c and the GUC
quote_all_identifiers.

This commit also adjusts generate_operator_clause() to support prefixed
arguments and identifier quoting directly, reducing the number of places
where callers need to inject string concatenation logic.

No user-visible behavior change is intended; the generated SQL should be
equivalent to the previous version.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/commands/matview.c      |   4 +-
 src/backend/utils/adt/ri_triggers.c | 188 +++++++++++++---------------
 src/backend/utils/adt/ruleutils.c   |  31 +++--
 src/include/utils/builtins.h        |   9 +-
 4 files changed, 120 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index ef7c0d624f1..f1ad5a8e059 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -797,9 +797,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 													 NameStr(attr->attname));
 
 				generate_operator_clause(&querybuf,
-										 leftop, attrtype,
+										 NULL, leftop, attrtype, false,
 										 op,
-										 rightop, attrtype);
+										 NULL, rightop, attrtype, false);
 
 				foundUniqueIndex = true;
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 9e13f526994..3cc4dc2b7c8 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -196,9 +196,9 @@ static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
-							const char *leftop, Oid leftoptype,
+							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 							Oid opoid,
-							const char *rightop, Oid rightoptype);
+							const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
 static int	ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
@@ -358,7 +358,7 @@ RI_FKey_check(TriggerData *trigdata)
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -393,11 +393,11 @@ RI_FKey_check(TriggerData *trigdata)
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -410,13 +410,12 @@ RI_FKey_check(TriggerData *trigdata)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pf_eq_oprs[i],
-							paramname, fk_type);
+							NULL, paramname, fk_type, false);
 			querysep = "AND";
 			queryoids[i] = fk_type;
 		}
@@ -428,9 +427,9 @@ RI_FKey_check(TriggerData *trigdata)
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -528,7 +527,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *pk_only;
@@ -563,11 +562,11 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -579,13 +578,12 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		{
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pp_eq_oprs[i],
-							paramname, pk_type);
+							NULL, paramname, pk_type, false);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -597,9 +595,9 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -757,7 +755,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
@@ -784,13 +782,12 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -828,7 +825,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			char	   *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 				"" : "ONLY ";
 
-			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
 			sprintf(paramname, "$%d", riinfo->nkeys);
 
 			appendStringInfoString(&querybuf, " AND NOT coalesce(");
@@ -837,9 +834,9 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&intersectbuf);
 			appendStringInfoChar(&intersectbuf, '(');
 			ri_GenerateQual(&intersectbuf, "",
-							attname, fk_period_type,
+							NULL, attname, fk_period_type, true,
 							riinfo->period_intersect_oper,
-							paramname, pk_period_type);
+							NULL, paramname, pk_period_type, false);
 			appendStringInfoChar(&intersectbuf, ')');
 
 			/* Find the remaining history */
@@ -857,22 +854,21 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			{
 				Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-				quoteOneName(attname,
-							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 				sprintf(paramname, "$%d", i + 1);
 				ri_GenerateQual(&replacementsbuf, querysep,
-								paramname, pk_type,
+								NULL, paramname, pk_type, false,
 								riinfo->pp_eq_oprs[i],
-								attname, pk_type);
+								NULL, attname, pk_type, true);
 				querysep = "AND";
 				queryoids[i] = pk_type;
 			}
 			appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
 
 			ri_GenerateQual(&querybuf, "",
-							intersectbuf.data, fk_period_type,
+							NULL, intersectbuf.data, fk_period_type, false,
 							riinfo->agged_period_contained_by_oper,
-							replacementsbuf.data, ANYMULTIRANGEOID);
+							NULL, replacementsbuf.data, ANYMULTIRANGEOID, false);
 			/* end of coalesce: */
 			appendStringInfoString(&querybuf, ", false)");
 		}
@@ -944,7 +940,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -969,13 +965,12 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1050,7 +1045,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1081,16 +1076,15 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = $%d",
-							 querysep, attname, i + 1);
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfo(&querybuf, "$%d", i + 1);
 			sprintf(paramname, "$%d", j + 1);
 			ri_GenerateQual(&qualbuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = ",";
 			qualsep = "AND";
 			queryoids[i] = pk_type;
@@ -1239,7 +1233,7 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1297,11 +1291,10 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		querysep = "";
 		for (int i = 0; i < num_cols_to_set; i++)
 		{
-			quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = %s",
-							 querysep, attname,
-							 is_set_null ? "NULL" : "DEFAULT");
+			attname = RIAttName(fk_rel, set_cols[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfoString(&querybuf, is_set_null ? "NULL" : "DEFAULT");
 			querysep = ",";
 		}
 
@@ -1314,14 +1307,13 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			qualsep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1519,8 +1511,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	StringInfoData querybuf;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *pk_perminfo;
 	RTEPermissionInfo *fk_perminfo;
@@ -1615,9 +1607,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1631,8 +1623,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
 					 fk_only, fkrelname, pk_only, pkrelname);
 
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1641,14 +1631,12 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1658,16 +1646,15 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * It's sufficient to test any one pk attribute for null to detect a join
 	 * failure.
 	 */
-	quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
-	appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
+	pkattname = RIAttName(pk_rel, riinfo->pk_attnums[0]);
+	appendStringInfoIdentifier(&querybuf, ") WHERE pk.", pkattname, " IS NULL AND (");
 
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -1814,8 +1801,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char	   *constraintDef;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	const char *sep;
 	const char *fk_only;
 	int			save_nestlevel;
@@ -1852,9 +1839,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1865,8 +1852,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendStringInfo(&querybuf,
 					 " FROM %s%s fk JOIN %s pk ON",
 					 fk_only, fkrelname, pkrelname);
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
+	/* strcpy(pkattname, "pk."); */
+	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1875,14 +1862,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1903,10 +1888,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -2064,13 +2048,15 @@ quoteRelationName(char *buffer, Relation rel)
 static void
 ri_GenerateQual(StringInfo buf,
 				const char *sep,
-				const char *leftop, Oid leftoptype,
+				const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 				Oid opoid,
-				const char *rightop, Oid rightoptype)
+				const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	appendStringInfo(buf, " %s ", sep);
-	generate_operator_clause(buf, leftop, leftoptype, opoid,
-							 rightop, rightoptype);
+	generate_operator_clause(buf,
+							 leftopprefix, leftop, leftoptype, quoteleftop,
+							 opoid,
+							 rightopprefix, rightop, rightoptype, quoterightop);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1d767deb6c4..b212a28d9be 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13570,9 +13570,9 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
  */
 void
 generate_operator_clause(StringInfo buf,
-						 const char *leftop, Oid leftoptype,
+						 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 						 Oid opoid,
-						 const char *rightop, Oid rightoptype)
+						 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	HeapTuple	opertup;
 	Form_pg_operator operform;
@@ -13585,15 +13585,32 @@ generate_operator_clause(StringInfo buf,
 	operform = (Form_pg_operator) GETSTRUCT(opertup);
 	Assert(operform->oprkind == 'b');
 	oprname = NameStr(operform->oprname);
-
 	nspname = get_namespace_name(operform->oprnamespace);
 
-	appendStringInfoString(buf, leftop);
+	if (quoteleftop)
+		appendStringInfoIdentifier(buf, leftopprefix, leftop, NULL);
+	else
+	{
+		if (leftopprefix)
+			appendStringInfoString(buf, leftopprefix);
+		appendStringInfoString(buf, leftop);
+	}
+
 	if (leftoptype != operform->oprleft)
 		add_cast_to(buf, operform->oprleft);
-	appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
-	appendStringInfoString(buf, oprname);
-	appendStringInfo(buf, ") %s", rightop);
+
+	appendStringInfoIdentifier(buf, " OPERATOR(", nspname, ".");
+	appendStringInfo(buf, "%s) ", oprname);
+
+	if (quoterightop)
+		appendStringInfoIdentifier(buf, rightopprefix, rightop, NULL);
+	else
+	{
+		if (rightopprefix)
+			appendStringInfoString(buf, rightopprefix);
+		appendStringInfoString(buf, rightop);
+	}
+
 	if (rightoptype != operform->oprright)
 		add_cast_to(buf, operform->oprright);
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ff4ba7265c2..fc4de4446ac 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -80,10 +80,15 @@ extern PGDLLIMPORT bool quote_all_identifiers;
 extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 										const char *ident);
+
+/* extern void generate_operator_clause(StringInfo buf, */
+/* 									 const char *leftop, Oid leftoptype, */
+/* 									 Oid opoid, */
+/* 									 const char *rightop, Oid rightoptype); */
 extern void generate_operator_clause(StringInfo buf,
-									 const char *leftop, Oid leftoptype,
+									 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 									 Oid opoid,
-									 const char *rightop, Oid rightoptype);
+									 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
 extern void appendStringInfoQualifiedIdentifier(StringInfo str,
 												const char *prefix,
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2025-11-20 12:28  Álvaro Herrera <[email protected]>
  parent: Chao Li <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: Álvaro Herrera @ 2025-11-20 12:28 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Postgres hackers <[email protected]>

Hi,

> -                appendStringInfo(&buffer, _("text search configuration %s"),
> -                                 quote_qualified_identifier(nspname,
> -                                                            NameStr(cfgForm->cfgname)));
> +                appendStringInfoQualifiedIdentifier(&buffer,
> +                                                    _("text search configuration "),
> +                                                    nspname, NameStr(cfgForm->cfgname), NULL);
>                  ReleaseSysCache(tup);
>                  break;
>              }

This doesn't work from a i18n point of view.  In the original
formulation, the translator is free to place the %s wherever it suits
the language.  In the new one there's no such freedom: the name will be
appended at the end.  Some existing translations:

ko.po:msgid "text search configuration %s"
ko.po-msgstr "%s 전문 검색 구성"

tr.po:msgid "text search configuration %s"
tr.po-msgstr "%s metin arama yapılandırması"

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"I must say, I am absolutely impressed with what pgsql's implementation of
VALUES allows me to do. It's kind of ridiculous how much "work" goes away in
my code.  Too bad I can't do this at work (Oracle 8/9)."       (Tom Allison)
           http://archives.postgresql.org/pgsql-general/2007-06/msg00016.php





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

*  Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2025-11-21 05:52  Chao Li <[email protected]>
  parent: Álvaro Herrera <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: Chao Li @ 2025-11-21 05:52 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Postgres hackers <[email protected]>

On Thu, Nov 20, 2025 at 8:28 PM Álvaro Herrera <[email protected]> wrote:

> Hi,
>
> > -                appendStringInfo(&buffer, _("text search configuration
> %s"),
> > -                                 quote_qualified_identifier(nspname,
> > -
> NameStr(cfgForm->cfgname)));
> > +                appendStringInfoQualifiedIdentifier(&buffer,
> > +                                                    _("text search
> configuration "),
> > +                                                    nspname,
> NameStr(cfgForm->cfgname), NULL);
> >                  ReleaseSysCache(tup);
> >                  break;
> >              }
>
> This doesn't work from a i18n point of view.  In the original
> formulation, the translator is free to place the %s wherever it suits
> the language.  In the new one there's no such freedom: the name will be
> appended at the end.  Some existing translations:
>
> ko.po:msgid "text search configuration %s"
> ko.po-msgstr "%s 전문 검색 구성"
>
> tr.po:msgid "text search configuration %s"
> tr.po-msgstr "%s metin arama yapılandırması"
>

Thanks for the feedback. I reverted that piece of change in v3.

Best regards,
Chao Li (Evan)
---------------------
HighGo Software Co., Ltd.
https://www.highgo.com/


Attachments:

  [application/octet-stream] v3-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch (10.8K, 3-v3-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch)
  download | inline diff:
From 648a9cbd2013ac82d76585610463e1ae30e09973 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 14 Nov 2025 21:44:29 +0800
Subject: [PATCH v3 1/4] Add appendStringInfoIdentifier() to avoid intermediate
 quoting buffers

Introduce appendStringInfoIdentifier() and
appendStringInfoQualifiedIdentifier(), helper functions that append an SQL
identifier directly to a StringInfo while applying quoting rules when
necessary.  This avoids allocating and copying through temporary palloc
buffers, as currently happens with quote_identifier() when used together
with appendStringInfoString().

The new functions improve both readability and efficiency of call sites that
construct SQL fragments, especially those that need to build qualified
names such as schema.table.

Convert several existing callers in objectaddress.c, explain.c and
ruleutils.c to use appendStringInfoIdentifier() /
appendStringInfoQualifiedIdentifier() as examples.

No functional behavior change is intended.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/objectaddress.c |  14 +--
 src/backend/commands/explain.c      |   3 +-
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 142 +++++++++++++++++++++++-----
 src/include/utils/builtins.h        |   6 ++
 5 files changed, 133 insertions(+), 40 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c75b7131ed7..70e05059f9f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -4883,8 +4883,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (attr)
 				{
-					appendStringInfo(&buffer, ".%s",
-									 quote_identifier(attr));
+					appendStringInfoIdentifier(&buffer, ".", attr, NULL);
 					if (objname)
 						*objname = lappend(*objname, attr);
 				}
@@ -5395,8 +5394,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer,
-									   quote_identifier(nspname));
+				appendStringInfoIdentifier(&buffer, NULL, nspname, NULL);
 				if (objname)
 					*objname = list_make1(nspname);
 				break;
@@ -5739,16 +5737,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
 				username = GetUserNameFromId(defacl->defaclrole, false);
-				appendStringInfo(&buffer,
-								 "for role %s",
-								 quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, "for role ", username, NULL);
 
 				if (OidIsValid(defacl->defaclnamespace))
 				{
 					schema = get_namespace_name_or_temp(defacl->defaclnamespace);
-					appendStringInfo(&buffer,
-									 " in schema %s",
-									 quote_identifier(schema));
+					appendStringInfoIdentifier(&buffer, " in schema ", schema, NULL);
 				}
 				else
 					schema = NULL;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7e699f8595e..cc979737845 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1705,8 +1705,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					explain_get_index_name(bitmapindexscan->indexid);
 
 				if (es->format == EXPLAIN_FORMAT_TEXT)
-					appendStringInfo(es->str, " on %s",
-									 quote_identifier(indexname));
+					appendStringInfoIdentifier(es->str,	" on ", indexname, NULL);
 				else
 					ExplainPropertyText("Index Name", indexname, es);
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 059fc5ebf60..9e13f526994 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -395,7 +395,6 @@ RI_FKey_check(TriggerData *trigdata)
 		{
 			quoteOneName(attname,
 						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
 			appendStringInfo(&querybuf,
 							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
 							 attname, pk_only, pkrelname);
@@ -2095,7 +2094,6 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	HeapTuple	tp;
 	Form_pg_collation colltup;
 	char	   *collname;
-	char		onename[MAX_QUOTED_NAME_LEN];
 
 	/* Nothing to do if it's a noncollatable data type */
 	if (!OidIsValid(collation))
@@ -2111,10 +2109,8 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	 * We qualify the name always, for simplicity and to ensure the query is
 	 * not search-path-dependent.
 	 */
-	quoteOneName(onename, get_namespace_name(colltup->collnamespace));
-	appendStringInfo(buf, " COLLATE %s", onename);
-	quoteOneName(onename, collname);
-	appendStringInfo(buf, ".%s", onename);
+	appendStringInfoIdentifier(buf, " COLLATE ", get_namespace_name(colltup->collnamespace), NULL);
+	appendStringInfoIdentifier(buf, ".", collname, NULL);
 
 	ReleaseSysCache(tp);
 }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 556ab057e5a..1d767deb6c4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13052,25 +13052,17 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	}
 }
 
-/*
- * quote_identifier			- Quote an identifier only if needed
- *
- * When quotes are needed, we palloc the required space; slightly
- * space-wasteful but well worth it for notational simplicity.
- */
-const char *
-quote_identifier(const char *ident)
+static inline bool
+is_identifier_safe(const char *ident, int *nquotes)
 {
 	/*
 	 * Can avoid quoting if ident starts with a lowercase letter or underscore
 	 * and contains only lowercase letters, digits, and underscores, *and* is
 	 * not any SQL keyword.  Otherwise, supply quotes.
 	 */
-	int			nquotes = 0;
 	bool		safe;
-	const char *ptr;
-	char	   *result;
-	char	   *optr;
+
+	*nquotes = 0;
 
 	/*
 	 * would like to use <ctype.h> macros here, but they might yield unwanted
@@ -13078,7 +13070,7 @@ quote_identifier(const char *ident)
 	 */
 	safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
 
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13092,7 +13084,7 @@ quote_identifier(const char *ident)
 		{
 			safe = false;
 			if (ch == '"')
-				nquotes++;
+				(*nquotes)++;
 		}
 	}
 
@@ -13115,14 +13107,20 @@ quote_identifier(const char *ident)
 			safe = false;
 	}
 
-	if (safe)
-		return ident;			/* no change needed */
+	return safe;
+}
 
-	result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+static inline const char *
+quote_identifier_internal(const char *ident, int nquotes, char **result)
+{
+	char	   *optr;
 
-	optr = result;
+	if (*result == NULL)
+		*result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+
+	optr = *result;
 	*optr++ = '"';
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13133,7 +13131,107 @@ quote_identifier(const char *ident)
 	*optr++ = '"';
 	*optr = '\0';
 
-	return result;
+	return *result;
+}
+
+/*
+ * quote_identifier			- Quote an identifier only if needed
+ *
+ * When quotes are needed, we palloc the required space; slightly
+ * space-wasteful but well worth it for notational simplicity.
+ */
+const char *
+quote_identifier(const char *ident)
+{
+	int			nquotes;
+	bool		safe;
+	char	   *result = NULL;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		return ident;			/* no change needed */
+
+	return quote_identifier_internal(ident, nquotes, &result);
+}
+
+/*
+ * appendStringInfoIdentifier
+ *      Append an SQL identifier to a StringInfo, quoting if required.
+ *
+ * This behaves like writing prefix + quote_identifier(ident) + suffix into
+ * the StringInfo, but emits the identifier directly into the buffer to avoid
+ * an intermediate palloc.  prefix and suffix may be NULL.
+ *
+ * The identifier is copied verbatim if it is safe to leave unquoted; otherwise
+ * it is written with double quotes and embedded double quotes are doubled.
+ * Quoting rules are local to ruleutils, so this helper is defined here rather
+ * than in stringinfo.c.
+ */
+
+void
+appendStringInfoIdentifier(StringInfo str, const char *prefix,
+						   const char *ident, const char *suffix)
+{
+	int			nquotes;
+	bool		safe;
+	int			ident_len;
+	int			prefix_len = 0;
+	int			suffix_len = 0;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		ident_len = strlen(ident);
+	else
+		ident_len = strlen(ident) + nquotes + 2;	/* quotes + possible
+													 * escapes */
+
+	if (prefix != NULL)
+		prefix_len = strlen(prefix);
+
+	if (suffix != NULL)
+		suffix_len = strlen(suffix);
+
+	enlargeStringInfo(str, ident_len + prefix_len + suffix_len + 1);	/* +1 for trailing null */
+
+	if (prefix != NULL)
+		appendBinaryStringInfo(str, prefix, prefix_len);
+
+	if (safe)
+		appendBinaryStringInfo(str, ident, ident_len);
+	else
+	{
+		char	   *result = str->data + str->len;
+
+		(void) quote_identifier_internal(ident, nquotes, &result);
+		str->len += ident_len;
+		str->data[str->len] = '\0';
+	}
+
+	if (suffix != NULL)
+		appendBinaryStringInfo(str, suffix, suffix_len);
+}
+
+/*
+ * appendStringInfoQualifiedIdentifier
+ *      Append a (possibly) qualified SQL identifier to a StringInfo.
+ *
+ * Writes prefix + qualifier + '.' + ident + suffix, quoting each identifier
+ * component if needed.  If no qualifier is given, only ident (plus optional
+ * prefix/suffix) is appended.  prefix and suffix may be NULL.
+ *
+ * This is a convenience wrapper around appendStringInfoIdentifier().
+ */
+void
+appendStringInfoQualifiedIdentifier(StringInfo str, const char *prefix,
+									const char *qualifier, const char *ident,
+									const char *suffix)
+{
+	if (qualifier)
+	{
+		appendStringInfoIdentifier(str, prefix, qualifier, ".");
+		prefix = NULL;
+	}
+	appendStringInfoIdentifier(str, prefix, ident, suffix);
 }
 
 /*
@@ -13150,8 +13248,8 @@ quote_qualified_identifier(const char *qualifier,
 
 	initStringInfo(&buf);
 	if (qualifier)
-		appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
-	appendStringInfoString(&buf, quote_identifier(ident));
+		appendStringInfoIdentifier(&buf, NULL, qualifier, ".");
+	appendStringInfoIdentifier(&buf, NULL, ident, NULL);
 	return buf.data;
 }
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce6285a2c03..ff4ba7265c2 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -84,6 +84,12 @@ extern void generate_operator_clause(StringInfo buf,
 									 const char *leftop, Oid leftoptype,
 									 Oid opoid,
 									 const char *rightop, Oid rightoptype);
+extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
+extern void appendStringInfoQualifiedIdentifier(StringInfo str,
+												const char *prefix,
+												const char *qualifier,
+												const char *ident,
+												const char *suffix);
 
 /* varchar.c */
 extern int	bpchartruelen(char *s, int len);
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch (11.5K, 4-v3-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch)
  download | inline diff:
From 1ea040c6761a91d807c8e63887a05ac01bb93b0a Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Tue, 18 Nov 2025 17:13:07 +0800
Subject: [PATCH v3 3/4] Remove quoteOneName() and related buffer-sizing macros
 from ri_triggers.c

After the previous refactoring, quoteOneName() and its callers are no longer
needed.  Remove the function along with MAX_QUOTED_NAME_LEN,
MAX_QUOTED_REL_NAME_LEN, and quoteRelationName(), and introduce
appendRelationName() as the remaining helper for writing qualified relation
names using appendStringInfoQualifiedIdentifier().

This reduces redundant quoting code and centralizes identifier handling in
appendStringInfoIdentifier() / appendStringInfoQualifiedIdentifier(), making
RI triggers consistent with other code that generates SQL fragments.

No functional behavior change is expected.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/utils/adt/ri_triggers.c | 112 ++++++++--------------------
 1 file changed, 30 insertions(+), 82 deletions(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3cc4dc2b7c8..d676930aca2 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -80,9 +80,6 @@
 #define RI_PLAN_SETDEFAULT_ONDELETE		9
 #define RI_PLAN_SETDEFAULT_ONUPDATE		10
 
-#define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
-#define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
-
 #define RIAttName(rel, attnum)	NameStr(*attnumAttName(rel, attnum))
 #define RIAttType(rel, attnum)	attnumTypeId(rel, attnum)
 #define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
@@ -192,8 +189,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 							  const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
 static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
-static void quoteOneName(char *buffer, const char *name);
-static void quoteRelationName(char *buffer, Relation rel);
+static void appendRelationName(StringInfo buffer, Relation rel, const char *prefix, const char *suffix);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
 							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
@@ -357,7 +353,6 @@ RI_FKey_check(TriggerData *trigdata)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -390,19 +385,16 @@ RI_FKey_check(TriggerData *trigdata)
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -526,7 +518,6 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -559,19 +550,16 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -753,10 +741,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
-		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -773,9 +758,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " x");
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -843,10 +827,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&replacementsbuf);
 			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-			quoteRelationName(pkrelname, pk_rel);
-			appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y",
-							 periodattname, pk_only, pkrelname);
+			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
 			querysep = "WHERE";
@@ -939,7 +921,6 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -956,9 +937,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "DELETE FROM %s%s",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "DELETE FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, NULL);
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -1044,7 +1024,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1066,9 +1045,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		initStringInfo(&qualbuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 		querysep = "";
 		qualsep = "WHERE";
 		for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
@@ -1232,7 +1210,6 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1281,9 +1258,8 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfo(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 
 		/*
 		 * Add assignment clauses
@@ -1509,8 +1485,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 {
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	RangeTblEntry *rte;
@@ -1613,15 +1587,13 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
 	pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
-					 fk_only, fkrelname, pk_only, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk LEFT OUTER JOIN ");
+	appendRelationName(&querybuf, pk_rel, pk_only, " pk ON");
 
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
@@ -1799,8 +1771,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
 	char	   *constraintDef;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	const char *sep;
@@ -1845,13 +1815,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk JOIN %s pk ON",
-					 fk_only, fkrelname, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
+	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
+
 	/* strcpy(pkattname, "pk."); */
 	/* strcpy(fkattname, "fk."); */
 	sep = "(";
@@ -2004,37 +1973,16 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 
 
 /*
- * quoteOneName --- safely quote a single SQL name
- *
- * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
- */
-static void
-quoteOneName(char *buffer, const char *name)
-{
-	/* Rather than trying to be smart, just always quote it. */
-	*buffer++ = '"';
-	while (*name)
-	{
-		if (*name == '"')
-			*buffer++ = '"';
-		*buffer++ = *name++;
-	}
-	*buffer++ = '"';
-	*buffer = '\0';
-}
+ * appendRelationName --- safely append a quoted fully qualified relation name
 
-/*
- * quoteRelationName --- safely quote a fully qualified relation name
- *
- * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
  */
 static void
-quoteRelationName(char *buffer, Relation rel)
+appendRelationName(StringInfo buffer, Relation rel,
+				   const char *prefix, const char *suffix)
 {
-	quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
-	buffer += strlen(buffer);
-	*buffer++ = '.';
-	quoteOneName(buffer, RelationGetRelationName(rel));
+	appendStringInfoQualifiedIdentifier(buffer, prefix,
+										get_namespace_name(RelationGetNamespace(rel)),
+										RelationGetRelationName(rel), suffix);
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0004-Use-appendStringInfoIdentifier-in-more-places.patch (35.6K, 5-v3-0004-Use-appendStringInfoIdentifier-in-more-places.patch)
  download | inline diff:
From bddf5fa9b910babc8209f788710955197dcfc72c Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 19 Nov 2025 15:55:23 +0800
Subject: [PATCH v3 4/4] Use appendStringInfoIdentifier() in more places.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/namespace.c     |   6 +-
 src/backend/catalog/objectaddress.c |  64 +++----
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 260 ++++++++++++----------------
 4 files changed, 147 insertions(+), 191 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..7c3b0bb7853 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3699,14 +3699,14 @@ NameListToQuotedString(const List *names)
 {
 	StringInfoData string;
 	ListCell   *l;
+	const char *sep = "";
 
 	initStringInfo(&string);
 
 	foreach(l, names)
 	{
-		if (l != list_head(names))
-			appendStringInfoChar(&string, '.');
-		appendStringInfoString(&string, quote_identifier(strVal(lfirst(l))));
+		appendStringInfoIdentifier(&string, sep, strVal(lfirst(l)), NULL);
+		sep = ".";
 	}
 
 	return string.data;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 70e05059f9f..fa8762db10d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -5004,8 +5004,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (OidIsValid(con->conrelid))
 				{
-					appendStringInfo(&buffer, "%s on ",
-									 quote_identifier(NameStr(con->conname)));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
 					getRelationIdentity(&buffer, con->conrelid, objname,
 										false);
 					if (objname)
@@ -5020,10 +5019,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 					domain.objectId = con->contypid;
 					domain.objectSubId = 0;
 
-					appendStringInfo(&buffer, "%s on %s",
-									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname,
-															objargs, false));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
+					appendStringInfoString(&buffer,
+										   getObjectIdentityParts(&domain, objname,
+																  objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -5096,8 +5095,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
-				appendStringInfoString(&buffer,
-									   quote_identifier(NameStr(langForm->lanname)));
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(langForm->lanname), NULL);
 				if (objname)
 					*objname = list_make1(pstrdup(NameStr(langForm->lanname)));
 				ReleaseSysCache(langTup);
@@ -5155,10 +5154,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 opcForm->opcmethod);
 				amForm = (Form_pg_am) GETSTRUCT(amTup);
 
-				appendStringInfo(&buffer, "%s USING %s",
-								 quote_qualified_identifier(schema,
-															NameStr(opcForm->opcname)),
-								 quote_identifier(NameStr(amForm->amname)));
+				appendStringInfoQualifiedIdentifier(&buffer, NULL,
+													schema, NameStr(opcForm->opcname),
+													" USING ");
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(amForm->amname), NULL);
 				if (objname)
 					*objname = list_make3(pstrdup(NameStr(amForm->amname)),
 										  schema,
@@ -5186,7 +5186,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(amname));
+				appendStringInfoIdentifier(&buffer, NULL, amname, NULL);
 				if (objname)
 					*objname = list_make1(amname);
 			}
@@ -5339,8 +5339,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(rule->rulename)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(rule->rulename), " on ");
 				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
@@ -5372,8 +5371,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(trig->tgname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(trig->tgname), " on ");
 				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
@@ -5544,8 +5542,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				if (objname)
 					*objname = list_make1(username);
-				appendStringInfoString(&buffer,
-									   quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, NULL, username, NULL);
 				break;
 			}
 
@@ -5606,8 +5603,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(datname);
-				appendStringInfoString(&buffer,
-									   quote_identifier(datname));
+				appendStringInfoIdentifier(&buffer, NULL, datname, NULL);
 				break;
 			}
 
@@ -5625,8 +5621,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(tblspace);
-				appendStringInfoString(&buffer,
-									   quote_identifier(tblspace));
+				appendStringInfoIdentifier(&buffer, NULL, tblspace, NULL);
 				break;
 			}
 
@@ -5638,7 +5633,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 													missing_ok);
 				if (fdw)
 				{
-					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					appendStringInfoIdentifier(&buffer, NULL, fdw->fdwname, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(fdw->fdwname));
 				}
@@ -5653,8 +5648,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 											   missing_ok);
 				if (srv)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(srv->servername));
+					appendStringInfoIdentifier(&buffer, NULL, srv->servername, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(srv->servername));
 				}
@@ -5695,9 +5689,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					*objargs = list_make1(pstrdup(srv->servername));
 				}
 
-				appendStringInfo(&buffer, "%s on server %s",
-								 quote_identifier(usename),
-								 srv->servername);
+				appendStringInfoIdentifier(&buffer, NULL, usename, " on server ");
+				appendStringInfoString(&buffer, srv->servername);
 				break;
 			}
 
@@ -5800,7 +5793,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(extname));
+				appendStringInfoIdentifier(&buffer, NULL, extname, NULL);
 				if (objname)
 					*objname = list_make1(extname);
 				break;
@@ -5823,7 +5816,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				evtname = pstrdup(NameStr(trigForm->evtname));
-				appendStringInfoString(&buffer, quote_identifier(evtname));
+				appendStringInfoIdentifier(&buffer, NULL, evtname, NULL);
 				if (objname)
 					*objname = list_make1(evtname);
 				ReleaseSysCache(tup);
@@ -5878,8 +5871,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(policy->polname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(policy->polname), " on ");
 				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
@@ -5895,8 +5887,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				pubname = get_publication_name(object->objectId, missing_ok);
 				if (pubname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(pubname));
+					appendStringInfoIdentifier(&buffer, NULL, pubname, NULL);
 					if (objname)
 						*objname = list_make1(pubname);
 				}
@@ -5963,8 +5954,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				subname = get_subscription_name(object->objectId, missing_ok);
 				if (subname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(subname));
+					appendStringInfoIdentifier(&buffer, NULL, subname, NULL);
 					if (objname)
 						*objname = list_make1(subname);
 				}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index d676930aca2..5b1c6547519 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -825,9 +825,11 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 
 			/* Find the remaining history */
 			initStringInfo(&replacementsbuf);
-			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendStringInfoIdentifier(&replacementsbuf,
+									   "(SELECT pg_catalog.range_agg(r) FROM (SELECT y.",
+									   RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]),
+									   " r FROM ");
 			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
@@ -1821,8 +1823,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
 	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
 
-	/* strcpy(pkattname, "pk."); */
-	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b212a28d9be..a83cac7f03a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -979,18 +979,17 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		/* tgattr is first var-width field, so OK to access directly */
 		if (trigrec->tgattr.dim1 > 0)
 		{
-			int			i;
+			const char *sep = "";
 
 			appendStringInfoString(&buf, " OF ");
-			for (i = 0; i < trigrec->tgattr.dim1; i++)
+			for (int i = 0; i < trigrec->tgattr.dim1; i++)
 			{
 				char	   *attname;
 
-				if (i > 0)
-					appendStringInfoString(&buf, ", ");
 				attname = get_attname(trigrec->tgrelid,
 									  trigrec->tgattr.values[i], false);
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, sep, attname, NULL);
+				sep = ", ";
 			}
 		}
 	}
@@ -1042,11 +1041,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	{
 		appendStringInfoString(&buf, "REFERENCING ");
 		if (tgoldtable != NULL)
-			appendStringInfo(&buf, "OLD TABLE AS %s ",
-							 quote_identifier(tgoldtable));
+			appendStringInfoIdentifier(&buf, "OLD TABLE AS ", tgoldtable, " ");
 		if (tgnewtable != NULL)
-			appendStringInfo(&buf, "NEW TABLE AS %s ",
-							 quote_identifier(tgnewtable));
+			appendStringInfoIdentifier(&buf, "NEW TABLE AS ", tgnewtable, " ");
 	}
 
 	if (TRIGGER_FOR_ROW(trigrec->tgtype))
@@ -1387,8 +1384,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 							 generate_qualified_relation_name(indrelid),
 							 quote_identifier(NameStr(amrec->amname)));
 		else					/* currently, must be EXCLUDE constraint */
-			appendStringInfo(&buf, "EXCLUDE USING %s (",
-							 quote_identifier(NameStr(amrec->amname)));
+			appendStringInfoIdentifier(&buf, "EXCLUDE USING ", NameStr(amrec->amname), " (");
 	}
 
 	/*
@@ -1426,7 +1422,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 
 			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(indrelid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -1536,8 +1532,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			{
 				if (isConstraint)
 					appendStringInfoString(&buf, " USING INDEX");
-				appendStringInfo(&buf, " TABLESPACE %s",
-								 quote_identifier(get_tablespace_name(tblspc)));
+				appendStringInfoIdentifier(&buf, " TABLESPACE ",
+										   get_tablespace_name(tblspc), NULL);
 			}
 		}
 
@@ -1794,7 +1790,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
 
 		attname = get_attname(statextrec->stxrelid, attnum, false);
 
-		appendStringInfoString(&buf, quote_identifier(attname));
+		appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 	}
 
 	context = deparse_context_for(get_relation_name(statextrec->stxrelid),
@@ -2038,7 +2034,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			int32		keycoltypmod;
 
 			attname = get_attname(relid, attnum, false);
-			appendStringInfoString(&buf, quote_identifier(attname));
+			appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -2246,17 +2242,19 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			 * we might need to let callers specify whether to put ONLY in the
 			 * command.
 			 */
-			appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
-							 generate_qualified_relation_name(conForm->conrelid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER TABLE %s ",
+							 generate_qualified_relation_name(conForm->conrelid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 		else
 		{
 			/* Must be a domain constraint */
 			Assert(OidIsValid(conForm->contypid));
-			appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
-							 generate_qualified_type_name(conForm->contypid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER DOMAIN %s ",
+							 generate_qualified_type_name(conForm->contypid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 	}
 
@@ -2423,6 +2421,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					Datum	   *keys;
 					int			nKeys;
 					int			j;
+					const char *sep = "";
 
 					appendStringInfoString(&buf, " INCLUDE (");
 
@@ -2438,9 +2437,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 						colName = get_attname(conForm->conrelid,
 											  DatumGetInt16(keys[j]), false);
-						if (j > keyatts)
-							appendStringInfoString(&buf, ", ");
-						appendStringInfoString(&buf, quote_identifier(colName));
+						appendStringInfoIdentifier(&buf, sep, colName, NULL);
+						sep = ", ";
 					}
 
 					appendStringInfoChar(&buf, ')');
@@ -2467,8 +2465,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					 */
 					tblspc = get_rel_tablespace(indexId);
 					if (OidIsValid(tblspc))
-						appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
-										 quote_identifier(get_tablespace_name(tblspc)));
+						appendStringInfoIdentifier(&buf, " USING INDEX TABLESPACE ",
+												   get_tablespace_name(tblspc), NULL);
 				}
 
 				break;
@@ -2529,9 +2527,9 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 					attnum = extractNotNullColumn(tup);
 
-					appendStringInfo(&buf, "NOT NULL %s",
-									 quote_identifier(get_attname(conForm->conrelid,
-																  attnum, false)));
+					appendStringInfoIdentifier(&buf, "NOT NULL ",
+											   get_attname(conForm->conrelid,
+														   attnum, false), NULL);
 					if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
 						appendStringInfoString(&buf, " NO INHERIT");
 				}
@@ -2635,11 +2633,14 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
-			appendStringInfoString(buf, quote_identifier(colName));
+			appendStringInfoIdentifier(buf, NULL, colName, NULL);
 		else
-			appendStringInfo(buf, ", %s%s",
-							 (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
-							 quote_identifier(colName));
+		{
+			appendStringInfoString(buf, ", ");
+			appendStringInfoIdentifier(buf,
+									   (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
+									   colName, NULL);
+		}
 	}
 
 	return nKeys;
@@ -2975,8 +2976,8 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 
 	print_function_trftypes(&buf, proctup);
 
-	appendStringInfo(&buf, " LANGUAGE %s\n",
-					 quote_identifier(get_language_name(proc->prolang, false)));
+	appendStringInfoIdentifier(&buf, " LANGUAGE ",
+							   get_language_name(proc->prolang, false), "\n");
 
 	/* Emit some miscellaneous options on one line */
 	oldlen = buf.len;
@@ -3075,8 +3076,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 					continue;
 				*pos++ = '\0';
 
-				appendStringInfo(&buf, " SET %s TO ",
-								 quote_identifier(configitem));
+				appendStringInfoIdentifier(&buf, " SET ", configitem, " TO ");
 
 				/*
 				 * Variables that are marked GUC_LIST_QUOTE were already fully
@@ -3418,7 +3418,7 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
 
 		appendStringInfoString(buf, modename);
 		if (argname && argname[0])
-			appendStringInfo(buf, "%s ", quote_identifier(argname));
+			appendStringInfoIdentifier(buf, NULL, argname, " ");
 		appendStringInfoString(buf, format_type_be(argtype));
 		if (print_defaults && isinput && inputargno > nlackdefaults)
 		{
@@ -5402,8 +5402,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 	/*
 	 * Build the rules definition text
 	 */
-	appendStringInfo(buf, "CREATE RULE %s AS",
-					 quote_identifier(rulename));
+	appendStringInfoIdentifier(buf, "CREATE RULE ", rulename, " AS");
 
 	if (prettyFlags & PRETTYFLAG_INDENT)
 		appendStringInfoString(buf, "\n    ON ");
@@ -5791,21 +5790,18 @@ get_with_clause(Query *query, deparse_context *context)
 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
 
 		appendStringInfoString(buf, sep);
-		appendStringInfoString(buf, quote_identifier(cte->ctename));
+		appendStringInfoIdentifier(buf, NULL, cte->ctename, NULL);
 		if (cte->aliascolnames)
 		{
-			bool		first = true;
 			ListCell   *col;
+			const char *colsep = "";
 
 			appendStringInfoChar(buf, '(');
 			foreach(col, cte->aliascolnames)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(col))));
+				appendStringInfoIdentifier(buf, colsep,
+										   strVal(lfirst(col)), NULL);
+				colsep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 		}
@@ -5834,43 +5830,35 @@ get_with_clause(Query *query, deparse_context *context)
 
 		if (cte->search_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *colsep = "";
 
 			appendStringInfo(buf, " SEARCH %s FIRST BY ",
 							 cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
 
 			foreach(lc, cte->search_clause->search_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, colsep, strVal(lfirst(lc)), NULL);
+				colsep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->search_clause->search_seq_column, NULL);
 		}
 
 		if (cte->cycle_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *sep = "";
 
 			appendStringInfoString(buf, " CYCLE ");
 
 			foreach(lc, cte->cycle_clause->cycle_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, sep, strVal(lfirst(lc)), NULL);
+				sep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->cycle_clause->cycle_mark_column, NULL);
 
 			{
 				Const	   *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
@@ -5886,7 +5874,7 @@ get_with_clause(Query *query, deparse_context *context)
 				}
 			}
 
-			appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
+			appendStringInfoIdentifier(buf, " USING ", cte->cycle_clause->cycle_path_column, NULL);
 		}
 
 		sep = ", ";
@@ -6022,9 +6010,7 @@ get_select_query_def(Query *query, deparse_context *context)
 					break;
 			}
 
-			appendStringInfo(buf, " OF %s",
-							 quote_identifier(get_rtable_name(rc->rti,
-															  context)));
+			appendStringInfoIdentifier(buf, " OF ", get_rtable_name(rc->rti, context), NULL);
 			if (rc->waitPolicy == LockWaitError)
 				appendStringInfoString(buf, " NOWAIT");
 			else if (rc->waitPolicy == LockWaitSkip)
@@ -6317,7 +6303,7 @@ get_target_list(List *targetList, deparse_context *context)
 		if (colname)			/* resname could be NULL */
 		{
 			if (attname == NULL || strcmp(attname, colname) != 0)
-				appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
+				appendStringInfoIdentifier(&targetbuf, " AS ", colname, NULL);
 		}
 
 		/* Restore context's output buffer */
@@ -6391,19 +6377,16 @@ get_returning_clause(Query *query, deparse_context *context)
 		/* Add WITH (OLD/NEW) options, if they're not the defaults */
 		if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
 		{
-			appendStringInfo(buf, " WITH (OLD AS %s",
-							 quote_identifier(query->returningOldAlias));
+			appendStringInfoIdentifier(buf, " WITH (OLD AS ", query->returningOldAlias, NULL);
 			have_with = true;
 		}
 		if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
 		{
 			if (have_with)
-				appendStringInfo(buf, ", NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, ", NEW AS ", query->returningNewAlias, NULL);
 			else
 			{
-				appendStringInfo(buf, " WITH (NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, " WITH (NEW AS ", query->returningNewAlias, NULL);
 				have_with = true;
 			}
 		}
@@ -6771,7 +6754,7 @@ get_rule_windowclause(Query *query, deparse_context *context)
 		else
 			appendStringInfoString(buf, sep);
 
-		appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
+		appendStringInfoIdentifier(buf, NULL, wc->name, " AS ");
 
 		get_rule_windowspec(wc, query->targetList, context);
 
@@ -6794,7 +6777,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 	appendStringInfoChar(buf, '(');
 	if (wc->refname)
 	{
-		appendStringInfoString(buf, quote_identifier(wc->refname));
+		appendStringInfoIdentifier(buf, NULL, wc->refname, NULL);
 		needspace = true;
 	}
 	/* partition clauses are always inherited, so only print if no refname */
@@ -7021,10 +7004,8 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL,
+								   get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7117,8 +7098,7 @@ get_insert_query_def(Query *query, deparse_context *context)
 			if (!constraint)
 				elog(ERROR, "cache lookup failed for constraint %u",
 					 confl->constraint);
-			appendStringInfo(buf, " ON CONSTRAINT %s",
-							 quote_identifier(constraint));
+			appendStringInfoIdentifier(buf, " ON CONSTRAINT ", constraint, NULL);
 		}
 
 		if (confl->action == ONCONFLICT_NOTHING)
@@ -7320,10 +7300,7 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL, get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7511,10 +7488,10 @@ get_merge_query_def(Query *query, deparse_context *context)
 				appendStringInfoString(buf, sep);
 				sep = ", ";
 
-				appendStringInfoString(buf,
-									   quote_identifier(get_attname(rte->relid,
-																	tle->resno,
-																	false)));
+				appendStringInfoIdentifier(buf, NULL,
+										   get_attname(rte->relid,
+													   tle->resno,
+													   false), NULL);
 				strippedexprs = lappend(strippedexprs,
 										processIndirection((Node *) tle->expr,
 														   context));
@@ -7573,8 +7550,8 @@ get_utility_query_def(Query *query, deparse_context *context)
 
 		appendContextKeyword(context, "",
 							 0, PRETTYINDENT_STD, 1);
-		appendStringInfo(buf, "NOTIFY %s",
-						 quote_identifier(stmt->conditionname));
+		appendStringInfoIdentifier(buf, "NOTIFY ",
+								   stmt->conditionname, NULL);
 		if (stmt->payload)
 		{
 			appendStringInfoString(buf, ", ");
@@ -7865,11 +7842,11 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 	if (refname && need_prefix)
 	{
-		appendStringInfoString(buf, quote_identifier(refname));
+		appendStringInfoIdentifier(buf, NULL, refname, NULL);
 		appendStringInfoChar(buf, '.');
 	}
 	if (attname)
-		appendStringInfoString(buf, quote_identifier(attname));
+		appendStringInfoIdentifier(buf, NULL, attname, NULL);
 	else
 	{
 		appendStringInfoChar(buf, '*');
@@ -8806,11 +8783,10 @@ get_parameter(Param *param, deparse_context *context)
 				}
 				if (should_qualify)
 				{
-					appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
-					appendStringInfoChar(context->buf, '.');
+					appendStringInfoIdentifier(context->buf, NULL, dpns->funcname, ".");
 				}
 
-				appendStringInfoString(context->buf, quote_identifier(argname));
+				appendStringInfoIdentifier(context->buf, NULL, argname, NULL);
 				return;
 			}
 		}
@@ -9391,7 +9367,7 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				NamedArgExpr *na = (NamedArgExpr *) node;
 
-				appendStringInfo(buf, "%s => ", quote_identifier(na->name));
+				appendStringInfoIdentifier(buf, NULL, na->name, " => ");
 				get_rule_expr((Node *) na->arg, context, showimplicit);
 			}
 			break;
@@ -9679,7 +9655,7 @@ get_rule_expr(Node *node, deparse_context *context,
 				 */
 				fieldname = get_name_for_var_field((Var *) arg, fno,
 												   0, context);
-				appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+				appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 			}
 			break;
 
@@ -10131,8 +10107,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				}
 				if (xexpr->name)
 				{
-					appendStringInfo(buf, "NAME %s",
-									 quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
+					appendStringInfoIdentifier(buf, "NAME ",
+											   map_xml_name_to_sql_identifier(xexpr->name), NULL);
 					needcomma = true;
 				}
 				if (xexpr->named_args)
@@ -10152,8 +10128,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						if (needcomma)
 							appendStringInfoString(buf, ", ");
 						get_rule_expr((Node *) e, context, true);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(map_xml_name_to_sql_identifier(argname)));
+						appendStringInfoIdentifier(buf, " AS ",
+												   map_xml_name_to_sql_identifier(argname), NULL);
 						needcomma = true;
 					}
 					if (xexpr->op != IS_XMLFOREST)
@@ -10369,8 +10345,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
 
 				if (cexpr->cursor_name)
-					appendStringInfo(buf, "CURRENT OF %s",
-									 quote_identifier(cexpr->cursor_name));
+					appendStringInfoIdentifier(buf, "CURRENT OF ",
+											   cexpr->cursor_name, NULL);
 				else
 					appendStringInfo(buf, "CURRENT OF $%d",
 									 cexpr->cursor_param);
@@ -10604,8 +10580,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						needcomma = true;
 
 						get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(lfirst_node(String, lc1)->sval));
+						appendStringInfoIdentifier(buf, " AS ",
+												   lfirst_node(String, lc1)->sval, NULL);
 					}
 				}
 
@@ -11135,7 +11111,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 			if (wc->winref == wfunc->winref)
 			{
 				if (wc->name)
-					appendStringInfoString(buf, quote_identifier(wc->name));
+					appendStringInfoIdentifier(buf, NULL, wc->name, NULL);
 				else
 					get_rule_windowspec(wc, context->targetList, context);
 				break;
@@ -11161,7 +11137,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 
 				if (wagg->winref == wfunc->winref)
 				{
-					appendStringInfoString(buf, quote_identifier(wagg->winname));
+					appendStringInfoIdentifier(buf, NULL, wagg->winname, NULL);
 					break;
 				}
 			}
@@ -12001,8 +11977,8 @@ get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
 			if (ns_node != NULL)
 			{
 				get_rule_expr(expr, context, showimplicit);
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(strVal(ns_node)));
+				appendStringInfoIdentifier(buf, " AS ",
+										   strVal(ns_node), NULL);
 			}
 			else
 			{
@@ -12088,7 +12064,7 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
 		appendStringInfoChar(context->buf, ' ');
 		appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
 		get_const_expr(scan->path->value, context, -1);
-		appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
+		appendStringInfoIdentifier(context->buf, " AS ", scan->path->name, NULL);
 		get_json_table_columns(tf, scan, context, showimplicit);
 	}
 	else if (IsA(plan, JsonTableSiblingJoin))
@@ -12231,7 +12207,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 
 	get_const_expr(root->path->value, context, -1);
 
-	appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
+	appendStringInfoIdentifier(buf, " AS ", root->path->name, NULL);
 
 	if (jexpr->passing_values)
 	{
@@ -12255,9 +12231,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 			appendContextKeyword(context, "", 0, 0, 0);
 
 			get_rule_expr((Node *) lfirst(lc2), context, false);
-			appendStringInfo(buf, " AS %s",
-							 quote_identifier((lfirst_node(String, lc1))->sval)
-				);
+			appendStringInfoIdentifier(buf, " AS ", lfirst_node(String, lc1)->sval, NULL);
 		}
 
 		if (PRETTY_INDENT(context))
@@ -12533,7 +12507,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 				appendStringInfoChar(buf, ')');
 				break;
 			case RTE_CTE:
-				appendStringInfoString(buf, quote_identifier(rte->ctename));
+				appendStringInfoIdentifier(buf, NULL, rte->ctename, NULL);
 				break;
 			default:
 				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
@@ -12620,7 +12594,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		if (j->usingClause)
 		{
 			ListCell   *lc;
-			bool		first = true;
+			const char *sep = "";
 
 			appendStringInfoString(buf, " USING (");
 			/* Use the assigned names, not what's in usingClause */
@@ -12628,17 +12602,13 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			{
 				char	   *colname = (char *) lfirst(lc);
 
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf, quote_identifier(colname));
+				appendStringInfoIdentifier(buf, sep, colname, NULL);
+				sep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 
 			if (j->join_using_alias)
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(j->join_using_alias->aliasname));
+				appendStringInfoIdentifier(buf, " AS ", j->join_using_alias->aliasname, NULL);
 		}
 		else if (j->quals)
 		{
@@ -12668,9 +12638,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			 * subtleties we don't want.  However, we might print a different
 			 * alias name than was there originally.
 			 */
-			appendStringInfo(buf, " %s",
-							 quote_identifier(get_rtable_name(j->rtindex,
-															  context)));
+			appendStringInfoIdentifier(buf, " ",
+									   get_rtable_name(j->rtindex,
+													   context), NULL);
 			get_column_alias_list(colinfo, context);
 		}
 	}
@@ -12745,9 +12715,9 @@ get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
 	}
 
 	if (printalias)
-		appendStringInfo(context->buf, "%s%s",
-						 use_as ? " AS " : " ",
-						 quote_identifier(refname));
+		appendStringInfoIdentifier(context->buf,
+								   use_as ? " AS " : " ",
+								   refname, NULL);
 }
 
 /*
@@ -12777,7 +12747,7 @@ get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
 		}
 		else
 			appendStringInfoString(buf, ", ");
-		appendStringInfoString(buf, quote_identifier(colname));
+		appendStringInfoIdentifier(buf, NULL, colname, NULL);
 	}
 	if (!first)
 		appendStringInfoChar(buf, ')');
@@ -12910,13 +12880,11 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
 		/* Okay, we need the opclass name.  Do we need to qualify it? */
 		opcname = NameStr(opcrec->opcname);
 		if (OpclassIsVisible(opclass))
-			appendStringInfo(buf, " %s", quote_identifier(opcname));
+			appendStringInfoIdentifier(buf, " ", opcname, NULL);
 		else
 		{
 			nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
-			appendStringInfo(buf, " %s.%s",
-							 quote_identifier(nspname),
-							 quote_identifier(opcname));
+			appendStringInfoQualifiedIdentifier(buf, NULL, nspname, opcname, NULL);
 		}
 	}
 	ReleaseSysCache(ht_opc);
@@ -12980,7 +12948,7 @@ processIndirection(Node *node, deparse_context *context)
 			Assert(list_length(fstore->fieldnums) == 1);
 			fieldname = get_attname(typrelid,
 									linitial_int(fstore->fieldnums), false);
-			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+			appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 
 			/*
 			 * We ignore arg since it should be an uninteresting reference to
@@ -13535,7 +13503,7 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
 	else
 	{
 		nspname = get_namespace_name_or_temp(operform->oprnamespace);
-		appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
+		appendStringInfoIdentifier(&buf, "OPERATOR(", nspname, ".");
 	}
 
 	appendStringInfoString(&buf, oprname);
@@ -13641,8 +13609,7 @@ add_cast_to(StringInfo buf, Oid typid)
 	typname = NameStr(typform->typname);
 	nspname = get_namespace_name_or_temp(typform->typnamespace);
 
-	appendStringInfo(buf, "::%s.%s",
-					 quote_identifier(nspname), quote_identifier(typname));
+	appendStringInfoQualifiedIdentifier(buf, "::", nspname, typname, NULL);
 
 	ReleaseSysCache(typetup);
 }
@@ -13739,12 +13706,12 @@ get_reloptions(StringInfo buf, Datum reloptions)
 {
 	Datum	   *options;
 	int			noptions;
-	int			i;
+	const char *sep = "";
 
 	deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
 							  &options, NULL, &noptions);
 
-	for (i = 0; i < noptions; i++)
+	for (int i = 0; i < noptions; i++)
 	{
 		char	   *option = TextDatumGetCString(options[i]);
 		char	   *name;
@@ -13765,9 +13732,8 @@ get_reloptions(StringInfo buf, Datum reloptions)
 		else
 			value = "";
 
-		if (i > 0)
-			appendStringInfoString(buf, ", ");
-		appendStringInfo(buf, "%s=", quote_identifier(name));
+		appendStringInfoIdentifier(buf, sep, name, "=");
+		sep = ", ";
 
 		/*
 		 * In general we need to quote the value; but to avoid unnecessary
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v3-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch (21.9K, 6-v3-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch)
  download | inline diff:
From 8043be01f1883ff419c1a732be73cbe05021dbf3 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Mon, 17 Nov 2025 12:09:12 +0800
Subject: [PATCH v3 2/4] Use appendStringInfoIdentifier() throughout
 ri_triggers.c

Replace most uses of quoteOneName() and manual stack buffers in
ri_triggers.c with appendStringInfoIdentifier() and related infrastructure.

This simplifies the construction of SQL queries generated by RI triggers and
eliminates the need for MAX_QUOTED_NAME_LEN / stack-allocated intermediate
buffers.  It also removes several code paths where identifiers were quoted
manually, making the quoting rules consistent with ruleutils.c and the GUC
quote_all_identifiers.

This commit also adjusts generate_operator_clause() to support prefixed
arguments and identifier quoting directly, reducing the number of places
where callers need to inject string concatenation logic.

No user-visible behavior change is intended; the generated SQL should be
equivalent to the previous version.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/commands/matview.c      |   4 +-
 src/backend/utils/adt/ri_triggers.c | 188 +++++++++++++---------------
 src/backend/utils/adt/ruleutils.c   |  31 +++--
 src/include/utils/builtins.h        |   4 +-
 4 files changed, 115 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index ef7c0d624f1..f1ad5a8e059 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -797,9 +797,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 													 NameStr(attr->attname));
 
 				generate_operator_clause(&querybuf,
-										 leftop, attrtype,
+										 NULL, leftop, attrtype, false,
 										 op,
-										 rightop, attrtype);
+										 NULL, rightop, attrtype, false);
 
 				foundUniqueIndex = true;
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 9e13f526994..3cc4dc2b7c8 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -196,9 +196,9 @@ static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
-							const char *leftop, Oid leftoptype,
+							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 							Oid opoid,
-							const char *rightop, Oid rightoptype);
+							const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
 static int	ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
@@ -358,7 +358,7 @@ RI_FKey_check(TriggerData *trigdata)
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -393,11 +393,11 @@ RI_FKey_check(TriggerData *trigdata)
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -410,13 +410,12 @@ RI_FKey_check(TriggerData *trigdata)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pf_eq_oprs[i],
-							paramname, fk_type);
+							NULL, paramname, fk_type, false);
 			querysep = "AND";
 			queryoids[i] = fk_type;
 		}
@@ -428,9 +427,9 @@ RI_FKey_check(TriggerData *trigdata)
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -528,7 +527,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *pk_only;
@@ -563,11 +562,11 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -579,13 +578,12 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		{
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pp_eq_oprs[i],
-							paramname, pk_type);
+							NULL, paramname, pk_type, false);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -597,9 +595,9 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -757,7 +755,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
@@ -784,13 +782,12 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -828,7 +825,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			char	   *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 				"" : "ONLY ";
 
-			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
 			sprintf(paramname, "$%d", riinfo->nkeys);
 
 			appendStringInfoString(&querybuf, " AND NOT coalesce(");
@@ -837,9 +834,9 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&intersectbuf);
 			appendStringInfoChar(&intersectbuf, '(');
 			ri_GenerateQual(&intersectbuf, "",
-							attname, fk_period_type,
+							NULL, attname, fk_period_type, true,
 							riinfo->period_intersect_oper,
-							paramname, pk_period_type);
+							NULL, paramname, pk_period_type, false);
 			appendStringInfoChar(&intersectbuf, ')');
 
 			/* Find the remaining history */
@@ -857,22 +854,21 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			{
 				Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-				quoteOneName(attname,
-							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 				sprintf(paramname, "$%d", i + 1);
 				ri_GenerateQual(&replacementsbuf, querysep,
-								paramname, pk_type,
+								NULL, paramname, pk_type, false,
 								riinfo->pp_eq_oprs[i],
-								attname, pk_type);
+								NULL, attname, pk_type, true);
 				querysep = "AND";
 				queryoids[i] = pk_type;
 			}
 			appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
 
 			ri_GenerateQual(&querybuf, "",
-							intersectbuf.data, fk_period_type,
+							NULL, intersectbuf.data, fk_period_type, false,
 							riinfo->agged_period_contained_by_oper,
-							replacementsbuf.data, ANYMULTIRANGEOID);
+							NULL, replacementsbuf.data, ANYMULTIRANGEOID, false);
 			/* end of coalesce: */
 			appendStringInfoString(&querybuf, ", false)");
 		}
@@ -944,7 +940,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -969,13 +965,12 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1050,7 +1045,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1081,16 +1076,15 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = $%d",
-							 querysep, attname, i + 1);
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfo(&querybuf, "$%d", i + 1);
 			sprintf(paramname, "$%d", j + 1);
 			ri_GenerateQual(&qualbuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = ",";
 			qualsep = "AND";
 			queryoids[i] = pk_type;
@@ -1239,7 +1233,7 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1297,11 +1291,10 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		querysep = "";
 		for (int i = 0; i < num_cols_to_set; i++)
 		{
-			quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = %s",
-							 querysep, attname,
-							 is_set_null ? "NULL" : "DEFAULT");
+			attname = RIAttName(fk_rel, set_cols[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfoString(&querybuf, is_set_null ? "NULL" : "DEFAULT");
 			querysep = ",";
 		}
 
@@ -1314,14 +1307,13 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			qualsep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1519,8 +1511,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	StringInfoData querybuf;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *pk_perminfo;
 	RTEPermissionInfo *fk_perminfo;
@@ -1615,9 +1607,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1631,8 +1623,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
 					 fk_only, fkrelname, pk_only, pkrelname);
 
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1641,14 +1631,12 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1658,16 +1646,15 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * It's sufficient to test any one pk attribute for null to detect a join
 	 * failure.
 	 */
-	quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
-	appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
+	pkattname = RIAttName(pk_rel, riinfo->pk_attnums[0]);
+	appendStringInfoIdentifier(&querybuf, ") WHERE pk.", pkattname, " IS NULL AND (");
 
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -1814,8 +1801,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char	   *constraintDef;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	const char *sep;
 	const char *fk_only;
 	int			save_nestlevel;
@@ -1852,9 +1839,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1865,8 +1852,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendStringInfo(&querybuf,
 					 " FROM %s%s fk JOIN %s pk ON",
 					 fk_only, fkrelname, pkrelname);
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
+	/* strcpy(pkattname, "pk."); */
+	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1875,14 +1862,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1903,10 +1888,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -2064,13 +2048,15 @@ quoteRelationName(char *buffer, Relation rel)
 static void
 ri_GenerateQual(StringInfo buf,
 				const char *sep,
-				const char *leftop, Oid leftoptype,
+				const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 				Oid opoid,
-				const char *rightop, Oid rightoptype)
+				const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	appendStringInfo(buf, " %s ", sep);
-	generate_operator_clause(buf, leftop, leftoptype, opoid,
-							 rightop, rightoptype);
+	generate_operator_clause(buf,
+							 leftopprefix, leftop, leftoptype, quoteleftop,
+							 opoid,
+							 rightopprefix, rightop, rightoptype, quoterightop);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1d767deb6c4..b212a28d9be 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13570,9 +13570,9 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
  */
 void
 generate_operator_clause(StringInfo buf,
-						 const char *leftop, Oid leftoptype,
+						 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 						 Oid opoid,
-						 const char *rightop, Oid rightoptype)
+						 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	HeapTuple	opertup;
 	Form_pg_operator operform;
@@ -13585,15 +13585,32 @@ generate_operator_clause(StringInfo buf,
 	operform = (Form_pg_operator) GETSTRUCT(opertup);
 	Assert(operform->oprkind == 'b');
 	oprname = NameStr(operform->oprname);
-
 	nspname = get_namespace_name(operform->oprnamespace);
 
-	appendStringInfoString(buf, leftop);
+	if (quoteleftop)
+		appendStringInfoIdentifier(buf, leftopprefix, leftop, NULL);
+	else
+	{
+		if (leftopprefix)
+			appendStringInfoString(buf, leftopprefix);
+		appendStringInfoString(buf, leftop);
+	}
+
 	if (leftoptype != operform->oprleft)
 		add_cast_to(buf, operform->oprleft);
-	appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
-	appendStringInfoString(buf, oprname);
-	appendStringInfo(buf, ") %s", rightop);
+
+	appendStringInfoIdentifier(buf, " OPERATOR(", nspname, ".");
+	appendStringInfo(buf, "%s) ", oprname);
+
+	if (quoterightop)
+		appendStringInfoIdentifier(buf, rightopprefix, rightop, NULL);
+	else
+	{
+		if (rightopprefix)
+			appendStringInfoString(buf, rightopprefix);
+		appendStringInfoString(buf, rightop);
+	}
+
 	if (rightoptype != operform->oprright)
 		add_cast_to(buf, operform->oprright);
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ff4ba7265c2..4bd04e1a57a 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -81,9 +81,9 @@ extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 										const char *ident);
 extern void generate_operator_clause(StringInfo buf,
-									 const char *leftop, Oid leftoptype,
+									 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 									 Oid opoid,
-									 const char *rightop, Oid rightoptype);
+									 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
 extern void appendStringInfoQualifiedIdentifier(StringInfo str,
 												const char *prefix,
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

*  Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2025-11-21 10:03  Chao Li <[email protected]>
  parent: Chao Li <[email protected]>
  0 siblings, 1 reply; 8+ messages in thread

From: Chao Li @ 2025-11-21 10:03 UTC (permalink / raw)
  To: Álvaro Herrera <[email protected]>; +Cc: Postgres hackers <[email protected]>

Fixed a compile warning in v4.

Best regards,
Chao Li (Evan)
---------------------
HighGo Software Co., Ltd.
https://www.highgo.com/


On Fri, Nov 21, 2025 at 1:52 PM Chao Li <[email protected]> wrote:

>
> On Thu, Nov 20, 2025 at 8:28 PM Álvaro Herrera <[email protected]>
> wrote:
>
>> Hi,
>>
>> > -                appendStringInfo(&buffer, _("text search configuration
>> %s"),
>> > -                                 quote_qualified_identifier(nspname,
>> > -
>> NameStr(cfgForm->cfgname)));
>> > +                appendStringInfoQualifiedIdentifier(&buffer,
>> > +                                                    _("text search
>> configuration "),
>> > +                                                    nspname,
>> NameStr(cfgForm->cfgname), NULL);
>> >                  ReleaseSysCache(tup);
>> >                  break;
>> >              }
>>
>> This doesn't work from a i18n point of view.  In the original
>> formulation, the translator is free to place the %s wherever it suits
>> the language.  In the new one there's no such freedom: the name will be
>> appended at the end.  Some existing translations:
>>
>> ko.po:msgid "text search configuration %s"
>> ko.po-msgstr "%s 전문 검색 구성"
>>
>> tr.po:msgid "text search configuration %s"
>> tr.po-msgstr "%s metin arama yapılandırması"
>>
>
> Thanks for the feedback. I reverted that piece of change in v3.
>
> Best regards,
> Chao Li (Evan)
> ---------------------
> HighGo Software Co., Ltd.
> https://www.highgo.com/
>


Attachments:

  [application/octet-stream] v4-0004-Use-appendStringInfoIdentifier-in-more-places.patch (35.7K, 3-v4-0004-Use-appendStringInfoIdentifier-in-more-places.patch)
  download | inline diff:
From 07e5607fa8185361a4da002d15a1a328a3c199fd Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 19 Nov 2025 15:55:23 +0800
Subject: [PATCH v4 4/4] Use appendStringInfoIdentifier() in more places.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/namespace.c     |   6 +-
 src/backend/catalog/objectaddress.c |  64 +++----
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 260 ++++++++++++----------------
 4 files changed, 147 insertions(+), 191 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index d23474da4fb..7c3b0bb7853 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3699,14 +3699,14 @@ NameListToQuotedString(const List *names)
 {
 	StringInfoData string;
 	ListCell   *l;
+	const char *sep = "";
 
 	initStringInfo(&string);
 
 	foreach(l, names)
 	{
-		if (l != list_head(names))
-			appendStringInfoChar(&string, '.');
-		appendStringInfoString(&string, quote_identifier(strVal(lfirst(l))));
+		appendStringInfoIdentifier(&string, sep, strVal(lfirst(l)), NULL);
+		sep = ".";
 	}
 
 	return string.data;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 70e05059f9f..fa8762db10d 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -5004,8 +5004,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (OidIsValid(con->conrelid))
 				{
-					appendStringInfo(&buffer, "%s on ",
-									 quote_identifier(NameStr(con->conname)));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
 					getRelationIdentity(&buffer, con->conrelid, objname,
 										false);
 					if (objname)
@@ -5020,10 +5019,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 					domain.objectId = con->contypid;
 					domain.objectSubId = 0;
 
-					appendStringInfo(&buffer, "%s on %s",
-									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname,
-															objargs, false));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
+					appendStringInfoString(&buffer,
+										   getObjectIdentityParts(&domain, objname,
+																  objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -5096,8 +5095,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
-				appendStringInfoString(&buffer,
-									   quote_identifier(NameStr(langForm->lanname)));
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(langForm->lanname), NULL);
 				if (objname)
 					*objname = list_make1(pstrdup(NameStr(langForm->lanname)));
 				ReleaseSysCache(langTup);
@@ -5155,10 +5154,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 opcForm->opcmethod);
 				amForm = (Form_pg_am) GETSTRUCT(amTup);
 
-				appendStringInfo(&buffer, "%s USING %s",
-								 quote_qualified_identifier(schema,
-															NameStr(opcForm->opcname)),
-								 quote_identifier(NameStr(amForm->amname)));
+				appendStringInfoQualifiedIdentifier(&buffer, NULL,
+													schema, NameStr(opcForm->opcname),
+													" USING ");
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(amForm->amname), NULL);
 				if (objname)
 					*objname = list_make3(pstrdup(NameStr(amForm->amname)),
 										  schema,
@@ -5186,7 +5186,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(amname));
+				appendStringInfoIdentifier(&buffer, NULL, amname, NULL);
 				if (objname)
 					*objname = list_make1(amname);
 			}
@@ -5339,8 +5339,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(rule->rulename)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(rule->rulename), " on ");
 				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
@@ -5372,8 +5371,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(trig->tgname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(trig->tgname), " on ");
 				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
@@ -5544,8 +5542,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				if (objname)
 					*objname = list_make1(username);
-				appendStringInfoString(&buffer,
-									   quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, NULL, username, NULL);
 				break;
 			}
 
@@ -5606,8 +5603,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(datname);
-				appendStringInfoString(&buffer,
-									   quote_identifier(datname));
+				appendStringInfoIdentifier(&buffer, NULL, datname, NULL);
 				break;
 			}
 
@@ -5625,8 +5621,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(tblspace);
-				appendStringInfoString(&buffer,
-									   quote_identifier(tblspace));
+				appendStringInfoIdentifier(&buffer, NULL, tblspace, NULL);
 				break;
 			}
 
@@ -5638,7 +5633,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 													missing_ok);
 				if (fdw)
 				{
-					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					appendStringInfoIdentifier(&buffer, NULL, fdw->fdwname, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(fdw->fdwname));
 				}
@@ -5653,8 +5648,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 											   missing_ok);
 				if (srv)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(srv->servername));
+					appendStringInfoIdentifier(&buffer, NULL, srv->servername, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(srv->servername));
 				}
@@ -5695,9 +5689,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					*objargs = list_make1(pstrdup(srv->servername));
 				}
 
-				appendStringInfo(&buffer, "%s on server %s",
-								 quote_identifier(usename),
-								 srv->servername);
+				appendStringInfoIdentifier(&buffer, NULL, usename, " on server ");
+				appendStringInfoString(&buffer, srv->servername);
 				break;
 			}
 
@@ -5800,7 +5793,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(extname));
+				appendStringInfoIdentifier(&buffer, NULL, extname, NULL);
 				if (objname)
 					*objname = list_make1(extname);
 				break;
@@ -5823,7 +5816,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				evtname = pstrdup(NameStr(trigForm->evtname));
-				appendStringInfoString(&buffer, quote_identifier(evtname));
+				appendStringInfoIdentifier(&buffer, NULL, evtname, NULL);
 				if (objname)
 					*objname = list_make1(evtname);
 				ReleaseSysCache(tup);
@@ -5878,8 +5871,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(policy->polname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(policy->polname), " on ");
 				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
@@ -5895,8 +5887,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				pubname = get_publication_name(object->objectId, missing_ok);
 				if (pubname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(pubname));
+					appendStringInfoIdentifier(&buffer, NULL, pubname, NULL);
 					if (objname)
 						*objname = list_make1(pubname);
 				}
@@ -5963,8 +5954,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				subname = get_subscription_name(object->objectId, missing_ok);
 				if (subname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(subname));
+					appendStringInfoIdentifier(&buffer, NULL, subname, NULL);
 					if (objname)
 						*objname = list_make1(subname);
 				}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index d676930aca2..5b1c6547519 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -825,9 +825,11 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 
 			/* Find the remaining history */
 			initStringInfo(&replacementsbuf);
-			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendStringInfoIdentifier(&replacementsbuf,
+									   "(SELECT pg_catalog.range_agg(r) FROM (SELECT y.",
+									   RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]),
+									   " r FROM ");
 			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
@@ -1821,8 +1823,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
 	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
 
-	/* strcpy(pkattname, "pk."); */
-	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index b212a28d9be..cafc4d9c002 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -979,18 +979,17 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		/* tgattr is first var-width field, so OK to access directly */
 		if (trigrec->tgattr.dim1 > 0)
 		{
-			int			i;
+			const char *sep = "";
 
 			appendStringInfoString(&buf, " OF ");
-			for (i = 0; i < trigrec->tgattr.dim1; i++)
+			for (int i = 0; i < trigrec->tgattr.dim1; i++)
 			{
 				char	   *attname;
 
-				if (i > 0)
-					appendStringInfoString(&buf, ", ");
 				attname = get_attname(trigrec->tgrelid,
 									  trigrec->tgattr.values[i], false);
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, sep, attname, NULL);
+				sep = ", ";
 			}
 		}
 	}
@@ -1042,11 +1041,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	{
 		appendStringInfoString(&buf, "REFERENCING ");
 		if (tgoldtable != NULL)
-			appendStringInfo(&buf, "OLD TABLE AS %s ",
-							 quote_identifier(tgoldtable));
+			appendStringInfoIdentifier(&buf, "OLD TABLE AS ", tgoldtable, " ");
 		if (tgnewtable != NULL)
-			appendStringInfo(&buf, "NEW TABLE AS %s ",
-							 quote_identifier(tgnewtable));
+			appendStringInfoIdentifier(&buf, "NEW TABLE AS ", tgnewtable, " ");
 	}
 
 	if (TRIGGER_FOR_ROW(trigrec->tgtype))
@@ -1387,8 +1384,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 							 generate_qualified_relation_name(indrelid),
 							 quote_identifier(NameStr(amrec->amname)));
 		else					/* currently, must be EXCLUDE constraint */
-			appendStringInfo(&buf, "EXCLUDE USING %s (",
-							 quote_identifier(NameStr(amrec->amname)));
+			appendStringInfoIdentifier(&buf, "EXCLUDE USING ", NameStr(amrec->amname), " (");
 	}
 
 	/*
@@ -1426,7 +1422,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 
 			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(indrelid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -1536,8 +1532,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			{
 				if (isConstraint)
 					appendStringInfoString(&buf, " USING INDEX");
-				appendStringInfo(&buf, " TABLESPACE %s",
-								 quote_identifier(get_tablespace_name(tblspc)));
+				appendStringInfoIdentifier(&buf, " TABLESPACE ",
+										   get_tablespace_name(tblspc), NULL);
 			}
 		}
 
@@ -1794,7 +1790,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
 
 		attname = get_attname(statextrec->stxrelid, attnum, false);
 
-		appendStringInfoString(&buf, quote_identifier(attname));
+		appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 	}
 
 	context = deparse_context_for(get_relation_name(statextrec->stxrelid),
@@ -2038,7 +2034,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			int32		keycoltypmod;
 
 			attname = get_attname(relid, attnum, false);
-			appendStringInfoString(&buf, quote_identifier(attname));
+			appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -2246,17 +2242,19 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			 * we might need to let callers specify whether to put ONLY in the
 			 * command.
 			 */
-			appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
-							 generate_qualified_relation_name(conForm->conrelid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER TABLE %s ",
+							 generate_qualified_relation_name(conForm->conrelid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 		else
 		{
 			/* Must be a domain constraint */
 			Assert(OidIsValid(conForm->contypid));
-			appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
-							 generate_qualified_type_name(conForm->contypid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER DOMAIN %s ",
+							 generate_qualified_type_name(conForm->contypid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 	}
 
@@ -2423,6 +2421,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					Datum	   *keys;
 					int			nKeys;
 					int			j;
+					const char *sep = "";
 
 					appendStringInfoString(&buf, " INCLUDE (");
 
@@ -2438,9 +2437,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 						colName = get_attname(conForm->conrelid,
 											  DatumGetInt16(keys[j]), false);
-						if (j > keyatts)
-							appendStringInfoString(&buf, ", ");
-						appendStringInfoString(&buf, quote_identifier(colName));
+						appendStringInfoIdentifier(&buf, sep, colName, NULL);
+						sep = ", ";
 					}
 
 					appendStringInfoChar(&buf, ')');
@@ -2467,8 +2465,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					 */
 					tblspc = get_rel_tablespace(indexId);
 					if (OidIsValid(tblspc))
-						appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
-										 quote_identifier(get_tablespace_name(tblspc)));
+						appendStringInfoIdentifier(&buf, " USING INDEX TABLESPACE ",
+												   get_tablespace_name(tblspc), NULL);
 				}
 
 				break;
@@ -2529,9 +2527,9 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 					attnum = extractNotNullColumn(tup);
 
-					appendStringInfo(&buf, "NOT NULL %s",
-									 quote_identifier(get_attname(conForm->conrelid,
-																  attnum, false)));
+					appendStringInfoIdentifier(&buf, "NOT NULL ",
+											   get_attname(conForm->conrelid,
+														   attnum, false), NULL);
 					if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
 						appendStringInfoString(&buf, " NO INHERIT");
 				}
@@ -2635,11 +2633,14 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
-			appendStringInfoString(buf, quote_identifier(colName));
+			appendStringInfoIdentifier(buf, NULL, colName, NULL);
 		else
-			appendStringInfo(buf, ", %s%s",
-							 (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
-							 quote_identifier(colName));
+		{
+			appendStringInfoString(buf, ", ");
+			appendStringInfoIdentifier(buf,
+									   (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
+									   colName, NULL);
+		}
 	}
 
 	return nKeys;
@@ -2975,8 +2976,8 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 
 	print_function_trftypes(&buf, proctup);
 
-	appendStringInfo(&buf, " LANGUAGE %s\n",
-					 quote_identifier(get_language_name(proc->prolang, false)));
+	appendStringInfoIdentifier(&buf, " LANGUAGE ",
+							   get_language_name(proc->prolang, false), "\n");
 
 	/* Emit some miscellaneous options on one line */
 	oldlen = buf.len;
@@ -3075,8 +3076,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 					continue;
 				*pos++ = '\0';
 
-				appendStringInfo(&buf, " SET %s TO ",
-								 quote_identifier(configitem));
+				appendStringInfoIdentifier(&buf, " SET ", configitem, " TO ");
 
 				/*
 				 * Variables that are marked GUC_LIST_QUOTE were already fully
@@ -3418,7 +3418,7 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
 
 		appendStringInfoString(buf, modename);
 		if (argname && argname[0])
-			appendStringInfo(buf, "%s ", quote_identifier(argname));
+			appendStringInfoIdentifier(buf, NULL, argname, " ");
 		appendStringInfoString(buf, format_type_be(argtype));
 		if (print_defaults && isinput && inputargno > nlackdefaults)
 		{
@@ -5402,8 +5402,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 	/*
 	 * Build the rules definition text
 	 */
-	appendStringInfo(buf, "CREATE RULE %s AS",
-					 quote_identifier(rulename));
+	appendStringInfoIdentifier(buf, "CREATE RULE ", rulename, " AS");
 
 	if (prettyFlags & PRETTYFLAG_INDENT)
 		appendStringInfoString(buf, "\n    ON ");
@@ -5791,21 +5790,18 @@ get_with_clause(Query *query, deparse_context *context)
 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
 
 		appendStringInfoString(buf, sep);
-		appendStringInfoString(buf, quote_identifier(cte->ctename));
+		appendStringInfoIdentifier(buf, NULL, cte->ctename, NULL);
 		if (cte->aliascolnames)
 		{
-			bool		first = true;
 			ListCell   *col;
+			const char *colsep = "";
 
 			appendStringInfoChar(buf, '(');
 			foreach(col, cte->aliascolnames)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(col))));
+				appendStringInfoIdentifier(buf, colsep,
+										   strVal(lfirst(col)), NULL);
+				colsep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 		}
@@ -5834,43 +5830,35 @@ get_with_clause(Query *query, deparse_context *context)
 
 		if (cte->search_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *colsep = "";
 
 			appendStringInfo(buf, " SEARCH %s FIRST BY ",
 							 cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
 
 			foreach(lc, cte->search_clause->search_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, colsep, strVal(lfirst(lc)), NULL);
+				colsep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->search_clause->search_seq_column, NULL);
 		}
 
 		if (cte->cycle_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *colsep = "";
 
 			appendStringInfoString(buf, " CYCLE ");
 
 			foreach(lc, cte->cycle_clause->cycle_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, colsep, strVal(lfirst(lc)), NULL);
+				colsep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->cycle_clause->cycle_mark_column, NULL);
 
 			{
 				Const	   *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
@@ -5886,7 +5874,7 @@ get_with_clause(Query *query, deparse_context *context)
 				}
 			}
 
-			appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
+			appendStringInfoIdentifier(buf, " USING ", cte->cycle_clause->cycle_path_column, NULL);
 		}
 
 		sep = ", ";
@@ -6022,9 +6010,7 @@ get_select_query_def(Query *query, deparse_context *context)
 					break;
 			}
 
-			appendStringInfo(buf, " OF %s",
-							 quote_identifier(get_rtable_name(rc->rti,
-															  context)));
+			appendStringInfoIdentifier(buf, " OF ", get_rtable_name(rc->rti, context), NULL);
 			if (rc->waitPolicy == LockWaitError)
 				appendStringInfoString(buf, " NOWAIT");
 			else if (rc->waitPolicy == LockWaitSkip)
@@ -6317,7 +6303,7 @@ get_target_list(List *targetList, deparse_context *context)
 		if (colname)			/* resname could be NULL */
 		{
 			if (attname == NULL || strcmp(attname, colname) != 0)
-				appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
+				appendStringInfoIdentifier(&targetbuf, " AS ", colname, NULL);
 		}
 
 		/* Restore context's output buffer */
@@ -6391,19 +6377,16 @@ get_returning_clause(Query *query, deparse_context *context)
 		/* Add WITH (OLD/NEW) options, if they're not the defaults */
 		if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
 		{
-			appendStringInfo(buf, " WITH (OLD AS %s",
-							 quote_identifier(query->returningOldAlias));
+			appendStringInfoIdentifier(buf, " WITH (OLD AS ", query->returningOldAlias, NULL);
 			have_with = true;
 		}
 		if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
 		{
 			if (have_with)
-				appendStringInfo(buf, ", NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, ", NEW AS ", query->returningNewAlias, NULL);
 			else
 			{
-				appendStringInfo(buf, " WITH (NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, " WITH (NEW AS ", query->returningNewAlias, NULL);
 				have_with = true;
 			}
 		}
@@ -6771,7 +6754,7 @@ get_rule_windowclause(Query *query, deparse_context *context)
 		else
 			appendStringInfoString(buf, sep);
 
-		appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
+		appendStringInfoIdentifier(buf, NULL, wc->name, " AS ");
 
 		get_rule_windowspec(wc, query->targetList, context);
 
@@ -6794,7 +6777,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 	appendStringInfoChar(buf, '(');
 	if (wc->refname)
 	{
-		appendStringInfoString(buf, quote_identifier(wc->refname));
+		appendStringInfoIdentifier(buf, NULL, wc->refname, NULL);
 		needspace = true;
 	}
 	/* partition clauses are always inherited, so only print if no refname */
@@ -7021,10 +7004,8 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL,
+								   get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7117,8 +7098,7 @@ get_insert_query_def(Query *query, deparse_context *context)
 			if (!constraint)
 				elog(ERROR, "cache lookup failed for constraint %u",
 					 confl->constraint);
-			appendStringInfo(buf, " ON CONSTRAINT %s",
-							 quote_identifier(constraint));
+			appendStringInfoIdentifier(buf, " ON CONSTRAINT ", constraint, NULL);
 		}
 
 		if (confl->action == ONCONFLICT_NOTHING)
@@ -7320,10 +7300,7 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL, get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7511,10 +7488,10 @@ get_merge_query_def(Query *query, deparse_context *context)
 				appendStringInfoString(buf, sep);
 				sep = ", ";
 
-				appendStringInfoString(buf,
-									   quote_identifier(get_attname(rte->relid,
-																	tle->resno,
-																	false)));
+				appendStringInfoIdentifier(buf, NULL,
+										   get_attname(rte->relid,
+													   tle->resno,
+													   false), NULL);
 				strippedexprs = lappend(strippedexprs,
 										processIndirection((Node *) tle->expr,
 														   context));
@@ -7573,8 +7550,8 @@ get_utility_query_def(Query *query, deparse_context *context)
 
 		appendContextKeyword(context, "",
 							 0, PRETTYINDENT_STD, 1);
-		appendStringInfo(buf, "NOTIFY %s",
-						 quote_identifier(stmt->conditionname));
+		appendStringInfoIdentifier(buf, "NOTIFY ",
+								   stmt->conditionname, NULL);
 		if (stmt->payload)
 		{
 			appendStringInfoString(buf, ", ");
@@ -7865,11 +7842,11 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 	if (refname && need_prefix)
 	{
-		appendStringInfoString(buf, quote_identifier(refname));
+		appendStringInfoIdentifier(buf, NULL, refname, NULL);
 		appendStringInfoChar(buf, '.');
 	}
 	if (attname)
-		appendStringInfoString(buf, quote_identifier(attname));
+		appendStringInfoIdentifier(buf, NULL, attname, NULL);
 	else
 	{
 		appendStringInfoChar(buf, '*');
@@ -8806,11 +8783,10 @@ get_parameter(Param *param, deparse_context *context)
 				}
 				if (should_qualify)
 				{
-					appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
-					appendStringInfoChar(context->buf, '.');
+					appendStringInfoIdentifier(context->buf, NULL, dpns->funcname, ".");
 				}
 
-				appendStringInfoString(context->buf, quote_identifier(argname));
+				appendStringInfoIdentifier(context->buf, NULL, argname, NULL);
 				return;
 			}
 		}
@@ -9391,7 +9367,7 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				NamedArgExpr *na = (NamedArgExpr *) node;
 
-				appendStringInfo(buf, "%s => ", quote_identifier(na->name));
+				appendStringInfoIdentifier(buf, NULL, na->name, " => ");
 				get_rule_expr((Node *) na->arg, context, showimplicit);
 			}
 			break;
@@ -9679,7 +9655,7 @@ get_rule_expr(Node *node, deparse_context *context,
 				 */
 				fieldname = get_name_for_var_field((Var *) arg, fno,
 												   0, context);
-				appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+				appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 			}
 			break;
 
@@ -10131,8 +10107,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				}
 				if (xexpr->name)
 				{
-					appendStringInfo(buf, "NAME %s",
-									 quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
+					appendStringInfoIdentifier(buf, "NAME ",
+											   map_xml_name_to_sql_identifier(xexpr->name), NULL);
 					needcomma = true;
 				}
 				if (xexpr->named_args)
@@ -10152,8 +10128,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						if (needcomma)
 							appendStringInfoString(buf, ", ");
 						get_rule_expr((Node *) e, context, true);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(map_xml_name_to_sql_identifier(argname)));
+						appendStringInfoIdentifier(buf, " AS ",
+												   map_xml_name_to_sql_identifier(argname), NULL);
 						needcomma = true;
 					}
 					if (xexpr->op != IS_XMLFOREST)
@@ -10369,8 +10345,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
 
 				if (cexpr->cursor_name)
-					appendStringInfo(buf, "CURRENT OF %s",
-									 quote_identifier(cexpr->cursor_name));
+					appendStringInfoIdentifier(buf, "CURRENT OF ",
+											   cexpr->cursor_name, NULL);
 				else
 					appendStringInfo(buf, "CURRENT OF $%d",
 									 cexpr->cursor_param);
@@ -10604,8 +10580,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						needcomma = true;
 
 						get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(lfirst_node(String, lc1)->sval));
+						appendStringInfoIdentifier(buf, " AS ",
+												   lfirst_node(String, lc1)->sval, NULL);
 					}
 				}
 
@@ -11135,7 +11111,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 			if (wc->winref == wfunc->winref)
 			{
 				if (wc->name)
-					appendStringInfoString(buf, quote_identifier(wc->name));
+					appendStringInfoIdentifier(buf, NULL, wc->name, NULL);
 				else
 					get_rule_windowspec(wc, context->targetList, context);
 				break;
@@ -11161,7 +11137,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 
 				if (wagg->winref == wfunc->winref)
 				{
-					appendStringInfoString(buf, quote_identifier(wagg->winname));
+					appendStringInfoIdentifier(buf, NULL, wagg->winname, NULL);
 					break;
 				}
 			}
@@ -12001,8 +11977,8 @@ get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
 			if (ns_node != NULL)
 			{
 				get_rule_expr(expr, context, showimplicit);
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(strVal(ns_node)));
+				appendStringInfoIdentifier(buf, " AS ",
+										   strVal(ns_node), NULL);
 			}
 			else
 			{
@@ -12088,7 +12064,7 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
 		appendStringInfoChar(context->buf, ' ');
 		appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
 		get_const_expr(scan->path->value, context, -1);
-		appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
+		appendStringInfoIdentifier(context->buf, " AS ", scan->path->name, NULL);
 		get_json_table_columns(tf, scan, context, showimplicit);
 	}
 	else if (IsA(plan, JsonTableSiblingJoin))
@@ -12231,7 +12207,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 
 	get_const_expr(root->path->value, context, -1);
 
-	appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
+	appendStringInfoIdentifier(buf, " AS ", root->path->name, NULL);
 
 	if (jexpr->passing_values)
 	{
@@ -12255,9 +12231,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 			appendContextKeyword(context, "", 0, 0, 0);
 
 			get_rule_expr((Node *) lfirst(lc2), context, false);
-			appendStringInfo(buf, " AS %s",
-							 quote_identifier((lfirst_node(String, lc1))->sval)
-				);
+			appendStringInfoIdentifier(buf, " AS ", lfirst_node(String, lc1)->sval, NULL);
 		}
 
 		if (PRETTY_INDENT(context))
@@ -12533,7 +12507,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 				appendStringInfoChar(buf, ')');
 				break;
 			case RTE_CTE:
-				appendStringInfoString(buf, quote_identifier(rte->ctename));
+				appendStringInfoIdentifier(buf, NULL, rte->ctename, NULL);
 				break;
 			default:
 				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
@@ -12620,7 +12594,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		if (j->usingClause)
 		{
 			ListCell   *lc;
-			bool		first = true;
+			const char *sep = "";
 
 			appendStringInfoString(buf, " USING (");
 			/* Use the assigned names, not what's in usingClause */
@@ -12628,17 +12602,13 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			{
 				char	   *colname = (char *) lfirst(lc);
 
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf, quote_identifier(colname));
+				appendStringInfoIdentifier(buf, sep, colname, NULL);
+				sep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 
 			if (j->join_using_alias)
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(j->join_using_alias->aliasname));
+				appendStringInfoIdentifier(buf, " AS ", j->join_using_alias->aliasname, NULL);
 		}
 		else if (j->quals)
 		{
@@ -12668,9 +12638,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			 * subtleties we don't want.  However, we might print a different
 			 * alias name than was there originally.
 			 */
-			appendStringInfo(buf, " %s",
-							 quote_identifier(get_rtable_name(j->rtindex,
-															  context)));
+			appendStringInfoIdentifier(buf, " ",
+									   get_rtable_name(j->rtindex,
+													   context), NULL);
 			get_column_alias_list(colinfo, context);
 		}
 	}
@@ -12745,9 +12715,9 @@ get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
 	}
 
 	if (printalias)
-		appendStringInfo(context->buf, "%s%s",
-						 use_as ? " AS " : " ",
-						 quote_identifier(refname));
+		appendStringInfoIdentifier(context->buf,
+								   use_as ? " AS " : " ",
+								   refname, NULL);
 }
 
 /*
@@ -12777,7 +12747,7 @@ get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
 		}
 		else
 			appendStringInfoString(buf, ", ");
-		appendStringInfoString(buf, quote_identifier(colname));
+		appendStringInfoIdentifier(buf, NULL, colname, NULL);
 	}
 	if (!first)
 		appendStringInfoChar(buf, ')');
@@ -12910,13 +12880,11 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
 		/* Okay, we need the opclass name.  Do we need to qualify it? */
 		opcname = NameStr(opcrec->opcname);
 		if (OpclassIsVisible(opclass))
-			appendStringInfo(buf, " %s", quote_identifier(opcname));
+			appendStringInfoIdentifier(buf, " ", opcname, NULL);
 		else
 		{
 			nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
-			appendStringInfo(buf, " %s.%s",
-							 quote_identifier(nspname),
-							 quote_identifier(opcname));
+			appendStringInfoQualifiedIdentifier(buf, NULL, nspname, opcname, NULL);
 		}
 	}
 	ReleaseSysCache(ht_opc);
@@ -12980,7 +12948,7 @@ processIndirection(Node *node, deparse_context *context)
 			Assert(list_length(fstore->fieldnums) == 1);
 			fieldname = get_attname(typrelid,
 									linitial_int(fstore->fieldnums), false);
-			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+			appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 
 			/*
 			 * We ignore arg since it should be an uninteresting reference to
@@ -13535,7 +13503,7 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
 	else
 	{
 		nspname = get_namespace_name_or_temp(operform->oprnamespace);
-		appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
+		appendStringInfoIdentifier(&buf, "OPERATOR(", nspname, ".");
 	}
 
 	appendStringInfoString(&buf, oprname);
@@ -13641,8 +13609,7 @@ add_cast_to(StringInfo buf, Oid typid)
 	typname = NameStr(typform->typname);
 	nspname = get_namespace_name_or_temp(typform->typnamespace);
 
-	appendStringInfo(buf, "::%s.%s",
-					 quote_identifier(nspname), quote_identifier(typname));
+	appendStringInfoQualifiedIdentifier(buf, "::", nspname, typname, NULL);
 
 	ReleaseSysCache(typetup);
 }
@@ -13739,12 +13706,12 @@ get_reloptions(StringInfo buf, Datum reloptions)
 {
 	Datum	   *options;
 	int			noptions;
-	int			i;
+	const char *sep = "";
 
 	deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
 							  &options, NULL, &noptions);
 
-	for (i = 0; i < noptions; i++)
+	for (int i = 0; i < noptions; i++)
 	{
 		char	   *option = TextDatumGetCString(options[i]);
 		char	   *name;
@@ -13765,9 +13732,8 @@ get_reloptions(StringInfo buf, Datum reloptions)
 		else
 			value = "";
 
-		if (i > 0)
-			appendStringInfoString(buf, ", ");
-		appendStringInfo(buf, "%s=", quote_identifier(name));
+		appendStringInfoIdentifier(buf, sep, name, "=");
+		sep = ", ";
 
 		/*
 		 * In general we need to quote the value; but to avoid unnecessary
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v4-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch (10.8K, 4-v4-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch)
  download | inline diff:
From 648a9cbd2013ac82d76585610463e1ae30e09973 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 14 Nov 2025 21:44:29 +0800
Subject: [PATCH v4 1/4] Add appendStringInfoIdentifier() to avoid intermediate
 quoting buffers

Introduce appendStringInfoIdentifier() and
appendStringInfoQualifiedIdentifier(), helper functions that append an SQL
identifier directly to a StringInfo while applying quoting rules when
necessary.  This avoids allocating and copying through temporary palloc
buffers, as currently happens with quote_identifier() when used together
with appendStringInfoString().

The new functions improve both readability and efficiency of call sites that
construct SQL fragments, especially those that need to build qualified
names such as schema.table.

Convert several existing callers in objectaddress.c, explain.c and
ruleutils.c to use appendStringInfoIdentifier() /
appendStringInfoQualifiedIdentifier() as examples.

No functional behavior change is intended.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/objectaddress.c |  14 +--
 src/backend/commands/explain.c      |   3 +-
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 142 +++++++++++++++++++++++-----
 src/include/utils/builtins.h        |   6 ++
 5 files changed, 133 insertions(+), 40 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index c75b7131ed7..70e05059f9f 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -4883,8 +4883,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (attr)
 				{
-					appendStringInfo(&buffer, ".%s",
-									 quote_identifier(attr));
+					appendStringInfoIdentifier(&buffer, ".", attr, NULL);
 					if (objname)
 						*objname = lappend(*objname, attr);
 				}
@@ -5395,8 +5394,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer,
-									   quote_identifier(nspname));
+				appendStringInfoIdentifier(&buffer, NULL, nspname, NULL);
 				if (objname)
 					*objname = list_make1(nspname);
 				break;
@@ -5739,16 +5737,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
 				username = GetUserNameFromId(defacl->defaclrole, false);
-				appendStringInfo(&buffer,
-								 "for role %s",
-								 quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, "for role ", username, NULL);
 
 				if (OidIsValid(defacl->defaclnamespace))
 				{
 					schema = get_namespace_name_or_temp(defacl->defaclnamespace);
-					appendStringInfo(&buffer,
-									 " in schema %s",
-									 quote_identifier(schema));
+					appendStringInfoIdentifier(&buffer, " in schema ", schema, NULL);
 				}
 				else
 					schema = NULL;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 7e699f8595e..cc979737845 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1705,8 +1705,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					explain_get_index_name(bitmapindexscan->indexid);
 
 				if (es->format == EXPLAIN_FORMAT_TEXT)
-					appendStringInfo(es->str, " on %s",
-									 quote_identifier(indexname));
+					appendStringInfoIdentifier(es->str,	" on ", indexname, NULL);
 				else
 					ExplainPropertyText("Index Name", indexname, es);
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 059fc5ebf60..9e13f526994 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -395,7 +395,6 @@ RI_FKey_check(TriggerData *trigdata)
 		{
 			quoteOneName(attname,
 						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
 			appendStringInfo(&querybuf,
 							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
 							 attname, pk_only, pkrelname);
@@ -2095,7 +2094,6 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	HeapTuple	tp;
 	Form_pg_collation colltup;
 	char	   *collname;
-	char		onename[MAX_QUOTED_NAME_LEN];
 
 	/* Nothing to do if it's a noncollatable data type */
 	if (!OidIsValid(collation))
@@ -2111,10 +2109,8 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	 * We qualify the name always, for simplicity and to ensure the query is
 	 * not search-path-dependent.
 	 */
-	quoteOneName(onename, get_namespace_name(colltup->collnamespace));
-	appendStringInfo(buf, " COLLATE %s", onename);
-	quoteOneName(onename, collname);
-	appendStringInfo(buf, ".%s", onename);
+	appendStringInfoIdentifier(buf, " COLLATE ", get_namespace_name(colltup->collnamespace), NULL);
+	appendStringInfoIdentifier(buf, ".", collname, NULL);
 
 	ReleaseSysCache(tp);
 }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 556ab057e5a..1d767deb6c4 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13052,25 +13052,17 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	}
 }
 
-/*
- * quote_identifier			- Quote an identifier only if needed
- *
- * When quotes are needed, we palloc the required space; slightly
- * space-wasteful but well worth it for notational simplicity.
- */
-const char *
-quote_identifier(const char *ident)
+static inline bool
+is_identifier_safe(const char *ident, int *nquotes)
 {
 	/*
 	 * Can avoid quoting if ident starts with a lowercase letter or underscore
 	 * and contains only lowercase letters, digits, and underscores, *and* is
 	 * not any SQL keyword.  Otherwise, supply quotes.
 	 */
-	int			nquotes = 0;
 	bool		safe;
-	const char *ptr;
-	char	   *result;
-	char	   *optr;
+
+	*nquotes = 0;
 
 	/*
 	 * would like to use <ctype.h> macros here, but they might yield unwanted
@@ -13078,7 +13070,7 @@ quote_identifier(const char *ident)
 	 */
 	safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
 
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13092,7 +13084,7 @@ quote_identifier(const char *ident)
 		{
 			safe = false;
 			if (ch == '"')
-				nquotes++;
+				(*nquotes)++;
 		}
 	}
 
@@ -13115,14 +13107,20 @@ quote_identifier(const char *ident)
 			safe = false;
 	}
 
-	if (safe)
-		return ident;			/* no change needed */
+	return safe;
+}
 
-	result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+static inline const char *
+quote_identifier_internal(const char *ident, int nquotes, char **result)
+{
+	char	   *optr;
 
-	optr = result;
+	if (*result == NULL)
+		*result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+
+	optr = *result;
 	*optr++ = '"';
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13133,7 +13131,107 @@ quote_identifier(const char *ident)
 	*optr++ = '"';
 	*optr = '\0';
 
-	return result;
+	return *result;
+}
+
+/*
+ * quote_identifier			- Quote an identifier only if needed
+ *
+ * When quotes are needed, we palloc the required space; slightly
+ * space-wasteful but well worth it for notational simplicity.
+ */
+const char *
+quote_identifier(const char *ident)
+{
+	int			nquotes;
+	bool		safe;
+	char	   *result = NULL;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		return ident;			/* no change needed */
+
+	return quote_identifier_internal(ident, nquotes, &result);
+}
+
+/*
+ * appendStringInfoIdentifier
+ *      Append an SQL identifier to a StringInfo, quoting if required.
+ *
+ * This behaves like writing prefix + quote_identifier(ident) + suffix into
+ * the StringInfo, but emits the identifier directly into the buffer to avoid
+ * an intermediate palloc.  prefix and suffix may be NULL.
+ *
+ * The identifier is copied verbatim if it is safe to leave unquoted; otherwise
+ * it is written with double quotes and embedded double quotes are doubled.
+ * Quoting rules are local to ruleutils, so this helper is defined here rather
+ * than in stringinfo.c.
+ */
+
+void
+appendStringInfoIdentifier(StringInfo str, const char *prefix,
+						   const char *ident, const char *suffix)
+{
+	int			nquotes;
+	bool		safe;
+	int			ident_len;
+	int			prefix_len = 0;
+	int			suffix_len = 0;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		ident_len = strlen(ident);
+	else
+		ident_len = strlen(ident) + nquotes + 2;	/* quotes + possible
+													 * escapes */
+
+	if (prefix != NULL)
+		prefix_len = strlen(prefix);
+
+	if (suffix != NULL)
+		suffix_len = strlen(suffix);
+
+	enlargeStringInfo(str, ident_len + prefix_len + suffix_len + 1);	/* +1 for trailing null */
+
+	if (prefix != NULL)
+		appendBinaryStringInfo(str, prefix, prefix_len);
+
+	if (safe)
+		appendBinaryStringInfo(str, ident, ident_len);
+	else
+	{
+		char	   *result = str->data + str->len;
+
+		(void) quote_identifier_internal(ident, nquotes, &result);
+		str->len += ident_len;
+		str->data[str->len] = '\0';
+	}
+
+	if (suffix != NULL)
+		appendBinaryStringInfo(str, suffix, suffix_len);
+}
+
+/*
+ * appendStringInfoQualifiedIdentifier
+ *      Append a (possibly) qualified SQL identifier to a StringInfo.
+ *
+ * Writes prefix + qualifier + '.' + ident + suffix, quoting each identifier
+ * component if needed.  If no qualifier is given, only ident (plus optional
+ * prefix/suffix) is appended.  prefix and suffix may be NULL.
+ *
+ * This is a convenience wrapper around appendStringInfoIdentifier().
+ */
+void
+appendStringInfoQualifiedIdentifier(StringInfo str, const char *prefix,
+									const char *qualifier, const char *ident,
+									const char *suffix)
+{
+	if (qualifier)
+	{
+		appendStringInfoIdentifier(str, prefix, qualifier, ".");
+		prefix = NULL;
+	}
+	appendStringInfoIdentifier(str, prefix, ident, suffix);
 }
 
 /*
@@ -13150,8 +13248,8 @@ quote_qualified_identifier(const char *qualifier,
 
 	initStringInfo(&buf);
 	if (qualifier)
-		appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
-	appendStringInfoString(&buf, quote_identifier(ident));
+		appendStringInfoIdentifier(&buf, NULL, qualifier, ".");
+	appendStringInfoIdentifier(&buf, NULL, ident, NULL);
 	return buf.data;
 }
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ce6285a2c03..ff4ba7265c2 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -84,6 +84,12 @@ extern void generate_operator_clause(StringInfo buf,
 									 const char *leftop, Oid leftoptype,
 									 Oid opoid,
 									 const char *rightop, Oid rightoptype);
+extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
+extern void appendStringInfoQualifiedIdentifier(StringInfo str,
+												const char *prefix,
+												const char *qualifier,
+												const char *ident,
+												const char *suffix);
 
 /* varchar.c */
 extern int	bpchartruelen(char *s, int len);
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v4-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch (11.5K, 5-v4-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch)
  download | inline diff:
From 1ea040c6761a91d807c8e63887a05ac01bb93b0a Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Tue, 18 Nov 2025 17:13:07 +0800
Subject: [PATCH v4 3/4] Remove quoteOneName() and related buffer-sizing macros
 from ri_triggers.c

After the previous refactoring, quoteOneName() and its callers are no longer
needed.  Remove the function along with MAX_QUOTED_NAME_LEN,
MAX_QUOTED_REL_NAME_LEN, and quoteRelationName(), and introduce
appendRelationName() as the remaining helper for writing qualified relation
names using appendStringInfoQualifiedIdentifier().

This reduces redundant quoting code and centralizes identifier handling in
appendStringInfoIdentifier() / appendStringInfoQualifiedIdentifier(), making
RI triggers consistent with other code that generates SQL fragments.

No functional behavior change is expected.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/utils/adt/ri_triggers.c | 112 ++++++++--------------------
 1 file changed, 30 insertions(+), 82 deletions(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 3cc4dc2b7c8..d676930aca2 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -80,9 +80,6 @@
 #define RI_PLAN_SETDEFAULT_ONDELETE		9
 #define RI_PLAN_SETDEFAULT_ONUPDATE		10
 
-#define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
-#define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
-
 #define RIAttName(rel, attnum)	NameStr(*attnumAttName(rel, attnum))
 #define RIAttType(rel, attnum)	attnumTypeId(rel, attnum)
 #define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
@@ -192,8 +189,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 							  const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
 static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
-static void quoteOneName(char *buffer, const char *name);
-static void quoteRelationName(char *buffer, Relation rel);
+static void appendRelationName(StringInfo buffer, Relation rel, const char *prefix, const char *suffix);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
 							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
@@ -357,7 +353,6 @@ RI_FKey_check(TriggerData *trigdata)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -390,19 +385,16 @@ RI_FKey_check(TriggerData *trigdata)
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -526,7 +518,6 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -559,19 +550,16 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -753,10 +741,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
-		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -773,9 +758,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " x");
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -843,10 +827,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&replacementsbuf);
 			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-			quoteRelationName(pkrelname, pk_rel);
-			appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y",
-							 periodattname, pk_only, pkrelname);
+			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
 			querysep = "WHERE";
@@ -939,7 +921,6 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -956,9 +937,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "DELETE FROM %s%s",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "DELETE FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, NULL);
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -1044,7 +1024,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1066,9 +1045,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		initStringInfo(&qualbuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 		querysep = "";
 		qualsep = "WHERE";
 		for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
@@ -1232,7 +1210,6 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1281,9 +1258,8 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfo(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 
 		/*
 		 * Add assignment clauses
@@ -1509,8 +1485,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 {
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	RangeTblEntry *rte;
@@ -1613,15 +1587,13 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
 	pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
-					 fk_only, fkrelname, pk_only, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk LEFT OUTER JOIN ");
+	appendRelationName(&querybuf, pk_rel, pk_only, " pk ON");
 
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
@@ -1799,8 +1771,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
 	char	   *constraintDef;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	const char *sep;
@@ -1845,13 +1815,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk JOIN %s pk ON",
-					 fk_only, fkrelname, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
+	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
+
 	/* strcpy(pkattname, "pk."); */
 	/* strcpy(fkattname, "fk."); */
 	sep = "(";
@@ -2004,37 +1973,16 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 
 
 /*
- * quoteOneName --- safely quote a single SQL name
- *
- * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
- */
-static void
-quoteOneName(char *buffer, const char *name)
-{
-	/* Rather than trying to be smart, just always quote it. */
-	*buffer++ = '"';
-	while (*name)
-	{
-		if (*name == '"')
-			*buffer++ = '"';
-		*buffer++ = *name++;
-	}
-	*buffer++ = '"';
-	*buffer = '\0';
-}
+ * appendRelationName --- safely append a quoted fully qualified relation name
 
-/*
- * quoteRelationName --- safely quote a fully qualified relation name
- *
- * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
  */
 static void
-quoteRelationName(char *buffer, Relation rel)
+appendRelationName(StringInfo buffer, Relation rel,
+				   const char *prefix, const char *suffix)
 {
-	quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
-	buffer += strlen(buffer);
-	*buffer++ = '.';
-	quoteOneName(buffer, RelationGetRelationName(rel));
+	appendStringInfoQualifiedIdentifier(buffer, prefix,
+										get_namespace_name(RelationGetNamespace(rel)),
+										RelationGetRelationName(rel), suffix);
 }
 
 /*
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v4-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch (21.9K, 6-v4-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch)
  download | inline diff:
From 8043be01f1883ff419c1a732be73cbe05021dbf3 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Mon, 17 Nov 2025 12:09:12 +0800
Subject: [PATCH v4 2/4] Use appendStringInfoIdentifier() throughout
 ri_triggers.c

Replace most uses of quoteOneName() and manual stack buffers in
ri_triggers.c with appendStringInfoIdentifier() and related infrastructure.

This simplifies the construction of SQL queries generated by RI triggers and
eliminates the need for MAX_QUOTED_NAME_LEN / stack-allocated intermediate
buffers.  It also removes several code paths where identifiers were quoted
manually, making the quoting rules consistent with ruleutils.c and the GUC
quote_all_identifiers.

This commit also adjusts generate_operator_clause() to support prefixed
arguments and identifier quoting directly, reducing the number of places
where callers need to inject string concatenation logic.

No user-visible behavior change is intended; the generated SQL should be
equivalent to the previous version.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/commands/matview.c      |   4 +-
 src/backend/utils/adt/ri_triggers.c | 188 +++++++++++++---------------
 src/backend/utils/adt/ruleutils.c   |  31 +++--
 src/include/utils/builtins.h        |   4 +-
 4 files changed, 115 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index ef7c0d624f1..f1ad5a8e059 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -797,9 +797,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 													 NameStr(attr->attname));
 
 				generate_operator_clause(&querybuf,
-										 leftop, attrtype,
+										 NULL, leftop, attrtype, false,
 										 op,
-										 rightop, attrtype);
+										 NULL, rightop, attrtype, false);
 
 				foundUniqueIndex = true;
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 9e13f526994..3cc4dc2b7c8 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -196,9 +196,9 @@ static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
-							const char *leftop, Oid leftoptype,
+							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 							Oid opoid,
-							const char *rightop, Oid rightoptype);
+							const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
 static int	ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
@@ -358,7 +358,7 @@ RI_FKey_check(TriggerData *trigdata)
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -393,11 +393,11 @@ RI_FKey_check(TriggerData *trigdata)
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -410,13 +410,12 @@ RI_FKey_check(TriggerData *trigdata)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pf_eq_oprs[i],
-							paramname, fk_type);
+							NULL, paramname, fk_type, false);
 			querysep = "AND";
 			queryoids[i] = fk_type;
 		}
@@ -428,9 +427,9 @@ RI_FKey_check(TriggerData *trigdata)
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -528,7 +527,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *pk_only;
@@ -563,11 +562,11 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -579,13 +578,12 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		{
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pp_eq_oprs[i],
-							paramname, pk_type);
+							NULL, paramname, pk_type, false);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -597,9 +595,9 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -757,7 +755,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
@@ -784,13 +782,12 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -828,7 +825,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			char	   *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 				"" : "ONLY ";
 
-			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
 			sprintf(paramname, "$%d", riinfo->nkeys);
 
 			appendStringInfoString(&querybuf, " AND NOT coalesce(");
@@ -837,9 +834,9 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&intersectbuf);
 			appendStringInfoChar(&intersectbuf, '(');
 			ri_GenerateQual(&intersectbuf, "",
-							attname, fk_period_type,
+							NULL, attname, fk_period_type, true,
 							riinfo->period_intersect_oper,
-							paramname, pk_period_type);
+							NULL, paramname, pk_period_type, false);
 			appendStringInfoChar(&intersectbuf, ')');
 
 			/* Find the remaining history */
@@ -857,22 +854,21 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			{
 				Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-				quoteOneName(attname,
-							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 				sprintf(paramname, "$%d", i + 1);
 				ri_GenerateQual(&replacementsbuf, querysep,
-								paramname, pk_type,
+								NULL, paramname, pk_type, false,
 								riinfo->pp_eq_oprs[i],
-								attname, pk_type);
+								NULL, attname, pk_type, true);
 				querysep = "AND";
 				queryoids[i] = pk_type;
 			}
 			appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
 
 			ri_GenerateQual(&querybuf, "",
-							intersectbuf.data, fk_period_type,
+							NULL, intersectbuf.data, fk_period_type, false,
 							riinfo->agged_period_contained_by_oper,
-							replacementsbuf.data, ANYMULTIRANGEOID);
+							NULL, replacementsbuf.data, ANYMULTIRANGEOID, false);
 			/* end of coalesce: */
 			appendStringInfoString(&querybuf, ", false)");
 		}
@@ -944,7 +940,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -969,13 +965,12 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1050,7 +1045,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1081,16 +1076,15 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = $%d",
-							 querysep, attname, i + 1);
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfo(&querybuf, "$%d", i + 1);
 			sprintf(paramname, "$%d", j + 1);
 			ri_GenerateQual(&qualbuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = ",";
 			qualsep = "AND";
 			queryoids[i] = pk_type;
@@ -1239,7 +1233,7 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1297,11 +1291,10 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		querysep = "";
 		for (int i = 0; i < num_cols_to_set; i++)
 		{
-			quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = %s",
-							 querysep, attname,
-							 is_set_null ? "NULL" : "DEFAULT");
+			attname = RIAttName(fk_rel, set_cols[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfoString(&querybuf, is_set_null ? "NULL" : "DEFAULT");
 			querysep = ",";
 		}
 
@@ -1314,14 +1307,13 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			qualsep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1519,8 +1511,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	StringInfoData querybuf;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *pk_perminfo;
 	RTEPermissionInfo *fk_perminfo;
@@ -1615,9 +1607,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1631,8 +1623,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
 					 fk_only, fkrelname, pk_only, pkrelname);
 
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1641,14 +1631,12 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1658,16 +1646,15 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * It's sufficient to test any one pk attribute for null to detect a join
 	 * failure.
 	 */
-	quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
-	appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
+	pkattname = RIAttName(pk_rel, riinfo->pk_attnums[0]);
+	appendStringInfoIdentifier(&querybuf, ") WHERE pk.", pkattname, " IS NULL AND (");
 
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -1814,8 +1801,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char	   *constraintDef;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	const char *sep;
 	const char *fk_only;
 	int			save_nestlevel;
@@ -1852,9 +1839,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1865,8 +1852,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendStringInfo(&querybuf,
 					 " FROM %s%s fk JOIN %s pk ON",
 					 fk_only, fkrelname, pkrelname);
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
+	/* strcpy(pkattname, "pk."); */
+	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1875,14 +1862,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1903,10 +1888,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -2064,13 +2048,15 @@ quoteRelationName(char *buffer, Relation rel)
 static void
 ri_GenerateQual(StringInfo buf,
 				const char *sep,
-				const char *leftop, Oid leftoptype,
+				const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 				Oid opoid,
-				const char *rightop, Oid rightoptype)
+				const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	appendStringInfo(buf, " %s ", sep);
-	generate_operator_clause(buf, leftop, leftoptype, opoid,
-							 rightop, rightoptype);
+	generate_operator_clause(buf,
+							 leftopprefix, leftop, leftoptype, quoteleftop,
+							 opoid,
+							 rightopprefix, rightop, rightoptype, quoterightop);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 1d767deb6c4..b212a28d9be 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -13570,9 +13570,9 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
  */
 void
 generate_operator_clause(StringInfo buf,
-						 const char *leftop, Oid leftoptype,
+						 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 						 Oid opoid,
-						 const char *rightop, Oid rightoptype)
+						 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	HeapTuple	opertup;
 	Form_pg_operator operform;
@@ -13585,15 +13585,32 @@ generate_operator_clause(StringInfo buf,
 	operform = (Form_pg_operator) GETSTRUCT(opertup);
 	Assert(operform->oprkind == 'b');
 	oprname = NameStr(operform->oprname);
-
 	nspname = get_namespace_name(operform->oprnamespace);
 
-	appendStringInfoString(buf, leftop);
+	if (quoteleftop)
+		appendStringInfoIdentifier(buf, leftopprefix, leftop, NULL);
+	else
+	{
+		if (leftopprefix)
+			appendStringInfoString(buf, leftopprefix);
+		appendStringInfoString(buf, leftop);
+	}
+
 	if (leftoptype != operform->oprleft)
 		add_cast_to(buf, operform->oprleft);
-	appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
-	appendStringInfoString(buf, oprname);
-	appendStringInfo(buf, ") %s", rightop);
+
+	appendStringInfoIdentifier(buf, " OPERATOR(", nspname, ".");
+	appendStringInfo(buf, "%s) ", oprname);
+
+	if (quoterightop)
+		appendStringInfoIdentifier(buf, rightopprefix, rightop, NULL);
+	else
+	{
+		if (rightopprefix)
+			appendStringInfoString(buf, rightopprefix);
+		appendStringInfoString(buf, rightop);
+	}
+
 	if (rightoptype != operform->oprright)
 		add_cast_to(buf, operform->oprright);
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index ff4ba7265c2..4bd04e1a57a 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -81,9 +81,9 @@ extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 										const char *ident);
 extern void generate_operator_clause(StringInfo buf,
-									 const char *leftop, Oid leftoptype,
+									 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 									 Oid opoid,
-									 const char *rightop, Oid rightoptype);
+									 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
 extern void appendStringInfoQualifiedIdentifier(StringInfo str,
 												const char *prefix,
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2026-04-13 02:53  Chao Li <[email protected]>
  parent: Chao Li <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Chao Li @ 2026-04-13 02:53 UTC (permalink / raw)
  To: Postgres hackers <[email protected]>; +Cc: Álvaro Herrera <[email protected]>; David Rowley <[email protected]>



> On Nov 21, 2025, at 18:03, Chao Li <[email protected]> wrote:
> 
> Fixed a compile warning in v4.
> 
> Best regards,
> Chao Li (Evan)
> ---------------------
> HighGo Software Co., Ltd.
> https://www.highgo.com/
> 
> 
> On Fri, Nov 21, 2025 at 1:52 PM Chao Li <[email protected]> wrote:
> 
> On Thu, Nov 20, 2025 at 8:28 PM Álvaro Herrera <[email protected]> wrote:
> Hi,
> 
> > -                appendStringInfo(&buffer, _("text search configuration %s"),
> > -                                 quote_qualified_identifier(nspname,
> > -                                                            NameStr(cfgForm->cfgname)));
> > +                appendStringInfoQualifiedIdentifier(&buffer,
> > +                                                    _("text search configuration "),
> > +                                                    nspname, NameStr(cfgForm->cfgname), NULL);
> >                  ReleaseSysCache(tup);
> >                  break;
> >              }
> 
> This doesn't work from a i18n point of view.  In the original
> formulation, the translator is free to place the %s wherever it suits
> the language.  In the new one there's no such freedom: the name will be
> appended at the end.  Some existing translations:
> 
> ko.po:msgid "text search configuration %s"
> ko.po-msgstr "%s 전문 검색 구성"
> 
> tr.po:msgid "text search configuration %s"
> tr.po-msgstr "%s metin arama yapılandırması"
> 
> Thanks for the feedback. I reverted that piece of change in v3.
> 
> Best regards,
> Chao Li (Evan)
> ---------------------
> HighGo Software Co., Ltd.
> https://www.highgo.com/ 

Rebased.

--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






Attachments:

  [application/octet-stream] v5-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch (11.6K, 2-v5-0001-Add-appendStringInfoIdentifier-to-avoid-intermedi.patch)
  download | inline diff:
From 595526adbdc845c0f1701753a99af8335e1c7495 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Fri, 14 Nov 2025 21:44:29 +0800
Subject: [PATCH v5 1/4] Add appendStringInfoIdentifier() to avoid intermediate
 quoting buffers

Introduce appendStringInfoIdentifier() and
appendStringInfoQualifiedIdentifier(), helper functions that append an SQL
identifier directly to a StringInfo while applying quoting rules when
necessary.  This avoids allocating and copying through temporary palloc
buffers, as currently happens with quote_identifier() when used together
with appendStringInfoString().

The new functions improve both readability and efficiency of call sites that
construct SQL fragments, especially those that need to build qualified
names such as schema.table.

Convert several existing callers in objectaddress.c, explain.c and
ruleutils.c to use appendStringInfoIdentifier() /
appendStringInfoQualifiedIdentifier() as examples.

No functional behavior change is intended.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/objectaddress.c |  14 +--
 src/backend/commands/explain.c      |   3 +-
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 150 +++++++++++++++++++++++-----
 src/include/utils/builtins.h        |   6 ++
 5 files changed, 137 insertions(+), 44 deletions(-)

diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 9771c6a9b63..88556339dd9 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -5150,8 +5150,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (attr)
 				{
-					appendStringInfo(&buffer, ".%s",
-									 quote_identifier(attr));
+					appendStringInfoIdentifier(&buffer, ".", attr, NULL);
 					if (objname)
 						*objname = lappend(*objname, attr);
 				}
@@ -5662,8 +5661,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer,
-									   quote_identifier(nspname));
+				appendStringInfoIdentifier(&buffer, NULL, nspname, NULL);
 				if (objname)
 					*objname = list_make1(nspname);
 				break;
@@ -6006,16 +6004,12 @@ getObjectIdentityParts(const ObjectAddress *object,
 				defacl = (Form_pg_default_acl) GETSTRUCT(tup);
 
 				username = GetUserNameFromId(defacl->defaclrole, false);
-				appendStringInfo(&buffer,
-								 "for role %s",
-								 quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, "for role ", username, NULL);
 
 				if (OidIsValid(defacl->defaclnamespace))
 				{
 					schema = get_namespace_name_or_temp(defacl->defaclnamespace);
-					appendStringInfo(&buffer,
-									 " in schema %s",
-									 quote_identifier(schema));
+					appendStringInfoIdentifier(&buffer, " in schema ", schema, NULL);
 				}
 				else
 					schema = NULL;
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 112c17b0d64..67ee6696e0a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1712,8 +1712,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
 					explain_get_index_name(bitmapindexscan->indexid);
 
 				if (es->format == EXPLAIN_FORMAT_TEXT)
-					appendStringInfo(es->str, " on %s",
-									 quote_identifier(indexname));
+					appendStringInfoIdentifier(es->str,	" on ", indexname, NULL);
 				else
 					ExplainPropertyText("Index Name", indexname, es);
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index e060280fcd4..dc8f40eff12 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -528,7 +528,6 @@ RI_FKey_check(TriggerData *trigdata)
 		{
 			quoteOneName(attname,
 						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
 			appendStringInfo(&querybuf,
 							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
 							 attname, pk_only, pkrelname);
@@ -2228,7 +2227,6 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	HeapTuple	tp;
 	Form_pg_collation colltup;
 	char	   *collname;
-	char		onename[MAX_QUOTED_NAME_LEN];
 
 	/* Nothing to do if it's a noncollatable data type */
 	if (!OidIsValid(collation))
@@ -2244,10 +2242,8 @@ ri_GenerateQualCollation(StringInfo buf, Oid collation)
 	 * We qualify the name always, for simplicity and to ensure the query is
 	 * not search-path-dependent.
 	 */
-	quoteOneName(onename, get_namespace_name(colltup->collnamespace));
-	appendStringInfo(buf, " COLLATE %s", onename);
-	quoteOneName(onename, collname);
-	appendStringInfo(buf, ".%s", onename);
+	appendStringInfoIdentifier(buf, " COLLATE ", get_namespace_name(colltup->collnamespace), NULL);
+	appendStringInfoIdentifier(buf, ".", collname, NULL);
 
 	ReleaseSysCache(tp);
 }
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 090e8cc28c1..af4a56f7bd7 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -10683,8 +10683,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				}
 				if (xexpr->name)
 				{
-					appendStringInfo(buf, "NAME %s",
-									 quote_identifier(map_xml_name_to_sql_identifier(xexpr->name)));
+					appendStringInfoIdentifier(buf, "NAME ",
+											   map_xml_name_to_sql_identifier(xexpr->name), NULL);
 					needcomma = true;
 				}
 				if (xexpr->named_args)
@@ -10704,8 +10704,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						if (needcomma)
 							appendStringInfoString(buf, ", ");
 						get_rule_expr(e, context, true);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(map_xml_name_to_sql_identifier(argname)));
+						appendStringInfoIdentifier(buf, " AS ",
+												   map_xml_name_to_sql_identifier(argname), NULL);
 						needcomma = true;
 					}
 					if (xexpr->op != IS_XMLFOREST)
@@ -13672,25 +13672,17 @@ printSubscripts(SubscriptingRef *sbsref, deparse_context *context)
 	}
 }
 
-/*
- * quote_identifier			- Quote an identifier only if needed
- *
- * When quotes are needed, we palloc the required space; slightly
- * space-wasteful but well worth it for notational simplicity.
- */
-const char *
-quote_identifier(const char *ident)
+static inline bool
+is_identifier_safe(const char *ident, int *nquotes)
 {
 	/*
 	 * Can avoid quoting if ident starts with a lowercase letter or underscore
 	 * and contains only lowercase letters, digits, and underscores, *and* is
 	 * not any SQL keyword.  Otherwise, supply quotes.
 	 */
-	int			nquotes = 0;
 	bool		safe;
-	const char *ptr;
-	char	   *result;
-	char	   *optr;
+
+	*nquotes = 0;
 
 	/*
 	 * would like to use <ctype.h> macros here, but they might yield unwanted
@@ -13698,7 +13690,7 @@ quote_identifier(const char *ident)
 	 */
 	safe = ((ident[0] >= 'a' && ident[0] <= 'z') || ident[0] == '_');
 
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13712,7 +13704,7 @@ quote_identifier(const char *ident)
 		{
 			safe = false;
 			if (ch == '"')
-				nquotes++;
+				(*nquotes)++;
 		}
 	}
 
@@ -13735,14 +13727,20 @@ quote_identifier(const char *ident)
 			safe = false;
 	}
 
-	if (safe)
-		return ident;			/* no change needed */
+	return safe;
+}
+
+static inline const char *
+quote_identifier_internal(const char *ident, int nquotes, char **result)
+{
+	char	   *optr;
 
-	result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
+	if (*result == NULL)
+		*result = (char *) palloc(strlen(ident) + nquotes + 2 + 1);
 
-	optr = result;
+	optr = *result;
 	*optr++ = '"';
-	for (ptr = ident; *ptr; ptr++)
+	for (const char *ptr = ident; *ptr; ptr++)
 	{
 		char		ch = *ptr;
 
@@ -13753,7 +13751,107 @@ quote_identifier(const char *ident)
 	*optr++ = '"';
 	*optr = '\0';
 
-	return result;
+	return *result;
+}
+
+/*
+ * quote_identifier			- Quote an identifier only if needed
+ *
+ * When quotes are needed, we palloc the required space; slightly
+ * space-wasteful but well worth it for notational simplicity.
+ */
+const char *
+quote_identifier(const char *ident)
+{
+	int			nquotes;
+	bool		safe;
+	char	   *result = NULL;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		return ident;			/* no change needed */
+
+	return quote_identifier_internal(ident, nquotes, &result);
+}
+
+/*
+ * appendStringInfoIdentifier
+ *      Append an SQL identifier to a StringInfo, quoting if required.
+ *
+ * This behaves like writing prefix + quote_identifier(ident) + suffix into
+ * the StringInfo, but emits the identifier directly into the buffer to avoid
+ * an intermediate palloc.  prefix and suffix may be NULL.
+ *
+ * The identifier is copied verbatim if it is safe to leave unquoted; otherwise
+ * it is written with double quotes and embedded double quotes are doubled.
+ * Quoting rules are local to ruleutils, so this helper is defined here rather
+ * than in stringinfo.c.
+ */
+
+void
+appendStringInfoIdentifier(StringInfo str, const char *prefix,
+						   const char *ident, const char *suffix)
+{
+	int			nquotes;
+	bool		safe;
+	int			ident_len;
+	int			prefix_len = 0;
+	int			suffix_len = 0;
+
+	safe = is_identifier_safe(ident, &nquotes);
+	if (safe)
+		ident_len = strlen(ident);
+	else
+		ident_len = strlen(ident) + nquotes + 2;	/* quotes + possible
+													 * escapes */
+
+	if (prefix != NULL)
+		prefix_len = strlen(prefix);
+
+	if (suffix != NULL)
+		suffix_len = strlen(suffix);
+
+	enlargeStringInfo(str, ident_len + prefix_len + suffix_len);
+
+	if (prefix != NULL)
+		appendBinaryStringInfo(str, prefix, prefix_len);
+
+	if (safe)
+		appendBinaryStringInfo(str, ident, ident_len);
+	else
+	{
+		char	   *result = str->data + str->len;
+
+		(void) quote_identifier_internal(ident, nquotes, &result);
+		str->len += ident_len;
+		str->data[str->len] = '\0';
+	}
+
+	if (suffix != NULL)
+		appendBinaryStringInfo(str, suffix, suffix_len);
+}
+
+/*
+ * appendStringInfoQualifiedIdentifier
+ *      Append a (possibly) qualified SQL identifier to a StringInfo.
+ *
+ * Writes prefix + qualifier + '.' + ident + suffix, quoting each identifier
+ * component if needed.  If no qualifier is given, only ident (plus optional
+ * prefix/suffix) is appended.  prefix and suffix may be NULL.
+ *
+ * This is a convenience wrapper around appendStringInfoIdentifier().
+ */
+void
+appendStringInfoQualifiedIdentifier(StringInfo str, const char *prefix,
+									const char *qualifier, const char *ident,
+									const char *suffix)
+{
+	if (qualifier)
+	{
+		appendStringInfoIdentifier(str, prefix, qualifier, ".");
+		prefix = NULL;
+	}
+	appendStringInfoIdentifier(str, prefix, ident, suffix);
 }
 
 /*
@@ -13770,8 +13868,8 @@ quote_qualified_identifier(const char *qualifier,
 
 	initStringInfo(&buf);
 	if (qualifier)
-		appendStringInfo(&buf, "%s.", quote_identifier(qualifier));
-	appendStringInfoString(&buf, quote_identifier(ident));
+		appendStringInfoIdentifier(&buf, NULL, qualifier, ".");
+	appendStringInfoIdentifier(&buf, NULL, ident, NULL);
 	return buf.data;
 }
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index b6a11bfa288..7dbbb4c7c7f 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -85,6 +85,12 @@ extern void generate_operator_clause(StringInfo buf,
 									 const char *leftop, Oid leftoptype,
 									 Oid opoid,
 									 const char *rightop, Oid rightoptype);
+extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
+extern void appendStringInfoQualifiedIdentifier(StringInfo str,
+												const char *prefix,
+												const char *qualifier,
+												const char *ident,
+												const char *suffix);
 
 /* varchar.c */
 extern int	bpchartruelen(char *s, int len);
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v5-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch (21.9K, 3-v5-0002-Use-appendStringInfoIdentifier-throughout-ri_trig.patch)
  download | inline diff:
From 10f4505cb302df59e74446104e21bf6ca2e72609 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Mon, 17 Nov 2025 12:09:12 +0800
Subject: [PATCH v5 2/4] Use appendStringInfoIdentifier() throughout
 ri_triggers.c

Replace most uses of quoteOneName() and manual stack buffers in
ri_triggers.c with appendStringInfoIdentifier() and related infrastructure.

This simplifies the construction of SQL queries generated by RI triggers and
eliminates the need for MAX_QUOTED_NAME_LEN / stack-allocated intermediate
buffers.  It also removes several code paths where identifiers were quoted
manually, making the quoting rules consistent with ruleutils.c and the GUC
quote_all_identifiers.

This commit also adjusts generate_operator_clause() to support prefixed
arguments and identifier quoting directly, reducing the number of places
where callers need to inject string concatenation logic.

No user-visible behavior change is intended; the generated SQL should be
equivalent to the previous version.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/commands/matview.c      |   4 +-
 src/backend/utils/adt/ri_triggers.c | 188 +++++++++++++---------------
 src/backend/utils/adt/ruleutils.c   |  31 +++--
 src/include/utils/builtins.h        |   4 +-
 4 files changed, 115 insertions(+), 112 deletions(-)

diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index f7d8007f796..96aa4553168 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -797,9 +797,9 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner,
 													 NameStr(attr->attname));
 
 				generate_operator_clause(&querybuf,
-										 leftop, attrtype,
+										 NULL, leftop, attrtype, false,
 										 op,
-										 rightop, attrtype);
+										 NULL, rightop, attrtype, false);
 
 				foundUniqueIndex = true;
 			}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index dc8f40eff12..e55fa8c6574 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -274,9 +274,9 @@ static void quoteOneName(char *buffer, const char *name);
 static void quoteRelationName(char *buffer, Relation rel);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
-							const char *leftop, Oid leftoptype,
+							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 							Oid opoid,
-							const char *rightop, Oid rightoptype);
+							const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
 static int	ri_NullCheck(TupleDesc tupDesc, TupleTableSlot *slot,
 						 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
@@ -491,7 +491,7 @@ RI_FKey_check(TriggerData *trigdata)
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -526,11 +526,11 @@ RI_FKey_check(TriggerData *trigdata)
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -543,13 +543,12 @@ RI_FKey_check(TriggerData *trigdata)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pf_eq_oprs[i],
-							paramname, fk_type);
+							NULL, paramname, fk_type, false);
 			querysep = "AND";
 			queryoids[i] = fk_type;
 		}
@@ -561,9 +560,9 @@ RI_FKey_check(TriggerData *trigdata)
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -661,7 +660,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	{
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *pk_only;
@@ -696,11 +695,11 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
-			quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
+			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
 			appendStringInfo(&querybuf,
-							 "SELECT 1 FROM (SELECT %s AS r FROM %s%s x",
-							 attname, pk_only, pkrelname);
+							 "%s%s x",
+							 pk_only, pkrelname);
 		}
 		else
 		{
@@ -712,13 +711,12 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		{
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+			attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							attname, pk_type,
+							NULL, attname, pk_type, true,
 							riinfo->pp_eq_oprs[i],
-							paramname, pk_type);
+							NULL, paramname, pk_type, false);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -730,9 +728,9 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 			appendStringInfoString(&querybuf, ") x1 HAVING ");
 			sprintf(paramname, "$%d", riinfo->nkeys);
 			ri_GenerateQual(&querybuf, "",
-							paramname, fk_type,
+							NULL, paramname, fk_type, false,
 							riinfo->agged_period_contained_by_oper,
-							"pg_catalog.range_agg", ANYMULTIRANGEOID);
+							NULL, "pg_catalog.range_agg", ANYMULTIRANGEOID, false);
 			appendStringInfoString(&querybuf, "(x1.r)");
 		}
 
@@ -890,7 +888,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		StringInfoData querybuf;
 		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
@@ -917,13 +915,12 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -961,7 +958,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			char	   *pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 				"" : "ONLY ";
 
-			quoteOneName(attname, RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[riinfo->nkeys - 1]);
 			sprintf(paramname, "$%d", riinfo->nkeys);
 
 			appendStringInfoString(&querybuf, " AND NOT coalesce(");
@@ -970,9 +967,9 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&intersectbuf);
 			appendStringInfoChar(&intersectbuf, '(');
 			ri_GenerateQual(&intersectbuf, "",
-							attname, fk_period_type,
+							NULL, attname, fk_period_type, true,
 							riinfo->period_intersect_oper,
-							paramname, pk_period_type);
+							NULL, paramname, pk_period_type, false);
 			appendStringInfoChar(&intersectbuf, ')');
 
 			/* Find the remaining history */
@@ -990,22 +987,21 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			{
 				Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 
-				quoteOneName(attname,
-							 RIAttName(pk_rel, riinfo->pk_attnums[i]));
+				attname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
 				sprintf(paramname, "$%d", i + 1);
 				ri_GenerateQual(&replacementsbuf, querysep,
-								paramname, pk_type,
+								NULL, paramname, pk_type, false,
 								riinfo->pp_eq_oprs[i],
-								attname, pk_type);
+								NULL, attname, pk_type, true);
 				querysep = "AND";
 				queryoids[i] = pk_type;
 			}
 			appendStringInfoString(&replacementsbuf, " FOR KEY SHARE OF y) y2)");
 
 			ri_GenerateQual(&querybuf, "",
-							intersectbuf.data, fk_period_type,
+							NULL, intersectbuf.data, fk_period_type, false,
 							riinfo->agged_period_contained_by_oper,
-							replacementsbuf.data, ANYMULTIRANGEOID);
+							NULL, replacementsbuf.data, ANYMULTIRANGEOID, false);
 			/* end of coalesce: */
 			appendStringInfoString(&querybuf, ", false)");
 		}
@@ -1077,7 +1073,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -1102,13 +1098,12 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, querysep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1183,7 +1178,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1214,16 +1209,15 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = $%d",
-							 querysep, attname, i + 1);
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfo(&querybuf, "$%d", i + 1);
 			sprintf(paramname, "$%d", j + 1);
 			ri_GenerateQual(&qualbuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			querysep = ",";
 			qualsep = "AND";
 			queryoids[i] = pk_type;
@@ -1372,7 +1366,7 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	{
 		StringInfoData querybuf;
 		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		attname[MAX_QUOTED_NAME_LEN];
+		const char *attname;
 		char		paramname[16];
 		const char *querysep;
 		const char *qualsep;
@@ -1430,11 +1424,10 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		querysep = "";
 		for (int i = 0; i < num_cols_to_set; i++)
 		{
-			quoteOneName(attname, RIAttName(fk_rel, set_cols[i]));
-			appendStringInfo(&querybuf,
-							 "%s %s = %s",
-							 querysep, attname,
-							 is_set_null ? "NULL" : "DEFAULT");
+			attname = RIAttName(fk_rel, set_cols[i]);
+			appendStringInfoString(&querybuf, querysep);
+			appendStringInfoIdentifier(&querybuf, " ", attname, " = ");
+			appendStringInfoString(&querybuf, is_set_null ? "NULL" : "DEFAULT");
 			querysep = ",";
 		}
 
@@ -1447,14 +1440,13 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 			Oid			pk_type = RIAttType(pk_rel, riinfo->pk_attnums[i]);
 			Oid			fk_type = RIAttType(fk_rel, riinfo->fk_attnums[i]);
 
-			quoteOneName(attname,
-						 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+			attname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 
 			sprintf(paramname, "$%d", i + 1);
 			ri_GenerateQual(&querybuf, qualsep,
-							paramname, pk_type,
+							NULL, paramname, pk_type, false,
 							riinfo->pf_eq_oprs[i],
-							attname, fk_type);
+							NULL, attname, fk_type, true);
 			qualsep = "AND";
 			queryoids[i] = pk_type;
 		}
@@ -1652,8 +1644,8 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	StringInfoData querybuf;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	RangeTblEntry *rte;
 	RTEPermissionInfo *pk_perminfo;
 	RTEPermissionInfo *fk_perminfo;
@@ -1748,9 +1740,9 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1764,8 +1756,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
 					 fk_only, fkrelname, pk_only, pkrelname);
 
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
@@ -1774,14 +1764,12 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -1791,16 +1779,15 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	 * It's sufficient to test any one pk attribute for null to detect a join
 	 * failure.
 	 */
-	quoteOneName(pkattname, RIAttName(pk_rel, riinfo->pk_attnums[0]));
-	appendStringInfo(&querybuf, ") WHERE pk.%s IS NULL AND (", pkattname);
+	pkattname = RIAttName(pk_rel, riinfo->pk_attnums[0]);
+	appendStringInfoIdentifier(&querybuf, ") WHERE pk.", pkattname, " IS NULL AND (");
 
 	sep = "";
 	for (int i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -1947,8 +1934,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	char	   *constraintDef;
 	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		pkattname[MAX_QUOTED_NAME_LEN + 3];
-	char		fkattname[MAX_QUOTED_NAME_LEN + 3];
+	const char *pkattname;
+	const char *fkattname;
 	const char *sep;
 	const char *fk_only;
 	int			save_nestlevel;
@@ -1985,9 +1972,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf, "%sfk.%s", sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, NULL);
 		sep = ", ";
 	}
 
@@ -1998,8 +1985,8 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendStringInfo(&querybuf,
 					 " FROM %s%s fk JOIN %s pk ON",
 					 fk_only, fkrelname, pkrelname);
-	strcpy(pkattname, "pk.");
-	strcpy(fkattname, "fk.");
+	/* strcpy(pkattname, "pk."); */
+	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
@@ -2008,14 +1995,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		Oid			pk_coll = RIAttCollation(pk_rel, riinfo->pk_attnums[i]);
 		Oid			fk_coll = RIAttCollation(fk_rel, riinfo->fk_attnums[i]);
 
-		quoteOneName(pkattname + 3,
-					 RIAttName(pk_rel, riinfo->pk_attnums[i]));
-		quoteOneName(fkattname + 3,
-					 RIAttName(fk_rel, riinfo->fk_attnums[i]));
+		pkattname = RIAttName(pk_rel, riinfo->pk_attnums[i]);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
 		ri_GenerateQual(&querybuf, sep,
-						pkattname, pk_type,
+						"pk.", pkattname, pk_type, true,
 						riinfo->pf_eq_oprs[i],
-						fkattname, fk_type);
+						"fk.", fkattname, fk_type, true);
 		if (pk_coll != fk_coll)
 			ri_GenerateQualCollation(&querybuf, pk_coll);
 		sep = "AND";
@@ -2036,10 +2021,9 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	sep = "";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		quoteOneName(fkattname, RIAttName(fk_rel, riinfo->fk_attnums[i]));
-		appendStringInfo(&querybuf,
-						 "%sfk.%s IS NOT NULL",
-						 sep, fkattname);
+		fkattname = RIAttName(fk_rel, riinfo->fk_attnums[i]);
+		appendStringInfoString(&querybuf, sep);
+		appendStringInfoIdentifier(&querybuf, "fk.", fkattname, " IS NOT NULL");
 		switch (riinfo->confmatchtype)
 		{
 			case FKCONSTR_MATCH_SIMPLE:
@@ -2197,13 +2181,15 @@ quoteRelationName(char *buffer, Relation rel)
 static void
 ri_GenerateQual(StringInfo buf,
 				const char *sep,
-				const char *leftop, Oid leftoptype,
+				const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 				Oid opoid,
-				const char *rightop, Oid rightoptype)
+				const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	appendStringInfo(buf, " %s ", sep);
-	generate_operator_clause(buf, leftop, leftoptype, opoid,
-							 rightop, rightoptype);
+	generate_operator_clause(buf,
+							 leftopprefix, leftop, leftoptype, quoteleftop,
+							 opoid,
+							 rightopprefix, rightop, rightoptype, quoterightop);
 }
 
 /*
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af4a56f7bd7..8dcab752ca5 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -14190,9 +14190,9 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
  */
 void
 generate_operator_clause(StringInfo buf,
-						 const char *leftop, Oid leftoptype,
+						 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 						 Oid opoid,
-						 const char *rightop, Oid rightoptype)
+						 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop)
 {
 	HeapTuple	opertup;
 	Form_pg_operator operform;
@@ -14205,15 +14205,32 @@ generate_operator_clause(StringInfo buf,
 	operform = (Form_pg_operator) GETSTRUCT(opertup);
 	Assert(operform->oprkind == 'b');
 	oprname = NameStr(operform->oprname);
-
 	nspname = get_namespace_name(operform->oprnamespace);
 
-	appendStringInfoString(buf, leftop);
+	if (quoteleftop)
+		appendStringInfoIdentifier(buf, leftopprefix, leftop, NULL);
+	else
+	{
+		if (leftopprefix)
+			appendStringInfoString(buf, leftopprefix);
+		appendStringInfoString(buf, leftop);
+	}
+
 	if (leftoptype != operform->oprleft)
 		add_cast_to(buf, operform->oprleft);
-	appendStringInfo(buf, " OPERATOR(%s.", quote_identifier(nspname));
-	appendStringInfoString(buf, oprname);
-	appendStringInfo(buf, ") %s", rightop);
+
+	appendStringInfoIdentifier(buf, " OPERATOR(", nspname, ".");
+	appendStringInfo(buf, "%s) ", oprname);
+
+	if (quoterightop)
+		appendStringInfoIdentifier(buf, rightopprefix, rightop, NULL);
+	else
+	{
+		if (rightopprefix)
+			appendStringInfoString(buf, rightopprefix);
+		appendStringInfoString(buf, rightop);
+	}
+
 	if (rightoptype != operform->oprright)
 		add_cast_to(buf, operform->oprright);
 
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 7dbbb4c7c7f..adb1f51768f 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -82,9 +82,9 @@ extern const char *quote_identifier(const char *ident);
 extern char *quote_qualified_identifier(const char *qualifier,
 										const char *ident);
 extern void generate_operator_clause(StringInfo buf,
-									 const char *leftop, Oid leftoptype,
+									 const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
 									 Oid opoid,
-									 const char *rightop, Oid rightoptype);
+									 const char *rightopprefix, const char *rightop, Oid rightoptype, bool quoterightop);
 extern void appendStringInfoIdentifier(StringInfo str, const char *prefix, const char *ident, const char *suffix);
 extern void appendStringInfoQualifiedIdentifier(StringInfo str,
 												const char *prefix,
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v5-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch (11.5K, 4-v5-0003-Remove-quoteOneName-and-related-buffer-sizing-mac.patch)
  download | inline diff:
From 10c7438f070f827cb4115bb912b59e1a3a7410f2 Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Tue, 18 Nov 2025 17:13:07 +0800
Subject: [PATCH v5 3/4] Remove quoteOneName() and related buffer-sizing macros
 from ri_triggers.c

After the previous refactoring, quoteOneName() and its callers are no longer
needed.  Remove the function along with MAX_QUOTED_NAME_LEN,
MAX_QUOTED_REL_NAME_LEN, and quoteRelationName(), and introduce
appendRelationName() as the remaining helper for writing qualified relation
names using appendStringInfoQualifiedIdentifier().

This reduces redundant quoting code and centralizes identifier handling in
appendStringInfoIdentifier() / appendStringInfoQualifiedIdentifier(), making
RI triggers consistent with other code that generates SQL fragments.

No functional behavior change is expected.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/utils/adt/ri_triggers.c | 112 ++++++++--------------------
 1 file changed, 30 insertions(+), 82 deletions(-)

diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index e55fa8c6574..9e14da86ca6 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -86,9 +86,6 @@
 #define RI_PLAN_SETDEFAULT_ONDELETE		9
 #define RI_PLAN_SETDEFAULT_ONUPDATE		10
 
-#define MAX_QUOTED_NAME_LEN  (NAMEDATALEN*2+3)
-#define MAX_QUOTED_REL_NAME_LEN  (MAX_QUOTED_NAME_LEN*2)
-
 #define RIAttName(rel, attnum)	NameStr(*attnumAttName(rel, attnum))
 #define RIAttType(rel, attnum)	attnumTypeId(rel, attnum)
 #define RIAttCollation(rel, attnum) attnumCollationId(rel, attnum)
@@ -270,8 +267,7 @@ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 							  const RI_ConstraintInfo *riinfo);
 static Datum ri_restrict(TriggerData *trigdata, bool is_no_action);
 static Datum ri_set(TriggerData *trigdata, bool is_set_null, int tgkind);
-static void quoteOneName(char *buffer, const char *name);
-static void quoteRelationName(char *buffer, Relation rel);
+static void appendRelationName(StringInfo buffer, Relation rel, const char *prefix, const char *suffix);
 static void ri_GenerateQual(StringInfo buf,
 							const char *sep,
 							const char *leftopprefix, const char *leftop, Oid leftoptype, bool quoteleftop,
@@ -490,7 +486,6 @@ RI_FKey_check(TriggerData *trigdata)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -523,19 +518,16 @@ RI_FKey_check(TriggerData *trigdata)
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -659,7 +651,6 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -692,19 +683,16 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 		initStringInfo(&querybuf);
 		pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(pkrelname, pk_rel);
 		if (riinfo->hasperiod)
 		{
 			attname = RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]);
 			appendStringInfoIdentifier(&querybuf, "SELECT 1 FROM (SELECT ", attname, " AS r FROM ");
-			appendStringInfo(&querybuf,
-							 "%s%s x",
-							 pk_only, pkrelname);
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		else
 		{
-			appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-							 pk_only, pkrelname);
+			appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+			appendRelationName(&querybuf, pk_rel, pk_only, " x");
 		}
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
@@ -886,10 +874,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
-		char		periodattname[MAX_QUOTED_NAME_LEN];
 		char		paramname[16];
 		const char *querysep;
 		Oid			queryoids[RI_MAX_NUMKEYS];
@@ -906,9 +891,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "SELECT 1 FROM %s%s x",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "SELECT 1 FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " x");
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -976,10 +960,8 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 			initStringInfo(&replacementsbuf);
 			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			quoteOneName(periodattname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
-			quoteRelationName(pkrelname, pk_rel);
-			appendStringInfo(&replacementsbuf, "(SELECT y.%s r FROM %s%s y",
-							 periodattname, pk_only, pkrelname);
+			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
 			querysep = "WHERE";
@@ -1072,7 +1054,6 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1089,9 +1070,8 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "DELETE FROM %s%s",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "DELETE FROM ");
+		appendRelationName(&querybuf, fk_rel, fk_only, NULL);
 		querysep = "WHERE";
 		for (int i = 0; i < riinfo->nkeys; i++)
 		{
@@ -1177,7 +1157,6 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	{
 		StringInfoData querybuf;
 		StringInfoData qualbuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1199,9 +1178,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 		initStringInfo(&qualbuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfoString(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 		querysep = "";
 		qualsep = "WHERE";
 		for (int i = 0, j = riinfo->nkeys; i < riinfo->nkeys; i++, j++)
@@ -1365,7 +1343,6 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 	if ((qplan = ri_FetchPreparedPlan(&qkey)) == NULL)
 	{
 		StringInfoData querybuf;
-		char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 		const char *attname;
 		char		paramname[16];
 		const char *querysep;
@@ -1414,9 +1391,8 @@ ri_set(TriggerData *trigdata, bool is_set_null, int tgkind)
 		initStringInfo(&querybuf);
 		fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 			"" : "ONLY ";
-		quoteRelationName(fkrelname, fk_rel);
-		appendStringInfo(&querybuf, "UPDATE %s%s SET",
-						 fk_only, fkrelname);
+		appendStringInfo(&querybuf, "UPDATE ");
+		appendRelationName(&querybuf, fk_rel, fk_only, " SET");
 
 		/*
 		 * Add assignment clauses
@@ -1642,8 +1618,6 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 {
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	RangeTblEntry *rte;
@@ -1746,15 +1720,13 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
 	pk_only = pk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk LEFT OUTER JOIN %s%s pk ON",
-					 fk_only, fkrelname, pk_only, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk LEFT OUTER JOIN ");
+	appendRelationName(&querybuf, pk_rel, pk_only, " pk ON");
 
 	sep = "(";
 	for (int i = 0; i < riinfo->nkeys; i++)
@@ -1932,8 +1904,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	const RI_ConstraintInfo *riinfo;
 	StringInfoData querybuf;
 	char	   *constraintDef;
-	char		pkrelname[MAX_QUOTED_REL_NAME_LEN];
-	char		fkrelname[MAX_QUOTED_REL_NAME_LEN];
 	const char *pkattname;
 	const char *fkattname;
 	const char *sep;
@@ -1978,13 +1948,12 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		sep = ", ";
 	}
 
-	quoteRelationName(pkrelname, pk_rel);
-	quoteRelationName(fkrelname, fk_rel);
 	fk_only = fk_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE ?
 		"" : "ONLY ";
-	appendStringInfo(&querybuf,
-					 " FROM %s%s fk JOIN %s pk ON",
-					 fk_only, fkrelname, pkrelname);
+	appendStringInfoString(&querybuf, " FROM ");
+	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
+	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
+
 	/* strcpy(pkattname, "pk."); */
 	/* strcpy(fkattname, "fk."); */
 	sep = "(";
@@ -2137,37 +2106,16 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 
 
 /*
- * quoteOneName --- safely quote a single SQL name
- *
- * buffer must be MAX_QUOTED_NAME_LEN long (includes room for \0)
- */
-static void
-quoteOneName(char *buffer, const char *name)
-{
-	/* Rather than trying to be smart, just always quote it. */
-	*buffer++ = '"';
-	while (*name)
-	{
-		if (*name == '"')
-			*buffer++ = '"';
-		*buffer++ = *name++;
-	}
-	*buffer++ = '"';
-	*buffer = '\0';
-}
+ * appendRelationName --- safely append a quoted fully qualified relation name
 
-/*
- * quoteRelationName --- safely quote a fully qualified relation name
- *
- * buffer must be MAX_QUOTED_REL_NAME_LEN long (includes room for \0)
  */
 static void
-quoteRelationName(char *buffer, Relation rel)
+appendRelationName(StringInfo buffer, Relation rel,
+				   const char *prefix, const char *suffix)
 {
-	quoteOneName(buffer, get_namespace_name(RelationGetNamespace(rel)));
-	buffer += strlen(buffer);
-	*buffer++ = '.';
-	quoteOneName(buffer, RelationGetRelationName(rel));
+	appendStringInfoQualifiedIdentifier(buffer, prefix,
+										get_namespace_name(RelationGetNamespace(rel)),
+										RelationGetRelationName(rel), suffix);
 }
 
 /*
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v5-0004-Use-appendStringInfoIdentifier-in-more-places.patch (34.9K, 5-v5-0004-Use-appendStringInfoIdentifier-in-more-places.patch)
  download | inline diff:
From 7ca12d18f5551184266e530d20eff48c5e12dafa Mon Sep 17 00:00:00 2001
From: "Chao Li (Evan)" <[email protected]>
Date: Wed, 19 Nov 2025 15:55:23 +0800
Subject: [PATCH v5 4/4] Use appendStringInfoIdentifier() in more places.

Author: Chao Li <[email protected]>
Discussion: https://postgr.es/m/CAEoWx2=g2RVkxXB=JzWphgfg4QGV+spaA3PQ1rBM2iMehrVvjg@mail.gmail.com
---
 src/backend/catalog/namespace.c     |   6 +-
 src/backend/catalog/objectaddress.c |  64 +++----
 src/backend/utils/adt/ri_triggers.c |   8 +-
 src/backend/utils/adt/ruleutils.c   | 252 ++++++++++++----------------
 4 files changed, 143 insertions(+), 187 deletions(-)

diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 56b87d878e8..948d0f428e8 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -3701,14 +3701,14 @@ NameListToQuotedString(const List *names)
 {
 	StringInfoData string;
 	ListCell   *l;
+	const char *sep = "";
 
 	initStringInfo(&string);
 
 	foreach(l, names)
 	{
-		if (l != list_head(names))
-			appendStringInfoChar(&string, '.');
-		appendStringInfoString(&string, quote_identifier(strVal(lfirst(l))));
+		appendStringInfoIdentifier(&string, sep, strVal(lfirst(l)), NULL);
+		sep = ".";
 	}
 
 	return string.data;
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 88556339dd9..f759a9dcf13 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -5271,8 +5271,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				if (OidIsValid(con->conrelid))
 				{
-					appendStringInfo(&buffer, "%s on ",
-									 quote_identifier(NameStr(con->conname)));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
 					getRelationIdentity(&buffer, con->conrelid, objname,
 										false);
 					if (objname)
@@ -5287,10 +5286,10 @@ getObjectIdentityParts(const ObjectAddress *object,
 					domain.objectId = con->contypid;
 					domain.objectSubId = 0;
 
-					appendStringInfo(&buffer, "%s on %s",
-									 quote_identifier(NameStr(con->conname)),
-									 getObjectIdentityParts(&domain, objname,
-															objargs, false));
+					appendStringInfoIdentifier(&buffer, NULL, NameStr(con->conname), " on ");
+					appendStringInfoString(&buffer,
+										   getObjectIdentityParts(&domain, objname,
+																  objargs, false));
 
 					if (objname)
 						*objargs = lappend(*objargs, pstrdup(NameStr(con->conname)));
@@ -5363,8 +5362,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				}
 				langForm = (Form_pg_language) GETSTRUCT(langTup);
-				appendStringInfoString(&buffer,
-									   quote_identifier(NameStr(langForm->lanname)));
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(langForm->lanname), NULL);
 				if (objname)
 					*objname = list_make1(pstrdup(NameStr(langForm->lanname)));
 				ReleaseSysCache(langTup);
@@ -5422,10 +5421,11 @@ getObjectIdentityParts(const ObjectAddress *object,
 						 opcForm->opcmethod);
 				amForm = (Form_pg_am) GETSTRUCT(amTup);
 
-				appendStringInfo(&buffer, "%s USING %s",
-								 quote_qualified_identifier(schema,
-															NameStr(opcForm->opcname)),
-								 quote_identifier(NameStr(amForm->amname)));
+				appendStringInfoQualifiedIdentifier(&buffer, NULL,
+													schema, NameStr(opcForm->opcname),
+													" USING ");
+				appendStringInfoIdentifier(&buffer, NULL,
+										   NameStr(amForm->amname), NULL);
 				if (objname)
 					*objname = list_make3(pstrdup(NameStr(amForm->amname)),
 										  schema,
@@ -5453,7 +5453,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(amname));
+				appendStringInfoIdentifier(&buffer, NULL, amname, NULL);
 				if (objname)
 					*objname = list_make1(amname);
 			}
@@ -5606,8 +5606,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				rule = (Form_pg_rewrite) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(rule->rulename)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(rule->rulename), " on ");
 				getRelationIdentity(&buffer, rule->ev_class, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(rule->rulename)));
@@ -5639,8 +5638,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				trig = (Form_pg_trigger) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(trig->tgname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(trig->tgname), " on ");
 				getRelationIdentity(&buffer, trig->tgrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(trig->tgname)));
@@ -5811,8 +5809,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 					break;
 				if (objname)
 					*objname = list_make1(username);
-				appendStringInfoString(&buffer,
-									   quote_identifier(username));
+				appendStringInfoIdentifier(&buffer, NULL, username, NULL);
 				break;
 			}
 
@@ -5873,8 +5870,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(datname);
-				appendStringInfoString(&buffer,
-									   quote_identifier(datname));
+				appendStringInfoIdentifier(&buffer, NULL, datname, NULL);
 				break;
 			}
 
@@ -5892,8 +5888,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				if (objname)
 					*objname = list_make1(tblspace);
-				appendStringInfoString(&buffer,
-									   quote_identifier(tblspace));
+				appendStringInfoIdentifier(&buffer, NULL, tblspace, NULL);
 				break;
 			}
 
@@ -5905,7 +5900,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 													missing_ok);
 				if (fdw)
 				{
-					appendStringInfoString(&buffer, quote_identifier(fdw->fdwname));
+					appendStringInfoIdentifier(&buffer, NULL, fdw->fdwname, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(fdw->fdwname));
 				}
@@ -5920,8 +5915,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 											   missing_ok);
 				if (srv)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(srv->servername));
+					appendStringInfoIdentifier(&buffer, NULL, srv->servername, NULL);
 					if (objname)
 						*objname = list_make1(pstrdup(srv->servername));
 				}
@@ -5962,9 +5956,8 @@ getObjectIdentityParts(const ObjectAddress *object,
 					*objargs = list_make1(pstrdup(srv->servername));
 				}
 
-				appendStringInfo(&buffer, "%s on server %s",
-								 quote_identifier(usename),
-								 srv->servername);
+				appendStringInfoIdentifier(&buffer, NULL, usename, " on server ");
+				appendStringInfoString(&buffer, srv->servername);
 				break;
 			}
 
@@ -6067,7 +6060,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 							 object->objectId);
 					break;
 				}
-				appendStringInfoString(&buffer, quote_identifier(extname));
+				appendStringInfoIdentifier(&buffer, NULL, extname, NULL);
 				if (objname)
 					*objname = list_make1(extname);
 				break;
@@ -6090,7 +6083,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				}
 				trigForm = (Form_pg_event_trigger) GETSTRUCT(tup);
 				evtname = pstrdup(NameStr(trigForm->evtname));
-				appendStringInfoString(&buffer, quote_identifier(evtname));
+				appendStringInfoIdentifier(&buffer, NULL, evtname, NULL);
 				if (objname)
 					*objname = list_make1(evtname);
 				ReleaseSysCache(tup);
@@ -6145,8 +6138,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 
 				policy = (Form_pg_policy) GETSTRUCT(tup);
 
-				appendStringInfo(&buffer, "%s on ",
-								 quote_identifier(NameStr(policy->polname)));
+				appendStringInfoIdentifier(&buffer, NULL, NameStr(policy->polname), " on ");
 				getRelationIdentity(&buffer, policy->polrelid, objname, false);
 				if (objname)
 					*objname = lappend(*objname, pstrdup(NameStr(policy->polname)));
@@ -6229,8 +6221,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				pubname = get_publication_name(object->objectId, missing_ok);
 				if (pubname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(pubname));
+					appendStringInfoIdentifier(&buffer, NULL, pubname, NULL);
 					if (objname)
 						*objname = list_make1(pubname);
 				}
@@ -6297,8 +6288,7 @@ getObjectIdentityParts(const ObjectAddress *object,
 				subname = get_subscription_name(object->objectId, missing_ok);
 				if (subname)
 				{
-					appendStringInfoString(&buffer,
-										   quote_identifier(subname));
+					appendStringInfoIdentifier(&buffer, NULL, subname, NULL);
 					if (objname)
 						*objname = list_make1(subname);
 				}
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index 9e14da86ca6..c03d7b46d14 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -958,9 +958,11 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 
 			/* Find the remaining history */
 			initStringInfo(&replacementsbuf);
-			appendStringInfoString(&replacementsbuf, "(SELECT pg_catalog.range_agg(r) FROM ");
 
-			appendStringInfoIdentifier(&replacementsbuf, "(SELECT y.", RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]), " r FROM ");
+			appendStringInfoIdentifier(&replacementsbuf,
+									   "(SELECT pg_catalog.range_agg(r) FROM (SELECT y.",
+									   RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]),
+									   " r FROM ");
 			appendRelationName(&replacementsbuf, pk_rel, pk_only, " y");
 
 			/* Restrict pk rows to what matches */
@@ -1954,8 +1956,6 @@ RI_PartitionRemove_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 	appendRelationName(&querybuf, fk_rel, fk_only, " fk JOIN ");
 	appendRelationName(&querybuf, pk_rel, NULL, " pk ON");
 
-	/* strcpy(pkattname, "pk."); */
-	/* strcpy(fkattname, "fk."); */
 	sep = "(";
 	for (i = 0; i < riinfo->nkeys; i++)
 	{
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8dcab752ca5..6126da4d4bd 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -990,18 +990,17 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		/* tgattr is first var-width field, so OK to access directly */
 		if (trigrec->tgattr.dim1 > 0)
 		{
-			int			i;
+			const char *sep = "";
 
 			appendStringInfoString(&buf, " OF ");
-			for (i = 0; i < trigrec->tgattr.dim1; i++)
+			for (int i = 0; i < trigrec->tgattr.dim1; i++)
 			{
 				char	   *attname;
 
-				if (i > 0)
-					appendStringInfoString(&buf, ", ");
 				attname = get_attname(trigrec->tgrelid,
 									  trigrec->tgattr.values[i], false);
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, sep, attname, NULL);
+				sep = ", ";
 			}
 		}
 	}
@@ -1053,11 +1052,9 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 	{
 		appendStringInfoString(&buf, "REFERENCING ");
 		if (tgoldtable != NULL)
-			appendStringInfo(&buf, "OLD TABLE AS %s ",
-							 quote_identifier(tgoldtable));
+			appendStringInfoIdentifier(&buf, "OLD TABLE AS ", tgoldtable, " ");
 		if (tgnewtable != NULL)
-			appendStringInfo(&buf, "NEW TABLE AS %s ",
-							 quote_identifier(tgnewtable));
+			appendStringInfoIdentifier(&buf, "NEW TABLE AS ", tgnewtable, " ");
 	}
 
 	if (TRIGGER_FOR_ROW(trigrec->tgtype))
@@ -1398,8 +1395,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 							 generate_qualified_relation_name(indrelid),
 							 quote_identifier(NameStr(amrec->amname)));
 		else					/* currently, must be EXCLUDE constraint */
-			appendStringInfo(&buf, "EXCLUDE USING %s (",
-							 quote_identifier(NameStr(amrec->amname)));
+			appendStringInfoIdentifier(&buf, "EXCLUDE USING ", NameStr(amrec->amname), " (");
 	}
 
 	/*
@@ -1437,7 +1433,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 
 			attname = get_attname(indrelid, attnum, false);
 			if (!colno || colno == keyno + 1)
-				appendStringInfoString(&buf, quote_identifier(attname));
+				appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(indrelid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -1547,8 +1543,8 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 			{
 				if (isConstraint)
 					appendStringInfoString(&buf, " USING INDEX");
-				appendStringInfo(&buf, " TABLESPACE %s",
-								 quote_identifier(get_tablespace_name(tblspc)));
+				appendStringInfoIdentifier(&buf, " TABLESPACE ",
+										   get_tablespace_name(tblspc), NULL);
 			}
 		}
 
@@ -2153,7 +2149,7 @@ pg_get_statisticsobj_worker(Oid statextid, bool columns_only, bool missing_ok)
 
 		attname = get_attname(statextrec->stxrelid, attnum, false);
 
-		appendStringInfoString(&buf, quote_identifier(attname));
+		appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 	}
 
 	context = deparse_context_for(get_relation_name(statextrec->stxrelid),
@@ -2397,7 +2393,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
 			int32		keycoltypmod;
 
 			attname = get_attname(relid, attnum, false);
-			appendStringInfoString(&buf, quote_identifier(attname));
+			appendStringInfoIdentifier(&buf, NULL, attname, NULL);
 			get_atttypetypmodcoll(relid, attnum,
 								  &keycoltype, &keycoltypmod,
 								  &keycolcollation);
@@ -2605,17 +2601,19 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 			 * we might need to let callers specify whether to put ONLY in the
 			 * command.
 			 */
-			appendStringInfo(&buf, "ALTER TABLE %s ADD CONSTRAINT %s ",
-							 generate_qualified_relation_name(conForm->conrelid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER TABLE %s ",
+							 generate_qualified_relation_name(conForm->conrelid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 		else
 		{
 			/* Must be a domain constraint */
 			Assert(OidIsValid(conForm->contypid));
-			appendStringInfo(&buf, "ALTER DOMAIN %s ADD CONSTRAINT %s ",
-							 generate_qualified_type_name(conForm->contypid),
-							 quote_identifier(NameStr(conForm->conname)));
+			appendStringInfo(&buf, "ALTER DOMAIN %s ",
+							 generate_qualified_type_name(conForm->contypid));
+			appendStringInfoIdentifier(&buf, "ADD CONSTRAINT ",
+									   NameStr(conForm->conname), " ");
 		}
 	}
 
@@ -2782,6 +2780,7 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					Datum	   *keys;
 					int			nKeys;
 					int			j;
+					const char *sep = "";
 
 					appendStringInfoString(&buf, " INCLUDE (");
 
@@ -2797,9 +2796,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 						colName = get_attname(conForm->conrelid,
 											  DatumGetInt16(keys[j]), false);
-						if (j > keyatts)
-							appendStringInfoString(&buf, ", ");
-						appendStringInfoString(&buf, quote_identifier(colName));
+						appendStringInfoIdentifier(&buf, sep, colName, NULL);
+						sep = ", ";
 					}
 
 					appendStringInfoChar(&buf, ')');
@@ -2826,8 +2824,8 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 					 */
 					tblspc = get_rel_tablespace(indexId);
 					if (OidIsValid(tblspc))
-						appendStringInfo(&buf, " USING INDEX TABLESPACE %s",
-										 quote_identifier(get_tablespace_name(tblspc)));
+						appendStringInfoIdentifier(&buf, " USING INDEX TABLESPACE ",
+												   get_tablespace_name(tblspc), NULL);
 				}
 
 				break;
@@ -2888,9 +2886,9 @@ pg_get_constraintdef_worker(Oid constraintId, bool fullCommand,
 
 					attnum = extractNotNullColumn(tup);
 
-					appendStringInfo(&buf, "NOT NULL %s",
-									 quote_identifier(get_attname(conForm->conrelid,
-																  attnum, false)));
+					appendStringInfoIdentifier(&buf, "NOT NULL ",
+											   get_attname(conForm->conrelid,
+														   attnum, false), NULL);
 					if (((Form_pg_constraint) GETSTRUCT(tup))->connoinherit)
 						appendStringInfoString(&buf, " NO INHERIT");
 				}
@@ -2994,11 +2992,14 @@ decompile_column_index_array(Datum column_index_array, Oid relId,
 		colName = get_attname(relId, DatumGetInt16(keys[j]), false);
 
 		if (j == 0)
-			appendStringInfoString(buf, quote_identifier(colName));
+			appendStringInfoIdentifier(buf, NULL, colName, NULL);
 		else
-			appendStringInfo(buf, ", %s%s",
-							 (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
-							 quote_identifier(colName));
+		{
+			appendStringInfoString(buf, ", ");
+			appendStringInfoIdentifier(buf,
+									   (withPeriod && j == nKeys - 1) ? "PERIOD " : "",
+									   colName, NULL);
+		}
 	}
 
 	return nKeys;
@@ -3334,8 +3335,8 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 
 	print_function_trftypes(&buf, proctup);
 
-	appendStringInfo(&buf, " LANGUAGE %s\n",
-					 quote_identifier(get_language_name(proc->prolang, false)));
+	appendStringInfoIdentifier(&buf, " LANGUAGE ",
+							   get_language_name(proc->prolang, false), "\n");
 
 	/* Emit some miscellaneous options on one line */
 	oldlen = buf.len;
@@ -3434,8 +3435,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
 					continue;
 				*pos++ = '\0';
 
-				appendStringInfo(&buf, " SET %s TO ",
-								 quote_identifier(configitem));
+				appendStringInfoIdentifier(&buf, " SET ", configitem, " TO ");
 
 				/*
 				 * Variables that are marked GUC_LIST_QUOTE were already fully
@@ -3777,7 +3777,7 @@ print_function_arguments(StringInfo buf, HeapTuple proctup,
 
 		appendStringInfoString(buf, modename);
 		if (argname && argname[0])
-			appendStringInfo(buf, "%s ", quote_identifier(argname));
+			appendStringInfoIdentifier(buf, NULL, argname, " ");
 		appendStringInfoString(buf, format_type_be(argtype));
 		if (print_defaults && isinput && inputargno > nlackdefaults)
 		{
@@ -5761,8 +5761,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 	/*
 	 * Build the rules definition text
 	 */
-	appendStringInfo(buf, "CREATE RULE %s AS",
-					 quote_identifier(rulename));
+	appendStringInfoIdentifier(buf, "CREATE RULE ", rulename, " AS");
 
 	if (prettyFlags & PRETTYFLAG_INDENT)
 		appendStringInfoString(buf, "\n    ON ");
@@ -6153,21 +6152,18 @@ get_with_clause(Query *query, deparse_context *context)
 		CommonTableExpr *cte = (CommonTableExpr *) lfirst(l);
 
 		appendStringInfoString(buf, sep);
-		appendStringInfoString(buf, quote_identifier(cte->ctename));
+		appendStringInfoIdentifier(buf, NULL, cte->ctename, NULL);
 		if (cte->aliascolnames)
 		{
-			bool		first = true;
 			ListCell   *col;
+			const char *colsep = "";
 
 			appendStringInfoChar(buf, '(');
 			foreach(col, cte->aliascolnames)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(col))));
+				appendStringInfoIdentifier(buf, colsep,
+										   strVal(lfirst(col)), NULL);
+				colsep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 		}
@@ -6196,43 +6192,35 @@ get_with_clause(Query *query, deparse_context *context)
 
 		if (cte->search_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *colsep = "";
 
 			appendStringInfo(buf, " SEARCH %s FIRST BY ",
 							 cte->search_clause->search_breadth_first ? "BREADTH" : "DEPTH");
 
 			foreach(lc, cte->search_clause->search_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, colsep, strVal(lfirst(lc)), NULL);
+				colsep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->search_clause->search_seq_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->search_clause->search_seq_column, NULL);
 		}
 
 		if (cte->cycle_clause)
 		{
-			bool		first = true;
 			ListCell   *lc;
+			const char *colsep = "";
 
 			appendStringInfoString(buf, " CYCLE ");
 
 			foreach(lc, cte->cycle_clause->cycle_col_list)
 			{
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf,
-									   quote_identifier(strVal(lfirst(lc))));
+				appendStringInfoIdentifier(buf, colsep, strVal(lfirst(lc)), NULL);
+				colsep = ", ";
 			}
 
-			appendStringInfo(buf, " SET %s", quote_identifier(cte->cycle_clause->cycle_mark_column));
+			appendStringInfoIdentifier(buf, " SET ", cte->cycle_clause->cycle_mark_column, NULL);
 
 			{
 				Const	   *cmv = castNode(Const, cte->cycle_clause->cycle_mark_value);
@@ -6248,7 +6236,7 @@ get_with_clause(Query *query, deparse_context *context)
 				}
 			}
 
-			appendStringInfo(buf, " USING %s", quote_identifier(cte->cycle_clause->cycle_path_column));
+			appendStringInfoIdentifier(buf, " USING ", cte->cycle_clause->cycle_path_column, NULL);
 		}
 
 		sep = ", ";
@@ -6363,9 +6351,7 @@ get_select_query_def(Query *query, deparse_context *context)
 								 get_lock_clause_strength(rc->strength),
 								 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
 
-			appendStringInfo(buf, " OF %s",
-							 quote_identifier(get_rtable_name(rc->rti,
-															  context)));
+			appendStringInfoIdentifier(buf, " OF ", get_rtable_name(rc->rti, context), NULL);
 			if (rc->waitPolicy == LockWaitError)
 				appendStringInfoString(buf, " NOWAIT");
 			else if (rc->waitPolicy == LockWaitSkip)
@@ -6680,7 +6666,7 @@ get_target_list(List *targetList, deparse_context *context)
 		if (colname)			/* resname could be NULL */
 		{
 			if (attname == NULL || strcmp(attname, colname) != 0)
-				appendStringInfo(&targetbuf, " AS %s", quote_identifier(colname));
+				appendStringInfoIdentifier(&targetbuf, " AS ", colname, NULL);
 		}
 
 		/* Restore context's output buffer */
@@ -6754,19 +6740,16 @@ get_returning_clause(Query *query, deparse_context *context)
 		/* Add WITH (OLD/NEW) options, if they're not the defaults */
 		if (query->returningOldAlias && strcmp(query->returningOldAlias, "old") != 0)
 		{
-			appendStringInfo(buf, " WITH (OLD AS %s",
-							 quote_identifier(query->returningOldAlias));
+			appendStringInfoIdentifier(buf, " WITH (OLD AS ", query->returningOldAlias, NULL);
 			have_with = true;
 		}
 		if (query->returningNewAlias && strcmp(query->returningNewAlias, "new") != 0)
 		{
 			if (have_with)
-				appendStringInfo(buf, ", NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, ", NEW AS ", query->returningNewAlias, NULL);
 			else
 			{
-				appendStringInfo(buf, " WITH (NEW AS %s",
-								 quote_identifier(query->returningNewAlias));
+				appendStringInfoIdentifier(buf, " WITH (NEW AS ", query->returningNewAlias, NULL);
 				have_with = true;
 			}
 		}
@@ -7134,7 +7117,7 @@ get_rule_windowclause(Query *query, deparse_context *context)
 		else
 			appendStringInfoString(buf, sep);
 
-		appendStringInfo(buf, "%s AS ", quote_identifier(wc->name));
+		appendStringInfoIdentifier(buf, NULL, wc->name, " AS ");
 
 		get_rule_windowspec(wc, query->targetList, context);
 
@@ -7157,7 +7140,7 @@ get_rule_windowspec(WindowClause *wc, List *targetList,
 	appendStringInfoChar(buf, '(');
 	if (wc->refname)
 	{
-		appendStringInfoString(buf, quote_identifier(wc->refname));
+		appendStringInfoIdentifier(buf, NULL, wc->refname, NULL);
 		needspace = true;
 	}
 	/* partition clauses are always inherited, so only print if no refname */
@@ -7384,10 +7367,8 @@ get_insert_query_def(Query *query, deparse_context *context)
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL,
+								   get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7480,8 +7461,7 @@ get_insert_query_def(Query *query, deparse_context *context)
 			if (!constraint)
 				elog(ERROR, "cache lookup failed for constraint %u",
 					 confl->constraint);
-			appendStringInfo(buf, " ON CONSTRAINT %s",
-							 quote_identifier(constraint));
+			appendStringInfoIdentifier(buf, " ON CONSTRAINT ", constraint, NULL);
 		}
 
 		if (confl->action == ONCONFLICT_NOTHING)
@@ -7703,10 +7683,7 @@ get_update_query_targetlist_def(Query *query, List *targetList,
 		 * Put out name of target column; look in the catalogs, not at
 		 * tle->resname, since resname will fail to track RENAME.
 		 */
-		appendStringInfoString(buf,
-							   quote_identifier(get_attname(rte->relid,
-															tle->resno,
-															false)));
+		appendStringInfoIdentifier(buf, NULL, get_attname(rte->relid, tle->resno, false), NULL);
 
 		/*
 		 * Print any indirection needed (subfields or subscripts), and strip
@@ -7897,10 +7874,10 @@ get_merge_query_def(Query *query, deparse_context *context)
 				appendStringInfoString(buf, sep);
 				sep = ", ";
 
-				appendStringInfoString(buf,
-									   quote_identifier(get_attname(rte->relid,
-																	tle->resno,
-																	false)));
+				appendStringInfoIdentifier(buf, NULL,
+										   get_attname(rte->relid,
+													   tle->resno,
+													   false), NULL);
 				strippedexprs = lappend(strippedexprs,
 										processIndirection((Node *) tle->expr,
 														   context));
@@ -7959,8 +7936,8 @@ get_utility_query_def(Query *query, deparse_context *context)
 
 		appendContextKeyword(context, "",
 							 0, PRETTYINDENT_STD, 1);
-		appendStringInfo(buf, "NOTIFY %s",
-						 quote_identifier(stmt->conditionname));
+		appendStringInfoIdentifier(buf, "NOTIFY ",
+								   stmt->conditionname, NULL);
 		if (stmt->payload)
 		{
 			appendStringInfoString(buf, ", ");
@@ -8416,11 +8393,11 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 	if (refname && need_prefix)
 	{
-		appendStringInfoString(buf, quote_identifier(refname));
+		appendStringInfoIdentifier(buf, NULL, refname, NULL);
 		appendStringInfoChar(buf, '.');
 	}
 	if (attname)
-		appendStringInfoString(buf, quote_identifier(attname));
+		appendStringInfoIdentifier(buf, NULL, attname, NULL);
 	else
 	{
 		appendStringInfoChar(buf, '*');
@@ -9358,11 +9335,10 @@ get_parameter(Param *param, deparse_context *context)
 				}
 				if (should_qualify)
 				{
-					appendStringInfoString(context->buf, quote_identifier(dpns->funcname));
-					appendStringInfoChar(context->buf, '.');
+					appendStringInfoIdentifier(context->buf, NULL, dpns->funcname, ".");
 				}
 
-				appendStringInfoString(context->buf, quote_identifier(argname));
+				appendStringInfoIdentifier(context->buf, NULL, argname, NULL);
 				return;
 			}
 		}
@@ -9943,7 +9919,7 @@ get_rule_expr(Node *node, deparse_context *context,
 			{
 				NamedArgExpr *na = (NamedArgExpr *) node;
 
-				appendStringInfo(buf, "%s => ", quote_identifier(na->name));
+				appendStringInfoIdentifier(buf, NULL, na->name, " => ");
 				get_rule_expr((Node *) na->arg, context, showimplicit);
 			}
 			break;
@@ -10231,7 +10207,7 @@ get_rule_expr(Node *node, deparse_context *context,
 				 */
 				fieldname = get_name_for_var_field((Var *) arg, fno,
 												   0, context);
-				appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+				appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 			}
 			break;
 
@@ -10921,8 +10897,8 @@ get_rule_expr(Node *node, deparse_context *context,
 				CurrentOfExpr *cexpr = (CurrentOfExpr *) node;
 
 				if (cexpr->cursor_name)
-					appendStringInfo(buf, "CURRENT OF %s",
-									 quote_identifier(cexpr->cursor_name));
+					appendStringInfoIdentifier(buf, "CURRENT OF ",
+											   cexpr->cursor_name, NULL);
 				else
 					appendStringInfo(buf, "CURRENT OF $%d",
 									 cexpr->cursor_param);
@@ -11156,8 +11132,8 @@ get_rule_expr(Node *node, deparse_context *context,
 						needcomma = true;
 
 						get_rule_expr((Node *) lfirst(lc2), context, showimplicit);
-						appendStringInfo(buf, " AS %s",
-										 quote_identifier(lfirst_node(String, lc1)->sval));
+						appendStringInfoIdentifier(buf, " AS ",
+												   lfirst_node(String, lc1)->sval, NULL);
 					}
 				}
 
@@ -11695,7 +11671,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 			if (wc->winref == wfunc->winref)
 			{
 				if (wc->name)
-					appendStringInfoString(buf, quote_identifier(wc->name));
+					appendStringInfoIdentifier(buf, NULL, wc->name, NULL);
 				else
 					get_rule_windowspec(wc, context->targetList, context);
 				break;
@@ -11721,7 +11697,7 @@ get_windowfunc_expr_helper(WindowFunc *wfunc, deparse_context *context,
 
 				if (wagg->winref == wfunc->winref)
 				{
-					appendStringInfoString(buf, quote_identifier(wagg->winname));
+					appendStringInfoIdentifier(buf, NULL, wagg->winname, NULL);
 					break;
 				}
 			}
@@ -12559,8 +12535,8 @@ get_xmltable(TableFunc *tf, deparse_context *context, bool showimplicit)
 			if (ns_node != NULL)
 			{
 				get_rule_expr(expr, context, showimplicit);
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(strVal(ns_node)));
+				appendStringInfoIdentifier(buf, " AS ",
+										   strVal(ns_node), NULL);
 			}
 			else
 			{
@@ -12646,7 +12622,7 @@ get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan,
 		appendStringInfoChar(context->buf, ' ');
 		appendContextKeyword(context, "NESTED PATH ", 0, 0, 0);
 		get_const_expr(scan->path->value, context, -1);
-		appendStringInfo(context->buf, " AS %s", quote_identifier(scan->path->name));
+		appendStringInfoIdentifier(context->buf, " AS ", scan->path->name, NULL);
 		get_json_table_columns(tf, scan, context, showimplicit);
 	}
 	else if (IsA(plan, JsonTableSiblingJoin))
@@ -12789,7 +12765,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 
 	get_const_expr(root->path->value, context, -1);
 
-	appendStringInfo(buf, " AS %s", quote_identifier(root->path->name));
+	appendStringInfoIdentifier(buf, " AS ", root->path->name, NULL);
 
 	if (jexpr->passing_values)
 	{
@@ -12813,9 +12789,7 @@ get_json_table(TableFunc *tf, deparse_context *context, bool showimplicit)
 			appendContextKeyword(context, "", 0, 0, 0);
 
 			get_rule_expr((Node *) lfirst(lc2), context, false);
-			appendStringInfo(buf, " AS %s",
-							 quote_identifier((lfirst_node(String, lc1))->sval)
-				);
+			appendStringInfoIdentifier(buf, " AS ", lfirst_node(String, lc1)->sval, NULL);
 		}
 
 		if (PRETTY_INDENT(context))
@@ -13120,7 +13094,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 				appendStringInfoChar(buf, ')');
 				break;
 			case RTE_CTE:
-				appendStringInfoString(buf, quote_identifier(rte->ctename));
+				appendStringInfoIdentifier(buf, NULL, rte->ctename, NULL);
 				break;
 			default:
 				elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
@@ -13207,7 +13181,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		if (j->usingClause)
 		{
 			ListCell   *lc;
-			bool		first = true;
+			const char *sep = "";
 
 			appendStringInfoString(buf, " USING (");
 			/* Use the assigned names, not what's in usingClause */
@@ -13215,17 +13189,13 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			{
 				char	   *colname = (char *) lfirst(lc);
 
-				if (first)
-					first = false;
-				else
-					appendStringInfoString(buf, ", ");
-				appendStringInfoString(buf, quote_identifier(colname));
+				appendStringInfoIdentifier(buf, sep, colname, NULL);
+				sep = ", ";
 			}
 			appendStringInfoChar(buf, ')');
 
 			if (j->join_using_alias)
-				appendStringInfo(buf, " AS %s",
-								 quote_identifier(j->join_using_alias->aliasname));
+				appendStringInfoIdentifier(buf, " AS ", j->join_using_alias->aliasname, NULL);
 		}
 		else if (j->quals)
 		{
@@ -13255,9 +13225,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			 * subtleties we don't want.  However, we might print a different
 			 * alias name than was there originally.
 			 */
-			appendStringInfo(buf, " %s",
-							 quote_identifier(get_rtable_name(j->rtindex,
-															  context)));
+			appendStringInfoIdentifier(buf, " ",
+									   get_rtable_name(j->rtindex,
+													   context), NULL);
 			get_column_alias_list(colinfo, context);
 		}
 	}
@@ -13332,9 +13302,9 @@ get_rte_alias(RangeTblEntry *rte, int varno, bool use_as,
 	}
 
 	if (printalias)
-		appendStringInfo(context->buf, "%s%s",
-						 use_as ? " AS " : " ",
-						 quote_identifier(refname));
+		appendStringInfoIdentifier(context->buf,
+								   use_as ? " AS " : " ",
+								   refname, NULL);
 }
 
 /*
@@ -13397,7 +13367,7 @@ get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
 		}
 		else
 			appendStringInfoString(buf, ", ");
-		appendStringInfoString(buf, quote_identifier(colname));
+		appendStringInfoIdentifier(buf, NULL, colname, NULL);
 	}
 	if (!first)
 		appendStringInfoChar(buf, ')');
@@ -13530,13 +13500,11 @@ get_opclass_name(Oid opclass, Oid actual_datatype,
 		/* Okay, we need the opclass name.  Do we need to qualify it? */
 		opcname = NameStr(opcrec->opcname);
 		if (OpclassIsVisible(opclass))
-			appendStringInfo(buf, " %s", quote_identifier(opcname));
+			appendStringInfoIdentifier(buf, " ", opcname, NULL);
 		else
 		{
 			nspname = get_namespace_name_or_temp(opcrec->opcnamespace);
-			appendStringInfo(buf, " %s.%s",
-							 quote_identifier(nspname),
-							 quote_identifier(opcname));
+			appendStringInfoQualifiedIdentifier(buf, NULL, nspname, opcname, NULL);
 		}
 	}
 	ReleaseSysCache(ht_opc);
@@ -13600,7 +13568,7 @@ processIndirection(Node *node, deparse_context *context)
 			Assert(list_length(fstore->fieldnums) == 1);
 			fieldname = get_attname(typrelid,
 									linitial_int(fstore->fieldnums), false);
-			appendStringInfo(buf, ".%s", quote_identifier(fieldname));
+			appendStringInfoIdentifier(buf, ".", fieldname, NULL);
 
 			/*
 			 * We ignore arg since it should be an uninteresting reference to
@@ -14155,7 +14123,7 @@ generate_operator_name(Oid operid, Oid arg1, Oid arg2)
 	else
 	{
 		nspname = get_namespace_name_or_temp(operform->oprnamespace);
-		appendStringInfo(&buf, "OPERATOR(%s.", quote_identifier(nspname));
+		appendStringInfoIdentifier(&buf, "OPERATOR(", nspname, ".");
 	}
 
 	appendStringInfoString(&buf, oprname);
@@ -14261,8 +14229,7 @@ add_cast_to(StringInfo buf, Oid typid)
 	typname = NameStr(typform->typname);
 	nspname = get_namespace_name_or_temp(typform->typnamespace);
 
-	appendStringInfo(buf, "::%s.%s",
-					 quote_identifier(nspname), quote_identifier(typname));
+	appendStringInfoQualifiedIdentifier(buf, "::", nspname, typname, NULL);
 
 	ReleaseSysCache(typetup);
 }
@@ -14359,12 +14326,12 @@ get_reloptions(StringInfo buf, Datum reloptions)
 {
 	Datum	   *options;
 	int			noptions;
-	int			i;
+	const char *sep = "";
 
 	deconstruct_array_builtin(DatumGetArrayTypeP(reloptions), TEXTOID,
 							  &options, NULL, &noptions);
 
-	for (i = 0; i < noptions; i++)
+	for (int i = 0; i < noptions; i++)
 	{
 		char	   *option = TextDatumGetCString(options[i]);
 		char	   *name;
@@ -14385,9 +14352,8 @@ get_reloptions(StringInfo buf, Datum reloptions)
 		else
 			value = "";
 
-		if (i > 0)
-			appendStringInfoString(buf, ", ");
-		appendStringInfo(buf, "%s=", quote_identifier(name));
+		appendStringInfoIdentifier(buf, sep, name, "=");
+		sep = ", ";
 
 		/*
 		 * In general we need to quote the value; but to avoid unnecessary
-- 
2.50.1 (Apple Git-155)



^ permalink  raw  reply  [nested|flat] 8+ messages in thread

*  Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2026-04-13 08:16  David Rowley <[email protected]>
  parent: Chao Li <[email protected]>
  1 sibling, 1 reply; 8+ messages in thread

From: David Rowley @ 2026-04-13 08:16 UTC (permalink / raw)
  To: Chao Li <[email protected]>; +Cc: Postgres hackers <[email protected]>

On Mon, 17 Nov 2025 at 22:49, Chao Li <[email protected]> wrote:
> ```
>     // Define a local buffer with size MAX_QUOTED_NAME_LEN
>     // MAX_QUOTED_NAME_LEN = MAX_NAMELEN * 2 + 3 to ensure no overflow
>     char attname[MAX_QUOTED_NAME_LEN];
>
>     // Add quotes and copy into the stack buffer
>     quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
>
>     // Copy the quoted identifier into a StringInfo
>     appendStringInfoString(&querybuf, attname);
> ```
>
> This pattern is expensive because:
>
> * it allocates a larger-than-necessary buffer on the stack
> * it incurs two pallocs and two data copies
>
> Looking further, the common pattern around quote_identifier() is similar:
>
> ```
>     appendStringInfoString(&relations, quote_identifier(relname));
> ```
>
> This also incurs two pallocs and two copies: quote_identifier() allocates a temporary buffer and copies the quoted identifier into it, and then appendStringInfoString() may allocate and copy again.

I'm trying to gauge where this patch is really coming from. You're
claiming it's to improve performance, but yet it only changes a small
subset of the code it could change. Are the ones that the v5 patches
change only the ones that matter for performance? or are you just
trying to improve these incrementally?

> Attached v1 is not intended to be the final version — it is mainly to demonstrate the idea and get feedback on the design direction.
>
> * 0001 implements `appendStringInfoIdentifier()` and uses it in a few places
> * 0002 switches ri_triggers.c to use it, resolving a complicated usage pattern and showing a path toward removing quoteOneName()
>
> Comments and suggestions on the overall direction would be very welcome.

I don't think this is a nice design. Most of the calls to
appendStringInfoIdentifier() have to pass NULL in one of both of the
prefix and/or suffix. This results in some hard to read code and
results in many extra function calls that results in the string being
harder to read for humans and harder to grep for. I think even if you
had a nice design, there'd be a huge amount of churn to fully
implement it. Do you have some sort of evidence as performance numbers
that this is worthwhile churn?

To acknowledge your off-list email pinging me about this thread and
referencing my recent commits in the area of StringInfo; to be clear,
those only change code that's new to or was changed in v19, and
they're all following a pattern that was agreed on many years ago. The
reason for doing those post-freeze is that we don't yet have a better
way to identify them sooner, and, per historical evidence of periodic
fixes, we expect these would eventually get fixed, and doing that
before we branch means there are fewer backpatching conflicts for
committers than there would be if we waited until after branching.

Looking around for better ideas for you... Going by the latest in [1],
there's been no progress getting custom format specifier checking
added to GCC, so I guess that means we don't want to improve this with
a custom format specifier.

This is just my view, so feel free to get someone else's, but I think
if you want to make improvements here, then you'll need to come up
with a design that's clearly better than what we have, otherwise the
churn is just not worthwhile. I don't have any great ideas on what you
could do. I see going by the following there are 20
appendStringInfoString() calls that use quote_identifier() on the
string argument. Those could be improved with a new function in
stringinfo.c that handles that, but that only gets you about 14% of
the way there, going by:

$ git grep -E "quote_identifier" | wc -l
143

$ git grep -E "appendStringInfoString.*quote_identifier" | wc -l
20

Perhaps there are more than 20, as that regex will miss ones that span
multiple lines.

David

[1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781





^ permalink  raw  reply  [nested|flat] 8+ messages in thread

* Re: quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed
@ 2026-04-13 09:28  Chao Li <[email protected]>
  parent: David Rowley <[email protected]>
  0 siblings, 0 replies; 8+ messages in thread

From: Chao Li @ 2026-04-13 09:28 UTC (permalink / raw)
  To: David Rowley <[email protected]>; +Cc: Postgres hackers <[email protected]>



> On Apr 13, 2026, at 16:16, David Rowley <[email protected]> wrote:
> 
> On Mon, 17 Nov 2025 at 22:49, Chao Li <[email protected]> wrote:
>> ```
>>    // Define a local buffer with size MAX_QUOTED_NAME_LEN
>>    // MAX_QUOTED_NAME_LEN = MAX_NAMELEN * 2 + 3 to ensure no overflow
>>    char attname[MAX_QUOTED_NAME_LEN];
>> 
>>    // Add quotes and copy into the stack buffer
>>    quoteOneName(attname, RIAttName(pk_rel, riinfo->pk_attnums[riinfo->nkeys - 1]));
>> 
>>    // Copy the quoted identifier into a StringInfo
>>    appendStringInfoString(&querybuf, attname);
>> ```
>> 
>> This pattern is expensive because:
>> 
>> * it allocates a larger-than-necessary buffer on the stack
>> * it incurs two pallocs and two data copies
>> 
>> Looking further, the common pattern around quote_identifier() is similar:
>> 
>> ```
>>    appendStringInfoString(&relations, quote_identifier(relname));
>> ```
>> 
>> This also incurs two pallocs and two copies: quote_identifier() allocates a temporary buffer and copies the quoted identifier into it, and then appendStringInfoString() may allocate and copy again.
> 
> I'm trying to gauge where this patch is really coming from. You're
> claiming it's to improve performance, but yet it only changes a small
> subset of the code it could change. Are the ones that the v5 patches
> change only the ones that matter for performance? or are you just
> trying to improve these incrementally?
> 
>> Attached v1 is not intended to be the final version — it is mainly to demonstrate the idea and get feedback on the design direction.
>> 
>> * 0001 implements `appendStringInfoIdentifier()` and uses it in a few places
>> * 0002 switches ri_triggers.c to use it, resolving a complicated usage pattern and showing a path toward removing quoteOneName()
>> 
>> Comments and suggestions on the overall direction would be very welcome.
> 
> I don't think this is a nice design. Most of the calls to
> appendStringInfoIdentifier() have to pass NULL in one of both of the
> prefix and/or suffix. This results in some hard to read code and
> results in many extra function calls that results in the string being
> harder to read for humans and harder to grep for. I think even if you
> had a nice design, there'd be a huge amount of churn to fully
> implement it. Do you have some sort of evidence as performance numbers
> that this is worthwhile churn?
> 
> To acknowledge your off-list email pinging me about this thread and
> referencing my recent commits in the area of StringInfo; to be clear,
> those only change code that's new to or was changed in v19, and
> they're all following a pattern that was agreed on many years ago. The
> reason for doing those post-freeze is that we don't yet have a better
> way to identify them sooner, and, per historical evidence of periodic
> fixes, we expect these would eventually get fixed, and doing that
> before we branch means there are fewer backpatching conflicts for
> committers than there would be if we waited until after branching.
> 
> Looking around for better ideas for you... Going by the latest in [1],
> there's been no progress getting custom format specifier checking
> added to GCC, so I guess that means we don't want to improve this with
> a custom format specifier.
> 
> This is just my view, so feel free to get someone else's, but I think
> if you want to make improvements here, then you'll need to come up
> with a design that's clearly better than what we have, otherwise the
> churn is just not worthwhile. I don't have any great ideas on what you
> could do. I see going by the following there are 20
> appendStringInfoString() calls that use quote_identifier() on the
> string argument. Those could be improved with a new function in
> stringinfo.c that handles that, but that only gets you about 14% of
> the way there, going by:
> 
> $ git grep -E "quote_identifier" | wc -l
> 143
> 
> $ git grep -E "appendStringInfoString.*quote_identifier" | wc -l
> 20
> 
> Perhaps there are more than 20, as that regex will miss ones that span
> multiple lines.
> 
> David
> 
> [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781

Thanks for the detailed explanation.

This is a fairly old patch, and the initial motivation was simply to deprecate quoteOneName(), as it seemed like a duplicate of quote_identifier(). However, as the implementation went on, the scope grew significantly and lost focus. In my off-list email, I mainly wanted to gauge whether you thought appendStringInfoIdentifier() was still worth pursuing, while it avoids a memory allocation and copy, I also had doubts regarding the design of the prefix and suffix parameters.

I think the answer is clear now. I'm going to withdraw this patch.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/









^ permalink  raw  reply  [nested|flat] 8+ messages in thread


end of thread, other threads:[~2026-04-13 09:28 UTC | newest]

Thread overview: 8+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-11-17 09:48  quoteOneName() inconsistency with quote_all_identifiers — replacement API proposed Chao Li <[email protected]>
2025-11-20 08:19 ` Chao Li <[email protected]>
2025-11-20 12:28   ` Álvaro Herrera <[email protected]>
2025-11-21 05:52     ` Chao Li <[email protected]>
2025-11-21 10:03       ` Chao Li <[email protected]>
2026-04-13 02:53         ` Chao Li <[email protected]>
2026-04-13 08:16 ` David Rowley <[email protected]>
2026-04-13 09:28   ` Chao Li <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox