
This builds on the previous "rainbow" patch to detect NFAs that will match any string, though possibly with constraints on the string length. This definition is chosen to match constructs such as ".*", ".+", and ".{1,100}". Recognizing such an NFA after the optimization pass is fairly cheap, since we basically just have to verify that all arcs are RAINBOW arcs and count the number of steps to the end state. (Well, there's a bit of complication with pseudo-color arcs for string boundary conditions, but not much.) Once we have these markings, the regex executor functions longest(), shortest(), and matchuntil() don't have to expend per-character work to determine whether a given substring satisfies such an NFA; they just need to check its length against the bounds. Since some matching problems require O(N) invocations of these functions, we've reduced the runtime for an N-character string from O(N^2) to O(N). Of course, this is no help for non-matchall sub-patterns, but those usually have constraints that allow us to avoid needing O(N) substring checks in the first place. It's precisely the unconstrained "match-all" cases that cause the most headaches. This is part of a patch series that in total reduces the regex engine's runtime by about a factor of four on a large corpus of real-world regexes. Patch by me, reviewed by Joel Jacobson Discussion: https://postgr.es/m/1340281.1613018383@sss.pgh.pa.us
269 lines
7.2 KiB
C
269 lines
7.2 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* regprefix.c
|
|
* Extract a common prefix, if any, from a compiled regex.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 2012-2021, 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 lookaround
|
|
* 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;
|
|
|
|
/* matchall NFAs never have a fixed prefix */
|
|
if (cnfa->flags & MATCHALL)
|
|
return REG_NOMATCH;
|
|
|
|
/*
|
|
* 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 "c" 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 can ignore BOS/BOL arcs */
|
|
if (ca->co == cnfa->bos[0] || ca->co == cnfa->bos[1])
|
|
continue;
|
|
|
|
/*
|
|
* ... but EOS/EOL arcs terminate the search, as do RAINBOW arcs
|
|
* and LACONs
|
|
*/
|
|
if (ca->co == cnfa->eos[0] || ca->co == cnfa->eos[1] ||
|
|
ca->co == RAINBOW || ca->co >= cnfa->ncolors)
|
|
{
|
|
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].nschrs != 1)
|
|
break;
|
|
/* Must not have any high-color-map entries */
|
|
if (cm->cd[thiscolor].nuchrs != 0)
|
|
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;
|
|
}
|