On Tue, 2026-08-11 at 14:31 -0400, Andres Freund wrote:
> Hm. If I infer the pg_strlower() API correctly - it's utterly
> underdocumented
Agreed. Patch attached.
> I'd also make i size_t, given that the input is size_t. Perhaps
> practically
> no problem, but I see no reason to not use size_t here.
Patch attached for that, too.
I also attached patches to make all the functions work with
collate_is_c, and fixed up the -1 API in 18.
Regards,
Jeff Davis
From 3dabc8c4ccc9b4b0d6dbe8f87353f511d6862bb4 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 13 Aug 2026 20:57:20 -0700
Subject: [PATCH vPG18 1/4] Fixup 5f003855e7 for srclen < 0.
No actual problem because no callers used that aspect of the API.
Only commit to 18, because that part of the API was removed in commit
6d22c67c3b.
Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2
---
src/backend/utils/adt/pg_locale.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 2f8d5fea8f2..9c721efb52f 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1328,6 +1328,8 @@ size_t
pg_strlower(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
+ srclen = (srclen < 0) ? strlen(src) : srclen;
+
if (locale->ctype_is_c)
return strlower_c(dst, dstsize, src, srclen);
else if (locale->provider == COLLPROVIDER_BUILTIN)
@@ -1349,6 +1351,8 @@ size_t
pg_strtitle(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
+ srclen = (srclen < 0) ? strlen(src) : srclen;
+
if (locale->ctype_is_c)
return strtitle_c(dst, dstsize, src, srclen);
else if (locale->provider == COLLPROVIDER_BUILTIN)
@@ -1370,6 +1374,8 @@ size_t
pg_strupper(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
+ srclen = (srclen < 0) ? strlen(src) : srclen;
+
if (locale->ctype_is_c)
return strupper_c(dst, dstsize, src, srclen);
else if (locale->provider == COLLPROVIDER_BUILTIN)
@@ -1391,6 +1397,8 @@ size_t
pg_strfold(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
+ srclen = (srclen < 0) ? strlen(src) : srclen;
+
/* in the C locale, casefolding is the same as lowercasing */
if (locale->ctype_is_c)
return strlower_c(dst, dstsize, src, srclen);
--
2.43.0
From 6f11163f52b320f1b4ef1cb66a06a73f210de46f Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 13 Aug 2026 21:12:34 -0700
Subject: [PATCH vPG18 2/4] pg_locale.c, unicode_case.c: use size_t for
iteration.
No actual problem, just cleanup. Only relevant to 18 and 19.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2
---
src/backend/utils/adt/pg_locale.c | 6 +++---
src/common/unicode_case.c | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 9c721efb52f..8e79ccbd9f6 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1277,7 +1277,7 @@ get_collation_actual_version(char collprovider, const char *collcollate)
static size_t
strlower_c(char *dst, size_t dstsize, const char *src, size_t srclen)
{
- int i;
+ size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
dst[i] = pg_ascii_tolower(src[i]);
@@ -1291,7 +1291,7 @@ static size_t
strtitle_c(char *dst, size_t dstsize, const char *src, size_t srclen)
{
bool wasalnum = false;
- int i;
+ size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
{
@@ -1315,7 +1315,7 @@ strtitle_c(char *dst, size_t dstsize, const char *src, size_t srclen)
static size_t
strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
{
- int i;
+ size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
dst[i] = pg_ascii_toupper(src[i]);
diff --git a/src/common/unicode_case.c b/src/common/unicode_case.c
index 8639b203e0c..86e0b57d7d3 100644
--- a/src/common/unicode_case.c
+++ b/src/common/unicode_case.c
@@ -341,7 +341,7 @@ check_final_sigma(const unsigned char *str, size_t len, size_t offset)
int ulen;
/* iterate backwards looking for preceding character */
- for (int i = offset; i > 0;)
+ for (size_t i = offset; i > 0;)
{
/* skip backwards through continuation bytes */
i--;
@@ -369,7 +369,7 @@ check_final_sigma(const unsigned char *str, size_t len, size_t offset)
ulen = utf8_mblen((const unsigned char *) str + offset);
/* iterate forward looking for following character */
- for (int i = offset + ulen; i < len;)
+ for (size_t i = offset + ulen; i < len;)
{
ulen = utf8_mblen((const unsigned char *) str + i);
--
2.43.0
From 70ebdd061f9ae158b1674453e4a6e03db1b74815 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 12 Aug 2026 07:32:19 -0700
Subject: [PATCH vPG18 3/4] Add missing comments in pg_locale.c.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v3nniwcrxejmcfvz56xbd22hphprqleuornd6hqkmw2bl7kgmz@cnytz2ee5ltk
Backpatch-through: 18
---
src/backend/utils/adt/pg_locale.c | 76 +++++++++++++++++++++++++------
1 file changed, 63 insertions(+), 13 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 8e79ccbd9f6..c21619f85cd 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1324,6 +1324,20 @@ strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
return srclen;
}
+/*
+ * pg_strlower()
+ *
+ * Convert src to lowercase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If srclen is
+ * -1, src must be NUL-terminated. If dstsize is zero, dst may be NULL, which
+ * is useful for calculating the required buffer size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strlower(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
@@ -1347,6 +1361,20 @@ pg_strlower(char *dst, size_t dstsize, const char *src, ssize_t srclen,
return 0; /* keep compiler quiet */
}
+/*
+ * pg_strtitle()
+ *
+ * Convert src to titlecase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If srclen is
+ * -1, src must be NUL-terminated. If dstsize is zero, dst may be NULL, which
+ * is useful for calculating the required buffer size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strtitle(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
@@ -1370,6 +1398,20 @@ pg_strtitle(char *dst, size_t dstsize, const char *src, ssize_t srclen,
return 0; /* keep compiler quiet */
}
+/*
+ * pg_strupper()
+ *
+ * Convert src to uppercase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If srclen is
+ * -1, src must be NUL-terminated. If dstsize is zero, dst may be NULL, which
+ * is useful for calculating the required buffer size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strupper(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
@@ -1393,6 +1435,19 @@ pg_strupper(char *dst, size_t dstsize, const char *src, ssize_t srclen,
return 0; /* keep compiler quiet */
}
+/*
+ * pg_strfold()
+ *
+ * Casefold src, and return the result length (not including terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If srclen is
+ * -1, src must be NUL-terminated. If dstsize is zero, dst may be NULL, which
+ * is useful for calculating the required buffer size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strfold(char *dst, size_t dstsize, const char *src, ssize_t srclen,
pg_locale_t locale)
@@ -1432,12 +1487,10 @@ pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
/*
* pg_strncoll
*
- * Call ucol_strcollUTF8(), ucol_strcoll(), strcoll_l() or wcscoll_l() as
- * appropriate for the given locale, platform, and database encoding. If the
- * locale is not specified, use the database collation.
+ * Compare strings according to the given locale.
*
- * The input strings must be encoded in the database encoding. If an input
- * string is NUL-terminated, its length may be specified as -1.
+ * Strings must be encoded in the database encoding with no embedded NULs. If
+ * an input string is NUL-terminated, its length may be specified as -1.
*
* The caller is responsible for breaking ties if the collation is
* deterministic; this maintains consistency with pg_strnxfrm(), which cannot
@@ -1453,9 +1506,6 @@ pg_strncoll(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2,
/*
* Return true if the collation provider supports pg_strxfrm() and
* pg_strnxfrm(); otherwise false.
- *
- *
- * No similar problem is known for the ICU provider.
*/
bool
pg_strxfrm_enabled(pg_locale_t locale)
@@ -1486,9 +1536,9 @@ pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
* ordinary strcmp() on transformed strings is equivalent to pg_strcoll() on
* untransformed strings.
*
- * The input string must be encoded in the database encoding. If the input
- * string is NUL-terminated, its length may be specified as -1. If 'destsize'
- * is zero, 'dest' may be NULL.
+ * String must be encoded in the database encoding with no embedded NULs. If
+ * srclen is -1, src must be NUL-terminated. If 'destsize' is zero, 'dest'
+ * may be NULL.
*
* Not all providers support pg_strnxfrm() safely. The caller should check
* pg_strxfrm_enabled() first, otherwise this function may return wrong
@@ -1534,8 +1584,8 @@ pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
* memcmp() on the byte sequence is equivalent to pg_strncoll() on
* untransformed strings. The result is not nul-terminated.
*
- * The input string must be encoded in the database encoding. If the input
- * string is NUL-terminated, its length may be specified as -1.
+ * String must be encoded in the database encoding with no embedded NULs. If
+ * the input string is NUL-terminated, its length may be specified as -1.
*
* Not all providers support pg_strnxfrm_prefix() safely. The caller should
* check pg_strxfrm_prefix_enabled() first, otherwise this function may return
--
2.43.0
From 27e336a2add2660fa28ba410dc8888aac7be9fb3 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 12 Aug 2026 07:46:48 -0700
Subject: [PATCH vPG18 4/4] Ensure all pg_locale.h APIs work with collate_is_c.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2
Backpatch-through: 18
---
src/backend/utils/adt/pg_locale.c | 64 ++++++++++++++++++++++++++++---
1 file changed, 58 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index c21619f85cd..ad9f416ec13 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1481,7 +1481,10 @@ pg_strfold(char *dst, size_t dstsize, const char *src, ssize_t srclen,
int
pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
{
- return locale->collate->strncoll(arg1, -1, arg2, -1, locale);
+ if (locale->collate == NULL)
+ return strcmp(arg1, arg2);
+ else
+ return locale->collate->strncoll(arg1, -1, arg2, -1, locale);
}
/*
@@ -1500,7 +1503,20 @@ int
pg_strncoll(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2,
pg_locale_t locale)
{
- return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
+ if (locale->collate == NULL)
+ {
+ int result;
+
+ len1 = (len1 < 0) ? strlen(arg1) : len1;
+ len2 = (len2 < 0) ? strlen(arg2) : len2;
+ result = memcmp(arg1, arg2, Min(len1, len2));
+
+ if ((result == 0) && (len1 != len2))
+ result = (len1 < len2) ? -1 : 1;
+ return result;
+ }
+ else
+ return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
}
/*
@@ -1510,6 +1526,9 @@ pg_strncoll(const char *arg1, ssize_t len1, const char *arg2, ssize_t len2,
bool
pg_strxfrm_enabled(pg_locale_t locale)
{
+ if (locale->collate == NULL)
+ return true;
+
/*
* locale->collate->strnxfrm is still a required method, even if it may
* have the wrong behavior, because the planner uses it for estimates in
@@ -1526,7 +1545,10 @@ pg_strxfrm_enabled(pg_locale_t locale)
size_t
pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
{
- return locale->collate->strnxfrm(dest, destsize, src, -1, locale);
+ if (locale->collate == NULL)
+ return pg_strnxfrm(dest, destsize, src, strlen(src), locale);
+ else
+ return locale->collate->strnxfrm(dest, destsize, src, -1, locale);
}
/*
@@ -1552,6 +1574,18 @@ size_t
pg_strnxfrm(char *dest, size_t destsize, const char *src, ssize_t srclen,
pg_locale_t locale)
{
+ if (locale->collate == NULL)
+ {
+ srclen = (srclen < 0) ? strlen(src) : srclen;
+
+ if (destsize > srclen)
+ {
+ memcpy(dest, src, srclen);
+ dest[srclen] = '\0';
+ }
+
+ return srclen;
+ }
return locale->collate->strnxfrm(dest, destsize, src, srclen, locale);
}
@@ -1562,7 +1596,10 @@ pg_strnxfrm(char *dest, size_t destsize, const char *src, ssize_t srclen,
bool
pg_strxfrm_prefix_enabled(pg_locale_t locale)
{
- return (locale->collate->strnxfrm_prefix != NULL);
+ if (locale->collate == NULL)
+ return true;
+ else
+ return (locale->collate->strnxfrm_prefix != NULL);
}
/*
@@ -1574,7 +1611,10 @@ size_t
pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
pg_locale_t locale)
{
- return locale->collate->strnxfrm_prefix(dest, destsize, src, -1, locale);
+ if (locale->collate == NULL)
+ return pg_strnxfrm_prefix(dest, destsize, src, strlen(src), locale);
+ else
+ return locale->collate->strnxfrm_prefix(dest, destsize, src, -1, locale);
}
/*
@@ -1599,7 +1639,19 @@ size_t
pg_strnxfrm_prefix(char *dest, size_t destsize, const char *src,
ssize_t srclen, pg_locale_t locale)
{
- return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
+ if (locale->collate == NULL)
+ {
+ size_t len;
+
+ srclen = (srclen < 0) ? strlen(src) : srclen;
+ len = Min(srclen, destsize);
+
+ if (destsize > 0)
+ memcpy(dest, src, len);
+ return len;
+ }
+ else
+ return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
}
/*
--
2.43.0
From c1a532d38ae63f27ff705a6bbdec78ca5aed5b6f Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Thu, 13 Aug 2026 21:12:34 -0700
Subject: [PATCH vPG19 1/3] pg_locale.c, unicode_case.c: use size_t for
iteration.
No actual problem, just cleanup. Only relevant to 18 and 19.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2
---
src/backend/utils/adt/pg_locale.c | 6 +++---
src/common/unicode_case.c | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 11d48a3916e..9eb99487e57 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1270,7 +1270,7 @@ get_collation_actual_version(char collprovider, const char *collcollate)
static size_t
strlower_c(char *dst, size_t dstsize, const char *src, size_t srclen)
{
- int i;
+ size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
dst[i] = pg_ascii_tolower(src[i]);
@@ -1284,7 +1284,7 @@ static size_t
strtitle_c(char *dst, size_t dstsize, const char *src, size_t srclen)
{
bool wasalnum = false;
- int i;
+ size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
{
@@ -1308,7 +1308,7 @@ strtitle_c(char *dst, size_t dstsize, const char *src, size_t srclen)
static size_t
strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
{
- int i;
+ size_t i;
for (i = 0; i < srclen && i < dstsize; i++)
dst[i] = pg_ascii_toupper(src[i]);
diff --git a/src/common/unicode_case.c b/src/common/unicode_case.c
index dd5b3ba86d0..744b9116b12 100644
--- a/src/common/unicode_case.c
+++ b/src/common/unicode_case.c
@@ -336,7 +336,7 @@ check_final_sigma(const unsigned char *str, size_t len, size_t offset)
int ulen;
/* iterate backwards looking for preceding character */
- for (int i = offset; i > 0;)
+ for (size_t i = offset; i > 0;)
{
/* skip backwards through continuation bytes */
i--;
@@ -364,7 +364,7 @@ check_final_sigma(const unsigned char *str, size_t len, size_t offset)
ulen = utf8_mblen((const unsigned char *) str + offset);
/* iterate forward looking for following character */
- for (int i = offset + ulen; i < len;)
+ for (size_t i = offset + ulen; i < len;)
{
ulen = utf8_mblen((const unsigned char *) str + i);
--
2.43.0
From 81ad1161ed864e1572e5319eecec037670e940b4 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 12 Aug 2026 07:32:19 -0700
Subject: [PATCH vPG19 2/3] Add missing comments in pg_locale.c.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v3nniwcrxejmcfvz56xbd22hphprqleuornd6hqkmw2bl7kgmz@cnytz2ee5ltk
Backpatch-through: 18
---
src/backend/utils/adt/pg_locale.c | 70 ++++++++++++++++++++++++++-----
1 file changed, 60 insertions(+), 10 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 9eb99487e57..da32590396f 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1317,6 +1317,20 @@ strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
return srclen;
}
+/*
+ * pg_strlower()
+ *
+ * Convert src to lowercase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1327,6 +1341,20 @@ pg_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strlower(dst, dstsize, src, srclen, locale);
}
+/*
+ * pg_strtitle()
+ *
+ * Convert src to titlecase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1337,6 +1365,20 @@ pg_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strtitle(dst, dstsize, src, srclen, locale);
}
+/*
+ * pg_strupper()
+ *
+ * Convert src to uppercase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1347,6 +1389,19 @@ pg_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strupper(dst, dstsize, src, srclen, locale);
}
+/*
+ * pg_strfold()
+ *
+ * Casefold src, and return the result length (not including terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strfold(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1392,11 +1447,9 @@ pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
/*
* pg_strncoll
*
- * Call ucol_strcollUTF8(), ucol_strcoll(), strcoll_l() or wcscoll_l() as
- * appropriate for the given locale, platform, and database encoding. If the
- * locale is not specified, use the database collation.
+ * Compare strings according to the given locale.
*
- * The input strings must be encoded in the database encoding.
+ * Strings must be encoded in the database encoding with no embedded NULs.
*
* The caller is responsible for breaking ties if the collation is
* deterministic; this maintains consistency with pg_strnxfrm(), which cannot
@@ -1412,9 +1465,6 @@ pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
/*
* Return true if the collation provider supports pg_strxfrm() and
* pg_strnxfrm(); otherwise false.
- *
- *
- * No similar problem is known for the ICU provider.
*/
bool
pg_strxfrm_enabled(pg_locale_t locale)
@@ -1445,8 +1495,8 @@ pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
* ordinary strcmp() on transformed strings is equivalent to pg_strcoll() on
* untransformed strings.
*
- * The input string must be encoded in the database encoding. If 'destsize' is
- * zero, 'dest' may be NULL.
+ * String must be encoded in the database encoding with no embedded NULs. If
+ * 'destsize' is zero, 'dest' may be NULL.
*
* Not all providers support pg_strnxfrm() safely. The caller should check
* pg_strxfrm_enabled() first, otherwise this function may return wrong
@@ -1492,7 +1542,7 @@ pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
* memcmp() on the byte sequence is equivalent to pg_strncoll() on
* untransformed strings. The result is not nul-terminated.
*
- * The input string must be encoded in the database encoding.
+ * String must be encoded in the database encoding with no embedded NULs.
*
* Not all providers support pg_strnxfrm_prefix() safely. The caller should
* check pg_strxfrm_prefix_enabled() first, otherwise this function may return
--
2.43.0
From acf021658ccc38e01bda2fb9900c1d0ecacf1535 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 12 Aug 2026 07:46:48 -0700
Subject: [PATCH vPG19 3/3] Ensure all pg_locale.h APIs work with collate_is_c.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2
Backpatch-through: 18
---
src/backend/utils/adt/pg_locale.c | 55 +++++++++++++++++++++++++++----
1 file changed, 49 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index da32590396f..2b501a7b6b7 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1441,7 +1441,10 @@ pg_downcase_ident(char *dst, size_t dstsize, const char *src, size_t srclen)
int
pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
{
- return locale->collate->strcoll(arg1, arg2, locale);
+ if (locale->collate == NULL)
+ return strcmp(arg1, arg2);
+ else
+ return locale->collate->strcoll(arg1, arg2, locale);
}
/*
@@ -1459,7 +1462,16 @@ int
pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
pg_locale_t locale)
{
- return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
+ if (locale->collate == NULL)
+ {
+ int result = memcmp(arg1, arg2, Min(len1, len2));
+
+ if ((result == 0) && (len1 != len2))
+ result = (len1 < len2) ? -1 : 1;
+ return result;
+ }
+ else
+ return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
}
/*
@@ -1469,6 +1481,9 @@ pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
bool
pg_strxfrm_enabled(pg_locale_t locale)
{
+ if (locale->collate == NULL)
+ return true;
+
/*
* locale->collate->strnxfrm is still a required method, even if it may
* have the wrong behavior, because the planner uses it for estimates in
@@ -1485,7 +1500,10 @@ pg_strxfrm_enabled(pg_locale_t locale)
size_t
pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
{
- return locale->collate->strxfrm(dest, destsize, src, locale);
+ if (locale->collate == NULL)
+ return pg_strnxfrm(dest, destsize, src, strlen(src), locale);
+ else
+ return locale->collate->strxfrm(dest, destsize, src, locale);
}
/*
@@ -1510,6 +1528,16 @@ size_t
pg_strnxfrm(char *dest, size_t destsize, const char *src, size_t srclen,
pg_locale_t locale)
{
+ if (locale->collate == NULL)
+ {
+ if (destsize > srclen)
+ {
+ memcpy(dest, src, srclen);
+ dest[srclen] = '\0';
+ }
+
+ return srclen;
+ }
return locale->collate->strnxfrm(dest, destsize, src, srclen, locale);
}
@@ -1520,7 +1548,10 @@ pg_strnxfrm(char *dest, size_t destsize, const char *src, size_t srclen,
bool
pg_strxfrm_prefix_enabled(pg_locale_t locale)
{
- return (locale->collate->strnxfrm_prefix != NULL);
+ if (locale->collate == NULL)
+ return true;
+ else
+ return (locale->collate->strnxfrm_prefix != NULL);
}
/*
@@ -1532,7 +1563,10 @@ size_t
pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
pg_locale_t locale)
{
- return locale->collate->strxfrm_prefix(dest, destsize, src, locale);
+ if (locale->collate == NULL)
+ return pg_strnxfrm_prefix(dest, destsize, src, strlen(src), locale);
+ else
+ return locale->collate->strxfrm_prefix(dest, destsize, src, locale);
}
/*
@@ -1556,7 +1590,16 @@ size_t
pg_strnxfrm_prefix(char *dest, size_t destsize, const char *src,
size_t srclen, pg_locale_t locale)
{
- return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
+ if (locale->collate == NULL)
+ {
+ size_t len = Min(srclen, destsize);
+
+ if (destsize > 0)
+ memcpy(dest, src, len);
+ return len;
+ }
+ else
+ return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
}
bool
--
2.43.0
From daaea4c338afb1c1389ffbfa6ac33d0bc38a5b48 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 12 Aug 2026 07:32:19 -0700
Subject: [PATCH vPG20 1/2] Add missing comments in pg_locale.c.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v3nniwcrxejmcfvz56xbd22hphprqleuornd6hqkmw2bl7kgmz@cnytz2ee5ltk
Backpatch-through: 18
---
src/backend/utils/adt/pg_locale.c | 70 ++++++++++++++++++++++++++-----
1 file changed, 60 insertions(+), 10 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index 9eb99487e57..da32590396f 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1317,6 +1317,20 @@ strupper_c(char *dst, size_t dstsize, const char *src, size_t srclen)
return srclen;
}
+/*
+ * pg_strlower()
+ *
+ * Convert src to lowercase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1327,6 +1341,20 @@ pg_strlower(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strlower(dst, dstsize, src, srclen, locale);
}
+/*
+ * pg_strtitle()
+ *
+ * Convert src to titlecase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1337,6 +1365,20 @@ pg_strtitle(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strtitle(dst, dstsize, src, srclen, locale);
}
+/*
+ * pg_strupper()
+ *
+ * Convert src to uppercase, and return the result length (not including
+ * terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1347,6 +1389,19 @@ pg_strupper(char *dst, size_t dstsize, const char *src, size_t srclen,
return locale->ctype->strupper(dst, dstsize, src, srclen, locale);
}
+/*
+ * pg_strfold()
+ *
+ * Casefold src, and return the result length (not including terminating NUL).
+ *
+ * src must be in the database encoding with no embedded NULs. If dstsize is
+ * zero, dst may be NULL, which is useful for calculating the required buffer
+ * size before allocating.
+ *
+ * If the result length is less than dstsize, the NUL-terminated result is
+ * stored in dst. Otherwise, the contents of dst are undefined, and the
+ * caller should use the return value to resize the buffer and retry.
+ */
size_t
pg_strfold(char *dst, size_t dstsize, const char *src, size_t srclen,
pg_locale_t locale)
@@ -1392,11 +1447,9 @@ pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
/*
* pg_strncoll
*
- * Call ucol_strcollUTF8(), ucol_strcoll(), strcoll_l() or wcscoll_l() as
- * appropriate for the given locale, platform, and database encoding. If the
- * locale is not specified, use the database collation.
+ * Compare strings according to the given locale.
*
- * The input strings must be encoded in the database encoding.
+ * Strings must be encoded in the database encoding with no embedded NULs.
*
* The caller is responsible for breaking ties if the collation is
* deterministic; this maintains consistency with pg_strnxfrm(), which cannot
@@ -1412,9 +1465,6 @@ pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
/*
* Return true if the collation provider supports pg_strxfrm() and
* pg_strnxfrm(); otherwise false.
- *
- *
- * No similar problem is known for the ICU provider.
*/
bool
pg_strxfrm_enabled(pg_locale_t locale)
@@ -1445,8 +1495,8 @@ pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
* ordinary strcmp() on transformed strings is equivalent to pg_strcoll() on
* untransformed strings.
*
- * The input string must be encoded in the database encoding. If 'destsize' is
- * zero, 'dest' may be NULL.
+ * String must be encoded in the database encoding with no embedded NULs. If
+ * 'destsize' is zero, 'dest' may be NULL.
*
* Not all providers support pg_strnxfrm() safely. The caller should check
* pg_strxfrm_enabled() first, otherwise this function may return wrong
@@ -1492,7 +1542,7 @@ pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
* memcmp() on the byte sequence is equivalent to pg_strncoll() on
* untransformed strings. The result is not nul-terminated.
*
- * The input string must be encoded in the database encoding.
+ * String must be encoded in the database encoding with no embedded NULs.
*
* Not all providers support pg_strnxfrm_prefix() safely. The caller should
* check pg_strxfrm_prefix_enabled() first, otherwise this function may return
--
2.43.0
From 7fbcfeeeb04ecefe917c29e53237d79d4447795e Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Wed, 12 Aug 2026 07:46:48 -0700
Subject: [PATCH vPG20 2/2] Ensure all pg_locale.h APIs work with collate_is_c.
Suggested-by: Andres Freund <[email protected]>
Discussion: https://postgr.es/m/v36ssaygf7grb3qzfsjhtdzi7kqd45ds56nyuf7gi5qjml4qbb@ezmfqzmhlrs2
Backpatch-through: 18
---
src/backend/utils/adt/pg_locale.c | 55 +++++++++++++++++++++++++++----
1 file changed, 49 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c
index da32590396f..2b501a7b6b7 100644
--- a/src/backend/utils/adt/pg_locale.c
+++ b/src/backend/utils/adt/pg_locale.c
@@ -1441,7 +1441,10 @@ pg_downcase_ident(char *dst, size_t dstsize, const char *src, size_t srclen)
int
pg_strcoll(const char *arg1, const char *arg2, pg_locale_t locale)
{
- return locale->collate->strcoll(arg1, arg2, locale);
+ if (locale->collate == NULL)
+ return strcmp(arg1, arg2);
+ else
+ return locale->collate->strcoll(arg1, arg2, locale);
}
/*
@@ -1459,7 +1462,16 @@ int
pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
pg_locale_t locale)
{
- return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
+ if (locale->collate == NULL)
+ {
+ int result = memcmp(arg1, arg2, Min(len1, len2));
+
+ if ((result == 0) && (len1 != len2))
+ result = (len1 < len2) ? -1 : 1;
+ return result;
+ }
+ else
+ return locale->collate->strncoll(arg1, len1, arg2, len2, locale);
}
/*
@@ -1469,6 +1481,9 @@ pg_strncoll(const char *arg1, size_t len1, const char *arg2, size_t len2,
bool
pg_strxfrm_enabled(pg_locale_t locale)
{
+ if (locale->collate == NULL)
+ return true;
+
/*
* locale->collate->strnxfrm is still a required method, even if it may
* have the wrong behavior, because the planner uses it for estimates in
@@ -1485,7 +1500,10 @@ pg_strxfrm_enabled(pg_locale_t locale)
size_t
pg_strxfrm(char *dest, const char *src, size_t destsize, pg_locale_t locale)
{
- return locale->collate->strxfrm(dest, destsize, src, locale);
+ if (locale->collate == NULL)
+ return pg_strnxfrm(dest, destsize, src, strlen(src), locale);
+ else
+ return locale->collate->strxfrm(dest, destsize, src, locale);
}
/*
@@ -1510,6 +1528,16 @@ size_t
pg_strnxfrm(char *dest, size_t destsize, const char *src, size_t srclen,
pg_locale_t locale)
{
+ if (locale->collate == NULL)
+ {
+ if (destsize > srclen)
+ {
+ memcpy(dest, src, srclen);
+ dest[srclen] = '\0';
+ }
+
+ return srclen;
+ }
return locale->collate->strnxfrm(dest, destsize, src, srclen, locale);
}
@@ -1520,7 +1548,10 @@ pg_strnxfrm(char *dest, size_t destsize, const char *src, size_t srclen,
bool
pg_strxfrm_prefix_enabled(pg_locale_t locale)
{
- return (locale->collate->strnxfrm_prefix != NULL);
+ if (locale->collate == NULL)
+ return true;
+ else
+ return (locale->collate->strnxfrm_prefix != NULL);
}
/*
@@ -1532,7 +1563,10 @@ size_t
pg_strxfrm_prefix(char *dest, const char *src, size_t destsize,
pg_locale_t locale)
{
- return locale->collate->strxfrm_prefix(dest, destsize, src, locale);
+ if (locale->collate == NULL)
+ return pg_strnxfrm_prefix(dest, destsize, src, strlen(src), locale);
+ else
+ return locale->collate->strxfrm_prefix(dest, destsize, src, locale);
}
/*
@@ -1556,7 +1590,16 @@ size_t
pg_strnxfrm_prefix(char *dest, size_t destsize, const char *src,
size_t srclen, pg_locale_t locale)
{
- return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
+ if (locale->collate == NULL)
+ {
+ size_t len = Min(srclen, destsize);
+
+ if (destsize > 0)
+ memcpy(dest, src, len);
+ return len;
+ }
+ else
+ return locale->collate->strnxfrm_prefix(dest, destsize, src, srclen, locale);
}
bool
--
2.43.0