Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/adt/like_match.c
Line
Count
Source
1
/*-------------------------------------------------------------------------
2
 *
3
 * like_match.c
4
 *    LIKE pattern matching internal code.
5
 *
6
 * This file is included by like.c four times, to provide matching code for
7
 * (1) single-byte encodings, (2) UTF8, (3) other multi-byte encodings,
8
 * and (4) case insensitive matches in single-byte encodings.
9
 * (UTF8 is a special case because we can use a much more efficient version
10
 * of NextChar than can be used for general multi-byte encodings.)
11
 *
12
 * Before the inclusion, we need to define the following macros:
13
 *
14
 * NextChar
15
 * MatchText - to name of function wanted
16
 * do_like_escape - name of function if wanted - needs CHAREQ and CopyAdvChar
17
 * MATCH_LOWER - define for case (4) to specify case folding for 1-byte chars
18
 *
19
 * Copyright (c) 1996-2026, PostgreSQL Global Development Group
20
 *
21
 * IDENTIFICATION
22
 *  src/backend/utils/adt/like_match.c
23
 *
24
 *-------------------------------------------------------------------------
25
 */
26
27
/*
28
 *  Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
29
 *  Rich $alz is now <rsalz@bbn.com>.
30
 *  Special thanks to Lars Mathiesen <thorinn@diku.dk> for the
31
 *  LIKE_ABORT code.
32
 *
33
 *  This code was shamelessly stolen from the "pql" code by myself and
34
 *  slightly modified :)
35
 *
36
 *  All references to the word "star" were replaced by "percent"
37
 *  All references to the word "wild" were replaced by "like"
38
 *
39
 *  All the nice shell RE matching stuff was replaced by just "_" and "%"
40
 *
41
 *  As I don't have a copy of the SQL standard handy I wasn't sure whether
42
 *  to leave in the '\' escape character handling.
43
 *
44
 *  Keith Parks. <keith@mtcc.demon.co.uk>
45
 *
46
 *  SQL lets you specify the escape character by saying
47
 *  LIKE <pattern> ESCAPE <escape character>. We are a small operation
48
 *  so we force you to use '\'. - ay 7/95
49
 *
50
 *  Now we have the like_escape() function that converts patterns with
51
 *  any specified escape character (or none at all) to the internal
52
 *  default escape character, which is still '\'. - tgl 9/2000
53
 *
54
 * The code is rewritten to avoid requiring null-terminated strings,
55
 * which in turn allows us to leave out some memcpy() operations.
56
 * This code should be faster and take less memory, but no promises...
57
 * - thomas 2000-08-06
58
 */
59
60
61
/*--------------------
62
 *  Match text and pattern, return LIKE_TRUE, LIKE_FALSE, or LIKE_ABORT.
63
 *
64
 *  LIKE_TRUE: they match
65
 *  LIKE_FALSE: they don't match
66
 *  LIKE_ABORT: not only don't they match, but the text is too short.
67
 *
68
 * If LIKE_ABORT is returned, then no suffix of the text can match the
69
 * pattern either, so an upper-level % scan can stop scanning now.
70
 *--------------------
71
 */
72
73
/*
74
 * MATCH_LOWER is defined for ILIKE in the C locale as an optimization. Other
75
 * locales must casefold the inputs before matching.
76
 */
77
#ifdef MATCH_LOWER
78
0
#define GETCHAR(t) pg_ascii_tolower(t)
79
#else
80
0
#define GETCHAR(t) (t)
81
#endif
82
83
static int
84
MatchText(const char *t, int tlen, const char *p, int plen, pg_locale_t locale)
85
0
{
86
0
  bool    nondeterministic = (locale && !locale->deterministic);
87
88
  /* Fast path for match-everything pattern */
89
0
  if (plen == 1 && *p == '%')
90
0
    return LIKE_TRUE;
91
92
  /* Since this function recurses, it could be driven to stack overflow */
93
0
  check_stack_depth();
94
95
  /*
96
   * In this loop, we advance by char when matching wildcards (and thus on
97
   * recursive entry to this function we are properly char-synced). On other
98
   * occasions it is safe to advance by byte, as the text and pattern will
99
   * be in lockstep. This allows us to perform all comparisons between the
100
   * text and pattern on a byte by byte basis, even for multi-byte
101
   * encodings.  (But that doesn't work in a nondeterministic locale, so the
102
   * nondeterministic case below has to advance the text by chars.)
103
   */
104
0
  while (tlen > 0 && plen > 0)
105
0
  {
106
    /*
107
     * At the top of this loop, we are not positioned immediately after an
108
     * escape, so we may take wildcards at face value.
109
     */
110
0
    if (*p == '%')
111
0
    {
112
0
      char    firstpat;
113
114
      /*
115
       * % processing is essentially a search for a text position at
116
       * which the remainder of the text matches the remainder of the
117
       * pattern, using a recursive call to check each potential match.
118
       *
119
       * If there are wildcards immediately following the %, we can skip
120
       * over them first, using the idea that any sequence of N _'s and
121
       * one or more %'s is equivalent to N _'s and one % (ie, it will
122
       * match any sequence of at least N text characters).  In this way
123
       * we will always run the recursive search loop using a pattern
124
       * fragment that begins with a literal character-to-match, thereby
125
       * not recursing more than we have to.
126
       */
127
0
      NextByte(p, plen);
128
129
0
      while (plen > 0)
130
0
      {
131
0
        if (*p == '%')
132
0
          NextByte(p, plen);
133
0
        else if (*p == '_')
134
0
        {
135
          /* If not enough text left to match the pattern, ABORT */
136
0
          if (tlen <= 0)
137
0
            return LIKE_ABORT;
138
0
          NextChar(t, tlen);
139
0
          NextByte(p, plen);
140
0
        }
141
0
        else
142
0
          break;   /* Reached a non-wildcard pattern char */
143
0
      }
144
145
      /*
146
       * If we're at end of pattern, match: we have a trailing % which
147
       * matches any remaining text string.
148
       */
149
0
      if (plen <= 0)
150
0
        return LIKE_TRUE;
151
152
      /*
153
       * Otherwise, scan for a text position at which we can match the
154
       * rest of the pattern.  The first remaining pattern char is known
155
       * to be a regular or escaped literal character, so we can compare
156
       * the first pattern byte to each text byte to avoid recursing
157
       * more than we have to.  This fact also guarantees that we don't
158
       * have to consider a match to the zero-length substring at the
159
       * end of the text.  But with a nondeterministic locale, we can't
160
       * rely on the first byte of a match being equal, so we have to
161
       * recurse in any case.
162
       */
163
0
      if (*p == '\\')
164
0
      {
165
0
        if (plen < 2)
166
0
          ereport(ERROR,
167
0
              (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
168
0
               errmsg("LIKE pattern must not end with escape character")));
169
0
        firstpat = GETCHAR(p[1]);
170
0
      }
171
0
      else
172
0
        firstpat = GETCHAR(*p);
173
174
0
      while (tlen > 0)
175
0
      {
176
0
        if (GETCHAR(*t) == firstpat || nondeterministic)
177
0
        {
178
0
          int     matched = MatchText(t, tlen, p, plen, locale);
179
180
0
          if (matched != LIKE_FALSE)
181
0
            return matched; /* TRUE or ABORT */
182
0
        }
183
184
0
        NextChar(t, tlen);
185
0
      }
186
187
      /*
188
       * End of text with no match, so no point in trying later places
189
       * to start matching this pattern.
190
       */
191
0
      return LIKE_ABORT;
192
0
    }
193
0
    else if (*p == '_')
194
0
    {
195
      /* _ matches any single character, and we know there is one */
196
0
      NextChar(t, tlen);
197
0
      NextByte(p, plen);
198
0
      continue;
199
0
    }
200
0
    else if (nondeterministic)
201
0
    {
202
      /*
203
       * For nondeterministic locales, we find the next substring of the
204
       * pattern that does not contain wildcards and try to find a
205
       * matching substring in the text.  Crucially, we cannot do this
206
       * character by character, as in the normal case, but must do it
207
       * substring by substring, partitioned by the wildcard characters.
208
       * (This is per SQL standard.)
209
       */
210
0
      const char *p1;
211
0
      size_t    p1len;
212
0
      const char *t1;
213
0
      size_t    t1len;
214
0
      bool    found_escape;
215
0
      const char *subpat;
216
0
      size_t    subpatlen;
217
0
      char     *buf = NULL;
218
219
      /*
220
       * Determine length of substring of pattern without wildcards.  p
221
       * is the start of the subpattern, p1 will advance to one past its
222
       * last byte.  Also track if we found an escape character.
223
       */
224
0
      p1 = p;
225
0
      p1len = plen;
226
0
      found_escape = false;
227
0
      while (p1len > 0)
228
0
      {
229
0
        if (*p1 == '\\')
230
0
        {
231
0
          found_escape = true;
232
0
          NextByte(p1, p1len);
233
0
          if (p1len == 0)
234
0
            ereport(ERROR,
235
0
                (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
236
0
                 errmsg("LIKE pattern must not end with escape character")));
237
0
        }
238
0
        else if (*p1 == '_' || *p1 == '%')
239
0
          break;
240
        /* Advance over regular or escaped character */
241
0
        NextByte(p1, p1len);
242
0
      }
243
244
      /*
245
       * If we found an escape character, then make a de-escaped copy of
246
       * the subpattern that we can use to match literally.  Otherwise
247
       * we can use the subpattern in-place.  (buf holds the de-escaped
248
       * copy; be sure to pfree it before returning.)
249
       */
250
0
      if (found_escape)
251
0
      {
252
0
        char     *b;
253
254
0
        b = buf = palloc(p1 - p);
255
0
        for (const char *c = p; c < p1; c++)
256
0
        {
257
0
          if (*c == '\\')
258
0
            c++; /* we already checked this isn't the end */
259
0
          *(b++) = *c;
260
0
        }
261
262
0
        subpat = buf;
263
0
        subpatlen = b - buf;
264
0
      }
265
0
      else
266
0
      {
267
0
        subpat = p;
268
0
        subpatlen = p1 - p;
269
0
      }
270
271
      /*
272
       * Shortcut: If this is the end of the pattern, then the rest of
273
       * the text has to match the rest of the pattern.
274
       */
275
0
      if (p1len == 0)
276
0
      {
277
0
        int     cmp;
278
279
0
        cmp = pg_strncoll(subpat, subpatlen, t, tlen, locale);
280
281
0
        if (buf)
282
0
          pfree(buf);
283
0
        if (cmp == 0)
284
0
          return LIKE_TRUE;
285
0
        else
286
0
          return LIKE_FALSE;
287
0
      }
288
289
      /*
290
       * Consider each successively-longer substring of the remaining
291
       * text and try to match it against the subpattern.  t is the
292
       * start of the substring, t1 is one past its last byte.  We start
293
       * with a zero-length substring.
294
       */
295
0
      t1 = t;
296
0
      t1len = tlen;
297
0
      for (;;)
298
0
      {
299
0
        int     cmp;
300
301
        /* This could be slow, so allow interrupts */
302
0
        CHECK_FOR_INTERRUPTS();
303
304
0
        cmp = pg_strncoll(subpat, subpatlen, t, (t1 - t), locale);
305
306
        /*
307
         * If we found a match, we have to test if the rest of pattern
308
         * can match against the rest of the text.  If not, we have to
309
         * continue and try the next longer substring.  (This is
310
         * similar to the recursion for the '%' wildcard above.)
311
         *
312
         * Note that we can't just wind forward p and t and continue
313
         * with the main loop.  This would fail for example with
314
         *
315
         * U&'\0061\0308bc' LIKE U&'\00E4_c' COLLATE ignore_accents
316
         *
317
         * You'd find that t=\0061 matches p=\00E4, but then the rest
318
         * won't match; but t=\0061\0308 also matches p=\00E4, and
319
         * then the rest will match.
320
         */
321
0
        if (cmp == 0)
322
0
        {
323
0
          int     matched = MatchText(t1, t1len, p1, p1len, locale);
324
325
0
          if (matched == LIKE_TRUE)
326
0
          {
327
0
            if (buf)
328
0
              pfree(buf);
329
0
            return matched;
330
0
          }
331
0
        }
332
333
        /*
334
         * Didn't match.  If we used up the whole text, then the match
335
         * fails.  Otherwise, try again with a longer substring.
336
         */
337
0
        if (t1len == 0)
338
0
        {
339
0
          if (buf)
340
0
            pfree(buf);
341
0
          return LIKE_FALSE;
342
0
        }
343
0
        else
344
0
          NextChar(t1, t1len);
345
0
      }         /* end loop over substrings starting at t */
346
0
    }
347
    /* the rest of this loop considers only deterministic cases */
348
0
    else if (*p == '\\')
349
0
    {
350
      /* Next pattern byte must match literally, whatever it is */
351
0
      NextByte(p, plen);
352
      /* ... and there had better be one, per SQL standard */
353
0
      if (plen <= 0)
354
0
        ereport(ERROR,
355
0
            (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
356
0
             errmsg("LIKE pattern must not end with escape character")));
357
0
      if (GETCHAR(*p) != GETCHAR(*t))
358
0
        return LIKE_FALSE;
359
0
    }
360
0
    else if (GETCHAR(*p) != GETCHAR(*t))
361
0
    {
362
      /* non-wildcard pattern char fails to match text char */
363
0
      return LIKE_FALSE;
364
0
    }
365
366
    /*
367
     * Pattern and text match, so advance.
368
     *
369
     * It is safe to use NextByte instead of NextChar here, even for
370
     * multi-byte character sets, because we are not following immediately
371
     * after a wildcard character. If we are in the middle of a multibyte
372
     * character, we must already have matched at least one byte of the
373
     * character from both text and pattern; so we cannot get out-of-sync
374
     * on character boundaries.  And we know that no backend-legal
375
     * encoding allows ASCII characters such as '%' to appear as non-first
376
     * bytes of characters, so we won't mistakenly detect a new wildcard.
377
     */
378
0
    NextByte(t, tlen);
379
0
    NextByte(p, plen);
380
0
  }
381
382
0
  if (tlen > 0)
383
0
    return LIKE_FALSE;   /* end of pattern, but not of text */
384
385
  /*
386
   * End of text, but perhaps not of pattern.  Match iff the remaining
387
   * pattern can match a zero-length string, ie, it's zero or more %'s.
388
   */
389
0
  while (plen > 0 && *p == '%')
390
0
    NextByte(p, plen);
391
0
  if (plen <= 0)
392
0
    return LIKE_TRUE;
393
394
  /*
395
   * End of text with no match, so no point in trying later places to start
396
   * matching this pattern.
397
   */
398
0
  return LIKE_ABORT;
399
0
}                /* MatchText() */
Unexecuted instantiation: like.c:UTF8_MatchText
Unexecuted instantiation: like.c:MB_MatchText
Unexecuted instantiation: like.c:SB_MatchText
Unexecuted instantiation: like.c:C_IMatchText
400
401
/*
402
 * like_escape() --- given a pattern and an ESCAPE string,
403
 * convert the pattern to use Postgres' standard backslash escape convention.
404
 */
405
#ifdef do_like_escape
406
407
static text *
408
do_like_escape(text *pat, text *esc)
409
0
{
410
0
  text     *result;
411
0
  char     *p,
412
0
         *e,
413
0
         *r;
414
0
  int     plen,
415
0
        elen;
416
0
  bool    afterescape;
417
418
0
  p = VARDATA_ANY(pat);
419
0
  plen = VARSIZE_ANY_EXHDR(pat);
420
0
  e = VARDATA_ANY(esc);
421
0
  elen = VARSIZE_ANY_EXHDR(esc);
422
423
  /*
424
   * Worst-case pattern growth is 2x --- unlikely, but it's hardly worth
425
   * trying to calculate the size more accurately than that.
426
   */
427
0
  result = (text *) palloc(plen * 2 + VARHDRSZ);
428
0
  r = VARDATA(result);
429
430
0
  if (elen == 0)
431
0
  {
432
    /*
433
     * No escape character is wanted.  Double any backslashes in the
434
     * pattern to make them act like ordinary characters.
435
     */
436
0
    while (plen > 0)
437
0
    {
438
0
      if (*p == '\\')
439
0
        *r++ = '\\';
440
0
      CopyAdvChar(r, p, plen);
441
0
    }
442
0
  }
443
0
  else
444
0
  {
445
    /*
446
     * The specified escape must be only a single character.
447
     */
448
0
    NextChar(e, elen);
449
0
    if (elen != 0)
450
0
      ereport(ERROR,
451
0
          (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
452
0
           errmsg("invalid escape string"),
453
0
           errhint("Escape string must be empty or one character.")));
454
455
0
    e = VARDATA_ANY(esc);
456
0
    elen = VARSIZE_ANY_EXHDR(esc);
457
458
    /*
459
     * If specified escape is '\', just copy the pattern as-is.
460
     */
461
0
    if (*e == '\\')
462
0
    {
463
0
      memcpy(result, pat, VARSIZE_ANY(pat));
464
0
      return result;
465
0
    }
466
467
    /*
468
     * Otherwise, convert occurrences of the specified escape character to
469
     * '\', and double occurrences of '\' --- unless they immediately
470
     * follow an escape character!
471
     */
472
0
    afterescape = false;
473
0
    while (plen > 0)
474
0
    {
475
0
      if (CHAREQ(p, plen, e, elen) && !afterescape)
476
0
      {
477
0
        *r++ = '\\';
478
0
        NextChar(p, plen);
479
0
        afterescape = true;
480
0
      }
481
0
      else if (*p == '\\')
482
0
      {
483
0
        *r++ = '\\';
484
0
        if (!afterescape)
485
0
          *r++ = '\\';
486
0
        NextChar(p, plen);
487
0
        afterescape = false;
488
0
      }
489
0
      else
490
0
      {
491
0
        CopyAdvChar(r, p, plen);
492
0
        afterescape = false;
493
0
      }
494
0
    }
495
0
  }
496
497
0
  SET_VARSIZE(result, r - ((char *) result));
498
499
0
  return result;
500
0
}
Unexecuted instantiation: like.c:SB_do_like_escape
Unexecuted instantiation: like.c:MB_do_like_escape
501
#endif              /* do_like_escape */
502
503
#ifdef CHAREQ
504
#undef CHAREQ
505
#endif
506
507
#undef NextChar
508
#undef CopyAdvChar
509
#undef MatchText
510
511
#ifdef do_like_escape
512
#undef do_like_escape
513
#endif
514
515
#undef GETCHAR
516
517
#ifdef MATCH_LOWER
518
#undef MATCH_LOWER
519
520
#endif