LCOV - code coverage report
Current view: top level - contrib/ltree - lquery_op.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 0.0 % 155 0
Test Date: 2026-01-26 10:56:24 Functions: 0.0 % 14 0
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            0 : PG_FUNCTION_INFO_V1(ltq_regex);
      17            0 : PG_FUNCTION_INFO_V1(ltq_rregex);
      18              : 
      19            0 : PG_FUNCTION_INFO_V1(lt_q_regex);
      20            0 : PG_FUNCTION_INFO_V1(lt_q_rregex);
      21              : 
      22              : #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
      23              : 
      24              : static char *
      25            0 : getlexeme(char *start, char *end, int *len)
      26              : {
      27            0 :         char       *ptr;
      28              : 
      29            0 :         while (start < end && t_iseq(start, '_'))
      30            0 :                 start += pg_mblen(start);
      31              : 
      32            0 :         ptr = start;
      33            0 :         if (ptr >= end)
      34            0 :                 return NULL;
      35              : 
      36            0 :         while (ptr < end && !t_iseq(ptr, '_'))
      37            0 :                 ptr += pg_mblen(ptr);
      38              : 
      39            0 :         *len = ptr - start;
      40            0 :         return start;
      41            0 : }
      42              : 
      43              : bool
      44            0 : compare_subnode(ltree_level *t, char *qn, int len,
      45              :                                 ltree_prefix_eq_func prefix_eq, bool anyend)
      46              : {
      47            0 :         char       *endt = t->name + t->len;
      48            0 :         char       *endq = qn + len;
      49            0 :         char       *tn;
      50            0 :         int                     lent,
      51              :                                 lenq;
      52            0 :         bool            isok;
      53              : 
      54            0 :         while ((qn = getlexeme(qn, endq, &lenq)) != NULL)
      55              :         {
      56            0 :                 tn = t->name;
      57            0 :                 isok = false;
      58            0 :                 while ((tn = getlexeme(tn, endt, &lent)) != NULL)
      59              :                 {
      60            0 :                         if ((lent == lenq || (lent > lenq && anyend)) &&
      61            0 :                                 (*prefix_eq) (qn, lenq, tn, lent))
      62              :                         {
      63              : 
      64            0 :                                 isok = true;
      65            0 :                                 break;
      66              :                         }
      67            0 :                         tn += lent;
      68              :                 }
      69              : 
      70            0 :                 if (!isok)
      71            0 :                         return false;
      72            0 :                 qn += lenq;
      73              :         }
      74              : 
      75            0 :         return true;
      76            0 : }
      77              : 
      78              : /*
      79              :  * Check if 'a' is a prefix of 'b'.
      80              :  */
      81              : bool
      82            0 : ltree_prefix_eq(const char *a, size_t a_sz, const char *b, size_t b_sz)
      83              : {
      84            0 :         if (a_sz > b_sz)
      85            0 :                 return false;
      86              :         else
      87            0 :                 return (strncmp(a, b, a_sz) == 0);
      88            0 : }
      89              : 
      90              : /*
      91              :  * Case-insensitive check if 'a' is a prefix of 'b'.
      92              :  */
      93              : bool
      94            0 : 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            0 :         size_t          al_sz = a_sz + 1;
      98            0 :         size_t          al_len;
      99            0 :         char       *al = palloc(al_sz);
     100            0 :         size_t          bl_sz = b_sz + 1;
     101            0 :         size_t          bl_len;
     102            0 :         char       *bl = palloc(bl_sz);
     103            0 :         bool            res;
     104              : 
     105            0 :         if (!locale)
     106            0 :                 locale = pg_database_locale();
     107              : 
     108              :         /* casefold both a and b */
     109              : 
     110            0 :         al_len = pg_strfold(al, al_sz, a, a_sz, locale);
     111            0 :         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            0 :                 Assert(al_len + 1 <= al_sz);
     118            0 :         }
     119              : 
     120            0 :         bl_len = pg_strfold(bl, bl_sz, b, b_sz, locale);
     121            0 :         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            0 :                 Assert(bl_len + 1 <= bl_sz);
     128            0 :         }
     129              : 
     130            0 :         if (al_len > bl_len)
     131            0 :                 res = false;
     132              :         else
     133            0 :                 res = (strncmp(al, bl, al_len) == 0);
     134              : 
     135            0 :         pfree(al);
     136            0 :         pfree(bl);
     137              : 
     138            0 :         return res;
     139            0 : }
     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            0 : checkLevel(lquery_level *curq, ltree_level *curt)
     149              : {
     150            0 :         lquery_variant *curvar = LQL_FIRST(curq);
     151            0 :         bool            success;
     152              : 
     153            0 :         success = (curq->flag & LQL_NOT) ? false : true;
     154              : 
     155              :         /* numvar == 0 means '*' which matches anything */
     156            0 :         if (curq->numvar == 0)
     157            0 :                 return success;
     158              : 
     159            0 :         for (int i = 0; i < curq->numvar; i++)
     160              :         {
     161            0 :                 ltree_prefix_eq_func prefix_eq;
     162              : 
     163            0 :                 prefix_eq = (curvar->flag & LVAR_INCASE) ? ltree_prefix_eq_ci : ltree_prefix_eq;
     164              : 
     165            0 :                 if (curvar->flag & LVAR_SUBLEXEME)
     166              :                 {
     167            0 :                         if (compare_subnode(curt, curvar->name, curvar->len, prefix_eq,
     168            0 :                                                                 (curvar->flag & LVAR_ANYEND)))
     169            0 :                                 return success;
     170            0 :                 }
     171            0 :                 else if ((curvar->len == curt->len ||
     172            0 :                                   (curt->len > curvar->len && (curvar->flag & LVAR_ANYEND))) &&
     173            0 :                                  (*prefix_eq) (curvar->name, curvar->len, curt->name, curt->len))
     174            0 :                         return success;
     175              : 
     176            0 :                 curvar = LVAR_NEXT(curvar);
     177            0 :         }
     178            0 :         return !success;
     179            0 : }
     180              : 
     181              : /*
     182              :  * Try to match an lquery (of qlen items) to an ltree (of tlen items)
     183              :  */
     184              : static bool
     185            0 : 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            0 :         check_stack_depth();
     190              : 
     191              :         /* Pathological patterns could take awhile, too */
     192            0 :         CHECK_FOR_INTERRUPTS();
     193              : 
     194              :         /* Loop while we have query items to consider */
     195            0 :         while (qlen > 0)
     196              :         {
     197            0 :                 int                     low,
     198              :                                         high;
     199            0 :                 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            0 :                 if ((curq->flag & LQL_COUNT) || curq->numvar == 0)
     207            0 :                         low = curq->low, high = curq->high;
     208              :                 else
     209            0 :                         low = high = 1;
     210              : 
     211              :                 /*
     212              :                  * We may limit "high" to the remaining text length; this avoids
     213              :                  * separate tests below.
     214              :                  */
     215            0 :                 if (high > tlen)
     216            0 :                         high = tlen;
     217              : 
     218              :                 /* Fail if a match of required number of items is impossible */
     219            0 :                 if (high < low)
     220            0 :                         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            0 :                 nextq = LQL_NEXT(curq);
     227            0 :                 qlen--;
     228              : 
     229            0 :                 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            0 :                         if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen))
     236            0 :                                 return true;
     237              : 
     238              :                         /*
     239              :                          * Otherwise, try to match one more text item to this query item.
     240              :                          */
     241            0 :                         if (!checkLevel(curq, curt))
     242            0 :                                 return false;
     243              : 
     244            0 :                         curt = LEVEL_NEXT(curt);
     245            0 :                         tlen--;
     246            0 :                 }
     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            0 :                 curq = nextq;
     254            0 :         }
     255              : 
     256              :         /*
     257              :          * Once we're out of query items, we match only if there's no remaining
     258              :          * text either.
     259              :          */
     260            0 :         return (tlen == 0);
     261            0 : }
     262              : 
     263              : Datum
     264            0 : ltq_regex(PG_FUNCTION_ARGS)
     265              : {
     266            0 :         ltree      *tree = PG_GETARG_LTREE_P(0);
     267            0 :         lquery     *query = PG_GETARG_LQUERY_P(1);
     268            0 :         bool            res;
     269              : 
     270            0 :         res = checkCond(LQUERY_FIRST(query), query->numlevel,
     271            0 :                                         LTREE_FIRST(tree), tree->numlevel);
     272              : 
     273            0 :         PG_FREE_IF_COPY(tree, 0);
     274            0 :         PG_FREE_IF_COPY(query, 1);
     275            0 :         PG_RETURN_BOOL(res);
     276            0 : }
     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            0 : lt_q_regex(PG_FUNCTION_ARGS)
     289              : {
     290            0 :         ltree      *tree = PG_GETARG_LTREE_P(0);
     291            0 :         ArrayType  *_query = PG_GETARG_ARRAYTYPE_P(1);
     292            0 :         lquery     *query = (lquery *) ARR_DATA_PTR(_query);
     293            0 :         bool            res = false;
     294            0 :         int                     num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query));
     295              : 
     296            0 :         if (ARR_NDIM(_query) > 1)
     297            0 :                 ereport(ERROR,
     298              :                                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
     299              :                                  errmsg("array must be one-dimensional")));
     300            0 :         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            0 :         while (num > 0)
     306              :         {
     307            0 :                 if (DatumGetBool(DirectFunctionCall2(ltq_regex,
     308              :                                                                                          PointerGetDatum(tree), PointerGetDatum(query))))
     309              :                 {
     310              : 
     311            0 :                         res = true;
     312            0 :                         break;
     313              :                 }
     314            0 :                 num--;
     315            0 :                 query = NEXTVAL(query);
     316              :         }
     317              : 
     318            0 :         PG_FREE_IF_COPY(tree, 0);
     319            0 :         PG_FREE_IF_COPY(_query, 1);
     320            0 :         PG_RETURN_BOOL(res);
     321            0 : }
     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 2.3.2-1