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