]> granicus.if.org Git - postgresql/commitdiff
Fix crash in contrib/ltree's lca() function for empty input array.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 13 Jul 2018 22:45:30 +0000 (18:45 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 13 Jul 2018 22:45:30 +0000 (18:45 -0400)
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

contrib/ltree/expected/ltree.out
contrib/ltree/ltree_op.c
contrib/ltree/sql/ltree.sql
doc/src/sgml/ltree.sgml

index 3d5737d41b191287eb615704324fa734951f30e5..822693090567919f9ac453377594c93326eb571d 100644 (file)
@@ -259,6 +259,24 @@ SELECT lca('{1.2.3,1.2.3.4.5.6}');
  1.2
 (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');
  lca 
 -----
index d62ca02521be87fb6f4ff7a3e21d4eaf8e971ee5..759f1f8d29be0a580d675f6f81700034e9accd79 100644 (file)
@@ -402,22 +402,34 @@ ltree_textadd(PG_FUNCTION_ARGS)
        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 *
 lca_inner(ltree **a, int len)
 {
        int                     tmp,
-                               num = ((*a)->numlevel) ? (*a)->numlevel - 1 : 0;
-       ltree     **ptr = a + 1;
-       int                     i,
-                               reslen = LTREE_HDRSIZE;
+                               num,
+                               i,
+                               reslen;
+       ltree     **ptr;
        ltree_level *l1,
                           *l2;
        ltree      *res;
 
-
+       if (len <= 0)
+               return NULL;                    /* no inputs? */
        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)
        {
                if ((*ptr)->numlevel == 0)
@@ -428,11 +440,12 @@ lca_inner(ltree **a, int len)
                {
                        l1 = LTREE_FIRST(*a);
                        l2 = LTREE_FIRST(*ptr);
-                       tmp = num;
+                       tmp = Min(num, (*ptr)->numlevel - 1);
                        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;
                                else
                                        break;
@@ -443,6 +456,8 @@ lca_inner(ltree **a, int len)
                ptr++;
        }
 
+       /* Now compute size of result ... */
+       reslen = LTREE_HDRSIZE;
        l1 = LTREE_FIRST(*a);
        for (i = 0; i < num; i++)
        {
@@ -450,6 +465,7 @@ lca_inner(ltree **a, int len)
                l1 = LEVEL_NEXT(l1);
        }
 
+       /* ... and construct it by copying from *a */
        res = (ltree *) palloc0(reslen);
        SET_VARSIZE(res, reslen);
        res->numlevel = num;
index e9f74909a64b92259bfa03ec5b9b29e27b97154f..846b04e48eefbec141eadadf959b07f94ccecb6f 100644 (file)
@@ -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('{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}');
+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.2.3','1.2.3.4.5.6');
 SELECT lca('1.2.2.3','1.2.3.4.5.6');
index 98e0ca5c6239f97c1ab1bf099ee70a34b1584999..3ddd335b8c929e29ec992de9f0471d1e9376a247 100644 (file)
@@ -457,17 +457,17 @@ Europe &amp; Russia*@ &amp; !Transportation
      <row>
       <entry><function>lca(ltree, ltree, ...)</function><indexterm><primary>lca</primary></indexterm></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>
-      <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>
      </row>
 
      <row>
       <entry><function>lca(ltree[])</function></entry>
       <entry><type>ltree</type></entry>
-      <entry>lowest common ancestor, i.e., longest common prefix of paths</entry>
-      <entry><literal>lca(array['1.2.2.3'::ltree,'1.2.3'])</literal></entry>
+      <entry>longest common ancestor of paths in array</entry>
+      <entry><literal>lca(array['1.2.3'::ltree,'1.2.3.4'])</literal></entry>
       <entry><literal>1.2</literal></entry>
      </row>