On 11/08/2026 12:19, Heikki Linnakangas wrote:
I think the best fix is to make pg_strupper() in REL_18_STABLE also work
with the C locale. It's an accident waiting to happen if it doesn't.
(And same for all the other pg_str*() functions, of course)
Like the attached.
There's some code duplication: the strupper_c() function is essentially
the same as asc_toupper(), and str_toupper() wouldn't really need to
have the special case for C locale anymore, it could just rely on
pg_strupper() now. But that's so in 'master' too, so I think cleaning
that up should be left for a separate patch.
- Heikki
From bb623fc827b367393e2378846fb12c52a947edfe Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 11 Aug 2026 12:32:49 +0300
Subject: [PATCH 1/1] Fix pg_strupper/lower/fold() functions work with C locale
---
src/backend/utils/adt/pg_locale.c | 68 +++++++++++++++++++++++++++++--
1 file changed, 64 insertions(+), 4 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 78243b2c795..2f8d5fea8f2 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1273,11 +1273,64 @@ get_collation_actual_version(char collprovider, const char *collcollate)
return collversion;
}
+/* lowercasing/casefolding in C locale */
+static size_t
+strlower_c(char *dst, size_t dstsize, const char *src, size_t srclen)
+{
+ int i;
+
+ for (i = 0; i < srclen && i < dstsize; i++)
+ dst[i] = pg_ascii_tolower(src[i]);
+ if (i < dstsize)
+ dst[i] = '\0';
+ return srclen;
+}
+
+/* titlecasing in C locale */
+static size_t
+strtitle_c(char *dst, size_t dstsize, const char *src, size_t srclen)
+{
+ bool wasalnum = false;
+ int i;
+
+ for (i = 0; i < srclen && i < dstsize; i++)
+ {
+ char c = src[i];
+
+ if (wasalnum)
+ dst[i] = pg_ascii_tolower(c);
+ else
+ dst[i] = pg_ascii_toupper(c);
+
+ wasalnum = ((c >= '0' && c <= '9') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= 'a' && c <= 'z'));
+ }
+ if (i < dstsize)
+ dst[i] = '\0';
+ return srclen;
+}
+
+/* uppercasing in C locale */
+static size_t
+strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
+{
+ int i;
+
+ for (i = 0; i < srclen && i < dstsize; i++)
+ dst[i] = pg_ascii_toupper(src[i]);
+ if (i < dstsize)
+ dst[i] = '\0';
+ return srclen;
+}
+
size_t
pg_strlower(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
- if (locale->provider == COLLPROVIDER_BUILTIN)
+ if (locale->ctype_is_c)
+ return strlower_c(dst, dstsize, src, srclen);
+ else if (locale->provider == COLLPROVIDER_BUILTIN)
return strlower_builtin(dst, dstsize, src, srclen, locale);
#ifdef USE_ICU
else if (locale->provider == COLLPROVIDER_ICU)
@@ -1296,7 +1349,9 @@ size_t
pg_strtitle(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
- if (locale->provider == COLLPROVIDER_BUILTIN)
+ if (locale->ctype_is_c)
+ return strtitle_c(dst, dstsize, src, srclen);
+ else if (locale->provider == COLLPROVIDER_BUILTIN)
return strtitle_builtin(dst, dstsize, src, srclen, locale);
#ifdef USE_ICU
else if (locale->provider == COLLPROVIDER_ICU)
@@ -1315,7 +1370,9 @@ size_t
pg_strupper(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
- if (locale->provider == COLLPROVIDER_BUILTIN)
+ if (locale->ctype_is_c)
+ return strupper_c(dst, dstsize, src, srclen);
+ else if (locale->provider == COLLPROVIDER_BUILTIN)
return strupper_builtin(dst, dstsize, src, srclen, locale);
#ifdef USE_ICU
else if (locale->provider == COLLPROVIDER_ICU)
@@ -1334,7 +1391,10 @@ size_t
pg_strfold(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
- if (locale->provider == COLLPROVIDER_BUILTIN)
+ /* in the C locale, casefolding is the same as lowercasing */
+ if (locale->ctype_is_c)
+ return strlower_c(dst, dstsize, src, srclen);
+ else if (locale->provider == COLLPROVIDER_BUILTIN)
return strfold_builtin(dst, dstsize, src, srclen, locale);
#ifdef USE_ICU
else if (locale->provider == COLLPROVIDER_ICU)
--
2.47.3