From 41c441a02db341952763d04bb822a634b711c735 Mon Sep 17 00:00:00 2001 From: Laurenz Albe Date: Tue, 2 Dec 2025 18:49:11 +0100 Subject: [PATCH v3] 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 Reported-By: Adam Warland Reviewed-By: Heikki Linnakangas Reviewed-By: Tom Lane 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.c index 3894457ab40..99d1bf09f20 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; + while (test_end <= haystack_end) { 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.out index 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.sql index 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