public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH] pg_dump: use a cursor in getBlobs..
2+ messages / 2 participants
[nested] [flat]

* [PATCH] pg_dump: use a cursor in getBlobs..
@ 2021-03-09 20:06  Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 2+ messages in thread

From: Justin Pryzby @ 2021-03-09 20:06 UTC (permalink / raw)

..to mitigate huge memory use in the case of millions of large objects
---
 src/bin/pg_dump/pg_dump.c | 96 +++++++++++++++++++++------------------
 1 file changed, 52 insertions(+), 44 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index aa02ada079..3fd7f48605 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -3333,7 +3333,7 @@ getBlobs(Archive *fout)
 	BlobInfo   *binfo;
 	DumpableObject *bdata;
 	PGresult   *res;
-	int			ntups;
+	int			ntups, total = 0;
 	int			i;
 	int			i_oid;
 	int			i_lomowner;
@@ -3341,9 +3341,12 @@ getBlobs(Archive *fout)
 	int			i_rlomacl;
 	int			i_initlomacl;
 	int			i_initrlomacl;
+	const char		*blobFetchQry = "FETCH 1000 IN blob";
 
 	pg_log_info("reading large objects");
 
+	appendPQExpBuffer(blobQry, "DECLARE blob CURSOR FOR ");
+
 	/* Fetch BLOB OIDs, and owner/ACL data if >= 9.0 */
 	if (fout->remoteVersion >= 90600)
 	{
@@ -3393,58 +3396,66 @@ getBlobs(Archive *fout)
 							 "NULL::oid AS initrlomacl "
 							 " FROM pg_largeobject");
 
-	res = ExecuteSqlQuery(fout, blobQry->data, PGRES_TUPLES_OK);
+	ExecuteSqlStatement(fout, blobQry->data);
+	destroyPQExpBuffer(blobQry);
 
-	i_oid = PQfnumber(res, "oid");
-	i_lomowner = PQfnumber(res, "rolname");
-	i_lomacl = PQfnumber(res, "lomacl");
-	i_rlomacl = PQfnumber(res, "rlomacl");
-	i_initlomacl = PQfnumber(res, "initlomacl");
-	i_initrlomacl = PQfnumber(res, "initrlomacl");
+	do {
+		res = ExecuteSqlQuery(fout, blobFetchQry, PGRES_TUPLES_OK);
 
-	ntups = PQntuples(res);
+		i_oid = PQfnumber(res, "oid");
+		i_lomowner = PQfnumber(res, "rolname");
+		i_lomacl = PQfnumber(res, "lomacl");
+		i_rlomacl = PQfnumber(res, "rlomacl");
+		i_initlomacl = PQfnumber(res, "initlomacl");
+		i_initrlomacl = PQfnumber(res, "initrlomacl");
 
-	/*
-	 * Each large object has its own BLOB archive entry.
-	 */
-	binfo = (BlobInfo *) pg_malloc(ntups * sizeof(BlobInfo));
+		ntups = PQntuples(res);
+		total += ntups;
 
-	for (i = 0; i < ntups; i++)
-	{
-		binfo[i].dobj.objType = DO_BLOB;
-		binfo[i].dobj.catId.tableoid = LargeObjectRelationId;
-		binfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
-		AssignDumpId(&binfo[i].dobj);
+		/*
+		 * Each large object has its own BLOB archive entry.
+		 */
+		binfo = (BlobInfo *) pg_malloc(ntups * sizeof(BlobInfo));
 
-		binfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_oid));
-		binfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_lomowner));
-		binfo[i].blobacl = pg_strdup(PQgetvalue(res, i, i_lomacl));
-		binfo[i].rblobacl = pg_strdup(PQgetvalue(res, i, i_rlomacl));
-		binfo[i].initblobacl = pg_strdup(PQgetvalue(res, i, i_initlomacl));
-		binfo[i].initrblobacl = pg_strdup(PQgetvalue(res, i, i_initrlomacl));
+		for (i = 0; i < ntups; i++)
+		{
+			binfo[i].dobj.objType = DO_BLOB;
+			binfo[i].dobj.catId.tableoid = LargeObjectRelationId;
+			binfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+			AssignDumpId(&binfo[i].dobj);
+
+			binfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_oid));
+			binfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_lomowner));
+			binfo[i].blobacl = pg_strdup(PQgetvalue(res, i, i_lomacl));
+			binfo[i].rblobacl = pg_strdup(PQgetvalue(res, i, i_rlomacl));
+			binfo[i].initblobacl = pg_strdup(PQgetvalue(res, i, i_initlomacl));
+			binfo[i].initrblobacl = pg_strdup(PQgetvalue(res, i, i_initrlomacl));
+
+			if (PQgetisnull(res, i, i_lomacl) &&
+				PQgetisnull(res, i, i_rlomacl) &&
+				PQgetisnull(res, i, i_initlomacl) &&
+				PQgetisnull(res, i, i_initrlomacl))
+				binfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;
 
-		if (PQgetisnull(res, i, i_lomacl) &&
-			PQgetisnull(res, i, i_rlomacl) &&
-			PQgetisnull(res, i, i_initlomacl) &&
-			PQgetisnull(res, i, i_initrlomacl))
-			binfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;
+			/*
+			 * In binary-upgrade mode for blobs, we do *not* dump out the blob
+			 * data, as it will be copied by pg_upgrade, which simply copies the
+			 * pg_largeobject table. We *do* however dump out anything but the
+			 * data, as pg_upgrade copies just pg_largeobject, but not
+			 * pg_largeobject_metadata, after the dump is restored.
+			 */
+			if (dopt->binary_upgrade)
+				binfo[i].dobj.dump &= ~DUMP_COMPONENT_DATA;
+		}
 
-		/*
-		 * In binary-upgrade mode for blobs, we do *not* dump out the blob
-		 * data, as it will be copied by pg_upgrade, which simply copies the
-		 * pg_largeobject table. We *do* however dump out anything but the
-		 * data, as pg_upgrade copies just pg_largeobject, but not
-		 * pg_largeobject_metadata, after the dump is restored.
-		 */
-		if (dopt->binary_upgrade)
-			binfo[i].dobj.dump &= ~DUMP_COMPONENT_DATA;
-	}
+		PQclear(res);
+	} while (ntups != 0);
 
 	/*
 	 * If we have any large objects, a "BLOBS" archive entry is needed. This
 	 * is just a placeholder for sorting; it carries no data now.
 	 */
-	if (ntups > 0)
+	if (total > 0)
 	{
 		bdata = (DumpableObject *) pg_malloc(sizeof(DumpableObject));
 		bdata->objType = DO_BLOB_DATA;
@@ -3452,9 +3463,6 @@ getBlobs(Archive *fout)
 		AssignDumpId(bdata);
 		bdata->name = pg_strdup("BLOBS");
 	}
-
-	PQclear(res);
-	destroyPQExpBuffer(blobQry);
 }
 
 /*
-- 
2.17.0


--gatW/ieO32f1wygP--





^ permalink  raw  reply  [nested|flat] 2+ messages in thread

* Fix more multibyte issues in ltree
@ 2026-02-13 21:56  Jeff Davis <[email protected]>
  0 siblings, 0 replies; 2+ messages in thread

From: Jeff Davis @ 2026-02-13 21:56 UTC (permalink / raw)
  To: pgsql-hackers

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers.

Repro:

   -- should return true, returns false
   SELECT 'sss'::ltree ~ 'ſſ*@'::lquery;

Patches attached; backport to 14 like 84d5efa7e3.

Regards,
	Jeff Davis



Attachments:

  [text/x-patch] v19.1-0001-Fix-more-multibyte-issues-in-ltree.patch (8.1K, ../../[email protected]/2-v19.1-0001-Fix-more-multibyte-issues-in-ltree.patch)
  download | inline diff:
From 9ac314bf51a2e603d02367d9d5303b896ccd6777 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Feb 2026 12:48:52 -0800
Subject: [PATCH v19.1] Fix more multibyte issues in ltree.

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers. The callers assumed that if the predicate string
is longer than the label string, then it couldn't possibly be a match,
but it can be when using case-insensitive matching (LVAR_INCASE) if
casefolding changes the byte length.

Fix by refactoring to get rid of the short-circuit logic as well as
the function pointer, and consolidate the logic in a replacement
function ltree_label_match().

Backpatch-through: 14
---
 contrib/ltree/lquery_op.c    | 103 +++++++++++++++++------------------
 contrib/ltree/ltree.h        |  10 ++--
 contrib/ltree/ltxtquery_op.c |  11 ++--
 3 files changed, 60 insertions(+), 64 deletions(-)

diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index 0adcdd8ff2a..e6a1969c3d3 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len,
-				ltree_prefix_eq_func prefix_eq, bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*prefix_eq) (qn, lenq, tn, lent))
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -76,64 +73,69 @@ compare_subnode(ltree_level *t, char *qn, int len,
 }
 
 /*
- * Check if 'a' is a prefix of 'b'.
- */
-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 'a' is a prefix of 'b'.
+ * Check if the label matches the predicate string. If 'prefix' is true, then
+ * the predicate string is treated as a prefix. If 'ci' is true, then the
+ * predicate string is case-insensitive (and locale-aware).
  */
 bool
-ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
+ltree_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
 	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);
+	char	   *fpred;			/* casefolded predicate */
+	size_t		fpred_len = pred_len;
+	char	   *flabel;			/* casefolded label */
+	size_t		flabel_len = label_len;
+	size_t		len;
 	bool		res;
 
+	/* fast path for binary match or binary prefix match */
+	if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
+		strncmp(pred, label, pred_len) == 0)
+		return true;
+	else if (!ci)
+		return false;
+
+	/*
+	 * Slow path for case-insensitive comparison: case fold and then compare.
+	 * This path is necessary even if pred_len > label_len, because the byte
+	 * lengths may change after casefolding.
+	 */
 	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)
+	fpred = palloc(fpred_len + 1);
+	len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale);
+	if (len > fpred_len)
 	{
 		/* 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);
+		fpred_len = len;
+		fpred = repalloc(fpred, fpred_len + 1);
+		len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale);
 	}
+	Assert(len <= fpred_len);
+	fpred_len = len;
 
-	bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
-	if (bl_len + 1 > bl_sz)
+	flabel = palloc(flabel_len + 1);
+	len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale);
+	if (len > flabel_len)
 	{
 		/* 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);
+		flabel_len = len;
+		flabel = repalloc(flabel, flabel_len + 1);
+		len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale);
 	}
+	Assert(len <= flabel_len);
+	flabel_len = len;
 
-	if (al_len > bl_len)
-		res = false;
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
 	else
-		res = (strncmp(al, bl, al_len) == 0);
+		res = false;
 
-	pfree(al);
-	pfree(bl);
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -158,19 +160,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		ltree_prefix_eq_func prefix_eq;
-
-		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
+		bool		prefix = (curvar->flag & LVAR_ANYEND);
+		bool		ci = (curvar->flag & LVAR_INCASE);
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
-								(curvar->flag & LVAR_ANYEND)))
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
 				return success;
 		}
-		else if ((curvar->len == curt->len ||
-				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
+		else if (ltree_label_match(curvar->name, curvar->len, curt->name,
+								   curt->len, prefix, ci))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index b0ded40eba9..226c1cb2115 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -157,8 +157,6 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } ltxtquery;
 
-typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t);
-
 #define HDRSIZEQT		MAXALIGN(VARHDRSZ + sizeof(int32))
 #define COMPUTESIZE(size,lenofoperand)	( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) )
 #define LTXTQUERY_TOO_BIG(size,lenofoperand) \
@@ -209,11 +207,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,
-							ltree_prefix_eq_func prefix_eq, bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-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);
+bool		ltree_label_match(const char *pred, size_t pred_len,
+							  const char *label, size_t label_len,
+							  bool prefix, bool ci);
 
 /* 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 3dcbab2c484..0e6612ff77a 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,18 @@ 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;
-	ltree_prefix_eq_func prefix_eq;
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	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, prefix_eq, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix, ci))
 				return true;
 		}
-		else if ((val->length == level->len ||
-				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (op, val->length, level->name, level->len))
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
-- 
2.43.0



  [text/x-patch] v18.1-0001-Fix-more-multibyte-issues-in-ltree.patch (8.5K, ../../[email protected]/3-v18.1-0001-Fix-more-multibyte-issues-in-ltree.patch)
  download | inline diff:
From f41622c3e371aacb5ac448cc4163d2a1a51c1147 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Feb 2026 12:48:52 -0800
Subject: [PATCH v18.1] Fix more multibyte issues in ltree.

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers. The callers assumed that if the predicate string
is longer than the label string, then it couldn't possibly be a match,
but it can be when using case-insensitive matching (LVAR_INCASE) if
casefolding changes the byte length.

Fix by refactoring to get rid of the short-circuit logic as well as
the function pointer, and consolidate the logic in a replacement
function ltree_label_match().

Backpatch-through: 14
---
 contrib/ltree/lquery_op.c    | 111 +++++++++++++++++------------------
 contrib/ltree/ltree.h        |  10 ++--
 contrib/ltree/ltxtquery_op.c |  11 ++--
 3 files changed, 63 insertions(+), 69 deletions(-)

diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index 14d84e81c0b..f50cfc334c4 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len,
-				ltree_prefix_eq_func prefix_eq, bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*prefix_eq) (qn, lenq, tn, lent))
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -76,81 +73,84 @@ compare_subnode(ltree_level *t, char *qn, int len,
 }
 
 /*
- * Check if 'a' is a prefix of 'b'.
- */
-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 'a' is a prefix of 'b'.
+ * Check if the label matches the predicate string. If 'prefix' is true, then
+ * the predicate string is treated as a prefix. If 'ci' is true, then the
+ * predicate string is case-insensitive (and locale-aware).
  */
 bool
-ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
+ltree_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
 	static pg_locale_t locale = NULL;
-	size_t		al_sz = a_sz + 1;
-	size_t		al_len;
-	char	   *al;
-	size_t		bl_sz = b_sz + 1;
-	size_t		bl_len;
-	char	   *bl;
+	char	   *fpred;			/* casefolded predicate */
+	size_t		fpred_len = pred_len;
+	char	   *flabel;			/* casefolded label */
+	size_t		flabel_len = label_len;
+	size_t		len;
 	bool		res;
 
+	/* fast path for binary match or binary prefix match */
+	if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
+		strncmp(pred, label, pred_len) == 0)
+		return true;
+	else if (!ci)
+		return false;
+
 	if (!locale)
 		locale = pg_newlocale_from_collation(DEFAULT_COLLATION_OID);
 
 	if (locale->ctype_is_c)
 	{
-		if (a_sz > b_sz)
+		if (pred_len > label_len)
 			return false;
 
-		for (int i = 0; i < a_sz; i++)
+		for (int i = 0; i < pred_len; i++)
 		{
-			if (pg_ascii_tolower(a[i]) != pg_ascii_tolower(b[i]))
+			if (pg_ascii_tolower(pred[i]) != pg_ascii_tolower(label[i]))
 				return false;
 		}
 
 		return true;
 	}
 
-	al = palloc(al_sz);
-	bl = palloc(bl_sz);
-
-	/* casefold both a and b */
+	/*
+	 * Slow path for case-insensitive comparison: case fold and then compare.
+	 * This path is necessary even if pred_len > label_len, because the byte
+	 * lengths may change after casefolding.
+	 */
 
-	al_len = pg_strfold(al, al_sz, a, a_sz, locale);
-	if (al_len + 1 > al_sz)
+	fpred = palloc(fpred_len + 1);
+	len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale);
+	if (len > fpred_len)
 	{
 		/* 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);
+		fpred_len = len;
+		fpred = repalloc(fpred, fpred_len + 1);
+		len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale);
 	}
+	Assert(len <= fpred_len);
+	fpred_len = len;
 
-	bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
-	if (bl_len + 1 > bl_sz)
+	flabel = palloc(flabel_len + 1);
+	len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale);
+	if (len > flabel_len)
 	{
 		/* 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);
+		flabel_len = len;
+		flabel = repalloc(flabel, flabel_len + 1);
+		len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale);
 	}
+	Assert(len <= flabel_len);
+	flabel_len = len;
 
-	if (al_len > bl_len)
-		res = false;
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
 	else
-		res = (strncmp(al, bl, al_len) == 0);
+		res = false;
 
-	pfree(al);
-	pfree(bl);
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -175,19 +175,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		ltree_prefix_eq_func prefix_eq;
-
-		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
+		bool		prefix = (curvar->flag & LVAR_ANYEND);
+		bool		ci = (curvar->flag & LVAR_INCASE);
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
-								(curvar->flag & LVAR_ANYEND)))
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
 				return success;
 		}
-		else if ((curvar->len == curt->len ||
-				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
+		else if (ltree_label_match(curvar->name, curvar->len, curt->name,
+								   curt->len, prefix, ci))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index b0ded40eba9..226c1cb2115 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -157,8 +157,6 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } ltxtquery;
 
-typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t);
-
 #define HDRSIZEQT		MAXALIGN(VARHDRSZ + sizeof(int32))
 #define COMPUTESIZE(size,lenofoperand)	( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) )
 #define LTXTQUERY_TOO_BIG(size,lenofoperand) \
@@ -209,11 +207,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,
-							ltree_prefix_eq_func prefix_eq, bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-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);
+bool		ltree_label_match(const char *pred, size_t pred_len,
+							  const char *label, size_t label_len,
+							  bool prefix, bool ci);
 
 /* 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 3dcbab2c484..0e6612ff77a 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,18 @@ 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;
-	ltree_prefix_eq_func prefix_eq;
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	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, prefix_eq, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix, ci))
 				return true;
 		}
-		else if ((val->length == level->len ||
-				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (op, val->length, level->name, level->len))
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
-- 
2.43.0



  [text/x-patch] v17.1-0001-Fix-more-multibyte-issues-in-ltree.patch (6.7K, ../../[email protected]/4-v17.1-0001-Fix-more-multibyte-issues-in-ltree.patch)
  download | inline diff:
From 7f17409d4e31f9d20292b63c964e84cbbab55d91 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Feb 2026 12:48:52 -0800
Subject: [PATCH v17.1] Fix more multibyte issues in ltree.

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers. The callers assumed that if the predicate string
is longer than the label string, then it couldn't possibly be a match,
but it can be when using case-insensitive matching (LVAR_INCASE) if
casefolding changes the byte length.

Fix by refactoring to get rid of the short-circuit logic as well as
the function pointer, and consolidate the logic in a replacement
function ltree_label_match().

Backpatch-through: 14
---
 contrib/ltree/lquery_op.c    | 65 +++++++++++++++++++-----------------
 contrib/ltree/ltree.h        | 10 +++---
 contrib/ltree/ltxtquery_op.c | 11 +++---
 3 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index 703085c67de..4dc610f5211 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len,
-				ltree_prefix_eq_func prefix_eq, bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*prefix_eq) (qn, lenq, tn, lent))
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -76,31 +73,40 @@ compare_subnode(ltree_level *t, char *qn, int len,
 }
 
 /*
- * Check if 'a' is a prefix of 'b'.
+ * Check if the label matches the predicate string. If 'prefix' is true, then
+ * the predicate string is treated as a prefix. If 'ci' is true, then the
+ * predicate string is case-insensitive (and locale-aware).
  */
 bool
-ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
+ltree_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
-	if (a_sz > b_sz)
+	char	   *fpred;			/* casefolded predicate */
+	size_t		fpred_len;
+	char	   *flabel;			/* casefolded label */
+	size_t		flabel_len;
+	bool		res;
+
+	/* fast path for binary match or binary prefix match */
+	if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
+		strncmp(pred, label, pred_len) == 0)
+		return true;
+	else if (!ci)
 		return false;
-	else
-		return (strncmp(a, b, a_sz) == 0);
-}
 
-/*
- * Case-insensitive check if 'a' is a prefix of 'b'.
- */
-bool
-ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
-{
-	char	   *al = str_tolower(a, a_sz, DEFAULT_COLLATION_OID);
-	char	   *bl = str_tolower(b, b_sz, DEFAULT_COLLATION_OID);
-	bool		res;
+	fpred = str_tolower(pred, pred_len, DEFAULT_COLLATION_OID);
+	fpred_len = strlen(fpred);
+	flabel = str_tolower(label, label_len, DEFAULT_COLLATION_OID);
+	flabel_len = strlen(flabel);
 
-	res = (strncmp(al, bl, a_sz) == 0);
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
+	else
+		res = false;
 
-	pfree(al);
-	pfree(bl);
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -125,19 +131,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		ltree_prefix_eq_func prefix_eq;
-
-		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
+		bool		prefix = (curvar->flag & LVAR_ANYEND);
+		bool		ci = (curvar->flag & LVAR_INCASE);
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
-								(curvar->flag & LVAR_ANYEND)))
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
 				return success;
 		}
-		else if ((curvar->len == curt->len ||
-				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
+		else if (ltree_label_match(curvar->name, curvar->len, curt->name,
+								   curt->len, prefix, ci))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index b0ded40eba9..226c1cb2115 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -157,8 +157,6 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } ltxtquery;
 
-typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t);
-
 #define HDRSIZEQT		MAXALIGN(VARHDRSZ + sizeof(int32))
 #define COMPUTESIZE(size,lenofoperand)	( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) )
 #define LTXTQUERY_TOO_BIG(size,lenofoperand) \
@@ -209,11 +207,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,
-							ltree_prefix_eq_func prefix_eq, bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-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);
+bool		ltree_label_match(const char *pred, size_t pred_len,
+							  const char *label, size_t label_len,
+							  bool prefix, bool ci);
 
 /* 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 3dcbab2c484..0e6612ff77a 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,18 @@ 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;
-	ltree_prefix_eq_func prefix_eq;
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	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, prefix_eq, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix, ci))
 				return true;
 		}
-		else if ((val->length == level->len ||
-				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (op, val->length, level->name, level->len))
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
-- 
2.43.0



  [text/x-patch] v16.1-0001-Fix-more-multibyte-issues-in-ltree.patch (6.7K, ../../[email protected]/5-v16.1-0001-Fix-more-multibyte-issues-in-ltree.patch)
  download | inline diff:
From 0ac14601da495b57216f230e42ccf74231d0f608 Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Feb 2026 12:48:52 -0800
Subject: [PATCH v16.1] Fix more multibyte issues in ltree.

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers. The callers assumed that if the predicate string
is longer than the label string, then it couldn't possibly be a match,
but it can be when using case-insensitive matching (LVAR_INCASE) if
casefolding changes the byte length.

Fix by refactoring to get rid of the short-circuit logic as well as
the function pointer, and consolidate the logic in a replacement
function ltree_label_match().

Backpatch-through: 14
---
 contrib/ltree/lquery_op.c    | 65 +++++++++++++++++++-----------------
 contrib/ltree/ltree.h        | 10 +++---
 contrib/ltree/ltxtquery_op.c | 11 +++---
 3 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index 703085c67de..4dc610f5211 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len,
-				ltree_prefix_eq_func prefix_eq, bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*prefix_eq) (qn, lenq, tn, lent))
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -76,31 +73,40 @@ compare_subnode(ltree_level *t, char *qn, int len,
 }
 
 /*
- * Check if 'a' is a prefix of 'b'.
+ * Check if the label matches the predicate string. If 'prefix' is true, then
+ * the predicate string is treated as a prefix. If 'ci' is true, then the
+ * predicate string is case-insensitive (and locale-aware).
  */
 bool
-ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
+ltree_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
-	if (a_sz > b_sz)
+	char	   *fpred;			/* casefolded predicate */
+	size_t		fpred_len;
+	char	   *flabel;			/* casefolded label */
+	size_t		flabel_len;
+	bool		res;
+
+	/* fast path for binary match or binary prefix match */
+	if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
+		strncmp(pred, label, pred_len) == 0)
+		return true;
+	else if (!ci)
 		return false;
-	else
-		return (strncmp(a, b, a_sz) == 0);
-}
 
-/*
- * Case-insensitive check if 'a' is a prefix of 'b'.
- */
-bool
-ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
-{
-	char	   *al = str_tolower(a, a_sz, DEFAULT_COLLATION_OID);
-	char	   *bl = str_tolower(b, b_sz, DEFAULT_COLLATION_OID);
-	bool		res;
+	fpred = str_tolower(pred, pred_len, DEFAULT_COLLATION_OID);
+	fpred_len = strlen(fpred);
+	flabel = str_tolower(label, label_len, DEFAULT_COLLATION_OID);
+	flabel_len = strlen(flabel);
 
-	res = (strncmp(al, bl, a_sz) == 0);
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
+	else
+		res = false;
 
-	pfree(al);
-	pfree(bl);
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -125,19 +131,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		ltree_prefix_eq_func prefix_eq;
-
-		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
+		bool		prefix = (curvar->flag & LVAR_ANYEND);
+		bool		ci = (curvar->flag & LVAR_INCASE);
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
-								(curvar->flag & LVAR_ANYEND)))
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
 				return success;
 		}
-		else if ((curvar->len == curt->len ||
-				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
+		else if (ltree_label_match(curvar->name, curvar->len, curt->name,
+								   curt->len, prefix, ci))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index b0ded40eba9..226c1cb2115 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -157,8 +157,6 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } ltxtquery;
 
-typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t);
-
 #define HDRSIZEQT		MAXALIGN(VARHDRSZ + sizeof(int32))
 #define COMPUTESIZE(size,lenofoperand)	( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) )
 #define LTXTQUERY_TOO_BIG(size,lenofoperand) \
@@ -209,11 +207,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,
-							ltree_prefix_eq_func prefix_eq, bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-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);
+bool		ltree_label_match(const char *pred, size_t pred_len,
+							  const char *label, size_t label_len,
+							  bool prefix, bool ci);
 
 /* 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 3dcbab2c484..0e6612ff77a 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,18 @@ 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;
-	ltree_prefix_eq_func prefix_eq;
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	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, prefix_eq, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix, ci))
 				return true;
 		}
-		else if ((val->length == level->len ||
-				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (op, val->length, level->name, level->len))
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
-- 
2.43.0



  [text/x-patch] v15.1-0001-Fix-more-multibyte-issues-in-ltree.patch (6.7K, ../../[email protected]/6-v15.1-0001-Fix-more-multibyte-issues-in-ltree.patch)
  download | inline diff:
From aaa0f14c822df8eb91249233c98d1bb4e4a4807f Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Feb 2026 12:48:52 -0800
Subject: [PATCH v15.1] Fix more multibyte issues in ltree.

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers. The callers assumed that if the predicate string
is longer than the label string, then it couldn't possibly be a match,
but it can be when using case-insensitive matching (LVAR_INCASE) if
casefolding changes the byte length.

Fix by refactoring to get rid of the short-circuit logic as well as
the function pointer, and consolidate the logic in a replacement
function ltree_label_match().

Backpatch-through: 14
---
 contrib/ltree/lquery_op.c    | 65 +++++++++++++++++++-----------------
 contrib/ltree/ltree.h        | 10 +++---
 contrib/ltree/ltxtquery_op.c | 11 +++---
 3 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index 46019a0e83a..540b678b038 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len,
-				ltree_prefix_eq_func prefix_eq, bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*prefix_eq) (qn, lenq, tn, lent))
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -76,31 +73,40 @@ compare_subnode(ltree_level *t, char *qn, int len,
 }
 
 /*
- * Check if 'a' is a prefix of 'b'.
+ * Check if the label matches the predicate string. If 'prefix' is true, then
+ * the predicate string is treated as a prefix. If 'ci' is true, then the
+ * predicate string is case-insensitive (and locale-aware).
  */
 bool
-ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
+ltree_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
-	if (a_sz > b_sz)
+	char	   *fpred;			/* casefolded predicate */
+	size_t		fpred_len;
+	char	   *flabel;			/* casefolded label */
+	size_t		flabel_len;
+	bool		res;
+
+	/* fast path for binary match or binary prefix match */
+	if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
+		strncmp(pred, label, pred_len) == 0)
+		return true;
+	else if (!ci)
 		return false;
-	else
-		return (strncmp(a, b, a_sz) == 0);
-}
 
-/*
- * Case-insensitive check if 'a' is a prefix of 'b'.
- */
-bool
-ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
-{
-	char	   *al = str_tolower(a, a_sz, DEFAULT_COLLATION_OID);
-	char	   *bl = str_tolower(b, b_sz, DEFAULT_COLLATION_OID);
-	bool		res;
+	fpred = str_tolower(pred, pred_len, DEFAULT_COLLATION_OID);
+	fpred_len = strlen(fpred);
+	flabel = str_tolower(label, label_len, DEFAULT_COLLATION_OID);
+	flabel_len = strlen(flabel);
 
-	res = (strncmp(al, bl, a_sz) == 0);
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
+	else
+		res = false;
 
-	pfree(al);
-	pfree(bl);
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -125,19 +131,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		ltree_prefix_eq_func prefix_eq;
-
-		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
+		bool		prefix = (curvar->flag & LVAR_ANYEND);
+		bool		ci = (curvar->flag & LVAR_INCASE);
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
-								(curvar->flag & LVAR_ANYEND)))
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
 				return success;
 		}
-		else if ((curvar->len == curt->len ||
-				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
+		else if (ltree_label_match(curvar->name, curvar->len, curt->name,
+								   curt->len, prefix, ci))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index a37ee6b2c67..3673b9d74c8 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -157,8 +157,6 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } ltxtquery;
 
-typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t);
-
 #define HDRSIZEQT		MAXALIGN(VARHDRSZ + sizeof(int32))
 #define COMPUTESIZE(size,lenofoperand)	( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) )
 #define LTXTQUERY_TOO_BIG(size,lenofoperand) \
@@ -209,11 +207,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,
-							ltree_prefix_eq_func prefix_eq, bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-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);
+bool		ltree_label_match(const char *pred, size_t pred_len,
+							  const char *label, size_t label_len,
+							  bool prefix, bool ci);
 
 /* 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 3dcbab2c484..0e6612ff77a 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,18 @@ 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;
-	ltree_prefix_eq_func prefix_eq;
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	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, prefix_eq, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix, ci))
 				return true;
 		}
-		else if ((val->length == level->len ||
-				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (op, val->length, level->name, level->len))
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
-- 
2.43.0



  [text/x-patch] v14.1-0001-Fix-more-multibyte-issues-in-ltree.patch (6.7K, ../../[email protected]/7-v14.1-0001-Fix-more-multibyte-issues-in-ltree.patch)
  download | inline diff:
From fc8acfb0d87167ca5ba305062e88c74bb438c7fd Mon Sep 17 00:00:00 2001
From: Jeff Davis <[email protected]>
Date: Fri, 13 Feb 2026 12:48:52 -0800
Subject: [PATCH v14.1] Fix more multibyte issues in ltree.

Commit 84d5efa7e3 missed some multibyte issues caused by short-circuit
logic in the callers. The callers assumed that if the predicate string
is longer than the label string, then it couldn't possibly be a match,
but it can be when using case-insensitive matching (LVAR_INCASE) if
casefolding changes the byte length.

Fix by refactoring to get rid of the short-circuit logic as well as
the function pointer, and consolidate the logic in a replacement
function ltree_label_match().

Backpatch-through: 14
---
 contrib/ltree/lquery_op.c    | 65 +++++++++++++++++++-----------------
 contrib/ltree/ltree.h        | 10 +++---
 contrib/ltree/ltxtquery_op.c | 11 +++---
 3 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index 46019a0e83a..540b678b038 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -41,8 +41,7 @@ getlexeme(char *start, char *end, int *len)
 }
 
 bool
-compare_subnode(ltree_level *t, char *qn, int len,
-				ltree_prefix_eq_func prefix_eq, bool anyend)
+compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
 {
 	char	   *endt = t->name + t->len;
 	char	   *endq = qn + len;
@@ -57,10 +56,8 @@ compare_subnode(ltree_level *t, char *qn, int len,
 		isok = false;
 		while ((tn = getlexeme(tn, endt, &lent)) != NULL)
 		{
-			if ((lent == lenq || (lent > lenq && anyend)) &&
-				(*prefix_eq) (qn, lenq, tn, lent))
+			if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
 			{
-
 				isok = true;
 				break;
 			}
@@ -76,31 +73,40 @@ compare_subnode(ltree_level *t, char *qn, int len,
 }
 
 /*
- * Check if 'a' is a prefix of 'b'.
+ * Check if the label matches the predicate string. If 'prefix' is true, then
+ * the predicate string is treated as a prefix. If 'ci' is true, then the
+ * predicate string is case-insensitive (and locale-aware).
  */
 bool
-ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
+ltree_label_match(const char *pred, size_t pred_len, const char *label,
+				  size_t label_len, bool prefix, bool ci)
 {
-	if (a_sz > b_sz)
+	char	   *fpred;			/* casefolded predicate */
+	size_t		fpred_len;
+	char	   *flabel;			/* casefolded label */
+	size_t		flabel_len;
+	bool		res;
+
+	/* fast path for binary match or binary prefix match */
+	if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
+		strncmp(pred, label, pred_len) == 0)
+		return true;
+	else if (!ci)
 		return false;
-	else
-		return (strncmp(a, b, a_sz) == 0);
-}
 
-/*
- * Case-insensitive check if 'a' is a prefix of 'b'.
- */
-bool
-ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
-{
-	char	   *al = str_tolower(a, a_sz, DEFAULT_COLLATION_OID);
-	char	   *bl = str_tolower(b, b_sz, DEFAULT_COLLATION_OID);
-	bool		res;
+	fpred = str_tolower(pred, pred_len, DEFAULT_COLLATION_OID);
+	fpred_len = strlen(fpred);
+	flabel = str_tolower(label, label_len, DEFAULT_COLLATION_OID);
+	flabel_len = strlen(flabel);
 
-	res = (strncmp(al, bl, a_sz) == 0);
+	if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
+		strncmp(fpred, flabel, fpred_len) == 0)
+		res = true;
+	else
+		res = false;
 
-	pfree(al);
-	pfree(bl);
+	pfree(fpred);
+	pfree(flabel);
 
 	return res;
 }
@@ -125,19 +131,16 @@ checkLevel(lquery_level *curq, ltree_level *curt)
 
 	for (int i = 0; i < curq->numvar; i++)
 	{
-		ltree_prefix_eq_func prefix_eq;
-
-		prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
+		bool		prefix = (curvar->flag & LVAR_ANYEND);
+		bool		ci = (curvar->flag & LVAR_INCASE);
 
 		if (curvar->flag & LVAR_SUBLEXEME)
 		{
-			if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
-								(curvar->flag & LVAR_ANYEND)))
+			if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
 				return success;
 		}
-		else if ((curvar->len == curt->len ||
-				  (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
+		else if (ltree_label_match(curvar->name, curvar->len, curt->name,
+								   curt->len, prefix, ci))
 			return success;
 
 		curvar = LVAR_NEXT(curvar);
diff --git a/contrib/ltree/ltree.h b/contrib/ltree/ltree.h
index c7f00a99b06..c3b38fcde4b 100644
--- a/contrib/ltree/ltree.h
+++ b/contrib/ltree/ltree.h
@@ -144,8 +144,6 @@ typedef struct
 	char		data[FLEXIBLE_ARRAY_MEMBER];
 } ltxtquery;
 
-typedef bool (*ltree_prefix_eq_func) (const char *, size_t, const char *, size_t);
-
 #define HDRSIZEQT		MAXALIGN(VARHDRSZ + sizeof(int32))
 #define COMPUTESIZE(size,lenofoperand)	( HDRSIZEQT + (size) * sizeof(ITEM) + (lenofoperand) )
 #define LTXTQUERY_TOO_BIG(size,lenofoperand) \
@@ -196,11 +194,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,
-							ltree_prefix_eq_func prefix_eq, bool anyend);
+bool		compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci);
 ltree	   *lca_inner(ltree **a, int len);
-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);
+bool		ltree_label_match(const char *pred, size_t pred_len,
+							  const char *label, size_t label_len,
+							  bool prefix, bool ci);
 
 /* 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 3dcbab2c484..0e6612ff77a 100644
--- a/contrib/ltree/ltxtquery_op.c
+++ b/contrib/ltree/ltxtquery_op.c
@@ -58,19 +58,18 @@ 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;
-	ltree_prefix_eq_func prefix_eq;
+	bool		prefix = (val->flag & LVAR_ANYEND);
+	bool		ci = (val->flag & LVAR_INCASE);
 
-	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, prefix_eq, (val->flag & LVAR_ANYEND)))
+			if (compare_subnode(level, op, val->length, prefix, ci))
 				return true;
 		}
-		else if ((val->length == level->len ||
-				  (level->len > val->length && (val->flag & LVAR_ANYEND))) &&
-				 (*prefix_eq) (op, val->length, level->name, level->len))
+		else if (ltree_label_match(op, val->length, level->name, level->len,
+								   prefix, ci))
 			return true;
 
 		tlen--;
-- 
2.43.0



^ permalink  raw  reply  [nested|flat] 2+ messages in thread


end of thread, other threads:[~2026-02-13 21:56 UTC | newest]

Thread overview: 2+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2021-03-09 20:06 [PATCH] pg_dump: use a cursor in getBlobs.. Justin Pryzby <[email protected]>
2026-02-13 21:56 Fix more multibyte issues in ltree Jeff Davis <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox