]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/adt/like.c
pgindent run for 8.2.
[postgresql] / src / backend / utils / adt / like.c
index e33e66ee62d8fc3c40621cd379287635cfb9df5e..4223bffb1883af2531f18fcc5fe667be3cc8c1ac 100644 (file)
 /*-------------------------------------------------------------------------
  *
- * like.c--
- *    like expression handling code.
+ * like.c
+ *       like expression handling code.
  *
- * Copyright (c) 1994, Regents of the University of California
+ *      NOTES
+ *             A big hack of the regexp.c code!! Contributed by
+ *             Keith Parks <emkxp01@mtcc.demon.co.uk> (7/95).
  *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *    /usr/local/devel/pglite/cvs/src/backend/utils/adt/like.c,v 1.1 1995/07/30 23:55:36 emkxp01 Exp
- *
- *
- *   NOTES
- *     A big hack of the regexp.c code!! Contributed by
- *     Keith Parks <emkxp01@mtcc.demon.co.uk> (7/95).
- *
+ *     $PostgreSQL: pgsql/src/backend/utils/adt/like.c,v 1.66 2006/10/04 00:29:59 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
-#include <string.h>
-#include "postgres.h"          /* postgres system include file */
-#include "utils/elog.h"                /* for logging postgres errors */
-#include "utils/palloc.h"
-#include "utils/builtins.h"    /* where the function declarations go */
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+
+
+#define LIKE_TRUE                                              1
+#define LIKE_FALSE                                             0
+#define LIKE_ABORT                                             (-1)
 
-int like(char *text, char *p);
+
+static int     MatchText(char *t, int tlen, char *p, int plen);
+static int     MatchTextIC(char *t, int tlen, char *p, int plen);
+static int     MatchBytea(char *t, int tlen, char *p, int plen);
+static text *do_like_escape(text *, text *);
+
+static int     MBMatchText(char *t, int tlen, char *p, int plen);
+static int     MBMatchTextIC(char *t, int tlen, char *p, int plen);
+static text *MB_do_like_escape(text *, text *);
+
+/*--------------------
+ * Support routine for MatchText. Compares given multibyte streams
+ * as wide characters. If they match, returns 1 otherwise returns 0.
+ *--------------------
+ */
+static int
+wchareq(char *p1, char *p2)
+{
+       int                     p1_len;
+
+       /* Optimization:  quickly compare the first byte. */
+       if (*p1 != *p2)
+               return 0;
+
+       p1_len = pg_mblen(p1);
+       if (pg_mblen(p2) != p1_len)
+               return 0;
+
+       /* They are the same length */
+       while (p1_len--)
+       {
+               if (*p1++ != *p2++)
+                       return 0;
+       }
+       return 1;
+}
 
 /*
- *  interface routines called by the function manager
+ * Formerly we had a routine iwchareq() here that tried to do case-insensitive
+ * comparison of multibyte characters. It did not work at all, however,
+ * because it relied on tolower() which has a single-byte API ... and
+ * towlower() wouldn't be much better since we have no suitably cheap way
+ * of getting a single character transformed to the system's wchar_t format.
+ * So now, we just downcase the strings using lower() and apply regular LIKE
+ * comparison. This should be revisited when we install better locale support.
+ *
+ * Note that MBMatchText and MBMatchTextIC do exactly the same thing now.
+ * Is it worth refactoring to avoid duplicated code?  They might become
+ * different again in the future.
  */
 
+/* Set up to compile like_match.c for multibyte characters */
+#define CHAREQ(p1, p2) wchareq(p1, p2)
+#define ICHAREQ(p1, p2) wchareq(p1, p2)
+#define NextChar(p, plen) \
+       do { int __l = pg_mblen(p); (p) +=__l; (plen) -=__l; } while (0)
+#define CopyAdvChar(dst, src, srclen) \
+       do { int __l = pg_mblen(src); \
+                (srclen) -= __l; \
+                while (__l-- > 0) \
+                        *(dst)++ = *(src)++; \
+          } while (0)
+
+#define MatchText      MBMatchText
+#define MatchTextIC MBMatchTextIC
+#define do_like_escape MB_do_like_escape
+
+#include "like_match.c"
+
+#undef CHAREQ
+#undef ICHAREQ
+#undef NextChar
+#undef CopyAdvChar
+#undef MatchText
+#undef MatchTextIC
+#undef do_like_escape
+
+/* Set up to compile like_match.c for single-byte characters */
+#define CHAREQ(p1, p2) (*(p1) == *(p2))
+#define ICHAREQ(p1, p2) (tolower((unsigned char) *(p1)) == tolower((unsigned char) *(p2)))
+#define NextChar(p, plen) ((p)++, (plen)--)
+#define CopyAdvChar(dst, src, srclen) (*(dst)++ = *(src)++, (srclen)--)
+
+#include "like_match.c"
+
+/* And some support for BYTEA */
+#define BYTEA_CHAREQ(p1, p2) (*(p1) == *(p2))
+#define BYTEA_NextChar(p, plen) ((p)++, (plen)--)
+#define BYTEA_CopyAdvChar(dst, src, srclen) (*(dst)++ = *(src)++, (srclen)--)
+
+
 /*
-   fixedlen_like:
-
-   a generic fixed length like routine
-         s      - the string to match against  (not necessarily null-terminated)
-        p         - the pattern
-        charlen   - the length of the string
-*/
-static bool 
-fixedlen_like(char *s, struct varlena* p, int charlen)
+ *     interface routines called by the function manager
+ */
+
+Datum
+namelike(PG_FUNCTION_ARGS)
 {
-    char *sterm, *pterm;
-    int result;
-
-    if (!s || !p)
-       return FALSE;
-    
-    /* be sure sterm is null-terminated */
-    sterm = (char *) palloc(charlen + 1);
-    memset(sterm, 0, charlen + 1);
-    strncpy(sterm, s, charlen);
-    
-    /* p is a text = varlena, not a string so we have to make 
-     * a string from the vl_data field of the struct. */
-    
-    /* palloc the length of the text + the null character */
-    pterm = (char *) palloc(VARSIZE(p) - VARHDRSZ + 1);
-    memmove(pterm, VARDATA(p), VARSIZE(p) - VARHDRSZ);
-    *(pterm + VARSIZE(p) - VARHDRSZ) = (char)NULL;
-    
-    /* do the regexp matching */
-    result = like(sterm, pterm);
-    
-    pfree(sterm);
-    pfree(pterm);
-    
-    return ((bool) result);
+       Name            str = PG_GETARG_NAME(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       s = NameStr(*str);
+       slen = strlen(s);
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+
+       if (pg_database_encoding_max_length() == 1)
+               result = (MatchText(s, slen, p, plen) == LIKE_TRUE);
+       else
+               result = (MBMatchText(s, slen, p, plen) == LIKE_TRUE);
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-char2like(uint16 arg1, struct varlena *p)
+Datum
+namenlike(PG_FUNCTION_ARGS)
 {
-    char *s = (char *) &arg1;
-    return (fixedlen_like(s, p, 2));
-}    
+       Name            str = PG_GETARG_NAME(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
 
-bool 
-char2nlike(uint16 arg1, struct varlena *p)
-{
-    return (!char2like(arg1, p));
+       s = NameStr(*str);
+       slen = strlen(s);
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+
+       if (pg_database_encoding_max_length() == 1)
+               result = (MatchText(s, slen, p, plen) != LIKE_TRUE);
+       else
+               result = (MBMatchText(s, slen, p, plen) != LIKE_TRUE);
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-char4like(uint32 arg1, struct varlena *p)
+Datum
+textlike(PG_FUNCTION_ARGS)
 {
-    char *s = (char *) &arg1;
-    return (fixedlen_like(s, p, 4));
+       text       *str = PG_GETARG_TEXT_P(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       s = VARDATA(str);
+       slen = (VARSIZE(str) - VARHDRSZ);
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+
+       if (pg_database_encoding_max_length() == 1)
+               result = (MatchText(s, slen, p, plen) == LIKE_TRUE);
+       else
+               result = (MBMatchText(s, slen, p, plen) == LIKE_TRUE);
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-char4nlike(uint32 arg1, struct varlena *p)
+Datum
+textnlike(PG_FUNCTION_ARGS)
 {
-    return (!char4like(arg1, p));
+       text       *str = PG_GETARG_TEXT_P(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       s = VARDATA(str);
+       slen = (VARSIZE(str) - VARHDRSZ);
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+
+       if (pg_database_encoding_max_length() == 1)
+               result = (MatchText(s, slen, p, plen) != LIKE_TRUE);
+       else
+               result = (MBMatchText(s, slen, p, plen) != LIKE_TRUE);
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-char8like(char *s, struct varlena *p)
+Datum
+bytealike(PG_FUNCTION_ARGS)
 {
-    return (fixedlen_like(s, p, 8));
+       bytea      *str = PG_GETARG_BYTEA_P(0);
+       bytea      *pat = PG_GETARG_BYTEA_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       s = VARDATA(str);
+       slen = (VARSIZE(str) - VARHDRSZ);
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+
+       result = (MatchBytea(s, slen, p, plen) == LIKE_TRUE);
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-char8nlike(char *s, struct varlena *p)
+Datum
+byteanlike(PG_FUNCTION_ARGS)
 {
-    return (!char8like(s, p));
+       bytea      *str = PG_GETARG_BYTEA_P(0);
+       bytea      *pat = PG_GETARG_BYTEA_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       s = VARDATA(str);
+       slen = (VARSIZE(str) - VARHDRSZ);
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+
+       result = (MatchBytea(s, slen, p, plen) != LIKE_TRUE);
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-char16like(char *s, struct varlena *p)
+/*
+ * Case-insensitive versions
+ */
+
+Datum
+nameiclike(PG_FUNCTION_ARGS)
 {
-    return (fixedlen_like(s, p, 16));
+       Name            str = PG_GETARG_NAME(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       if (pg_database_encoding_max_length() == 1)
+       {
+               s = NameStr(*str);
+               slen = strlen(s);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MatchTextIC(s, slen, p, plen) == LIKE_TRUE);
+       }
+       else
+       {
+               /* Force inputs to lower case to achieve case insensitivity */
+               text       *strtext;
+
+               strtext = DatumGetTextP(DirectFunctionCall1(name_text,
+                                                                                                       NameGetDatum(str)));
+               strtext = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                                 PointerGetDatum(strtext)));
+               pat = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                               PointerGetDatum(pat)));
+
+               s = VARDATA(strtext);
+               slen = (VARSIZE(strtext) - VARHDRSZ);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MBMatchTextIC(s, slen, p, plen) == LIKE_TRUE);
+       }
+
+       PG_RETURN_BOOL(result);
 }
-bool 
-char16nlike(char *s, struct varlena *p)
+
+Datum
+nameicnlike(PG_FUNCTION_ARGS)
 {
-    return (!char16like(s, p));
+       Name            str = PG_GETARG_NAME(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       if (pg_database_encoding_max_length() == 1)
+       {
+               s = NameStr(*str);
+               slen = strlen(s);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MatchTextIC(s, slen, p, plen) != LIKE_TRUE);
+       }
+       else
+       {
+               /* Force inputs to lower case to achieve case insensitivity */
+               text       *strtext;
+
+               strtext = DatumGetTextP(DirectFunctionCall1(name_text,
+                                                                                                       NameGetDatum(str)));
+               strtext = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                                 PointerGetDatum(strtext)));
+               pat = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                               PointerGetDatum(pat)));
+
+               s = VARDATA(strtext);
+               slen = (VARSIZE(strtext) - VARHDRSZ);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MBMatchTextIC(s, slen, p, plen) != LIKE_TRUE);
+       }
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-namelike(NameData *n, struct varlena *p)
+Datum
+texticlike(PG_FUNCTION_ARGS)
 {
-    return (fixedlen_like(n->data, p, NAMEDATALEN));
+       text       *str = PG_GETARG_TEXT_P(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       if (pg_database_encoding_max_length() == 1)
+       {
+               s = VARDATA(str);
+               slen = (VARSIZE(str) - VARHDRSZ);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MatchTextIC(s, slen, p, plen) == LIKE_TRUE);
+       }
+       else
+       {
+               /* Force inputs to lower case to achieve case insensitivity */
+               str = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                               PointerGetDatum(str)));
+               pat = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                               PointerGetDatum(pat)));
+               s = VARDATA(str);
+               slen = (VARSIZE(str) - VARHDRSZ);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MBMatchTextIC(s, slen, p, plen) == LIKE_TRUE);
+       }
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-namenlike(NameData *s, struct varlena *p)
+Datum
+texticnlike(PG_FUNCTION_ARGS)
 {
-    return (!namelike(s, p));
+       text       *str = PG_GETARG_TEXT_P(0);
+       text       *pat = PG_GETARG_TEXT_P(1);
+       bool            result;
+       char       *s,
+                          *p;
+       int                     slen,
+                               plen;
+
+       if (pg_database_encoding_max_length() == 1)
+       {
+               s = VARDATA(str);
+               slen = (VARSIZE(str) - VARHDRSZ);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MatchTextIC(s, slen, p, plen) != LIKE_TRUE);
+       }
+       else
+       {
+               /* Force inputs to lower case to achieve case insensitivity */
+               str = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                               PointerGetDatum(str)));
+               pat = DatumGetTextP(DirectFunctionCall1(lower,
+                                                                                               PointerGetDatum(pat)));
+               s = VARDATA(str);
+               slen = (VARSIZE(str) - VARHDRSZ);
+               p = VARDATA(pat);
+               plen = (VARSIZE(pat) - VARHDRSZ);
+               result = (MBMatchTextIC(s, slen, p, plen) != LIKE_TRUE);
+       }
+
+       PG_RETURN_BOOL(result);
 }
 
-bool 
-textlike(struct varlena *s, struct varlena *p)
+/*
+ * like_escape() --- given a pattern and an ESCAPE string,
+ * convert the pattern to use Postgres' standard backslash escape convention.
+ */
+Datum
+like_escape(PG_FUNCTION_ARGS)
 {
-    return (fixedlen_like(VARDATA(s), p, VARSIZE(s) - VARHDRSZ));
+       text       *pat = PG_GETARG_TEXT_P(0);
+       text       *esc = PG_GETARG_TEXT_P(1);
+       text       *result;
+
+       if (pg_database_encoding_max_length() == 1)
+               result = do_like_escape(pat, esc);
+       else
+               result = MB_do_like_escape(pat, esc);
+
+       PG_RETURN_TEXT_P(result);
 }
 
-bool textnlike(struct varlena *s, struct varlena *p)
+/*
+ * like_escape_bytea() --- given a pattern and an ESCAPE string,
+ * convert the pattern to use Postgres' standard backslash escape convention.
+ */
+Datum
+like_escape_bytea(PG_FUNCTION_ARGS)
 {
-    return (!textlike(s, p));
-}
+       bytea      *pat = PG_GETARG_BYTEA_P(0);
+       bytea      *esc = PG_GETARG_BYTEA_P(1);
+       bytea      *result;
+       char       *p,
+                          *e,
+                          *r;
+       int                     plen,
+                               elen;
+       bool            afterescape;
+
+       p = VARDATA(pat);
+       plen = (VARSIZE(pat) - VARHDRSZ);
+       e = VARDATA(esc);
+       elen = (VARSIZE(esc) - VARHDRSZ);
+
+       /*
+        * Worst-case pattern growth is 2x --- unlikely, but it's hardly worth
+        * trying to calculate the size more accurately than that.
+        */
+       result = (text *) palloc(plen * 2 + VARHDRSZ);
+       r = VARDATA(result);
+
+       if (elen == 0)
+       {
+               /*
+                * No escape character is wanted.  Double any backslashes in the
+                * pattern to make them act like ordinary characters.
+                */
+               while (plen > 0)
+               {
+                       if (*p == '\\')
+                               *r++ = '\\';
+                       BYTEA_CopyAdvChar(r, p, plen);
+               }
+       }
+       else
+       {
+               /*
+                * The specified escape must be only a single character.
+                */
+               BYTEA_NextChar(e, elen);
+               if (elen != 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
+                                        errmsg("invalid escape string"),
+                                 errhint("Escape string must be empty or one character.")));
+
+               e = VARDATA(esc);
+
+               /*
+                * If specified escape is '\', just copy the pattern as-is.
+                */
+               if (*e == '\\')
+               {
+                       memcpy(result, pat, VARSIZE(pat));
+                       PG_RETURN_BYTEA_P(result);
+               }
 
+               /*
+                * Otherwise, convert occurrences of the specified escape character to
+                * '\', and double occurrences of '\' --- unless they immediately
+                * follow an escape character!
+                */
+               afterescape = false;
+               while (plen > 0)
+               {
+                       if (BYTEA_CHAREQ(p, e) && !afterescape)
+                       {
+                               *r++ = '\\';
+                               BYTEA_NextChar(p, plen);
+                               afterescape = true;
+                       }
+                       else if (*p == '\\')
+                       {
+                               *r++ = '\\';
+                               if (!afterescape)
+                                       *r++ = '\\';
+                               BYTEA_NextChar(p, plen);
+                               afterescape = false;
+                       }
+                       else
+                       {
+                               BYTEA_CopyAdvChar(r, p, plen);
+                               afterescape = false;
+                       }
+               }
+       }
+
+       VARATT_SIZEP(result) = r - ((char *) result);
 
-/*  $Revision: 1.1.1.1 $
-**  "like.c" A first attempt at a LIKE operator for Postgres95.
-**
-**  Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
-**  Rich $alz is now <rsalz@bbn.com>.
-**  Special thanks to Lars Mathiesen <thorinn@diku.dk> for the LABORT code.
-** 
-**  This code was shamelessly stolen from the "pql" code by myself and
-**  slightly modified :)
-** 
-**  All references to the word "star" were replaced by "percent"
-**  All references to the word "wild" were replaced by "like"
-** 
-**  All the nice shell RE matching stuff was replaced by just "_" and "%"
-** 
-**  As I don't have a copy of the SQL standard handy I wasn't sure whether
-**  to leave in the '\' escape character handling. (I suspect the standard
-**  handles "%%" as a single literal percent)
-**
-**  Keith Parks. <keith@mtcc.demon.co.uk>
-**
-**  [SQL92 lets you specify the escape character by saying
-**   LIKE <pattern> ESCAPE <escape character>. We are a small operation
-**   so we force you to use '\'. - ay 7/95]
-**
-*/
-
-#define LIKE_TRUE                      1
-#define LIKE_FALSE                     0
-#define LIKE_ABORT                     -1
+       PG_RETURN_BYTEA_P(result);
+}
 
 /*
-**  Match text and p, return LIKE_TRUE, LIKE_FALSE, or LIKE_ABORT.
-*/
+ * Same as above, but specifically for bytea (binary) datatype
+ */
 static int
-DoMatch(register char *text, register char *p)
+MatchBytea(char *t, int tlen, char *p, int plen)
 {
-    register int       matched;
-
-    for ( ; *p; text++, p++) {
-       if (*text == '\0' && *p != '%')
-           return LIKE_ABORT;
-       switch (*p) {
-       case '\\':
-           /* Literal match with following character. */
-           p++;
-           /* FALLTHROUGH */
-       default:
-           if (*text != *p)
-               return LIKE_FALSE;
-           continue;
-       case '_':
-           /* Match anything. */
-           continue;
-       case '%':
-           while (*++p == '%')
-               /* Consecutive percents act just like one. */
-               continue;
-           if (*p == '\0')
-               /* Trailing percent matches everything. */
+       /* Fast path for match-everything pattern */
+       if ((plen == 1) && (*p == '%'))
                return LIKE_TRUE;
-           while (*text)
-               if ((matched = DoMatch(text++, p)) != LIKE_FALSE)
-                   return matched;
-           return LIKE_ABORT;
+
+       while ((tlen > 0) && (plen > 0))
+       {
+               if (*p == '\\')
+               {
+                       /* Next pattern char must match literally, whatever it is */
+                       BYTEA_NextChar(p, plen);
+                       if ((plen <= 0) || !BYTEA_CHAREQ(t, p))
+                               return LIKE_FALSE;
+               }
+               else if (*p == '%')
+               {
+                       /* %% is the same as % according to the SQL standard */
+                       /* Advance past all %'s */
+                       while ((plen > 0) && (*p == '%'))
+                               BYTEA_NextChar(p, plen);
+                       /* Trailing percent matches everything. */
+                       if (plen <= 0)
+                               return LIKE_TRUE;
+
+                       /*
+                        * Otherwise, scan for a text position at which we can match the
+                        * rest of the pattern.
+                        */
+                       while (tlen > 0)
+                       {
+                               /*
+                                * Optimization to prevent most recursion: don't recurse
+                                * unless first pattern char might match this text char.
+                                */
+                               if (BYTEA_CHAREQ(t, p) || (*p == '\\') || (*p == '_'))
+                               {
+                                       int                     matched = MatchBytea(t, tlen, p, plen);
+
+                                       if (matched != LIKE_FALSE)
+                                               return matched; /* TRUE or ABORT */
+                               }
+
+                               BYTEA_NextChar(t, tlen);
+                       }
+
+                       /*
+                        * End of text with no match, so no point in trying later places
+                        * to start matching this pattern.
+                        */
+                       return LIKE_ABORT;
+               }
+               else if ((*p != '_') && !BYTEA_CHAREQ(t, p))
+               {
+                       /*
+                        * Not the single-character wildcard and no explicit match? Then
+                        * time to quit...
+                        */
+                       return LIKE_FALSE;
+               }
+
+               BYTEA_NextChar(t, tlen);
+               BYTEA_NextChar(p, plen);
        }
-    }
 
-    return *text == '\0';
-}
+       if (tlen > 0)
+               return LIKE_FALSE;              /* end of pattern, but not of text */
 
+       /* End of input string.  Do we have matching pattern remaining? */
+       while ((plen > 0) && (*p == '%'))       /* allow multiple %'s at end of
+                                                                                * pattern */
+               BYTEA_NextChar(p, plen);
+       if (plen <= 0)
+               return LIKE_TRUE;
 
-/*
-**  User-level routine.  Returns TRUE or FALSE.
-*/
-int
-like(char *text, char *p)
-{
-    if (p[0] == '%' && p[1] == '\0')
-       return TRUE;
-    return (DoMatch(text, p) == LIKE_TRUE);
-}
+       /*
+        * End of text with no match, so no point in trying later places to start
+        * matching this pattern.
+        */
+       return LIKE_ABORT;
+}      /* MatchBytea() */