From c58566d06f964166370e93f1bc8444a534bd0bd2 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Thu, 10 Sep 2026 18:13:17 -0700 Subject: [PATCH] Revert 87b2968df0 and 0a90df58cf. Those changes were intended to avoid dependencies on the global libc locale settings by basing identifier casefolding on the default locale. They introduced an unintentional behavior difference for the builtin provider with locale C and a single-byte encoding. Instead of fixing that edge case, just revert the changes. The identifier casefolding behavior has always been based on the global libc locale, and clients make an effort to use the same rules. Until we have a plan to change that, provider-specific identifier casefolding doesn't make sense. Reported-by: Noah Misch Discussion: https://postgr.es/m/20260826225510.10.noahmisch@microsoft.com Backpatch-through: 19 --- src/backend/parser/scansup.c | 36 +++++++---- src/backend/utils/adt/pg_locale.c | 24 ------- src/backend/utils/adt/pg_locale_builtin.c | 2 - src/backend/utils/adt/pg_locale_icu.c | 76 +---------------------- src/backend/utils/adt/pg_locale_libc.c | 33 ---------- src/include/utils/pg_locale.h | 8 --- 6 files changed, 25 insertions(+), 154 deletions(-) diff --git a/src/backend/parser/scansup.c b/src/backend/parser/scansup.c index cd9594226d6..8e7078f42c8 100644 --- a/src/backend/parser/scansup.c +++ b/src/backend/parser/scansup.c @@ -18,7 +18,6 @@ #include "mb/pg_wchar.h" #include "parser/scansup.h" -#include "utils/pg_locale.h" /* @@ -47,22 +46,35 @@ char * downcase_identifier(const char *ident, int len, bool warn, bool truncate) { char *result; - size_t needed pg_attribute_unused(); + int i; + bool enc_is_single_byte; + + result = palloc(len + 1); + enc_is_single_byte = pg_database_encoding_max_length() == 1; /* - * Preserves string length. - * - * NB: if we decide to support Unicode-aware identifier case folding, then - * we need to account for a change in string length. + * SQL99 specifies Unicode-aware case normalization, which we don't yet + * have the infrastructure for. Instead we use tolower() to provide a + * locale-aware translation. However, there are some locales where this + * is not right either (eg, Turkish may do strange things with 'i' and + * 'I'). Our current compromise is to use tolower() for characters with + * the high bit set, as long as they aren't part of a multi-byte + * character, and use an ASCII-only downcasing for 7-bit characters. */ - result = palloc(len + 1); + for (i = 0; i < len; i++) + { + unsigned char ch = (unsigned char) ident[i]; - needed = pg_downcase_ident(result, len + 1, ident, len); - Assert(needed == len); - Assert(result[len] == '\0'); + if (ch >= 'A' && ch <= 'Z') + ch += 'a' - 'A'; + else if (enc_is_single_byte && IS_HIGHBIT_SET(ch) && isupper(ch)) + ch = tolower(ch); + result[i] = (char) ch; + } + result[i] = '\0'; - if (len >= NAMEDATALEN && truncate) - truncate_identifier(result, len, warn); + if (i >= NAMEDATALEN && truncate) + truncate_identifier(result, i, warn); return result; } diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index cca08ceac0b..88d2d54d388 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -1434,30 +1434,6 @@ pg_strfold(char *dst, size_t dstsize, const char *src, size_t srclen, return locale->ctype->strfold(dst, dstsize, src, srclen, locale); } -/* - * pg_downcase_ident() - * - * Lowercase an identifier using historical identifier-folding semantics, and - * return the result length (not including terminating NUL). If the result - * length is less than dstsize, the NUL-terminated result is stored in dst; - * otherwise the contents of dst are undefined. - * - * XXX: callers currently depend on the result length being equal to srclen, - * but that may change in the future if we change to proper case folding. - */ -size_t -pg_downcase_ident(char *dst, size_t dstsize, const char *src, size_t srclen) -{ - pg_locale_t locale = default_locale; - - if (locale == NULL || locale->ctype == NULL || - locale->ctype->downcase_ident == NULL) - return strlower_c(dst, dstsize, src, srclen); - else - return locale->ctype->downcase_ident(dst, dstsize, src, srclen, - locale); -} - /* * pg_strcoll * diff --git a/src/backend/utils/adt/pg_locale_builtin.c b/src/backend/utils/adt/pg_locale_builtin.c index 8fa0b637b06..184d35f9360 100644 --- a/src/backend/utils/adt/pg_locale_builtin.c +++ b/src/backend/utils/adt/pg_locale_builtin.c @@ -219,8 +219,6 @@ static const struct ctype_methods ctype_methods_builtin = { .strtitle = strtitle_builtin, .strupper = strupper_builtin, .strfold = strfold_builtin, - /* uses plain ASCII semantics for historical reasons */ - .downcase_ident = NULL, .wc_isdigit = wc_isdigit_builtin, .wc_isalpha = wc_isalpha_builtin, .wc_isalnum = wc_isalnum_builtin, diff --git a/src/backend/utils/adt/pg_locale_icu.c b/src/backend/utils/adt/pg_locale_icu.c index c35b9edc652..d0081264ea5 100644 --- a/src/backend/utils/adt/pg_locale_icu.c +++ b/src/backend/utils/adt/pg_locale_icu.c @@ -72,8 +72,6 @@ static size_t strupper_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen, pg_locale_t locale); static size_t strfold_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen, pg_locale_t locale); -static size_t downcase_ident_icu(char *dst, size_t dstsize, const char *src, - size_t srclen, pg_locale_t locale); static int strncoll_icu(const char *arg1, size_t len1, const char *arg2, size_t len2, pg_locale_t locale); @@ -145,7 +143,7 @@ static int32_t foldcase_options(const char *locale); /* * XXX: many of the functions below rely on casts directly from pg_wchar to - * UChar32, which is correct for UTF-8 and LATIN1, but not in general. + * UChar32, which is correct for the UTF-8 encoding, but not in general. */ static pg_wchar @@ -270,7 +268,6 @@ static const struct ctype_methods ctype_methods_icu = { .strtitle = strtitle_icu, .strupper = strupper_icu, .strfold = strfold_icu, - .downcase_ident = downcase_ident_icu, .wc_isdigit = wc_isdigit_icu, .wc_isalpha = wc_isalpha_icu, .wc_isalnum = wc_isalnum_icu, @@ -291,8 +288,6 @@ static const struct ctype_methods ctype_methods_icu_utf8 = { .strtitle = strtitle_icu_utf8, .strupper = strupper_icu_utf8, .strfold = strfold_icu_utf8, - /* uses plain ASCII semantics for historical reasons */ - .downcase_ident = NULL, .wc_isdigit = wc_isdigit_icu, .wc_isalpha = wc_isalpha_icu, .wc_isalnum = wc_isalnum_icu, @@ -308,28 +303,6 @@ static const struct ctype_methods ctype_methods_icu_utf8 = { .wc_tolower = tolower_icu, }; -/* - * ICU still depends on libc for compatibility with certain historical - * behavior for single-byte encodings. See downcase_ident_icu(). - * - * XXX: consider fixing by decoding the single byte into a code point, and - * using u_tolower(). - */ -static locale_t -make_libc_ctype_locale(const char *ctype) -{ - locale_t loc; - -#ifndef WIN32 - loc = newlocale(LC_CTYPE_MASK, ctype, NULL); -#else - loc = _create_locale(LC_ALL, ctype); -#endif - if (!loc) - report_newlocale_failure(ctype); - - return loc; -} #endif pg_locale_t @@ -340,7 +313,6 @@ create_pg_locale_icu(Oid collid, MemoryContext context) const char *iculocstr; const char *icurules = NULL; UCollator *collator; - locale_t loc = (locale_t) 0; pg_locale_t result; if (collid == DEFAULT_COLLATION_OID) @@ -363,18 +335,6 @@ create_pg_locale_icu(Oid collid, MemoryContext context) if (!isnull) icurules = TextDatumGetCString(datum); - /* libc only needed for default locale and single-byte encoding */ - if (pg_database_encoding_max_length() == 1) - { - const char *ctype; - - datum = SysCacheGetAttrNotNull(DATABASEOID, tp, - Anum_pg_database_datctype); - ctype = TextDatumGetCString(datum); - - loc = make_libc_ctype_locale(ctype); - } - ReleaseSysCache(tp); } else @@ -405,7 +365,6 @@ create_pg_locale_icu(Oid collid, MemoryContext context) result = MemoryContextAllocZero(context, sizeof(struct pg_locale_struct)); result->icu.locale = MemoryContextStrdup(context, iculocstr); result->icu.ucol = collator; - result->icu.lt = loc; result->deterministic = deterministic; result->collate_is_c = false; result->ctype_is_c = false; @@ -716,39 +675,6 @@ strfold_icu_utf8(char *dest, size_t destsize, const char *src, size_t srclen, return needed; } -/* - * For historical compatibility, behavior is not multibyte-aware. - * - * NB: uses libc tolower_l() for single-byte encodings (also for historical - * compatibility), and therefore relies on the LC_CTYPE setting. - */ -static size_t -downcase_ident_icu(char *dst, size_t dstsize, const char *src, - size_t srclen, pg_locale_t locale) -{ - size_t i; - bool libc_lower; - locale_t lt = locale->icu.lt; - - libc_lower = lt && (pg_database_encoding_max_length() == 1); - - for (i = 0; i < srclen && i < dstsize; i++) - { - unsigned char ch = (unsigned char) src[i]; - - if (ch >= 'A' && ch <= 'Z') - ch = pg_ascii_tolower(ch); - else if (libc_lower && IS_HIGHBIT_SET(ch) && isupper_l(ch, lt)) - ch = tolower_l(ch, lt); - dst[i] = (char) ch; - } - - if (i < dstsize) - dst[i] = '\0'; - - return srclen; -} - /* * strncoll_icu_utf8() * diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c index 7348ed90fc3..02c419aa62a 100644 --- a/src/backend/utils/adt/pg_locale_libc.c +++ b/src/backend/utils/adt/pg_locale_libc.c @@ -390,41 +390,12 @@ tolower_libc_mb(pg_wchar wc, pg_locale_t locale) return wc; } -/* - * Characters A..Z always downcase to a..z, even in the Turkish - * locale. Characters beyond 127 use tolower(). - */ -static size_t -downcase_ident_libc_sb(char *dst, size_t dstsize, const char *src, - size_t srclen, pg_locale_t locale) -{ - locale_t loc = locale->lt; - size_t i; - - for (i = 0; i < srclen && i < dstsize; i++) - { - unsigned char ch = (unsigned char) src[i]; - - if (ch >= 'A' && ch <= 'Z') - ch = pg_ascii_tolower(ch); - else if (IS_HIGHBIT_SET(ch) && isupper_l(ch, loc)) - ch = tolower_l(ch, loc); - dst[i] = (char) ch; - } - - if (i < dstsize) - dst[i] = '\0'; - - return srclen; -} - static const struct ctype_methods ctype_methods_libc_sb = { .strlower = strlower_libc_sb, .strtitle = strtitle_libc_sb, .strupper = strupper_libc_sb, /* in libc, casefolding is the same as lowercasing */ .strfold = strlower_libc_sb, - .downcase_ident = downcase_ident_libc_sb, .wc_isdigit = wc_isdigit_libc_sb, .wc_isalpha = wc_isalpha_libc_sb, .wc_isalnum = wc_isalnum_libc_sb, @@ -451,8 +422,6 @@ static const struct ctype_methods ctype_methods_libc_other_mb = { .strupper = strupper_libc_mb, /* in libc, casefolding is the same as lowercasing */ .strfold = strlower_libc_mb, - /* uses plain ASCII semantics for historical reasons */ - .downcase_ident = NULL, .wc_isdigit = wc_isdigit_libc_sb, .wc_isalpha = wc_isalpha_libc_sb, .wc_isalnum = wc_isalnum_libc_sb, @@ -474,8 +443,6 @@ static const struct ctype_methods ctype_methods_libc_utf8 = { .strupper = strupper_libc_mb, /* in libc, casefolding is the same as lowercasing */ .strfold = strlower_libc_mb, - /* uses plain ASCII semantics for historical reasons */ - .downcase_ident = NULL, .wc_isdigit = wc_isdigit_libc_mb, .wc_isalpha = wc_isalpha_libc_mb, .wc_isalnum = wc_isalnum_libc_mb, diff --git a/src/include/utils/pg_locale.h b/src/include/utils/pg_locale.h index fcd508f5dd6..36cd22dd255 100644 --- a/src/include/utils/pg_locale.h +++ b/src/include/utils/pg_locale.h @@ -116,11 +116,6 @@ struct ctype_methods const char *src, size_t srclen, pg_locale_t locale); - /* optional */ - size_t (*downcase_ident) (char *dest, size_t destsize, - const char *src, size_t srclen, - pg_locale_t locale); - /* required */ bool (*wc_isdigit) (pg_wchar wc, pg_locale_t locale); bool (*wc_isalpha) (pg_wchar wc, pg_locale_t locale); @@ -175,7 +170,6 @@ struct pg_locale_struct const char *locale; struct UCollator *ucol; struct UCaseMap *ucasemap; - locale_t lt; } icu; #endif }; @@ -199,8 +193,6 @@ extern size_t pg_strupper(char *dst, size_t dstsize, extern size_t pg_strfold(char *dst, size_t dstsize, const char *src, size_t srclen, pg_locale_t locale); -extern size_t pg_downcase_ident(char *dst, size_t dstsize, - const char *src, size_t srclen); extern int pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale); extern int pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2, pg_locale_t locale);