v11-0003-Fix-inconsistency-between-ltree_strncasecmp-and-.patch

text/x-patch

Filename: v11-0003-Fix-inconsistency-between-ltree_strncasecmp-and-.patch
Type: text/x-patch
Part: 2
Message: Re: Remaining dependency on setlocale()
From d485548107cc9c5833185932d462febe8fdf7ef1 Mon Sep 17 00:00:00 2001
From: Jeff Davis <jeff@j-davis.com>
Date: Wed, 19 Nov 2025 10:20:36 -0800
Subject: [PATCH v11 3/9] 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
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
---
 contrib/ltree/crc32.c        | 46 ++++++++++++++++++----
 contrib/ltree/lquery_op.c    | 74 ++++++++++++++++++++++++++++++------
 contrib/ltree/ltree.h        |  6 ++-
 contrib/ltree/ltxtquery_op.c |  8 ++--
 4 files changed, 108 insertions(+), 26 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 <ctype.h>
-#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..ba8e114d742 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,7 +41,9 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *, const char *, size_t), bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len,
+				bool (*prefix_eq) (const char *, size_t, const char *, size_t),
+				bool anyend)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,7 +59,7 @@ compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *,
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
 			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*cmpptr) (qn, tn, lenq) == 0)
+				(*prefix_eq) (qn, lenq, tn, lent))
 			{
 
 				isok = true;
@@ -74,14 +76,62 @@ compare_subnode(ltree_level *t, char *qn, int len, int (*cmpptr) (const char *,
 	return true;
 }
 
-int
-ltree_strncasecmp(const char *a, const char *b, size_t s)
+/*
+ * Check if b has a prefix of a.
+ */
+bool
+ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
+{
+	if (a_sz > b_sz)
+		return false;
+	else
+		return (strncmp(a, b, a_sz) == 0);
+}
+
+/*
+ * Case-insensitive check if b has a prefix of a.
+ */
+bool
+ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
 {
-	char	   *al = str_tolower(a, s, DEFAULT_COLLATION_OID);
-	char	   *bl = str_tolower(b, s, DEFAULT_COLLATION_OID);
-	int			res;
+	static pg_locale_t locale = NULL;
+	size_t		al_sz = a_sz + 1;
+	size_t		al_len;
+	char	   *al = palloc(al_sz);
+	size_t		bl_sz = b_sz + 1;
+	size_t		bl_len;
+	char	   *bl = palloc(bl_sz);
+	bool		res;
+
+	if (!locale)
+		locale = pg_database_locale();
+
+	/* casefold both a and b */
+
+	al_len = pg_strfold(al, al_sz, a, a_sz, locale);
+	if (al_len + 1 > al_sz)
+	{
+		/* grow buffer if needed and retry */
+		al_sz = al_len + 1;
+		al = repalloc(al, al_sz);
+		al_len = pg_strfold(al, al_sz, a, a_sz, locale);
+		Assert(al_len + 1 <= al_sz);
+	}
+
+	bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
+	if (bl_len + 1 > bl_sz)
+	{
+		/* grow buffer if needed and retry */
+		bl_sz = bl_len + 1;
+		bl = repalloc(bl, bl_sz);
+		bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
+		Assert(bl_len + 1 <= bl_sz);
+	}
 
-	res = strncmp(al, bl, s);
+	if (al_len > bl_len)
+		res = false;
+	else
+		res = (strncmp(al, bl, al_len) == 0);
 
 	pfree(al);
 	pfree(bl);
@@ -109,19 +159,19 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		int			(*cmpptr) (const char *, const char *, size_t);
+		bool			(*prefix_eq) (const char *, size_t, const char *, size_t);
 
-		cmpptr = (curvar->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;
+		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, cmpptr,
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
 								(curvar->flag & LVAR_ANYEND)))
 				return success;
 		}
 		else if ((curvar->len == curt->len ||
 				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*cmpptr) (curvar->name, curt->name, curvar->len) == 0)
+				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index 5e0761641d3..08199ceb588 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -208,9 +208,11 @@ bool		ltree_execute(ITEM *curitem, void *checkval,
 int			ltree_compare(const ltree *a, const ltree *b);
 bool		inner_isparent(const ltree *c, const ltree *p);
 bool		compare_subnode(ltree_level *t, char *qn, int len,
-							int (*cmpptr) (const char *, const char *, size_t), bool anyend);
+							bool (*prefix_eq) (const char *, size_t, const char *, size_t),
+							bool anyend);
 ltree	   *lca_inner(ltree **a, int len);
-int			ltree_strncasecmp(const char *a, const char *b, size_t s);
+bool		ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz);
+bool		ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz);
 
 /* fmgr macros for ltree objects */
 #define DatumGetLtreeP(X)			((ltree *) PG_DETOAST_DATUM(X))
diff --git a/contrib/ltree/ltxtquery_op.c b/contrib/ltree/ltxtquery_op.c
index 002102c9c75..e3666a2d46e 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,19 @@ checkcondition_str(void *checkval, ITEM *val)
 	ltree_level *level = LTREE_FIRST(((CHKVAL *) checkval)->node);
 	int			tlen = ((CHKVAL *) checkval)->node->numlevel;
 	char	   *op = ((CHKVAL *) checkval)->operand + val->distance;
-	int			(*cmpptr) (const char *, const char *, size_t);
+	bool		(*prefix_eq) (const char *, size_t, const char *, size_t);
 
-	cmpptr = (val->flag & LVAR_INCASE) ? ltree_strncasecmp : strncmp;
+	prefix_eq = (val->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
 	while (tlen > 0)
 	{
 		if (val->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(level, op, val->length, cmpptr, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix_eq, (val->flag & LVAR_ANYEND)))
 				return true;
 		}
 		else if ((val->length == level->len ||
 				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*cmpptr) (op, level->name, val->length) == 0)
+				 (*prefix_eq) (op, val->length, level->name, level->len))
 			return true;
 
 		tlen--;
-- 
2.43.0