From dcd90b2751f4aaf11d6164ea9fec3309e0817125 Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Wed, 19 Nov 2025 10:20:36 -0800 Subject: [PATCH v10 06/11] Fix inconsistency between ltree_strncasecmp() and ltree_crc32_sz(). Previously, ltree_strncasecmp() used lowercasing with the default collation; while ltree_crc32_sz used tolower() directly. These were equivalent only if the default collation provider was libc and the encoding is single-byte. Change both to use casefolding with the default collation. Discussion: https://postgr.es/m/450ceb6260cad30d7afdf155d991a9caafee7c0d.camel@j-davis.com --- contrib/ltree/crc32.c | 46 ++++++++++++++++++++++++++++++++------- contrib/ltree/lquery_op.c | 31 ++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/contrib/ltree/crc32.c b/contrib/ltree/crc32.c index 134f46a805e..3918d4a0ec2 100644 --- a/contrib/ltree/crc32.c +++ b/contrib/ltree/crc32.c @@ -10,31 +10,61 @@ #include "postgres.h" #include "ltree.h" +#include "crc32.h" +#include "utils/pg_crc.h" #ifdef LOWER_NODE -#include -#define TOLOWER(x) tolower((unsigned char) (x)) -#else -#define TOLOWER(x) (x) +#include "utils/pg_locale.h" #endif -#include "crc32.h" -#include "utils/pg_crc.h" +#ifdef LOWER_NODE unsigned int ltree_crc32_sz(const char *buf, int size) { pg_crc32 crc; const char *p = buf; + static pg_locale_t locale = NULL; + + if (!locale) + locale = pg_database_locale(); INIT_TRADITIONAL_CRC32(crc); while (size > 0) { - char c = (char) TOLOWER(*p); + char foldstr[UNICODE_CASEMAP_BUFSZ]; + int srclen = pg_mblen(p); + size_t foldlen; + + /* fold one codepoint at a time */ + foldlen = pg_strfold(foldstr, UNICODE_CASEMAP_BUFSZ, p, srclen, + locale); + + COMP_TRADITIONAL_CRC32(crc, foldstr, foldlen); + + size -= srclen; + p += srclen; + } + FIN_TRADITIONAL_CRC32(crc); + return (unsigned int) crc; +} + +#else - COMP_TRADITIONAL_CRC32(crc, &c, 1); +unsigned int +ltree_crc32_sz(const char *buf, int size) +{ + pg_crc32 crc; + const char *p = buf; + + INIT_TRADITIONAL_CRC32(crc); + while (size > 0) + { + COMP_TRADITIONAL_CRC32(crc, p, 1); size--; p++; } FIN_TRADITIONAL_CRC32(crc); return (unsigned int) crc; } + +#endif /* !LOWER_NODE */ diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c index a6466f575fd..d6754eb613f 100644 --- a/contrib/ltree/lquery_op.c +++ b/contrib/ltree/lquery_op.c @@ -77,10 +77,37 @@ compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *, int ltree_strncasecmp(const char *a, const char *b, size_t s) { - char *al = str_tolower(a, s, DEFAULT_COLLATION_OID); - char *bl = str_tolower(b, s, DEFAULT_COLLATION_OID); + static pg_locale_t locale = NULL; + size_t al_sz = s + 1; + char *al = palloc(al_sz); + size_t bl_sz = s + 1; + char *bl = palloc(bl_sz); + size_t needed; int res; + if (!locale) + locale = pg_database_locale(); + + needed = pg_strfold(al, al_sz, a, s, locale); + if (needed + 1 > al_sz) + { + /* grow buffer if needed and retry */ + al_sz = needed + 1; + al = repalloc(al, al_sz); + needed = pg_strfold(al, al_sz, a, s, locale); + Assert(needed + 1 <= al_sz); + } + + needed = pg_strfold(bl, bl_sz, b, s, locale); + if (needed + 1 > bl_sz) + { + /* grow buffer if needed and retry */ + bl_sz = needed + 1; + bl = repalloc(bl, bl_sz); + needed = pg_strfold(bl, bl_sz, b, s, locale); + Assert(needed + 1 <= bl_sz); + } + res = strncmp(al, bl, s); pfree(al); -- 2.43.0