diff --git a/src/backend/utils/adt/pg_locale.c b/src/backend/utils/adt/pg_locale.c index 7f73cd75956..cca08ceac0b 100644 --- a/src/backend/utils/adt/pg_locale.c +++ b/src/backend/utils/adt/pg_locale.c @@ -1743,6 +1743,11 @@ pg_iswxdigit(pg_wchar wc, pg_locale_t locale) return locale->ctype->wc_isxdigit(wc, locale); } +/* + * Is the character potentially case-varying? Used by ILIKE to extract a + * prefix suitable for an index search. Safe to return true if the character + * can't be easily classified. + */ bool pg_iswcased(pg_wchar wc, pg_locale_t locale) { diff --git a/src/backend/utils/adt/pg_locale_icu.c b/src/backend/utils/adt/pg_locale_icu.c index 0c8ddde630a..c35b9edc652 100644 --- a/src/backend/utils/adt/pg_locale_icu.c +++ b/src/backend/utils/adt/pg_locale_icu.c @@ -247,6 +247,20 @@ wc_isxdigit_icu(pg_wchar wc, pg_locale_t locale) static bool wc_iscased_icu(pg_wchar wc, pg_locale_t locale) +{ + /* + * For non-UTF8 encodings (single or multibyte), pg_wchar may not be a + * codepoint, so we conservatively assume that any non-ASCII character + * could be case-varying. + */ + if (wc > (pg_wchar) 127) + return true; + + return u_hasBinaryProperty(wc, UCHAR_CASED); +} + +static bool +wc_iscased_icu_utf8(pg_wchar wc, pg_locale_t locale) { return u_hasBinaryProperty(wc, UCHAR_CASED); } @@ -289,7 +303,7 @@ static const struct ctype_methods ctype_methods_icu_utf8 = { .wc_ispunct = wc_ispunct_icu, .wc_isspace = wc_isspace_icu, .wc_isxdigit = wc_isxdigit_icu, - .wc_iscased = wc_iscased_icu, + .wc_iscased = wc_iscased_icu_utf8, .wc_toupper = toupper_icu, .wc_tolower = tolower_icu, }; diff --git a/src/backend/utils/adt/pg_locale_libc.c b/src/backend/utils/adt/pg_locale_libc.c index d9a33db8de5..7348ed90fc3 100644 --- a/src/backend/utils/adt/pg_locale_libc.c +++ b/src/backend/utils/adt/pg_locale_libc.c @@ -299,10 +299,37 @@ wc_isxdigit_libc_mb(pg_wchar wc, pg_locale_t locale) } static bool -wc_iscased_libc_mb(pg_wchar wc, pg_locale_t locale) +wc_iscased_libc_other_mb(pg_wchar wc, pg_locale_t locale) { + /* + * For non-UTF8 multibyte encodings, we conservatively assume that any + * non-ASCII character could be case-varying. + */ + if (wc > (pg_wchar) 127) + return true; + + /* ASCII: pass directly to isupper_l()/islower_l() */ + return isupper_l((unsigned char) wc, locale->lt) || + islower_l((unsigned char) wc, locale->lt); +} + +static bool +wc_iscased_libc_utf8(pg_wchar wc, pg_locale_t locale) +{ + /* + * If sizeof(wchar_t) < 4 (that is, on Windows), then return false. This + * is consistent with the behavior of strlower_libc_mb(): the UTF8 string + * will be decoded into 16-bit wchar_t, so strlower_libc_mb() will never + * deal with codepoints beyond 0xFFFF. It may deal with surrogate pairs, + * but those characters map to themselves anyway. + */ if (sizeof(wchar_t) < 4 && wc > (pg_wchar) 0xFFFF) return false; + + /* + * For UTF8, pg_wchar is a codepoint and we assume we can pass it directly + * to iswupper_l()/iswlower_l(). + */ return iswupper_l((wint_t) wc, locale->lt) || iswlower_l((wint_t) wc, locale->lt); } @@ -415,7 +442,8 @@ static const struct ctype_methods ctype_methods_libc_sb = { /* * Non-UTF8 multibyte encodings use multibyte semantics for case mapping, but - * single-byte semantics for pattern matching. + * single-byte semantics for pattern matching (except wc_iscased which needs + * to be consistent with case mapping). */ static const struct ctype_methods ctype_methods_libc_other_mb = { .strlower = strlower_libc_mb, @@ -435,7 +463,7 @@ static const struct ctype_methods ctype_methods_libc_other_mb = { .wc_ispunct = wc_ispunct_libc_sb, .wc_isspace = wc_isspace_libc_sb, .wc_isxdigit = wc_isxdigit_libc_sb, - .wc_iscased = wc_iscased_libc_sb, + .wc_iscased = wc_iscased_libc_other_mb, .wc_toupper = toupper_libc_sb, .wc_tolower = tolower_libc_sb, }; @@ -458,7 +486,7 @@ static const struct ctype_methods ctype_methods_libc_utf8 = { .wc_ispunct = wc_ispunct_libc_mb, .wc_isspace = wc_isspace_libc_mb, .wc_isxdigit = wc_isxdigit_libc_mb, - .wc_iscased = wc_iscased_libc_mb, + .wc_iscased = wc_iscased_libc_utf8, .wc_toupper = toupper_libc_mb, .wc_tolower = tolower_libc_mb, };