Re: regex_fixed_prefix() is still a few bricks shy of a load - Mailing list pgsql-hackers

From Tom Lane
Subject Re: regex_fixed_prefix() is still a few bricks shy of a load
Date
Msg-id 17440.1341787170@sss.pgh.pa.us
Whole thread Raw
In response to Re: regex_fixed_prefix() is still a few bricks shy of a load  (Robert Haas <robertmhaas@gmail.com>)
List pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
> On Jul 7, 2012, at 1:46 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>> 3. Try another approach entirely.  The idea that I've got in mind here
>> is to compile the regex using the regex library and then look at the
>> compiled NFA representation to see if there must be a fixed prefix.

> I think this is clearly the best way forward, probably even in the
> back branches.  It's true that the wchar to mb conversion is largely
> untested, but it's also pretty simple code.  Sure, it could have bugs,
> but so could whatever work-around you cobble together to avoid
> back-patching it.  And it's not like we'll break anything else,
> either: the code will only be used in the case that is buggy right now
> anyway.

Attached is a draft patch for that.  I'm fairly happy with the way this
turned out.  The code is a bit longer than before, but most of the net
addition is boilerplate code associated with maintaining a well-defined
API for the regex library proper and the regex caching code in
utils/adt/regexp.c.  The code that actually extracts the prefix string
(findprefix()) is about 150 lines, comparable to the net removal from
regex_fixed_prefix(), and it is *way* less heuristic: basically, given
the data structure it's working with, there is just one right answer.

One thing that remains slightly klugy is identification of the character
code associated with a colormap "color".  The regex library's colormap
data structure is only designed to provide forward lookup from char
codes to colors; if you want to go the other way, the only possibility
is to serially probe each char value, which is untenable for non-Western
alphabets.  What I did about this was to tweak the colormap building
code to remember just the first char code entered for each color.  If,
after the dust settles, that's the only char belonging to the color,
we can use it --- otherwise, we just punt and stop extending the prefix
string.  Given that we only care about doing reverse mapping for
singleton colors anyway, I believe that this is adequate.  There are
cases where a color might have only one member but it isn't the first
one added, for example in "^abc[de]d".  Here, d and e will be added to
a color for the bracket expression, and afterwards d will be split out
to its own subcolor, leaving e as the sole member of a color it wasn't
the first member of.  But for our purposes it doesn't matter, because
both the d and e colors will be outarcs from the state after c, so
we couldn't extend the fixed prefix to include e anyway.  There might
be some obscure corner cases where this implementation fails to
recognize a usable fixed-prefix character, but they have to be pretty
darn obscure.

Another loose end is that the API for regex_fixed_prefix() supposes
that it can return not only the fixed prefix string extracted from
the pattern, but also the "rest" of the pattern.  There's no way to
reconstitute a "remaining pattern" in what I've added to backend/regex,
so in this patch regex_fixed_prefix() is just passing back the whole
pattern in the Pattern_Prefix_Partial case, which is likely to lead
to a lowball selectivity estimate when there's a long prefix.

Now the previous implementation of that is pretty darn brain-dead
anyway, because what it hands back is whatever's left when it stops
scanning.  For instance, given "^(ab[de])xyz" it would extract the fixed
prefix "ab" and then return "rest" as "[de])xyz", which isn't a valid
regex.  The only thing we are doing with that is passing it to
regex_selectivity(), which is too stupid to have a problem with it; but
obviously there is no chance of ever upgrading the logic without fixing
that somehow.  (Note that right now we will never reach any of this code
anyway unless the target column has a small histogram; we realized long
ago that it was so bogus that relying on histogram_selectivity is a much
better idea if there's a reasonable amount of data...)

It's interesting to think about building something that would examine
the NFA representation of the regex, starting from whatever state
findprefix() stops at, and apply heuristic selectivity calculations
similar to what regex_selectivity() does.  However I don't see where
to put such code while maintaining a reasonable API wall between PG's
selectivity estimators and the regex library.  It's surely not something
that could be considered a general-purpose addition to a standalone
regex library.

What I have in mind to do for the moment is to refactor
regex_fixed_prefix() so that it doesn't hand back a "rest of pattern"
per se, but is directly responsible for handing back a selectivity
estimate for the rest of the pattern (a change we'd need to make anyway
if we ever did what's contemplated in the previous para).  What it can
do is run regex_selectivity() over the whole pattern, and then divide
out FIXED_CHAR_SEL times the length of the fixed prefix, which should
compensate reasonably well for letting regex_selectivity() see the
prefix portion of the pattern.

One other point is that although this change adds regexp compilation
to the planner code path, that should be nearly free in common cases,
because we're caching the compiled regexp, thus saving having to compile
it at query runtime.  I haven't tried to measure but I think the total
runtime should be pretty similar to what it was before.

Comments?

            regards, tom lane

diff --git a/src/backend/regex/Makefile b/src/backend/regex/Makefile
index 21e7fa5329b9384333d6a8c9912be81dd24a4867..74a4c0c89d8efedcb8699dffac465def523431a9 100644
*** a/src/backend/regex/Makefile
--- b/src/backend/regex/Makefile
*************** subdir = src/backend/regex
*** 12,18 ****
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global

! OBJS = regcomp.o regerror.o regexec.o regfree.o

  include $(top_srcdir)/src/backend/common.mk

--- 12,18 ----
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global

! OBJS = regcomp.o regerror.o regexec.o regfree.o regprefix.o

  include $(top_srcdir)/src/backend/common.mk

diff --git a/src/backend/regex/README b/src/backend/regex/README
index 89ba6a62ea2f70bfda729f03efedba4b9b6fce9b..c5d21e8c99da1c512379c1702bb7e422355ba9d9 100644
*** a/src/backend/regex/README
--- b/src/backend/regex/README
*************** So this file is an attempt to reverse-en
*** 7,18 ****
  General source-file layout
  --------------------------

! There are four separately-compilable source files, each exposing exactly
  one exported function:
      regcomp.c: pg_regcomp
      regexec.c: pg_regexec
      regerror.c: pg_regerror
      regfree.c: pg_regfree
  (The pg_ prefixes were added by the Postgres project to distinguish this
  library version from any similar one that might be present on a particular
  system.  They'd need to be removed or replaced in any standalone version
--- 7,19 ----
  General source-file layout
  --------------------------

! There are five separately-compilable source files, each exposing exactly
  one exported function:
      regcomp.c: pg_regcomp
      regexec.c: pg_regexec
      regerror.c: pg_regerror
      regfree.c: pg_regfree
+     regprefix.c: pg_regprefix
  (The pg_ prefixes were added by the Postgres project to distinguish this
  library version from any similar one that might be present on a particular
  system.  They'd need to be removed or replaced in any standalone version
*************** regexec.c        Top-level regex execution cod
*** 44,49 ****
--- 45,51 ----
  rege_dfa.c        DFA creation and execution
  regerror.c        pg_regerror: generate text for a regex error code
  regfree.c        pg_regfree: API to free a no-longer-needed regex_t
+ regprefix.c        Code for extracting a common prefix from a regex_t

  The locale-specific code is concerned primarily with case-folding and with
  expanding locale-specific character classes, such as [[:alnum:]].  It
diff --git a/src/backend/regex/regc_color.c b/src/backend/regex/regc_color.c
index 2aeb861d9762f0d9f5ec667007ce1b00d22fccde..1c60566fbf57458a1f43a4116432e0636ebcd92d 100644
*** a/src/backend/regex/regc_color.c
--- b/src/backend/regex/regc_color.c
*************** initcm(struct vars * v,
*** 66,73 ****
      cd = cm->cd;                /* cm->cd[WHITE] */
      cd->sub = NOSUB;
      cd->arcs = NULL;
!     cd->flags = 0;
      cd->nchrs = CHR_MAX - CHR_MIN + 1;

      /* upper levels of tree */
      for (t = &cm->tree[0], j = NBYTS - 1; j > 0; t = nextt, j--)
--- 66,74 ----
      cd = cm->cd;                /* cm->cd[WHITE] */
      cd->sub = NOSUB;
      cd->arcs = NULL;
!     cd->firstchr = CHR_MIN;
      cd->nchrs = CHR_MAX - CHR_MIN + 1;
+     cd->flags = 0;

      /* upper levels of tree */
      for (t = &cm->tree[0], j = NBYTS - 1; j > 0; t = nextt, j--)
*************** newcolor(struct colormap * cm)
*** 272,277 ****
--- 273,279 ----
      cd->nchrs = 0;
      cd->sub = NOSUB;
      cd->arcs = NULL;
+     cd->firstchr = CHR_MIN;        /* in case never set otherwise */
      cd->flags = 0;
      cd->block = NULL;

*************** subcolor(struct colormap * cm, chr c)
*** 371,376 ****
--- 373,380 ----
      if (co == sco)                /* already in an open subcolor */
          return co;                /* rest is redundant */
      cm->cd[co].nchrs--;
+     if (cm->cd[sco].nchrs == 0)
+         cm->cd[sco].firstchr = c;
      cm->cd[sco].nchrs++;
      setcolor(cm, c, sco);
      return sco;
*************** subrange(struct vars * v,
*** 438,443 ****
--- 442,452 ----

  /*
   * subblock - allocate new subcolors for one tree block of chrs, fill in arcs
+  *
+  * Note: subcolors that are created during execution of this function
+  * will not be given a useful value of firstchr; it'll be left as CHR_MIN.
+  * For the current usage of firstchr in pg_regprefix, this does not matter
+  * because such subcolors won't occur in the common prefix of a regex.
   */
  static void
  subblock(struct vars * v,
diff --git a/src/backend/regex/regprefix.c b/src/backend/regex/regprefix.c
index ...3b4124a71acf966a16db6f51f8e345ac2e703dcd .
*** a/src/backend/regex/regprefix.c
--- b/src/backend/regex/regprefix.c
***************
*** 0 ****
--- 1,259 ----
+ /*-------------------------------------------------------------------------
+  *
+  * regprefix.c
+  *      Extract a common prefix, if any, from a compiled regex.
+  *
+  *
+  * Portions Copyright (c) 2012, PostgreSQL Global Development Group
+  * Portions Copyright (c) 1998, 1999 Henry Spencer
+  *
+  * IDENTIFICATION
+  *      src/backend/regex/regprefix.c
+  *
+  *-------------------------------------------------------------------------
+  */
+
+ #include "regex/regguts.h"
+
+
+ /*
+  * forward declarations
+  */
+ static int findprefix(struct cnfa * cnfa, struct colormap * cm,
+                       chr *string, size_t *slength);
+
+
+ /*
+  * pg_regprefix - get common prefix for regular expression
+  *
+  * Returns one of:
+  *    REG_NOMATCH: there is no common prefix of strings matching the regex
+  *    REG_PREFIX: there is a common prefix of strings matching the regex
+  *    REG_EXACT: all strings satisfying the regex must match the same string
+  *    or a REG_XXX error code
+  *
+  * In the non-failure cases, *string is set to a malloc'd string containing
+  * the common prefix or exact value, of length *slength (measured in chrs
+  * not bytes!).
+  *
+  * This function does not analyze all complex cases (such as lookahead
+  * constraints) exactly.  Therefore it is possible that some strings matching
+  * the reported prefix or exact-match string do not satisfy the regex.  But
+  * it should never be the case that a string satisfying the regex does not
+  * match the reported prefix or exact-match string.
+  */
+ int
+ pg_regprefix(regex_t *re,
+              chr **string,
+              size_t *slength)
+ {
+     struct guts *g;
+     struct cnfa *cnfa;
+     int            st;
+
+     /* sanity checks */
+     if (string == NULL || slength == NULL)
+         return REG_INVARG;
+     *string = NULL;                /* initialize for failure cases */
+     *slength = 0;
+     if (re == NULL || re->re_magic != REMAGIC)
+         return REG_INVARG;
+     if (re->re_csize != sizeof(chr))
+         return REG_MIXED;
+
+     /* Initialize locale-dependent support */
+     pg_set_regex_collation(re->re_collation);
+
+     /* setup */
+     g = (struct guts *) re->re_guts;
+     if (g->info & REG_UIMPOSSIBLE)
+         return REG_NOMATCH;
+
+     /*
+      * This implementation considers only the search NFA for the topmost regex
+      * tree node.  Therefore, constraints such as backrefs are not fully
+      * applied, which is allowed per the function's API spec.
+      */
+     assert(g->tree != NULL);
+     cnfa = &g->tree->cnfa;
+
+     /*
+      * Since a correct NFA should never contain any exit-free loops, it should
+      * not be possible for our traversal to return to a previously visited
+      * NFA state.  Hence we need at most nstates chrs in the output string.
+      */
+     *string = (chr *) MALLOC(cnfa->nstates * sizeof(chr));
+     if (*string == NULL)
+         return REG_ESPACE;
+
+     /* do it */
+     st = findprefix(cnfa, &g->cmap, *string, slength);
+
+     assert(*slength <= cnfa->nstates);
+
+     /* clean up */
+     if (st != REG_PREFIX && st != REG_EXACT)
+     {
+         FREE(*string);
+         *string = NULL;
+         *slength = 0;
+     }
+
+     return st;
+ }
+
+ /*
+  * findprefix - extract common prefix from cNFA
+  *
+  * Results are returned into the preallocated chr array string[], with
+  * *slength (which must be preset to zero) incremented for each chr.
+  */
+ static int                        /* regprefix return code */
+ findprefix(struct cnfa * cnfa,
+            struct colormap * cm,
+            chr *string,
+            size_t *slength)
+ {
+     int            st;
+     int            nextst;
+     color        thiscolor;
+     chr            c;
+     struct carc *ca;
+
+     /*
+      * The "pre" state must have only BOS/BOL outarcs, else pattern isn't
+      * anchored left.  If we have both BOS and BOL, they must go to the
+      * same next state.
+      */
+     st = cnfa->pre;
+     nextst = -1;
+     for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+     {
+         if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
+         {
+             if (nextst == -1)
+                 nextst = ca->to;
+             else if (nextst != ca->to)
+                 return REG_NOMATCH;
+         }
+         else
+             return REG_NOMATCH;
+     }
+     if (nextst == -1)
+         return REG_NOMATCH;
+
+     /*
+      * Scan through successive states, stopping as soon as we find one with
+      * more than one acceptable transition character (either multiple colors
+      * on out-arcs, or a color with more than one member chr).
+      *
+      * We could find a state with multiple out-arcs that are all labeled with
+      * the same singleton color; this comes from patterns like "^ab(cde|cxy)".
+      * In that case we add the chr to the output string but then exit the loop
+      * with nextst == -1.  This leaves a little bit on the table: if the
+      * pattern is like "^ab(cde|cdy)", we won't notice that "d" could be added
+      * to the prefix.  But chasing multiple parallel state chains doesn't seem
+      * worth the trouble.
+      */
+     do
+     {
+         st = nextst;
+         nextst = -1;
+         thiscolor = COLORLESS;
+         for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+         {
+             /* We ignore lookahead constraints */
+             if (ca->co >= cnfa->ncolors)
+                 continue;
+             /* We can also ignore BOS/BOL arcs */
+             if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
+                 continue;
+             /* ... but EOS/EOL arcs terminate the search */
+             if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1])
+             {
+                 thiscolor = COLORLESS;
+                 break;
+             }
+             if (thiscolor == COLORLESS)
+             {
+                 /* First plain outarc */
+                 thiscolor = ca->co;
+                 nextst = ca->to;
+             }
+             else if (thiscolor == ca->co)
+             {
+                 /* Another plain outarc for same color */
+                 nextst = -1;
+             }
+             else
+             {
+                 /* More than one plain outarc color terminates the search */
+                 thiscolor = COLORLESS;
+                 break;
+             }
+         }
+         /* Done if we didn't find exactly one color on plain outarcs */
+         if (thiscolor == COLORLESS)
+             break;
+         /* The color must be a singleton */
+         if (cm->cd[thiscolor].nchrs != 1)
+             break;
+
+         /*
+          * Identify the color's sole member chr and add it to the prefix
+          * string.  In general the colormap data structure doesn't provide a
+          * way to find color member chrs, except by trying GETCOLOR() on each
+          * possible chr value, which won't do at all.  However, for the cases
+          * we care about it should be sufficient to test the "firstchr" value,
+          * that is the first chr ever added to the color.  There are cases
+          * where this might no longer be a member of the color (so we do need
+          * to test), but none of them are likely to arise for a character that
+          * is a member of a common prefix.  If we do hit such a corner case,
+          * we just fall out without adding anything to the prefix string.
+          */
+         c = cm->cd[thiscolor].firstchr;
+         if (GETCOLOR(cm, c) != thiscolor)
+             break;
+
+         string[(*slength)++] = c;
+
+         /* Advance to next state, but only if we have a unique next state */
+     } while (nextst != -1);
+
+     /*
+      * If we ended at a state that only has EOS/EOL outarcs leading to the
+      * "post" state, then we have an exact-match string.  Note this is true
+      * even if the string is of zero length.
+      */
+     nextst = -1;
+     for (ca = cnfa->states[st]; ca->co != COLORLESS; ca++)
+     {
+         if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1])
+         {
+             if (nextst == -1)
+                 nextst = ca->to;
+             else if (nextst != ca->to)
+             {
+                 nextst = -1;
+                 break;
+             }
+         }
+         else
+         {
+             nextst = -1;
+             break;
+         }
+     }
+     if (nextst == cnfa->post)
+         return REG_EXACT;
+
+     /*
+      * Otherwise, if we were unable to identify any prefix characters, say
+      * NOMATCH --- the pattern is anchored left, but doesn't specify any
+      * particular first character.
+      */
+     if (*slength > 0)
+         return REG_PREFIX;
+
+     return REG_NOMATCH;
+ }
diff --git a/src/backend/utils/adt/regexp.c b/src/backend/utils/adt/regexp.c
index 96c77078c8b51f434ea9ddb9947abb5e93143410..074142e7985499f152e07ab0c4e1c1d0675c4042 100644
*** a/src/backend/utils/adt/regexp.c
--- b/src/backend/utils/adt/regexp.c
*************** build_regexp_split_result(regexp_matches
*** 1170,1172 ****
--- 1170,1237 ----
                                     Int32GetDatum(startpos + 1));
      }
  }
+
+ /*
+  * regexp_fixed_prefix - extract fixed prefix, if any, for a regexp
+  *
+  * The result is NULL if there is no fixed prefix, else a palloc'd string.
+  * If it is an exact match, not just a prefix, *exact is returned as TRUE.
+  */
+ char *
+ regexp_fixed_prefix(text *text_re, bool case_insensitive, Oid collation,
+                     bool *exact)
+ {
+     char       *result;
+     regex_t    *re;
+     int            cflags;
+     int            re_result;
+     pg_wchar   *str;
+     size_t        slen;
+     size_t        maxlen;
+     char        errMsg[100];
+
+     *exact = false;                /* default result */
+
+     /* Compile RE */
+     cflags = REG_ADVANCED;
+     if (case_insensitive)
+         cflags |= REG_ICASE;
+
+     re = RE_compile_and_cache(text_re, cflags, collation);
+
+     /* Examine it to see if there's a fixed prefix */
+     re_result = pg_regprefix(re, &str, &slen);
+
+     switch (re_result)
+     {
+         case REG_NOMATCH:
+             return NULL;
+
+         case REG_PREFIX:
+             /* continue with wchar conversion */
+             break;
+
+         case REG_EXACT:
+             *exact = true;
+             /* continue with wchar conversion */
+             break;
+
+         default:
+             /* re failed??? */
+             pg_regerror(re_result, re, errMsg, sizeof(errMsg));
+             ereport(ERROR,
+                     (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                      errmsg("regular expression failed: %s", errMsg)));
+             break;
+     }
+
+     /* Convert pg_wchar result back to database encoding */
+     maxlen = pg_database_encoding_max_length() * slen + 1;
+     result = (char *) palloc(maxlen);
+     slen = pg_wchar2mb_with_len(str, result, slen);
+     Assert(slen < maxlen);
+
+     free(str);
+
+     return result;
+ }
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 95e46276f0a8911758f4ec02b993193bf55eee15..b364bfa39d9c218fb5c96527b056777e175b935c 100644
*** a/src/backend/utils/adt/selfuncs.c
--- b/src/backend/utils/adt/selfuncs.c
*************** static Pattern_Prefix_Status
*** 5236,5253 ****
  regex_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation,
                     Const **prefix_const, Const **rest_const)
  {
-     char       *match;
-     int            pos,
-                 match_pos,
-                 prev_pos,
-                 prev_match_pos;
-     bool        have_leading_paren;
-     char       *patt;
-     char       *rest;
      Oid            typeid = patt_const->consttype;
!     bool        is_multibyte = (pg_database_encoding_max_length() > 1);
!     pg_locale_t locale = 0;
!     bool        locale_is_c = false;

      /*
       * Should be unnecessary, there are no bytea regex operators defined. As
--- 5236,5244 ----
  regex_fixed_prefix(Const *patt_const, bool case_insensitive, Oid collation,
                     Const **prefix_const, Const **rest_const)
  {
      Oid            typeid = patt_const->consttype;
!     char       *prefix;
!     bool        exact;

      /*
       * Should be unnecessary, there are no bytea regex operators defined. As
*************** regex_fixed_prefix(Const *patt_const, bo
*** 5259,5438 ****
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
           errmsg("regular-expression matching not supported on type bytea")));

!     if (case_insensitive)
!     {
!         /* If case-insensitive, we need locale info */
!         if (lc_ctype_is_c(collation))
!             locale_is_c = true;
!         else if (collation != DEFAULT_COLLATION_OID)
!         {
!             if (!OidIsValid(collation))
!             {
!                 /*
!                  * This typically means that the parser could not resolve a
!                  * conflict of implicit collations, so report it that way.
!                  */
!                 ereport(ERROR,
!                         (errcode(ERRCODE_INDETERMINATE_COLLATION),
!                          errmsg("could not determine which collation to use for regular expression"),
!                          errhint("Use the COLLATE clause to set the collation explicitly.")));
!             }
!             locale = pg_newlocale_from_collation(collation);
!         }
!     }
!
!     /* the right-hand const is type text for all of these */
!     patt = TextDatumGetCString(patt_const->constvalue);
!
!     /*
!      * Check for ARE director prefix.  It's worth our trouble to recognize
!      * this because similar_escape() used to use it, and some other code might
!      * still use it, to force ARE mode.
!      */
!     pos = 0;
!     if (strncmp(patt, "***:", 4) == 0)
!         pos = 4;
!
!     /* Pattern must be anchored left */
!     if (patt[pos] != '^')
!     {
!         rest = patt;
!
!         *prefix_const = NULL;
!         *rest_const = string_to_const(rest, typeid);
!
!         return Pattern_Prefix_None;
!     }
!     pos++;

!     /*
!      * If '|' is present in pattern, then there may be multiple alternatives
!      * for the start of the string.  (There are cases where this isn't so, for
!      * instance if the '|' is inside parens, but detecting that reliably is
!      * too hard.)
!      */
!     if (strchr(patt + pos, '|') != NULL)
      {
-         rest = patt;
-
          *prefix_const = NULL;
!         *rest_const = string_to_const(rest, typeid);

          return Pattern_Prefix_None;
      }

!     /* OK, allocate space for pattern */
!     match = palloc(strlen(patt) + 1);
!     prev_match_pos = match_pos = 0;
!
!     /*
!      * We special-case the syntax '^(...)$' because psql uses it.  But beware:
!      * sequences beginning "(?" are not what they seem, unless they're "(?:".
!      * (We must recognize that because of similar_escape().)
!      */
!     have_leading_paren = false;
!     if (patt[pos] == '(' &&
!         (patt[pos + 1] != '?' || patt[pos + 2] == ':'))
!     {
!         have_leading_paren = true;
!         pos += (patt[pos + 1] != '?' ? 1 : 3);
!     }
!
!     /* Scan remainder of pattern */
!     prev_pos = pos;
!     while (patt[pos])
!     {
!         int            len;
!
!         /*
!          * Check for characters that indicate multiple possible matches here.
!          * Also, drop out at ')' or '$' so the termination test works right.
!          */
!         if (patt[pos] == '.' ||
!             patt[pos] == '(' ||
!             patt[pos] == ')' ||
!             patt[pos] == '[' ||
!             patt[pos] == '^' ||
!             patt[pos] == '$')
!             break;
!
!         /* Stop if case-varying character (it's sort of a wildcard) */
!         if (case_insensitive &&
!           pattern_char_isalpha(patt[pos], is_multibyte, locale, locale_is_c))
!             break;
!
!         /*
!          * Check for quantifiers.  Except for +, this means the preceding
!          * character is optional, so we must remove it from the prefix too!
!          */
!         if (patt[pos] == '*' ||
!             patt[pos] == '?' ||
!             patt[pos] == '{')
!         {
!             match_pos = prev_match_pos;
!             pos = prev_pos;
!             break;
!         }
!         if (patt[pos] == '+')
!         {
!             pos = prev_pos;
!             break;
!         }
!
!         /*
!          * Normally, backslash quotes the next character.  But in AREs,
!          * backslash followed by alphanumeric is an escape, not a quoted
!          * character.  Must treat it as having multiple possible matches.
!          * Note: since only ASCII alphanumerics are escapes, we don't have to
!          * be paranoid about multibyte or collations here.
!          */
!         if (patt[pos] == '\\')
!         {
!             if (isalnum((unsigned char) patt[pos + 1]))
!                 break;
!             pos++;
!             if (patt[pos] == '\0')
!                 break;
!         }
!         /* save position in case we need to back up on next loop cycle */
!         prev_match_pos = match_pos;
!         prev_pos = pos;
!         /* must use encoding-aware processing here */
!         len = pg_mblen(&patt[pos]);
!         memcpy(&match[match_pos], &patt[pos], len);
!         match_pos += len;
!         pos += len;
!     }
!
!     match[match_pos] = '\0';
!     rest = &patt[pos];
!
!     if (have_leading_paren && patt[pos] == ')')
!         pos++;
!
!     if (patt[pos] == '$' && patt[pos + 1] == '\0')
!     {
!         rest = &patt[pos + 1];

!         *prefix_const = string_to_const(match, typeid);
!         *rest_const = string_to_const(rest, typeid);

!         pfree(patt);
!         pfree(match);

          return Pattern_Prefix_Exact;    /* pattern specifies exact match */
!     }
!
!     *prefix_const = string_to_const(match, typeid);
!     *rest_const = string_to_const(rest, typeid);
!
!     pfree(patt);
!     pfree(match);
!
!     if (match_pos > 0)
          return Pattern_Prefix_Partial;
-
-     return Pattern_Prefix_None;
  }

  Pattern_Prefix_Status
--- 5250,5281 ----
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
           errmsg("regular-expression matching not supported on type bytea")));

!     /* Use the regexp machinery to extract the prefix, if any */
!     prefix = regexp_fixed_prefix(DatumGetTextPP(patt_const->constvalue),
!                                  case_insensitive, collation,
!                                  &exact);

!     if (prefix == NULL)
      {
          *prefix_const = NULL;
!         *rest_const = patt_const;

          return Pattern_Prefix_None;
      }

!     *prefix_const = string_to_const(prefix, typeid);

!     if (exact)
!         *rest_const = string_to_const("", typeid);
!     else
!         *rest_const = patt_const;        /* bogus */

!     pfree(prefix);

+     if (exact)
          return Pattern_Prefix_Exact;    /* pattern specifies exact match */
!     else
          return Pattern_Prefix_Partial;
  }

  Pattern_Prefix_Status
diff --git a/src/include/regex/regex.h b/src/include/regex/regex.h
index cec4b837cd15665da8e800ac1729245c21f1b22d..616c2c6450d80443361ca7e4660f52aaa266c9ad 100644
*** a/src/include/regex/regex.h
--- b/src/include/regex/regex.h
*************** typedef struct
*** 156,161 ****
--- 156,164 ----
  /* two specials for debugging and testing */
  #define REG_ATOI    101            /* convert error-code name to number */
  #define REG_ITOA    102            /* convert error-code number to name */
+ /* non-error result codes for pg_regprefix */
+ #define REG_PREFIX    (-1)        /* identified a common prefix */
+ #define REG_EXACT    (-2)        /* identified an exact match */



*************** typedef struct
*** 164,169 ****
--- 167,173 ----
   */
  extern int    pg_regcomp(regex_t *, const pg_wchar *, size_t, int, Oid);
  extern int    pg_regexec(regex_t *, const pg_wchar *, size_t, size_t, rm_detail_t *, size_t, regmatch_t[], int);
+ extern int    pg_regprefix(regex_t *, pg_wchar **, size_t *);
  extern void pg_regfree(regex_t *);
  extern size_t pg_regerror(int, const regex_t *, char *, size_t);
  extern void pg_set_regex_collation(Oid collation);
diff --git a/src/include/regex/regguts.h b/src/include/regex/regguts.h
index b8788506d417e83b3a293efae391ef63f8468e5b..e1e406f4eaa77720d88938cf150348ce903f88d8 100644
*** a/src/include/regex/regguts.h
--- b/src/include/regex/regguts.h
*************** struct colordesc
*** 199,217 ****
      color        sub;            /* open subcolor, if any; or free-chain ptr */
  #define  NOSUB     COLORLESS        /* value of "sub" when no open subcolor */
      struct arc *arcs;            /* chain of all arcs of this color */
      int            flags;            /* bit values defined next */
  #define  FREECOL 01                /* currently free */
  #define  PSEUDO  02                /* pseudocolor, no real chars */
! #define  UNUSEDCOLOR(cd) ((cd)->flags&FREECOL)
      union tree *block;            /* block of solid color, if any */
  };

  /*
   * The color map itself
   *
!  * Only the "tree" part is used at execution time, and that only via the
!  * GETCOLOR() macro.  Possibly that should be separated from the compile-time
!  * data.
   */
  struct colormap
  {
--- 199,219 ----
      color        sub;            /* open subcolor, if any; or free-chain ptr */
  #define  NOSUB     COLORLESS        /* value of "sub" when no open subcolor */
      struct arc *arcs;            /* chain of all arcs of this color */
+     chr            firstchr;        /* char first assigned to this color */
      int            flags;            /* bit values defined next */
  #define  FREECOL 01                /* currently free */
  #define  PSEUDO  02                /* pseudocolor, no real chars */
! #define  UNUSEDCOLOR(cd) ((cd)->flags & FREECOL)
      union tree *block;            /* block of solid color, if any */
  };

  /*
   * The color map itself
   *
!  * Much of the data in the colormap struct is only used at compile time.
!  * However, the bulk of the space usage is in the "tree" structure, so it's
!  * not clear that there's much point in converting the rest to a more compact
!  * form when compilation is finished.
   */
  struct colormap
  {
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 10634039a440e329d67a40e17dda944a4799911b..b43a4f1f6f39b389d70c2a09a6a230ea1aa598dd 100644
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
*************** extern Datum regexp_split_to_table(PG_FU
*** 568,573 ****
--- 568,575 ----
  extern Datum regexp_split_to_table_no_flags(PG_FUNCTION_ARGS);
  extern Datum regexp_split_to_array(PG_FUNCTION_ARGS);
  extern Datum regexp_split_to_array_no_flags(PG_FUNCTION_ARGS);
+ extern char *regexp_fixed_prefix(text *text_re, bool case_insensitive,
+                                  Oid collation, bool *exact);

  /* regproc.c */
  extern Datum regprocin(PG_FUNCTION_ARGS);

pgsql-hackers by date:

Previous
From: Peter Eisentraut
Date:
Subject: Re: Schema version management
Next
From: Tom Lane
Date:
Subject: Re: Schema version management