LCOV - code coverage report
Current view: top level - contrib/ltree - lquery_op.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 90.7 % 129 117
Test Date: 2026-07-03 19:57:34 Functions: 84.6 % 13 11
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 85.1 % 94 80

             Branch data     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                 :           3 : PG_FUNCTION_INFO_V1(ltq_regex);
      17                 :           2 : PG_FUNCTION_INFO_V1(ltq_rregex);
      18                 :             : 
      19                 :           3 : PG_FUNCTION_INFO_V1(lt_q_regex);
      20                 :           2 : PG_FUNCTION_INFO_V1(lt_q_rregex);
      21                 :             : 
      22                 :             : #define NEXTVAL(x) ( (lquery*)( (char*)(x) + INTALIGN( VARSIZE(x) ) ) )
      23                 :             : 
      24                 :             : static char *
      25                 :          36 : getlexeme(char *start, char *end, int *len)
      26                 :             : {
      27                 :             :     char       *ptr;
      28                 :             : 
      29   [ +  +  +  + ]:          44 :     while (start < end && t_iseq(start, '_'))
      30                 :           8 :         start += pg_mblen_range(start, end);
      31                 :             : 
      32                 :          36 :     ptr = start;
      33         [ +  + ]:          36 :     if (ptr >= end)
      34                 :           8 :         return NULL;
      35                 :             : 
      36   [ +  +  +  + ]:         114 :     while (ptr < end && !t_iseq(ptr, '_'))
      37                 :          86 :         ptr += pg_mblen_range(ptr, end);
      38                 :             : 
      39                 :          28 :     *len = ptr - start;
      40                 :          28 :     return start;
      41                 :             : }
      42                 :             : 
      43                 :             : bool
      44                 :           8 : compare_subnode(ltree_level *t, char *qn, int len, bool prefix, bool ci)
      45                 :             : {
      46                 :           8 :     char       *endt = t->name + t->len;
      47                 :           8 :     char       *endq = qn + len;
      48                 :             :     char       *tn;
      49                 :             :     int         lent,
      50                 :             :                 lenq;
      51                 :             :     bool        isok;
      52                 :             : 
      53         [ +  + ]:          16 :     while ((qn = getlexeme(qn, endq, &lenq)) != NULL)
      54                 :             :     {
      55                 :          12 :         tn = t->name;
      56                 :          12 :         isok = false;
      57         [ +  + ]:          20 :         while ((tn = getlexeme(tn, endt, &lent)) != NULL)
      58                 :             :         {
      59         [ +  + ]:          16 :             if (ltree_label_match(qn, lenq, tn, lent, prefix, ci))
      60                 :             :             {
      61                 :           8 :                 isok = true;
      62                 :           8 :                 break;
      63                 :             :             }
      64                 :           8 :             tn += lent;
      65                 :             :         }
      66                 :             : 
      67         [ +  + ]:          12 :         if (!isok)
      68                 :           4 :             return false;
      69                 :           8 :         qn += lenq;
      70                 :             :     }
      71                 :             : 
      72                 :           4 :     return true;
      73                 :             : }
      74                 :             : 
      75                 :             : /*
      76                 :             :  * Check if the label matches the predicate string. If 'prefix' is true, then
      77                 :             :  * the predicate string is treated as a prefix. If 'ci' is true, then the
      78                 :             :  * predicate string is case-insensitive (and locale-aware).
      79                 :             :  */
      80                 :             : bool
      81                 :      198433 : ltree_label_match(const char *pred, size_t pred_len, const char *label,
      82                 :             :                   size_t label_len, bool prefix, bool ci)
      83                 :             : {
      84                 :             :     static pg_locale_t locale = NULL;
      85                 :             :     char       *fpred;          /* casefolded predicate */
      86                 :      198433 :     size_t      fpred_len = pred_len;
      87                 :             :     char       *flabel;         /* casefolded label */
      88                 :      198433 :     size_t      flabel_len = label_len;
      89                 :             :     size_t      len;
      90                 :             :     bool        res;
      91                 :             : 
      92                 :             :     /* fast path for binary match or binary prefix match */
      93   [ +  +  +  +  :      198433 :     if ((pred_len == label_len || (prefix && pred_len < label_len)) &&
                   +  - ]
      94         [ +  + ]:       94688 :         strncmp(pred, label, pred_len) == 0)
      95                 :        9195 :         return true;
      96         [ +  + ]:      189238 :     else if (!ci)
      97                 :      189208 :         return false;
      98                 :             : 
      99                 :             :     /*
     100                 :             :      * Slow path for case-insensitive comparison: case fold and then compare.
     101                 :             :      * This path is necessary even if pred_len > label_len, because the byte
     102                 :             :      * lengths may change after casefolding.
     103                 :             :      */
     104         [ +  + ]:          30 :     if (!locale)
     105                 :           1 :         locale = pg_database_locale();
     106                 :             : 
     107                 :          30 :     fpred = palloc(fpred_len + 1);
     108                 :          30 :     len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale);
     109         [ -  + ]:          30 :     if (len > fpred_len)
     110                 :             :     {
     111                 :             :         /* grow buffer if needed and retry */
     112                 :           0 :         fpred_len = len;
     113                 :           0 :         fpred = repalloc(fpred, fpred_len + 1);
     114                 :           0 :         len = pg_strfold(fpred, fpred_len + 1, pred, pred_len, locale);
     115                 :             :     }
     116                 :             :     Assert(len <= fpred_len);
     117                 :          30 :     fpred_len = len;
     118                 :             : 
     119                 :          30 :     flabel = palloc(flabel_len + 1);
     120                 :          30 :     len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale);
     121         [ -  + ]:          30 :     if (len > flabel_len)
     122                 :             :     {
     123                 :             :         /* grow buffer if needed and retry */
     124                 :           0 :         flabel_len = len;
     125                 :           0 :         flabel = repalloc(flabel, flabel_len + 1);
     126                 :           0 :         len = pg_strfold(flabel, flabel_len + 1, label, label_len, locale);
     127                 :             :     }
     128                 :             :     Assert(len <= flabel_len);
     129                 :          30 :     flabel_len = len;
     130                 :             : 
     131   [ +  +  +  +  :          30 :     if ((fpred_len == flabel_len || (prefix && fpred_len < flabel_len)) &&
                   +  - ]
     132         [ +  + ]:          25 :         strncmp(fpred, flabel, fpred_len) == 0)
     133                 :          13 :         res = true;
     134                 :             :     else
     135                 :          17 :         res = false;
     136                 :             : 
     137                 :          30 :     pfree(fpred);
     138                 :          30 :     pfree(flabel);
     139                 :             : 
     140                 :          30 :     return res;
     141                 :             : }
     142                 :             : 
     143                 :             : /*
     144                 :             :  * See if an lquery_level matches an ltree_level
     145                 :             :  *
     146                 :             :  * This accounts for all flags including LQL_NOT, but does not
     147                 :             :  * consider repetition counts.
     148                 :             :  */
     149                 :             : static bool
     150                 :      217423 : checkLevel(lquery_level *curq, ltree_level *curt)
     151                 :             : {
     152                 :      217423 :     lquery_variant *curvar = LQL_FIRST(curq);
     153                 :             :     bool        success;
     154                 :             : 
     155                 :      217423 :     success = (curq->flag & LQL_NOT) ? false : true;
     156                 :             : 
     157                 :             :     /* numvar == 0 means '*' which matches anything */
     158         [ +  + ]:      217423 :     if (curq->numvar == 0)
     159                 :       83195 :         return success;
     160                 :             : 
     161         [ +  + ]:      261907 :     for (int i = 0; i < curq->numvar; i++)
     162                 :             :     {
     163                 :      134233 :         bool        prefix = (curvar->flag & LVAR_ANYEND);
     164                 :      134233 :         bool        ci = (curvar->flag & LVAR_INCASE);
     165                 :             : 
     166         [ +  + ]:      134233 :         if (curvar->flag & LVAR_SUBLEXEME)
     167                 :             :         {
     168         [ +  + ]:           4 :             if (compare_subnode(curt, curvar->name, curvar->len, prefix, ci))
     169                 :           3 :                 return success;
     170                 :             :         }
     171         [ +  + ]:      134229 :         else if (ltree_label_match(curvar->name, curvar->len, curt->name,
     172                 :      134229 :                                    curt->len, prefix, ci))
     173                 :        6551 :             return success;
     174                 :             : 
     175                 :      127679 :         curvar = LVAR_NEXT(curvar);
     176                 :             :     }
     177                 :      127674 :     return !success;
     178                 :             : }
     179                 :             : 
     180                 :             : /*
     181                 :             :  * Try to match an lquery (of qlen items) to an ltree (of tlen items)
     182                 :             :  */
     183                 :             : static bool
     184                 :      144094 : checkCond(lquery_level *curq, int qlen,
     185                 :             :           ltree_level *curt, int tlen)
     186                 :             : {
     187                 :             :     /* Since this function recurses, it could be driven to stack overflow */
     188                 :      144094 :     check_stack_depth();
     189                 :             : 
     190                 :             :     /* Pathological patterns could take awhile, too */
     191         [ -  + ]:      144094 :     CHECK_FOR_INTERRUPTS();
     192                 :             : 
     193                 :             :     /* Loop while we have query items to consider */
     194         [ +  + ]:      163373 :     while (qlen > 0)
     195                 :             :     {
     196                 :             :         int         low,
     197                 :             :                     high;
     198                 :             :         lquery_level *nextq;
     199                 :             : 
     200                 :             :         /*
     201                 :             :          * Get min and max repetition counts for this query item, dealing with
     202                 :             :          * the backwards-compatibility hack that the low/high fields aren't
     203                 :             :          * meaningful for non-'*' items unless LQL_COUNT is set.
     204                 :             :          */
     205   [ +  +  +  + ]:      159660 :         if ((curq->flag & LQL_COUNT) || curq->numvar == 0)
     206                 :       13307 :             low = curq->low, high = curq->high;
     207                 :             :         else
     208                 :      146353 :             low = high = 1;
     209                 :             : 
     210                 :             :         /*
     211                 :             :          * We may limit "high" to the remaining text length; this avoids
     212                 :             :          * separate tests below.
     213                 :             :          */
     214         [ +  + ]:      159660 :         if (high > tlen)
     215                 :       24988 :             high = tlen;
     216                 :             : 
     217                 :             :         /* Fail if a match of required number of items is impossible */
     218         [ +  + ]:      159660 :         if (high < low)
     219                 :       12173 :             return false;
     220                 :             : 
     221                 :             :         /*
     222                 :             :          * Recursively check the rest of the pattern against each possible
     223                 :             :          * start point following some of this item's match(es).
     224                 :             :          */
     225                 :      147487 :         nextq = LQL_NEXT(curq);
     226                 :      147487 :         qlen--;
     227                 :             : 
     228         [ +  + ]:      237296 :         for (int matchcnt = 0; matchcnt < high; matchcnt++)
     229                 :             :         {
     230                 :             :             /*
     231                 :             :              * If we've consumed an acceptable number of matches of this item,
     232                 :             :              * and the rest of the pattern matches beginning here, we're good.
     233                 :             :              */
     234   [ +  +  +  + ]:      218017 :             if (matchcnt >= low && checkCond(nextq, qlen, curt, tlen))
     235                 :         594 :                 return true;
     236                 :             : 
     237                 :             :             /*
     238                 :             :              * Otherwise, try to match one more text item to this query item.
     239                 :             :              */
     240         [ +  + ]:      217423 :             if (!checkLevel(curq, curt))
     241                 :      127614 :                 return false;
     242                 :             : 
     243                 :       89809 :             curt = LEVEL_NEXT(curt);
     244                 :       89809 :             tlen--;
     245                 :             :         }
     246                 :             : 
     247                 :             :         /*
     248                 :             :          * Once we've consumed "high" matches, we can succeed only if the rest
     249                 :             :          * of the pattern matches beginning here.  Loop around (if you prefer,
     250                 :             :          * think of this as tail recursion).
     251                 :             :          */
     252                 :       19279 :         curq = nextq;
     253                 :             :     }
     254                 :             : 
     255                 :             :     /*
     256                 :             :      * Once we're out of query items, we match only if there's no remaining
     257                 :             :      * text either.
     258                 :             :      */
     259                 :        3713 :     return (tlen == 0);
     260                 :             : }
     261                 :             : 
     262                 :             : Datum
     263                 :       60776 : ltq_regex(PG_FUNCTION_ARGS)
     264                 :             : {
     265                 :       60776 :     ltree      *tree = PG_GETARG_LTREE_P(0);
     266                 :       60776 :     lquery     *query = PG_GETARG_LQUERY_P(1);
     267                 :             :     bool        res;
     268                 :             : 
     269                 :       60776 :     res = checkCond(LQUERY_FIRST(query), query->numlevel,
     270                 :       60776 :                     LTREE_FIRST(tree), tree->numlevel);
     271                 :             : 
     272         [ +  + ]:       60776 :     PG_FREE_IF_COPY(tree, 0);
     273         [ -  + ]:       60776 :     PG_FREE_IF_COPY(query, 1);
     274                 :       60776 :     PG_RETURN_BOOL(res);
     275                 :             : }
     276                 :             : 
     277                 :             : Datum
     278                 :           0 : ltq_rregex(PG_FUNCTION_ARGS)
     279                 :             : {
     280                 :           0 :     PG_RETURN_DATUM(DirectFunctionCall2(ltq_regex,
     281                 :             :                                         PG_GETARG_DATUM(1),
     282                 :             :                                         PG_GETARG_DATUM(0)
     283                 :             :                                         ));
     284                 :             : }
     285                 :             : 
     286                 :             : Datum
     287                 :        1701 : lt_q_regex(PG_FUNCTION_ARGS)
     288                 :             : {
     289                 :        1701 :     ltree      *tree = PG_GETARG_LTREE_P(0);
     290                 :        1701 :     ArrayType  *_query = PG_GETARG_ARRAYTYPE_P(1);
     291         [ -  + ]:        1701 :     lquery     *query = (lquery *) ARR_DATA_PTR(_query);
     292                 :        1701 :     bool        res = false;
     293                 :        1701 :     int         num = ArrayGetNItems(ARR_NDIM(_query), ARR_DIMS(_query));
     294                 :             : 
     295         [ -  + ]:        1701 :     if (ARR_NDIM(_query) > 1)
     296         [ #  # ]:           0 :         ereport(ERROR,
     297                 :             :                 (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
     298                 :             :                  errmsg("array must be one-dimensional")));
     299         [ -  + ]:        1701 :     if (array_contains_nulls(_query))
     300         [ #  # ]:           0 :         ereport(ERROR,
     301                 :             :                 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
     302                 :             :                  errmsg("array must not contain nulls")));
     303                 :             : 
     304         [ +  + ]:        5078 :     while (num > 0)
     305                 :             :     {
     306         [ +  + ]:        3391 :         if (DatumGetBool(DirectFunctionCall2(ltq_regex,
     307                 :             :                                              PointerGetDatum(tree), PointerGetDatum(query))))
     308                 :             :         {
     309                 :             : 
     310                 :          14 :             res = true;
     311                 :          14 :             break;
     312                 :             :         }
     313                 :        3377 :         num--;
     314                 :        3377 :         query = NEXTVAL(query);
     315                 :             :     }
     316                 :             : 
     317         [ +  + ]:        1701 :     PG_FREE_IF_COPY(tree, 0);
     318         [ -  + ]:        1701 :     PG_FREE_IF_COPY(_query, 1);
     319                 :        1701 :     PG_RETURN_BOOL(res);
     320                 :             : }
     321                 :             : 
     322                 :             : Datum
     323                 :           0 : lt_q_rregex(PG_FUNCTION_ARGS)
     324                 :             : {
     325                 :           0 :     PG_RETURN_DATUM(DirectFunctionCall2(lt_q_regex,
     326                 :             :                                         PG_GETARG_DATUM(1),
     327                 :             :                                         PG_GETARG_DATUM(0)
     328                 :             :                                         ));
     329                 :             : }
        

Generated by: LCOV version 2.0-1