Commit 28a1ae53 authored by Tom Lane's avatar Tom Lane

Fix crash in contrib/ltree's lca() function for empty input array.

lca_inner() wasn't prepared for the possibility of getting no inputs.
Fix that, and make some cosmetic improvements to the code while at it.

Also, I thought the documentation of this function as returning the
"longest common prefix" of the paths was entirely misleading; it really
returns a path one shorter than the longest common prefix, for the typical
definition of "prefix".  Don't use that term in the docs, and adjust the
examples to clarify what really happens.

This has been broken since its beginning, so back-patch to all supported
branches.

Per report from Hailong Li.  Thanks to Pierre Ducroquet for diagnosing
and for the initial patch, though I whacked it around some and added
test cases.

Discussion: https://postgr.es/m/5b0d8e4f-f2a3-1305-d612-e00e35a7be66@qunar.com
parent 333224c9
...@@ -259,6 +259,24 @@ SELECT lca('{1.2.3,1.2.3.4.5.6}'); ...@@ -259,6 +259,24 @@ SELECT lca('{1.2.3,1.2.3.4.5.6}');
1.2 1.2
(1 row) (1 row)
SELECT lca('{1.2.3}');
lca
-----
1.2
(1 row)
SELECT lca('{1}'), lca('{1}') IS NULL;
lca | ?column?
-----+----------
| f
(1 row)
SELECT lca('{}') IS NULL;
?column?
----------
t
(1 row)
SELECT lca('1.la.2.3','1.2.3.4.5.6'); SELECT lca('1.la.2.3','1.2.3.4.5.6');
lca lca
----- -----
......
...@@ -402,22 +402,34 @@ ltree_textadd(PG_FUNCTION_ARGS) ...@@ -402,22 +402,34 @@ ltree_textadd(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(r); PG_RETURN_POINTER(r);
} }
/*
* Common code for variants of lca(), find longest common ancestor of inputs
*
* Returns NULL if there is no common ancestor, ie, the longest common
* prefix is empty.
*/
ltree * ltree *
lca_inner(ltree **a, int len) lca_inner(ltree **a, int len)
{ {
int tmp, int tmp,
num = ((*a)->numlevel) ? (*a)->numlevel - 1 : 0; num,
ltree **ptr = a + 1; i,
int i, reslen;
reslen = LTREE_HDRSIZE; ltree **ptr;
ltree_level *l1, ltree_level *l1,
*l2; *l2;
ltree *res; ltree *res;
if (len <= 0)
return NULL; /* no inputs? */
if ((*a)->numlevel == 0) if ((*a)->numlevel == 0)
return NULL; return NULL; /* any empty input means NULL result */
/* num is the length of the longest common ancestor so far */
num = (*a)->numlevel - 1;
/* Compare each additional input to *a */
ptr = a + 1;
while (ptr - a < len) while (ptr - a < len)
{ {
if ((*ptr)->numlevel == 0) if ((*ptr)->numlevel == 0)
...@@ -428,11 +440,12 @@ lca_inner(ltree **a, int len) ...@@ -428,11 +440,12 @@ lca_inner(ltree **a, int len)
{ {
l1 = LTREE_FIRST(*a); l1 = LTREE_FIRST(*a);
l2 = LTREE_FIRST(*ptr); l2 = LTREE_FIRST(*ptr);
tmp = num; tmp = Min(num, (*ptr)->numlevel - 1);
num = 0; num = 0;
for (i = 0; i < Min(tmp, (*ptr)->numlevel - 1); i++) for (i = 0; i < tmp; i++)
{ {
if (l1->len == l2->len && memcmp(l1->name, l2->name, l1->len) == 0) if (l1->len == l2->len &&
memcmp(l1->name, l2->name, l1->len) == 0)
num = i + 1; num = i + 1;
else else
break; break;
...@@ -443,6 +456,8 @@ lca_inner(ltree **a, int len) ...@@ -443,6 +456,8 @@ lca_inner(ltree **a, int len)
ptr++; ptr++;
} }
/* Now compute size of result ... */
reslen = LTREE_HDRSIZE;
l1 = LTREE_FIRST(*a); l1 = LTREE_FIRST(*a);
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
{ {
...@@ -450,6 +465,7 @@ lca_inner(ltree **a, int len) ...@@ -450,6 +465,7 @@ lca_inner(ltree **a, int len)
l1 = LEVEL_NEXT(l1); l1 = LEVEL_NEXT(l1);
} }
/* ... and construct it by copying from *a */
res = (ltree *) palloc0(reslen); res = (ltree *) palloc0(reslen);
SET_VARSIZE(res, reslen); SET_VARSIZE(res, reslen);
res->numlevel = num; res->numlevel = num;
......
...@@ -54,6 +54,9 @@ SELECT lca('{la.2.3,1.2.3.4.5.6,""}') IS NULL; ...@@ -54,6 +54,9 @@ SELECT lca('{la.2.3,1.2.3.4.5.6,""}') IS NULL;
SELECT lca('{la.2.3,1.2.3.4.5.6}') IS NULL; SELECT lca('{la.2.3,1.2.3.4.5.6}') IS NULL;
SELECT lca('{1.la.2.3,1.2.3.4.5.6}'); SELECT lca('{1.la.2.3,1.2.3.4.5.6}');
SELECT lca('{1.2.3,1.2.3.4.5.6}'); SELECT lca('{1.2.3,1.2.3.4.5.6}');
SELECT lca('{1.2.3}');
SELECT lca('{1}'), lca('{1}') IS NULL;
SELECT lca('{}') IS NULL;
SELECT lca('1.la.2.3','1.2.3.4.5.6'); SELECT lca('1.la.2.3','1.2.3.4.5.6');
SELECT lca('1.2.3','1.2.3.4.5.6'); SELECT lca('1.2.3','1.2.3.4.5.6');
SELECT lca('1.2.2.3','1.2.3.4.5.6'); SELECT lca('1.2.2.3','1.2.3.4.5.6');
......
...@@ -457,17 +457,17 @@ Europe &amp; Russia*@ &amp; !Transportation ...@@ -457,17 +457,17 @@ Europe &amp; Russia*@ &amp; !Transportation
<row> <row>
<entry><function>lca(ltree, ltree, ...)</function><indexterm><primary>lca</primary></indexterm></entry> <entry><function>lca(ltree, ltree, ...)</function><indexterm><primary>lca</primary></indexterm></entry>
<entry><type>ltree</type></entry> <entry><type>ltree</type></entry>
<entry>lowest common ancestor, i.e., longest common prefix of paths <entry>longest common ancestor of paths
(up to 8 arguments supported)</entry> (up to 8 arguments supported)</entry>
<entry><literal>lca('1.2.2.3','1.2.3.4.5.6')</literal></entry> <entry><literal>lca('1.2.3','1.2.3.4.5.6')</literal></entry>
<entry><literal>1.2</literal></entry> <entry><literal>1.2</literal></entry>
</row> </row>
<row> <row>
<entry><function>lca(ltree[])</function></entry> <entry><function>lca(ltree[])</function></entry>
<entry><type>ltree</type></entry> <entry><type>ltree</type></entry>
<entry>lowest common ancestor, i.e., longest common prefix of paths</entry> <entry>longest common ancestor of paths in array</entry>
<entry><literal>lca(array['1.2.2.3'::ltree,'1.2.3'])</literal></entry> <entry><literal>lca(array['1.2.3'::ltree,'1.2.3.4'])</literal></entry>
<entry><literal>1.2</literal></entry> <entry><literal>1.2</literal></entry>
</row> </row>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment