LCOV - code coverage report
Current view: top level - contrib/ltree - lquery_op.c (source / functions) Hit Total Coverage
Test: PostgreSQL 19devel Lines: 113 127 89.0 %
Date: 2026-01-10 02:17:43 Functions: 12 14 85.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :  * op function for ltree and lquery
       3             :  * Teodor Sigaev <teodor@stack.net>
       4             :  * contrib/ltree/lquery_op.c
       5             :  */
       6             : #include "postgres.h"
       7             : 
       8             : #include <ctype.h>
       9             : 
      10             : #include "catalog/pg_collation.h"
      11             : #include "ltree.h"
      12             : #include "miscadmin.h"
      13             : #include "utils/array.h"
      14             : #include "utils/formatting.h"
      15             : 
      16           6 : PG_FUNCTION_INFO_V1(ltq_regex);
      17           4 : PG_FUNCTION_INFO_V1(ltq_rregex);
      18             : 
      19           6 : PG_FUNCTION_INFO_V1(lt_q_regex);
      20           4 : PG_FUNCTION_INFO_V1(lt_q_rregex);
      21             : 
      22             : #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
      23             : 
      24             : static char *
      25          72 : getlexeme(char *start, char *end, int *len)
      26             : {
      27             :     char       *ptr;
      28             : 
      29          88 :     while (start < end && t_iseq(start, '_'))
      30          16 :         start += pg_mblen(start);
      31             : 
      32          72 :     ptr = start;
      33          72 :     if (ptr >= end)
      34          16 :         return NULL;
      35             : 
      36         228 :     while (ptr < end && !t_iseq(ptr, '_'))
      37         172 :         ptr += pg_mblen(ptr);
      38             : 
      39          56 :     *len = ptr - start;
      40          56 :     return start;
      41             : }
      42             : 
      43             : bool
      44          16 : compare_subnode(ltree_level *t, char *qn, int len,
      45             :                 ltree_prefix_eq_func prefix_eq, bool anyend)
      46             : {
      47          16 :     char       *endt = t->name + t->len;
      48          16 :     char       *endq = qn + len;
      49             :     char       *tn;
      50             :     int         lent,
      51             :                 lenq;
      52             :     bool        isok;
      53             : 
      54          32 :     while ((qn = getlexeme(qn, endq, &lenq)) != NULL)
      55             :     {
      56          24 :         tn = t->name;
      57          24 :         isok = false;
      58          40 :         while ((tn = getlexeme(tn, endt, &lent)) != NULL)
      59             :         {
      60          64 :             if ((lent == lenq || (lent > lenq && anyend)) &&
      61          32 :                 (*prefix_eq) (qn, lenq, tn, lent))
      62             :             {
      63             : 
      64          16 :                 isok = true;
      65          16 :                 break;
      66             :             }
      67          16 :             tn += lent;
      68             :         }
      69             : 
      70          24 :         if (!isok)
      71           8 :             return false;
      72          16 :         qn += lenq;
      73             :     }
      74             : 
      75           8 :     return true;
      76             : }
      77             : 
      78             : /*
      79             :  * Check if 'a' is a prefix of 'b'.
      80             :  */
      81             : bool
      82      188244 : ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
      83             : {
      84      188244 :     if (a_sz > b_sz)
      85           0 :         return false;
      86             :     else
      87      188244 :         return (strncmp(a, b, a_sz) == 0);
      88             : }
      89             : 
      90             : /*
      91             :  * Case-insensitive check if 'a' is a prefix of 'b'.
      92             :  */
      93             : bool
      94          52 : ltree_prefix_eq_ci(const char *a, size_t a_sz, const char *b, size_t b_sz)
      95             : {
      96             :     static pg_locale_t locale = NULL;
      97          52 :     size_t      al_sz = a_sz + 1;
      98             :     size_t      al_len;
      99          52 :     char       *al = palloc(al_sz);
     100          52 :     size_t      bl_sz = b_sz + 1;
     101             :     size_t      bl_len;
     102          52 :     char       *bl = palloc(bl_sz);
     103             :     bool        res;
     104             : 
     105          52 :     if (!locale)
     106           2 :         locale = pg_database_locale();
     107             : 
     108             :     /* casefold both a and b */
     109             : 
     110          52 :     al_len = pg_strfold(al, al_sz, a, a_sz, locale);
     111          52 :     if (al_len + 1 > al_sz)
     112             :     {
     113             :         /* grow buffer if needed and retry */
     114           0 :         al_sz = al_len + 1;
     115           0 :         al = repalloc(al, al_sz);
     116           0 :         al_len = pg_strfold(al, al_sz, a, a_sz, locale);
     117             :         Assert(al_len + 1 <= al_sz);
     118             :     }
     119             : 
     120          52 :     bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
     121          52 :     if (bl_len + 1 > bl_sz)
     122             :     {
     123             :         /* grow buffer if needed and retry */
     124           0 :         bl_sz = bl_len + 1;
     125           0 :         bl = repalloc(bl, bl_sz);
     126           0 :         bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
     127             :         Assert(bl_len + 1 <= bl_sz);
     128             :     }
     129             : 
     130          52 :     if (al_len > bl_len)
     131           0 :         res = false;
     132             :     else
     133          52 :         res = (strncmp(al, bl, al_len) == 0);
     134             : 
     135          52 :     pfree(al);
     136          52 :     pfree(bl);
     137             : 
     138          52 :     return res;
     139             : }
     140             : 
     141             : /*
     142             :  * See if an lquery_level matches an ltree_level
     143             :  *
     144             :  * This accounts for all flags including LQL_NOT, but does not
     145             :  * consider repetition counts.
     146             :  */
     147             : static bool
     148      433726 : checkLevel(lquery_level *curq, ltree_level *curt)
     149             : {
     150      433726 :     lquery_variant *curvar = LQL_FIRST(curq);
     151             :     bool        success;
     152             : 
     153      433726 :     success = (curq->flag & LQL_NOT) ? false : true;
     154             : 
     155             :     /* numvar == 0 means '*' which matches anything */
     156      433726 :     if (curq->numvar == 0)
     157      166390 :         return success;
     158             : 
     159      521574 :     for (int i = 0; i < curq->numvar; i++)
     160             :     {
     161             :         ltree_prefix_eq_func prefix_eq;
     162             : 
     163      267346 :         prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
     164             : 
     165      267346 :         if (curvar->flag & LVAR_SUBLEXEME)
     166             :         {
     167           8 :             if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
     168           8 :                                 (curvar->flag & LVAR_ANYEND)))
     169           6 :                 return success;
     170             :         }
     171      267338 :         else if ((curvar->len == curt->len ||
     172      267344 :                   (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
     173      110052 :                  (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
     174       13102 :             return success;
     175             : 
     176      254238 :         curvar = LVAR_NEXT(curvar);
     177             :     }
     178      254228 :     return !success;
     179             : }
     180             : 
     181             : /*
     182             :  * Try to match an lquery (of qlen items) to an ltree (of tlen items)
     183             :  */
     184             : static bool
     185      287068 : checkCond(lquery_level *curq, int qlen,
     186             :           ltree_level *curt, int tlen)
     187             : {
     188             :     /* Since this function recurses, it could be driven to stack overflow */
     189      287068 :     check_stack_depth();
     190             : 
     191             :     /* Pathological patterns could take awhile, too */
     192      287068 :     CHECK_FOR_INTERRUPTS();
     193             : 
     194             :     /* Loop while we have query items to consider */
     195      325626 :     while (qlen > 0)
     196             :     {
     197             :         int         low,
     198             :                     high;
     199             :         lquery_level *nextq;
     200             : 
     201             :         /*
     202             :          * Get min and max repetition counts for this query item, dealing with
     203             :          * the backwards-compatibility hack that the low/high fields aren't
     204             :          * meaningful for non-'*' items unless LQL_COUNT is set.
     205             :          */
     206      318200 :         if ((curq->flag & LQL_COUNT) || curq->numvar == 0)
     207       26614 :             low = curq->low, high = curq->high;
     208             :         else
     209      291586 :             low = high = 1;
     210             : 
     211             :         /*
     212             :          * We may limit "high" to the remaining text length; this avoids
     213             :          * separate tests below.
     214             :          */
     215      318200 :         if (high > tlen)
     216       49976 :             high = tlen;
     217             : 
     218             :         /* Fail if a match of required number of items is impossible */
     219      318200 :         if (high < low)
     220       24346 :             return false;
     221             : 
     222             :         /*
     223             :          * Recursively check the rest of the pattern against each possible
     224             :          * start point following some of this item's match(es).
     225             :          */
     226      293854 :         nextq = LQL_NEXT(curq);
     227      293854 :         qlen--;
     228             : 
     229      473472 :         for (int matchcnt = 0; matchcnt < high; matchcnt++)
     230             :         {
     231             :             /*
     232             :              * If we've consumed an acceptable number of matches of this item,
     233             :              * and the rest of the pattern matches beginning here, we're good.
     234             :              */
     235      434914 :             if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen))
     236        1188 :                 return true;
     237             : 
     238             :             /*
     239             :              * Otherwise, try to match one more text item to this query item.
     240             :              */
     241      433726 :             if (!checkLevel(curq, curt))
     242      254108 :                 return false;
     243             : 
     244      179618 :             curt = LEVEL_NEXT(curt);
     245      179618 :             tlen--;
     246             :         }
     247             : 
     248             :         /*
     249             :          * Once we've consumed "high" matches, we can succeed only if the rest
     250             :          * of the pattern matches beginning here.  Loop around (if you prefer,
     251             :          * think of this as tail recursion).
     252             :          */
     253       38558 :         curq = nextq;
     254             :     }
     255             : 
     256             :     /*
     257             :      * Once we're out of query items, we match only if there's no remaining
     258             :      * text either.
     259             :      */
     260        7426 :     return (tlen == 0);
     261             : }
     262             : 
     263             : Datum
     264      120432 : ltq_regex(PG_FUNCTION_ARGS)
     265             : {
     266      120432 :     ltree      *tree = PG_GETARG_LTREE_P(0);
     267      120432 :     lquery     *query = PG_GETARG_LQUERY_P(1);
     268             :     bool        res;
     269             : 
     270      120432 :     res = checkCond(LQUERY_FIRST(query), query->numlevel,
     271      120432 :                     LTREE_FIRST(tree), tree->numlevel);
     272             : 
     273      120432 :     PG_FREE_IF_COPY(tree, 0);
     274      120432 :     PG_FREE_IF_COPY(query, 1);
     275      120432 :     PG_RETURN_BOOL(res);
     276             : }
     277             : 
     278             : Datum
     279           0 : ltq_rregex(PG_FUNCTION_ARGS)
     280             : {
     281           0 :     PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex,
     282             :                                         PG_GETARG_DATUM(1),
     283             :                                         PG_GETARG_DATUM(0)
     284             :                                         ));
     285             : }
     286             : 
     287             : Datum
     288        3178 : lt_q_regex(PG_FUNCTION_ARGS)
     289             : {
     290        3178 :     ltree      *tree = PG_GETARG_LTREE_P(0);
     291        3178 :     ArrayType  *_query = PG_GETARG_ARRAYTYPE_P(1);
     292        3178 :     lquery     *query = (lquery *) ARR_DATA_PTR(_query);
     293        3178 :     bool        res = false;
     294        3178 :     int         num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query));
     295             : 
     296        3178 :     if (ARR_NDIM(_query) > 1)
     297           0 :         ereport(ERROR,
     298             :                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
     299             :                  errmsg("array must be one-dimensional")));
     300        3178 :     if (array_contains_nulls(_query))
     301           0 :         ereport(ERROR,
     302             :                 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     303             :                  errmsg("array must not contain nulls")));
     304             : 
     305        9484 :     while (num > 0)
     306             :     {
     307        6334 :         if (DatumGetBool(DirectFunctionCall2(ltq_regex,
     308             :                                              PointerGetDatum(tree), PointerGetDatum(query))))
     309             :         {
     310             : 
     311          28 :             res = true;
     312          28 :             break;
     313             :         }
     314        6306 :         num--;
     315        6306 :         query = NEXTVAL(query);
     316             :     }
     317             : 
     318        3178 :     PG_FREE_IF_COPY(tree, 0);
     319        3178 :     PG_FREE_IF_COPY(_query, 1);
     320        3178 :     PG_RETURN_BOOL(res);
     321             : }
     322             : 
     323             : Datum
     324           0 : lt_q_rregex(PG_FUNCTION_ARGS)
     325             : {
     326           0 :     PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex,
     327             :                                         PG_GETARG_DATUM(1),
     328             :                                         PG_GETARG_DATUM(0)
     329             :                                         ));
     330             : }

Generated by: LCOV version 1.16