On Thu, 2025-06-05 at 22:15 -0700, Jeff Davis wrote:
> To continue this thread, I did a symbol search in the meson build
> directory like (patterns.txt attached):

Attached a rough patch series which does what everyone seemed to agree
on:

  * Change some trivial ASCII cases to use pg_ascii_* variants
  * Set LC_COLLATE and LC_CTYPE to C with pg_perm_setlocale
  * Introduce a new global_lc_ctype for callers that still need to use
operations that depend on datctype

There should be no behavior changes in this series.

Benefits:

  * a tiny step toward multithreading, because connections to different
databases no longer require different setlocale() settings
  * easier to identify dependence on datctype, because callers will
need to refer to global_lc_ctype.
  * harder to accidentally depend on datctype or datcollate

Ideally, when the database locale provider is not libc, the user
shouldn't need to even think about a valid LC_CTYPE locale at all. But
that requires more work, and potentially risk of breakage.

Regards,
        Jeff Davis

`


From 21f5cc0bca48ef8d2fdc746385e3afda575fbd9e Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 09:50:53 -0700
Subject: [PATCH v1 1/8] copyfromparse.c: use pg_ascii_tolower() rather than
 tolower().

---
 src/backend/commands/copyfromparse.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index f5fc346e201..f52f2477df1 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -1538,7 +1538,7 @@ GetDecimalFromHex(char hex)
 	if (isdigit((unsigned char) hex))
 		return hex - '0';
 	else
-		return tolower((unsigned char) hex) - 'a' + 10;
+		return pg_ascii_tolower((unsigned char) hex) - 'a' + 10;
 }
 
 /*
-- 
2.43.0

From 5b60fbc629f6466c435021b8d3997b5763268ed1 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 09:54:40 -0700
Subject: [PATCH v1 2/8] contrib/spi/refint.c: use pg_ascii_tolower() instead.

---
 contrib/spi/refint.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c
index d5e25e07ae9..89898cad7b0 100644
--- a/contrib/spi/refint.c
+++ b/contrib/spi/refint.c
@@ -321,7 +321,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
 	if (nrefs < 1)
 		/* internal error */
 		elog(ERROR, "check_foreign_key: %d (< 1) number of references specified", nrefs);
-	action = tolower((unsigned char) *(args[1]));
+	action = pg_ascii_tolower((unsigned char) *(args[1]));
 	if (action != 'r' && action != 'c' && action != 's')
 		/* internal error */
 		elog(ERROR, "check_foreign_key: invalid action %s", args[1]);
-- 
2.43.0

From 8fcbfb8d5a5e38d6bb2e0d21486a81cebfa45721 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 09:58:24 -0700
Subject: [PATCH v1 3/8] isn.c: use pg_ascii_toupper() instead of toupper().

---
 contrib/isn/isn.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/contrib/isn/isn.c b/contrib/isn/isn.c
index 038c8ed4db7..1880c91844e 100644
--- a/contrib/isn/isn.c
+++ b/contrib/isn/isn.c
@@ -726,7 +726,7 @@ string2ean(const char *str, struct Node *escontext, ean13 *result,
 			if (type != INVALID)
 				goto eaninvalid;
 			type = ISSN;
-			*aux1++ = toupper((unsigned char) *aux2);
+			*aux1++ = pg_ascii_toupper((unsigned char) *aux2);
 			length++;
 		}
 		else if (length == 9 && (digit || *aux2 == 'X' || *aux2 == 'x') && last)
@@ -736,7 +736,7 @@ string2ean(const char *str, struct Node *escontext, ean13 *result,
 				goto eaninvalid;
 			if (type == INVALID)
 				type = ISBN;	/* ISMN must start with 'M' */
-			*aux1++ = toupper((unsigned char) *aux2);
+			*aux1++ = pg_ascii_toupper((unsigned char) *aux2);
 			length++;
 		}
 		else if (length == 11 && digit && last)
-- 
2.43.0

From d3bd2f24a9c450cbb2ff2f88f8efd4ba7c455be0 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 10:06:01 -0700
Subject: [PATCH v1 4/8] inet_net_pton.c: use pg_ascii_tolower() rather than
 tolower().

---
 src/backend/utils/adt/inet_net_pton.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/inet_net_pton.c b/src/backend/utils/adt/inet_net_pton.c
index ef2236d9f04..3b0db2a3799 100644
--- a/src/backend/utils/adt/inet_net_pton.c
+++ b/src/backend/utils/adt/inet_net_pton.c
@@ -115,8 +115,7 @@ inet_cidr_pton_ipv4(const char *src, u_char *dst, size_t size)
 		src++;					/* skip x or X. */
 		while ((ch = *src++) != '\0' && isxdigit((unsigned char) ch))
 		{
-			if (isupper((unsigned char) ch))
-				ch = tolower((unsigned char) ch);
+			ch = pg_ascii_tolower((unsigned char) ch);
 			n = strchr(xdigits, ch) - xdigits;
 			assert(n >= 0 && n <= 15);
 			if (dirty == 0)
-- 
2.43.0

From 60e659df02c33b14ee232c578d8dfdbfe1fb6dc6 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 14:13:16 -0700
Subject: [PATCH v1 5/8] Add global_lc_ctype to hold locale_t for datctype.

Callers of locale-aware ctype operations should use the "_l" variants
of the functions and pass global_lc_ctype for the locale. Doing so
avoids depending on setlocale().
---
 src/backend/utils/adt/pg_locale_libc.c | 32 ++++++++++++++++++++++++--
 src/backend/utils/init/postinit.c      |  2 ++
 src/include/utils/pg_locale.h          |  7 ++++++
 3 files changed, 39 insertions(+), 2 deletions(-)

diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c
index 199857e22db..a45fb4df38c 100644
--- a/src/backend/utils/adt/pg_locale_libc.c
+++ b/src/backend/utils/adt/pg_locale_libc.c
@@ -85,6 +85,12 @@ static size_t strupper_libc_mb(char *dest, size_t destsize,
 							   const char *src, ssize_t srclen,
 							   pg_locale_t locale);
 
+/*
+ * Represents datctype in a global variable, so that we don't need to rely on
+ * setlocale().
+ */
+locale_t	global_lc_ctype = NULL;
+
 static const struct collate_methods collate_methods_libc = {
 	.strncoll = strncoll_libc,
 	.strnxfrm = strnxfrm_libc,
@@ -417,6 +423,28 @@ strupper_libc_mb(char *dest, size_t destsize, const char *src, ssize_t srclen,
 	return result_size;
 }
 
+void
+init_global_lc_ctype(const char *ctype)
+{
+	locale_t	loc;
+
+	errno = 0;
+#ifndef WIN32
+	loc = newlocale(LC_CTYPE_MASK, ctype, NULL);
+#else
+	loc = _create_locale(LC_ALL, ctype);
+#endif
+
+	if (!loc)
+		ereport(FATAL,
+				(errmsg("database locale is incompatible with operating system"),
+				 errdetail("The database was initialized with LC_CTYPE \"%s\", "
+						   " which is not recognized by setlocale().", ctype),
+				 errhint("Recreate the database with another locale or install the missing locale.")));
+
+	global_lc_ctype = loc;
+}
+
 pg_locale_t
 create_pg_locale_libc(Oid collid, MemoryContext context)
 {
@@ -912,7 +940,7 @@ wchar2char(char *to, const wchar_t *from, size_t tolen, pg_locale_t locale)
 	if (locale == (pg_locale_t) 0)
 	{
 		/* Use wcstombs directly for the default locale */
-		result = wcstombs(to, from, tolen);
+		result = wcstombs_l(to, from, tolen, global_lc_ctype);
 	}
 	else
 	{
@@ -972,7 +1000,7 @@ char2wchar(wchar_t *to, size_t tolen, const char *from, size_t fromlen,
 		if (locale == (pg_locale_t) 0)
 		{
 			/* Use mbstowcs directly for the default locale */
-			result = mbstowcs(to, str, tolen);
+			result = mbstowcs_l(to, str, tolen, global_lc_ctype);
 		}
 		else
 		{
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index c86ceefda94..3eaa1486f6f 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -431,6 +431,8 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
 						   " which is not recognized by setlocale().", ctype),
 				 errhint("Recreate the database with another locale or install the missing locale.")));
 
+	init_global_lc_ctype(ctype);
+
 	if (strcmp(ctype, "C") == 0 ||
 		strcmp(ctype, "POSIX") == 0)
 		database_ctype_is_c = true;
diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h
index 7b8cbf58d2c..7fdf420dd7a 100644
--- a/src/include/utils/pg_locale.h
+++ b/src/include/utils/pg_locale.h
@@ -32,6 +32,12 @@ extern PGDLLIMPORT char *localized_full_days[];
 extern PGDLLIMPORT char *localized_abbrev_months[];
 extern PGDLLIMPORT char *localized_full_months[];
 
+/*
+ * Represents datctype in a global variable, so that we don't need to rely on
+ * setlocale().
+ */
+extern PGDLLIMPORT locale_t global_lc_ctype;
+
 /* is the databases's LC_CTYPE the C locale? */
 extern PGDLLIMPORT bool database_ctype_is_c;
 
@@ -121,6 +127,7 @@ struct pg_locale_struct
 	}			info;
 };
 
+extern void init_global_lc_ctype(const char *ctype);
 extern void init_database_collation(void);
 extern pg_locale_t pg_newlocale_from_collation(Oid collid);
 
-- 
2.43.0

From c4eaa408a045d0ff9baaa44571469be0ffabf2f2 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 14:17:22 -0700
Subject: [PATCH v1 6/8] Use global_lc_ctype for callers of locale-aware
 functions.

Rather than relying on setlocale() to set the right LC_CTYPE, use
global_lc_ctype explicitly to refer to datctype.
---
 contrib/fuzzystrmatch/dmetaphone.c    |  3 ++-
 contrib/fuzzystrmatch/fuzzystrmatch.c | 19 +++++++++++--------
 contrib/ltree/crc32.c                 |  2 +-
 src/backend/parser/scansup.c          |  3 ++-
 src/backend/tsearch/ts_locale.c       |  4 ++--
 src/port/pgstrcasecmp.c               | 20 ++++++++++++++------
 6 files changed, 32 insertions(+), 19 deletions(-)

diff --git a/contrib/fuzzystrmatch/dmetaphone.c b/contrib/fuzzystrmatch/dmetaphone.c
index 6627b2b8943..07d8781cd2a 100644
--- a/contrib/fuzzystrmatch/dmetaphone.c
+++ b/contrib/fuzzystrmatch/dmetaphone.c
@@ -99,6 +99,7 @@ The remaining code is authored by Andrew Dunstan <amduns...@ncshp.org> and
 #include "postgres.h"
 
 #include "utils/builtins.h"
+#include "utils/pg_locale.h"
 
 /* turn off assertions for embedded function */
 #define NDEBUG
@@ -284,7 +285,7 @@ MakeUpper(metastring *s)
 	char	   *i;
 
 	for (i = s->str; *i; i++)
-		*i = toupper((unsigned char) *i);
+		*i = toupper_l((unsigned char) *i, global_lc_ctype);
 }
 
 
diff --git a/contrib/fuzzystrmatch/fuzzystrmatch.c b/contrib/fuzzystrmatch/fuzzystrmatch.c
index e7cc314b763..b619178a1f6 100644
--- a/contrib/fuzzystrmatch/fuzzystrmatch.c
+++ b/contrib/fuzzystrmatch/fuzzystrmatch.c
@@ -41,6 +41,7 @@
 #include <ctype.h>
 
 #include "utils/builtins.h"
+#include "utils/pg_locale.h"
 #include "utils/varlena.h"
 #include "varatt.h"
 
@@ -56,13 +57,15 @@ static void _soundex(const char *instr, char *outstr);
 
 #define SOUNDEX_LEN 4
 
+#define TOUPPER(x) toupper_l((unsigned char) (x), global_lc_ctype)
+
 /*									ABCDEFGHIJKLMNOPQRSTUVWXYZ */
 static const char *const soundex_table = "01230120022455012623010202";
 
 static char
 soundex_code(char letter)
 {
-	letter = toupper((unsigned char) letter);
+	letter = TOUPPER((unsigned char) letter);
 	/* Defend against non-ASCII letters */
 	if (letter >= 'A' && letter <= 'Z')
 		return soundex_table[letter - 'A'];
@@ -124,7 +127,7 @@ getcode(char c)
 {
 	if (isalpha((unsigned char) c))
 	{
-		c = toupper((unsigned char) c);
+		c = TOUPPER((unsigned char) c);
 		/* Defend against non-ASCII letters */
 		if (c >= 'A' && c <= 'Z')
 			return _codes[c - 'A'];
@@ -301,18 +304,18 @@ metaphone(PG_FUNCTION_ARGS)
  * accessing the array directly... */
 
 /* Look at the next letter in the word */
-#define Next_Letter (toupper((unsigned char) word[w_idx+1]))
+#define Next_Letter (TOUPPER((unsigned char) word[w_idx+1]))
 /* Look at the current letter in the word */
-#define Curr_Letter (toupper((unsigned char) word[w_idx]))
+#define Curr_Letter (TOUPPER((unsigned char) word[w_idx]))
 /* Go N letters back. */
 #define Look_Back_Letter(n) \
-	(w_idx >= (n) ? toupper((unsigned char) word[w_idx-(n)]) : '\0')
+	(w_idx >= (n) ? TOUPPER((unsigned char) word[w_idx-(n)]) : '\0')
 /* Previous letter.  I dunno, should this return null on failure? */
 #define Prev_Letter (Look_Back_Letter(1))
 /* Look two letters down.  It makes sure you don't walk off the string. */
 #define After_Next_Letter \
-	(Next_Letter != '\0' ? toupper((unsigned char) word[w_idx+2]) : '\0')
-#define Look_Ahead_Letter(n) toupper((unsigned char) Lookahead(word+w_idx, n))
+	(Next_Letter != '\0' ? TOUPPER((unsigned char) word[w_idx+2]) : '\0')
+#define Look_Ahead_Letter(n) TOUPPER((unsigned char) Lookahead(word+w_idx, n))
 
 
 /* Allows us to safely look ahead an arbitrary # of letters */
@@ -742,7 +745,7 @@ _soundex(const char *instr, char *outstr)
 	}
 
 	/* Take the first letter as is */
-	*outstr++ = (char) toupper((unsigned char) *instr++);
+	*outstr++ = (char) TOUPPER((unsigned char) *instr++);
 
 	count = 1;
 	while (*instr && count < SOUNDEX_LEN)
diff --git a/contrib/ltree/crc32.c b/contrib/ltree/crc32.c
index 134f46a805e..2ea7c8a5ec0 100644
--- a/contrib/ltree/crc32.c
+++ b/contrib/ltree/crc32.c
@@ -12,7 +12,7 @@
 
 #ifdef LOWER_NODE
 #include <ctype.h>
-#define TOLOWER(x)	tolower((unsigned char) (x))
+#define TOLOWER(x)	tolower_l((unsigned char) (x), global_lc_ctype)
 #else
 #define TOLOWER(x)	(x)
 #endif
diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c
index 2feb2b6cf5a..98c1f30d04f 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"
 
 
 /*
@@ -68,7 +69,7 @@ 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);
+			ch = tolower_l(ch, global_lc_ctype);
 		result[i] = (char) ch;
 	}
 	result[i] = '\0';
diff --git a/src/backend/tsearch/ts_locale.c b/src/backend/tsearch/ts_locale.c
index b77d8c23d36..51ba3b41813 100644
--- a/src/backend/tsearch/ts_locale.c
+++ b/src/backend/tsearch/ts_locale.c
@@ -43,7 +43,7 @@ t_isalpha(const char *ptr)
 
 	char2wchar(character, WC_BUF_LEN, ptr, clen, mylocale);
 
-	return iswalpha((wint_t) character[0]);
+	return iswalpha_l((wint_t) character[0], global_lc_ctype);
 }
 
 int
@@ -58,7 +58,7 @@ t_isalnum(const char *ptr)
 
 	char2wchar(character, WC_BUF_LEN, ptr, clen, mylocale);
 
-	return iswalnum((wint_t) character[0]);
+	return iswalnum_l((wint_t) character[0], global_lc_ctype);
 }
 
 
diff --git a/src/port/pgstrcasecmp.c b/src/port/pgstrcasecmp.c
index ec2b3a75c3d..f6dc6b0ff3b 100644
--- a/src/port/pgstrcasecmp.c
+++ b/src/port/pgstrcasecmp.c
@@ -28,6 +28,14 @@
 
 #include <ctype.h>
 
+#ifndef FRONTEND
+extern PGDLLIMPORT locale_t global_lc_ctype;
+#define TOUPPER(x) toupper_l((unsigned char) (x), global_lc_ctype)
+#define TOLOWER(x) tolower_l((unsigned char) (x), global_lc_ctype)
+#else
+#define TOUPPER(x) toupper(x)
+#define TOLOWER(x) tolower(x)
+#endif
 
 /*
  * Case-independent comparison of two null-terminated strings.
@@ -45,12 +53,12 @@ pg_strcasecmp(const char *s1, const char *s2)
 			if (ch1 >= 'A' && ch1 <= 'Z')
 				ch1 += 'a' - 'A';
 			else if (IS_HIGHBIT_SET(ch1) && isupper(ch1))
-				ch1 = tolower(ch1);
+				ch1 = TOLOWER(ch1);
 
 			if (ch2 >= 'A' && ch2 <= 'Z')
 				ch2 += 'a' - 'A';
 			else if (IS_HIGHBIT_SET(ch2) && isupper(ch2))
-				ch2 = tolower(ch2);
+				ch2 = TOLOWER(ch2);
 
 			if (ch1 != ch2)
 				return (int) ch1 - (int) ch2;
@@ -78,12 +86,12 @@ pg_strncasecmp(const char *s1, const char *s2, size_t n)
 			if (ch1 >= 'A' && ch1 <= 'Z')
 				ch1 += 'a' - 'A';
 			else if (IS_HIGHBIT_SET(ch1) && isupper(ch1))
-				ch1 = tolower(ch1);
+				ch1 = TOLOWER(ch1);
 
 			if (ch2 >= 'A' && ch2 <= 'Z')
 				ch2 += 'a' - 'A';
 			else if (IS_HIGHBIT_SET(ch2) && isupper(ch2))
-				ch2 = tolower(ch2);
+				ch2 = TOLOWER(ch2);
 
 			if (ch1 != ch2)
 				return (int) ch1 - (int) ch2;
@@ -107,7 +115,7 @@ pg_toupper(unsigned char ch)
 	if (ch >= 'a' && ch <= 'z')
 		ch += 'A' - 'a';
 	else if (IS_HIGHBIT_SET(ch) && islower(ch))
-		ch = toupper(ch);
+		ch = TOUPPER(ch);
 	return ch;
 }
 
@@ -124,7 +132,7 @@ pg_tolower(unsigned char ch)
 	if (ch >= 'A' && ch <= 'Z')
 		ch += 'a' - 'A';
 	else if (IS_HIGHBIT_SET(ch) && isupper(ch))
-		ch = tolower(ch);
+		ch = TOLOWER(ch);
 	return ch;
 }
 
-- 
2.43.0

From 0fb479743de60a59c9139d27d981881f337becb2 Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 14:17:33 -0700
Subject: [PATCH v1 7/8] Fix the last remaining callers relying on setlocale().

---
 configure                         |  2 +-
 configure.ac                      |  2 ++
 meson.build                       |  2 ++
 src/backend/tsearch/wparser_def.c | 40 +++++++++++++++++++++++++++++--
 src/include/pg_config.h.in        |  6 +++++
 5 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/configure b/configure
index 4f15347cc95..2660c29e0d2 100755
--- a/configure
+++ b/configure
@@ -15616,7 +15616,7 @@ fi
 LIBS_including_readline="$LIBS"
 LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
 
-for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info getauxval getifaddrs getpeerucred inet_pton kqueue localeconv_l mbstowcs_l posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l
+for ac_func in backtrace_symbols copyfile copy_file_range elf_aux_info getauxval getifaddrs getpeerucred inet_pton iswxdigit_l isxdigit_l kqueue localeconv_l mbstowcs_l posix_fallocate ppoll pthread_is_threaded_np setproctitle setproctitle_fast strsignal syncfs sync_file_range uselocale wcstombs_l
 do :
   as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
 ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"
diff --git a/configure.ac b/configure.ac
index 4b8335dc613..2d16c5fd43f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1789,6 +1789,8 @@ AC_CHECK_FUNCS(m4_normalize([
 	getifaddrs
 	getpeerucred
 	inet_pton
+	iswxdigit_l
+	isxdigit_l
 	kqueue
 	localeconv_l
 	mbstowcs_l
diff --git a/meson.build b/meson.build
index d142e3e408b..0bd6f9f2076 100644
--- a/meson.build
+++ b/meson.build
@@ -2880,6 +2880,8 @@ func_checks = [
   ['getpeerucred'],
   ['inet_aton'],
   ['inet_pton'],
+  ['iswxdigit_l'],
+  ['isxdigit_l'],
   ['kqueue'],
   ['localeconv_l'],
   ['mbstowcs_l'],
diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c
index 79bcd32a063..0fe90b7ad8d 100644
--- a/src/backend/tsearch/wparser_def.c
+++ b/src/backend/tsearch/wparser_def.c
@@ -411,6 +411,40 @@ TParserCopyClose(TParser *prs)
 }
 
 
+#ifndef HAVE_ISXDIGIT_L
+static int
+isxdigit_l(wint_t wc, locale_t loc)
+{
+#ifdef WIN32
+	return _isxdigit_l(wc, loc);
+#else
+	size_t		result;
+	locale_t	save_locale = uselocale(loc);
+
+	result = isxdigit(wc);
+	uselocale(save_locale);
+	return result;
+#endif
+}
+#endif
+#ifndef HAVE_ISWXDIGIT_L
+static int
+iswxdigit_l(wint_t wc, locale_t loc)
+{
+#ifdef WIN32
+	return _iswxdigit_l(wc, loc);
+#else
+	size_t		result;
+	locale_t	save_locale = uselocale(loc);
+
+	result = iswxdigit(wc);
+	uselocale(save_locale);
+	return result;
+#endif
+}
+#endif
+
+
 /*
  * Character-type support functions, equivalent to is* macros, but
  * working with any possible encodings and locales. Notes:
@@ -436,9 +470,11 @@ p_is##type(TParser *prs)													\
 				return nonascii;											\
 			return is##type(c);												\
 		}																	\
-		return isw##type(*(prs->wstr + prs->state->poschar));				\
+		return isw##type##_l(*(prs->wstr + prs->state->poschar),			\
+							 global_lc_ctype);								\
 	}																		\
-	return is##type(*(unsigned char *) (prs->str + prs->state->posbyte));	\
+	return is##type##_l(*(unsigned char *) (prs->str + prs->state->posbyte),	\
+						global_lc_ctype);									\
 }																			\
 																			\
 static int																	\
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 726a7c1be1f..f06396c94f4 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -229,6 +229,12 @@
 /* Define to 1 if you have the global variable 'int timezone'. */
 #undef HAVE_INT_TIMEZONE
 
+/* Define to 1 if you have the `iswxdigit_l' function. */
+#undef HAVE_ISWXDIGIT_L
+
+/* Define to 1 if you have the `isxdigit_l' function. */
+#undef HAVE_ISXDIGIT_L
+
 /* Define to 1 if __builtin_constant_p(x) implies "i"(x) acceptance. */
 #undef HAVE_I_CONSTRAINT__BUILTIN_CONSTANT_P
 
-- 
2.43.0

From 9dc49121a391ccdefa19904329171e1fbe9a8a3d Mon Sep 17 00:00:00 2001
From: Jeff Davis <j...@j-davis.com>
Date: Fri, 6 Jun 2025 14:14:22 -0700
Subject: [PATCH v1 8/8] Set process LC_COLLATE=C and LC_CTYPE=C.

Now that locale-aware functions use global_lc_locale rather than
relying on setlocale(), set LC_COLLATE and LC_CTYPE to C for
consistency.
---
 src/backend/utils/init/postinit.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 3eaa1486f6f..9841a33689a 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -417,19 +417,17 @@ CheckMyDatabase(const char *name, bool am_superuser, bool override_allow_connect
 	datum = SysCacheGetAttrNotNull(DATABASEOID, tup, Anum_pg_database_datctype);
 	ctype = TextDatumGetCString(datum);
 
-	if (pg_perm_setlocale(LC_COLLATE, collate) == NULL)
-		ereport(FATAL,
-				(errmsg("database locale is incompatible with operating system"),
-				 errdetail("The database was initialized with LC_COLLATE \"%s\", "
-						   " which is not recognized by setlocale().", collate),
-				 errhint("Recreate the database with another locale or install the missing locale.")));
-
-	if (pg_perm_setlocale(LC_CTYPE, ctype) == NULL)
-		ereport(FATAL,
-				(errmsg("database locale is incompatible with operating system"),
-				 errdetail("The database was initialized with LC_CTYPE \"%s\", "
-						   " which is not recognized by setlocale().", ctype),
-				 errhint("Recreate the database with another locale or install the missing locale.")));
+	/*
+	 * Set LC_COLLATE and LC_CTYPE both to "C" for consistency.
+	 *
+	 * Historically, these were set to datcollate and datctype, respectively,
+	 * but that made it too easy to depend on setlocale() at odd places
+	 * throughout the server.
+	 */
+	if (pg_perm_setlocale(LC_COLLATE, "C") == NULL)
+		elog(ERROR, "failure setting LC_COLLATE=\"C\"");
+	if (pg_perm_setlocale(LC_CTYPE, "C") == NULL)
+		elog(ERROR, "failure setting LC_CTYPE=\"C\"");
 
 	init_global_lc_ctype(ctype);
 
-- 
2.43.0

Reply via email to