Coverage Report

Created: 2026-11-12 07:20

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
/* cast to char to match data types in caller */
79
0
#define GETCHAR(t) ((char) pg_ascii_tolower(t))
80
#else
81
0
#define GETCHAR(t) (t)
82
#endif
83
84
static int
85
MatchText(const char *t, int tlen, const char *p, int plen, pg_locale_t locale)
86
0
{
87
0
  bool    nondeterministic = (locale && !locale->deterministic);
88
89
  /* Fast path for match-everything pattern */
90
0
  if (plen == 1 && *p == '%')
91
0
    return LIKE_TRUE;
92
93
  /* Since this function recurses, it could be driven to stack overflow */
94
0
  check_stack_depth();
95
96
  /*
97
   * In this loop, we advance by char when matching wildcards (and thus on
98
   * recursive entry to this function we are properly char-synced). On other
99
   * occasions it is safe to advance by byte, as the text and pattern will
100
   * be in lockstep. This allows us to perform all comparisons between the
101
   * text and pattern on a byte by byte basis, even for multi-byte
102
   * encodings.  (But that doesn't work in a nondeterministic locale, so the
103
   * nondeterministic case below has to advance the text by chars.)
104
   */
105
0
  while (tlen > 0 && plen > 0)
106
0
  {
107
    /*
108
     * At the top of this loop, we are not positioned immediately after an
109
     * escape, so we may take wildcards at face value.
110
     */
111
0
    if (*p == '%')
112
0
    {
113
0
      char    firstpat;
114
115
      /*
116
       * % processing is essentially a search for a text position at
117
       * which the remainder of the text matches the remainder of the
118
       * pattern, using a recursive call to check each potential match.
119
       *
120
       * If there are wildcards immediately following the %, we can skip
121
       * over them first, using the idea that any sequence of N _'s and
122
       * one or more %'s is equivalent to N _'s and one % (ie, it will
123
       * match any sequence of at least N text characters).  In this way
124
       * we will always run the recursive search loop using a pattern
125
       * fragment that begins with a literal character-to-match, thereby
126
       * not recursing more than we have to.
127
       */
128
0
      NextByte(p, plen);
129
130
0
      while (plen > 0)
131
0
      {
132
0
        if (*p == '%')
133
0
          NextByte(p, plen);
134
0
        else if (*p == '_')
135
0
        {
136
          /* If not enough text left to match the pattern, ABORT */
137
0
          if (tlen <= 0)
138
0
            return LIKE_ABORT;
139
0
          NextChar(t, tlen);
140
0
          NextByte(p, plen);
141
0
        }
142
0
        else
143
0
          break;   /* Reached a non-wildcard pattern char */
144
0
      }
145
146
      /*
147
       * If we're at end of pattern, match: we have a trailing % which
148
       * matches any remaining text string.
149
       */
150
0
      if (plen <= 0)
151
0
        return LIKE_TRUE;
152
153
      /*
154
       * Otherwise, scan for a text position at which we can match the
155
       * rest of the pattern.  The first remaining pattern char is known
156
       * to be a regular or escaped literal character, so we can compare
157
       * the first pattern byte to each text byte to avoid recursing
158
       * more than we have to.  This fact also guarantees that we don't
159
       * have to consider a match to the zero-length substring at the
160
       * end of the text.  But with a nondeterministic locale, we can't
161
       * rely on the first byte of a match being equal, so we have to
162
       * recurse in any case.
163
       */
164
0
      if (*p == '\\')
165
0
      {
166
0
        if (plen < 2)
167
0
          ereport(ERROR,
168
0
              (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
169
0
               errmsg("LIKE pattern must not end with escape character")));
170
0
        firstpat = GETCHAR(p[1]);
171
0
      }
172
0
      else
173
0
        firstpat = GETCHAR(*p);
174
175
0
      while (tlen > 0)
176
0
      {
177
0
        if (GETCHAR(*t) == firstpat || nondeterministic)
178
0
        {
179
0
          int     matched = MatchText(t, tlen, p, plen, locale);
180
181
0
          if (matched != LIKE_FALSE)
182
0
            return matched; /* TRUE or ABORT */
183
0
        }
184
185
0
        NextChar(t, tlen);
186
0
      }
187
188
      /*
189
       * End of text with no match, so no point in trying later places
190
       * to start matching this pattern.
191
       */
192
0
      return LIKE_ABORT;
193
0
    }
194
0
    else if (*p == '_')
195
0
    {
196
      /* _ matches any single character, and we know there is one */
197
0
      NextChar(t, tlen);
198
0
      NextByte(p, plen);
199
0
      continue;
200
0
    }
201
0
    else if (nondeterministic)
202
0
    {
203
      /*
204
       * For nondeterministic locales, we find the next substring of the
205
       * pattern that does not contain wildcards and try to find a
206
       * matching substring in the text.  Crucially, we cannot do this
207
       * character by character, as in the normal case, but must do it
208
       * substring by substring, partitioned by the wildcard characters.
209
       * (This is per SQL standard.)
210
       */
211
0
      const char *p1;
212
0
      size_t    p1len;
213
0
      const char *t1;
214
0
      size_t    t1len;
215
0
      bool    found_escape;
216
0
      const char *subpat;
217
0
      size_t    subpatlen;
218
0
      char     *buf = NULL;
219
220
      /*
221
       * Determine length of substring of pattern without wildcards.  p
222
       * is the start of the subpattern, p1 will advance to one past its
223
       * last byte.  Also track if we found an escape character.
224
       */
225
0
      p1 = p;
226
0
      p1len = plen;
227
0
      found_escape = false;
228
0
      while (p1len > 0)
229
0
      {
230
0
        if (*p1 == '\\')
231
0
        {
232
0
          found_escape = true;
233
0
          NextByte(p1, p1len);
234
0
          if (p1len == 0)
235
0
            ereport(ERROR,
236
0
                (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
237
0
                 errmsg("LIKE pattern must not end with escape character")));
238
0
        }
239
0
        else if (*p1 == '_' || *p1 == '%')
240
0
          break;
241
        /* Advance over regular or escaped character */
242
0
        NextByte(p1, p1len);
243
0
      }
244
245
      /*
246
       * If we found an escape character, then make a de-escaped copy of
247
       * the subpattern that we can use to match literally.  Otherwise
248
       * we can use the subpattern in-place.  (buf holds the de-escaped
249
       * copy; be sure to pfree it before returning.)
250
       */
251
0
      if (found_escape)
252
0
      {
253
0
        char     *b;
254
255
0
        b = buf = palloc(p1 - p);
256
0
        for (const char *c = p; c < p1; c++)
257
0
        {
258
0
          if (*c == '\\')
259
0
            c++; /* we already checked this isn't the end */
260
0
          *(b++) = *c;
261
0
        }
262
263
0
        subpat = buf;
264
0
        subpatlen = b - buf;
265
0
      }
266
0
      else
267
0
      {
268
0
        subpat = p;
269
0
        subpatlen = p1 - p;
270
0
      }
271
272
      /*
273
       * Shortcut: If this is the end of the pattern, then the rest of
274
       * the text has to match the rest of the pattern.
275
       */
276
0
      if (p1len == 0)
277
0
      {
278
0
        int     cmp;
279
280
0
        cmp = pg_strncoll(subpat, subpatlen, t, tlen, locale);
281
282
0
        if (buf)
283
0
          pfree(buf);
284
0
        if (cmp == 0)
285
0
          return LIKE_TRUE;
286
0
        else
287
0
          return LIKE_FALSE;
288
0
      }
289
290
      /*
291
       * Consider each successively-longer substring of the remaining
292
       * text and try to match it against the subpattern.  t is the
293
       * start of the substring, t1 is one past its last byte.  We start
294
       * with a zero-length substring.
295
       */
296
0
      t1 = t;
297
0
      t1len = tlen;
298
0
      for (;;)
299
0
      {
300
0
        int     cmp;
301
302
        /* This could be slow, so allow interrupts */
303
0
        CHECK_FOR_INTERRUPTS();
304
305
0
        cmp = pg_strncoll(subpat, subpatlen, t, (t1 - t), locale);
306
307
        /*
308
         * If we found a match, we have to test if the rest of pattern
309
         * can match against the rest of the text.  If not, we have to
310
         * continue and try the next longer substring.  (This is
311
         * similar to the recursion for the '%' wildcard above.)
312
         *
313
         * Note that we can't just wind forward p and t and continue
314
         * with the main loop.  This would fail for example with
315
         *
316
         * U&'\0061\0308bc' LIKE U&'\00E4_c' COLLATE ignore_accents
317
         *
318
         * You'd find that t=\0061 matches p=\00E4, but then the rest
319
         * won't match; but t=\0061\0308 also matches p=\00E4, and
320
         * then the rest will match.
321
         */
322
0
        if (cmp == 0)
323
0
        {
324
0
          int     matched = MatchText(t1, t1len, p1, p1len, locale);
325
326
0
          if (matched == LIKE_TRUE)
327
0
          {
328
0
            if (buf)
329
0
              pfree(buf);
330
0
            return matched;
331
0
          }
332
0
        }
333
334
        /*
335
         * Didn't match.  If we used up the whole text, then the match
336
         * fails.  Otherwise, try again with a longer substring.
337
         */
338
0
        if (t1len == 0)
339
0
        {
340
0
          if (buf)
341
0
            pfree(buf);
342
0
          return LIKE_FALSE;
343
0
        }
344
0
        else
345
0
          NextChar(t1, t1len);
346
0
      }         /* end loop over substrings starting at t */
347
0
    }
348
    /* the rest of this loop considers only deterministic cases */
349
0
    else if (*p == '\\')
350
0
    {
351
      /* Next pattern byte must match literally, whatever it is */
352
0
      NextByte(p, plen);
353
      /* ... and there had better be one, per SQL standard */
354
0
      if (plen <= 0)
355
0
        ereport(ERROR,
356
0
            (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
357
0
             errmsg("LIKE pattern must not end with escape character")));
358
0
      if (GETCHAR(*p) != GETCHAR(*t))
359
0
        return LIKE_FALSE;
360
0
    }
361
0
    else if (GETCHAR(*p) != GETCHAR(*t))
362
0
    {
363
      /* non-wildcard pattern char fails to match text char */
364
0
      return LIKE_FALSE;
365
0
    }
366
367
    /*
368
     * Pattern and text match, so advance.
369
     *
370
     * It is safe to use NextByte instead of NextChar here, even for
371
     * multi-byte character sets, because we are not following immediately
372
     * after a wildcard character. If we are in the middle of a multibyte
373
     * character, we must already have matched at least one byte of the
374
     * character from both text and pattern; so we cannot get out-of-sync
375
     * on character boundaries.  And we know that no backend-legal
376
     * encoding allows ASCII characters such as '%' to appear as non-first
377
     * bytes of characters, so we won't mistakenly detect a new wildcard.
378
     */
379
0
    NextByte(t, tlen);
380
0
    NextByte(p, plen);
381
0
  }
382
383
0
  if (tlen > 0)
384
0
    return LIKE_FALSE;   /* end of pattern, but not of text */
385
386
  /*
387
   * End of text, but perhaps not of pattern.  Match iff the remaining
388
   * pattern can match a zero-length string, ie, it's zero or more %'s.
389
   */
390
0
  while (plen > 0 && *p == '%')
391
0
    NextByte(p, plen);
392
0
  if (plen <= 0)
393
0
    return LIKE_TRUE;
394
395
  /*
396
   * End of text with no match, so no point in trying later places to start
397
   * matching this pattern.
398
   */
399
0
  return LIKE_ABORT;
400
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
401
402
/*
403
 * like_escape() --- given a pattern and an ESCAPE string,
404
 * convert the pattern to use Postgres' standard backslash escape convention.
405
 */
406
#ifdef do_like_escape
407
408
static text *
409
do_like_escape(text *pat, text *esc)
410
0
{
411
0
  text     *result;
412
0
  char     *p,
413
0
         *e,
414
0
         *r;
415
0
  int     plen,
416
0
        elen;
417
0
  bool    afterescape;
418
419
0
  p = VARDATA_ANY(pat);
420
0
  plen = VARSIZE_ANY_EXHDR(pat);
421
0
  e = VARDATA_ANY(esc);
422
0
  elen = VARSIZE_ANY_EXHDR(esc);
423
424
  /*
425
   * Worst-case pattern growth is 2x --- unlikely, but it's hardly worth
426
   * trying to calculate the size more accurately than that.
427
   */
428
0
  result = (text *) palloc(plen * 2 + VARHDRSZ);
429
0
  r = VARDATA(result);
430
431
0
  if (elen == 0)
432
0
  {
433
    /*
434
     * No escape character is wanted.  Double any backslashes in the
435
     * pattern to make them act like ordinary characters.
436
     */
437
0
    while (plen > 0)
438
0
    {
439
0
      if (*p == '\\')
440
0
        *r++ = '\\';
441
0
      CopyAdvChar(r, p, plen);
442
0
    }
443
0
  }
444
0
  else
445
0
  {
446
    /*
447
     * The specified escape must be only a single character.
448
     */
449
0
    NextChar(e, elen);
450
0
    if (elen != 0)
451
0
      ereport(ERROR,
452
0
          (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
453
0
           errmsg("invalid escape string"),
454
0
           errhint("Escape string must be empty or one character.")));
455
456
0
    e = VARDATA_ANY(esc);
457
0
    elen = VARSIZE_ANY_EXHDR(esc);
458
459
    /*
460
     * If specified escape is '\', just copy the pattern as-is.
461
     */
462
0
    if (*e == '\\')
463
0
    {
464
0
      memcpy(result, pat, VARSIZE_ANY(pat));
465
0
      return result;
466
0
    }
467
468
    /*
469
     * Otherwise, convert occurrences of the specified escape character to
470
     * '\', and double occurrences of '\' --- unless they immediately
471
     * follow an escape character!
472
     */
473
0
    afterescape = false;
474
0
    while (plen > 0)
475
0
    {
476
0
      if (CHAREQ(p, plen, e, elen) && !afterescape)
477
0
      {
478
0
        *r++ = '\\';
479
0
        NextChar(p, plen);
480
0
        afterescape = true;
481
0
      }
482
0
      else if (*p == '\\')
483
0
      {
484
0
        *r++ = '\\';
485
0
        if (!afterescape)
486
0
          *r++ = '\\';
487
0
        NextChar(p, plen);
488
0
        afterescape = false;
489
0
      }
490
0
      else
491
0
      {
492
0
        CopyAdvChar(r, p, plen);
493
0
        afterescape = false;
494
0
      }
495
0
    }
496
0
  }
497
498
0
  SET_VARSIZE(result, r - ((char *) result));
499
500
0
  return result;
501
0
}
Unexecuted instantiation: like.c:SB_do_like_escape
Unexecuted instantiation: like.c:MB_do_like_escape
502
#endif              /* do_like_escape */
503
504
#ifdef CHAREQ
505
#undef CHAREQ
506
#endif
507
508
#undef NextChar
509
#undef CopyAdvChar
510
#undef MatchText
511
512
#ifdef do_like_escape
513
#undef do_like_escape
514
#endif
515
516
#undef GETCHAR
517
518
#ifdef MATCH_LOWER
519
#undef MATCH_LOWER
520
521
#endif