From ddc5eb689d4d5801778e1f8f5b5a1056a44a001b Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Sun, 26 Oct 2025 15:04:58 -0700
Subject: [PATCH v7 4/9] Avoid global LC_CTYPE dependency in scansup.c.

Call char_tolower() instead of tolower() in downcase_identifier().

The function downcase_identifier() may be called before locale support
is initialized -- e.g. during GUC processing in the postmaster -- so
if the locale is unavailable, char_tolower() uses plain ASCII
semantics.

That can result in a difference in behavior during that early stage of
processing, but previously it would have depended on the postmaster
environment variable LC_CTYPE, which would have been fragile anyway.
---
 src/backend/parser/scansup.c      |  5 +++--
 src/backend/utils/adt/pg_locale.c | 16 ++++++++++++++--
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c
index 2feb2b6cf5a..872075ba220 100644
--- a/src/backend/parser/scansup.c
+++ b/src/backend/parser/scansup.c
@@ -18,6 +18,7 @@
 
 #include "mb/pg_wchar.h"
 #include "parser/scansup.h"
+#include "utils/pg_locale.h"
 
 
 /*
@@ -67,8 +68,8 @@ downcase_identifier(const char *ident, int len, bool warn, bool truncate)
 
 		if (ch >= 'A' && ch <= 'Z')
 			ch += 'a' - 'A';
-		else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch))
-			ch = tolower(ch);
+		else if (enc_is_single_byte && IS_HIGHBIT_SET(ch))
+			ch = char_tolower(ch, NULL);
 		result[i] = (char) ch;
 	}
 	result[i] = '\0';
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 9631d274611..7e13c601643 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1568,11 +1568,17 @@ char_is_cased(char ch, pg_locale_t locale)
  *
  * Convert single-byte char to lowercase. Not correct for multibyte encodings,
  * but needed for historical compatibility purposes.
+ *
+ * If locale is NULL, use the default database locale. This function may be
+ * called before the database locale is initialized, in which case it uses
+ * plain ASCII semantics.
  */
 char
 char_tolower(unsigned char ch, pg_locale_t locale)
 {
-	if (locale->ctype == NULL)
+	if (locale == NULL)
+		locale = default_locale;
+	if (locale == NULL || locale->ctype == NULL)
 	{
 		if (ch >= 'A' && ch <= 'Z')
 			return ch + ('a' - 'A');
@@ -1586,11 +1592,17 @@ char_tolower(unsigned char ch, pg_locale_t locale)
  *
  * Convert single-byte char to uppercase. Not correct for multibyte encodings,
  * but needed for historical compatibility purposes.
+ *
+ * If locale is NULL, use the default database locale. This function may be
+ * called before the database locale is initialized, in which case it uses
+ * plain ASCII semantics.
  */
 char
 char_toupper(unsigned char ch, pg_locale_t locale)
 {
-	if (locale->ctype == NULL)
+	if (locale == NULL)
+		locale = default_locale;
+	if (locale == NULL || locale->ctype == NULL)
 	{
 		if (ch >= 'a' && ch <= 'z')
 			return ch - ('a' - 'A');
-- 
2.43.0

