From: Laurenz Albe <laurenz.albe@cybertec.at>
To: Tom Lane <tgl@sss.pgh.pa.us>
Cc: Heikki Linnakangas <hlinnaka@iki.fi>
Cc: adam.warland@infor.com
Cc: pgsql-bugs@lists.postgresql.org
Subject: Re: BUG #19341: REPLACE() fails to match final character when using nondeterministic ICU collation
Date: Wed, 03 Dec 2025 08:51:22 +0100
Message-ID: <0bbe14779f2c37e5a2fbd514861ebedaf8f53df6.camel@cybertec.at> (raw)
In-Reply-To: <436944.1764708826@sss.pgh.pa.us>
References: <19341-1d9a22915edfec58@postgresql.org>
<60b467ac47d5c6bbb9c21c3bdfecce8e5de60cda.camel@cybertec.at>
<6935ea1e-cfb1-400b-9057-3d43b55d621a@iki.fi>
<9f47c6b24d6cf59002603caf19b8b3f4854176be.camel@cybertec.at>
<375807.1764696352@sss.pgh.pa.us>
<464a14fcbee9567ace136716714b8c18f9bd87fb.camel@cybertec.at>
<436944.1764708826@sss.pgh.pa.us>
On Tue, 2025-12-02 at 15:53 -0500, Tom Lane wrote:
> > The attached patch v3 turns it into a while loop to avoid
> > the problem.
>
> Looking at the code overall, I wonder if the outer loop doesn't have
> the same issue. The comments claim that we should be able to handle
> zero-length matches, but if the overall haystack is of length zero,
> we will fail to check for such a match.
If you can find zero-length matches at all, you could find a
zero-length match in a non-empty haystack. Perhaps the function is
never called with an empty haystack...
> Also, since we have haystack <= haystack_end as a starting condition,
> I think both loops could omit the initial test. I'd be inclined
> to code them like
>
> test_ptr = start point;
> for (;;)
> {
> ...
> if (test_ptr >= haystack_end)
> break;
> test_ptr += pg_mblen(test_ptr);
> }
True. The attached v4 patch does it like that.
> On the other hand ... is that comment really right about zero-length
> match being possible? If it is, the API for this function is in
> need of redesign, because callers that try to find "the next match"
> would go into an infinite loop re-finding the same zero-length
> match over and over.
Right. I'll see if I can trigger such a case.
Yours,
Laurenz Albe
Attachments:
[text/x-patch] v4-0001-Fix-greedy-substring-search-for-non-deterministic.patch (3.5K, ../0bbe14779f2c37e5a2fbd514861ebedaf8f53df6.camel@cybertec.at/2-v4-0001-Fix-greedy-substring-search-for-non-deterministic.patch)
download | inline diff:
From 8a4363a1a6222a03c40e3df413faa84f810252ee Mon Sep 17 00:00:00 2001
From: Laurenz Albe <laurenz.albe@cybertec.at>
Date: Wed, 3 Dec 2025 08:41:45 +0100
Subject: [PATCH v4] Fix greedy substring search for non-deterministic
collations
Due to an off-by-one error, the code failed to find matches at the
end of the haystack.
Author: Laurenz Albe <laurenz.albe@cybertec.at>
Reported-By: Adam Warland <adam.warland@infor.com>
Reviewed-By: Heikki Linnakangas <hlinnaka@iki.fi>
Reviewed-By: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/19341-1d9a22915edfec58%40postgresql.org
---
src/backend/utils/adt/varlena.c | 9 ++++++++-
src/test/regress/expected/collate.icu.utf8.out | 7 +++++++
src/test/regress/sql/collate.icu.utf8.sql | 3 +++
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.cindex 3894457ab40..d4d0b3531af 100644--- a/src/backend/utils/adt/varlena.c+++ b/src/backend/utils/adt/varlena.c@@ -1136,6 +1136,8 @@ text_position_next_internal(char *start_ptr, TextPositionState *state)
hptr = start_ptr;
while (hptr < haystack_end)
{
+ const char *test_end;+
/*
* First check the common case that there is a match in the
* haystack of exactly the length of the needle.
@@ -1149,7 +1151,8 @@ text_position_next_internal(char *start_ptr, TextPositionState *state)
* Else check if any of the possible substrings starting at hptr
* are equal to the needle.
*/
- for (const char *test_end = hptr; test_end < haystack_end; test_end += pg_mblen(test_end))+ test_end = hptr;+ for (;;)
{
if (pg_strncoll(hptr, (test_end - hptr), needle, needle_len, state->locale) == 0)
{
@@ -1158,6 +1161,10 @@ text_position_next_internal(char *start_ptr, TextPositionState *state)
if (!state->greedy)
break;
}
++ if (test_end >= haystack_end)+ break;+ test_end += pg_mblen(test_end);
}
if (result_hptr)
break;
diff --git a/src/test/regress/expected/collate.icu.utf8.out b/src/test/regress/expected/collate.icu.utf8.outindex b8579a1efc6..0a14c1d93ff 100644--- a/src/test/regress/expected/collate.icu.utf8.out+++ b/src/test/regress/expected/collate.icu.utf8.out@@ -1484,6 +1484,13 @@ SELECT array_sort('{a,B}'::text[] COLLATE "C");
{B,a}
(1 row)
+-- test replace() at the end of the string (bug #19341)+SELECT replace('testX' COLLATE case_insensitive, 'x' COLLATE case_insensitive, 'es');+ replace +---------+ testes+(1 row)+
-- test language tags
CREATE COLLATION lt_insensitive (provider = icu, locale = 'en-u-ks-level1', deterministic = false);
SELECT 'aBcD' COLLATE lt_insensitive = 'AbCd' COLLATE lt_insensitive;
diff --git a/src/test/regress/sql/collate.icu.utf8.sql b/src/test/regress/sql/collate.icu.utf8.sqlindex 6f5abac0dc0..5e3fef9b381 100644--- a/src/test/regress/sql/collate.icu.utf8.sql+++ b/src/test/regress/sql/collate.icu.utf8.sql@@ -568,6 +568,9 @@ SELECT 'abc' <= 'ABC' COLLATE case_insensitive, 'abc' >= 'ABC' COLLATE case_inse
SELECT array_sort('{a,B}'::text[] COLLATE case_insensitive);
SELECT array_sort('{a,B}'::text[] COLLATE "C");
+-- test replace() at the end of the string (bug #19341)+SELECT replace('testX' COLLATE case_insensitive, 'x' COLLATE case_insensitive, 'es');+
-- test language tags
CREATE COLLATION lt_insensitive (provider = icu, locale = 'en-u-ks-level1', deterministic = false);
SELECT 'aBcD' COLLATE lt_insensitive = 'AbCd' COLLATE lt_insensitive;
--
2.52.0
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-bugs@postgresql.org
Cc: laurenz.albe@cybertec.at, tgl@sss.pgh.pa.us, hlinnaka@iki.fi, adam.warland@infor.com, pgsql-bugs@lists.postgresql.org
Subject: Re: BUG #19341: REPLACE() fails to match final character when using nondeterministic ICU collation
In-Reply-To: <0bbe14779f2c37e5a2fbd514861ebedaf8f53df6.camel@cybertec.at>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox