]> granicus.if.org Git - postgresql/commitdiff
Fix contrib/pg_trgm's extraction of trigrams from regular expressions.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 22 Feb 2017 20:04:07 +0000 (15:04 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 22 Feb 2017 20:04:26 +0000 (15:04 -0500)
The logic for removing excess trigrams from the result was faulty.
It intends to avoid merging the initial and final states of the NFA,
which is necessary, but in testing whether removal of a specific trigram
would cause that, it failed to consider the combined effects of all the
state merges that that trigram's removal would cause.  This could result
in a broken final graph that would never match anything, leading to GIN
or GiST indexscans not finding anything.

To fix, add a "tentParent" field that is used only within this loop,
and set it to show state merges that we are tentatively going to do.
While examining a particular arc, we must chase up through tentParent
links as well as regular parent links (the former can only appear atop
the latter), and we must account for state init/fin flag merges that
haven't actually been done yet.

To simplify the latter, combine the separate init and fin bool fields
into a bitmap flags field.  I also chose to get rid of the "children"
state list, which seems entirely inessential.

Per bug #14563 from Alexey Isayko, which the added test cases are based on.
Back-patch to 9.3 where this code was added.

Report: https://postgr.es/m/20170222111446.1256.67547@wrigleys.postgresql.org
Discussion: https://postgr.es/m/8816.1487787594@sss.pgh.pa.us

contrib/pg_trgm/expected/pg_trgm.out
contrib/pg_trgm/sql/pg_trgm.sql
contrib/pg_trgm/trgm_regexp.c

index f646cbf90af215509bb83176ba9e9073c21685df..df6c1ff5139fc29cb34d9d6f17f001634314e9c5 100644 (file)
@@ -1202,6 +1202,12 @@ select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988
       0.5 | qwertyu0987
 (2 rows)
 
+select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}';
+ count 
+-------
+  1000
+(1 row)
+
 create index trgm_idx on test_trgm using gist (t gist_trgm_ops);
 set enable_seqscan=off;
 select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t;
@@ -2346,6 +2352,12 @@ select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988
       0.5 | qwertyu0987
 (2 rows)
 
+select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}';
+ count 
+-------
+  1000
+(1 row)
+
 drop index trgm_idx;
 create index trgm_idx on test_trgm using gin (t gin_trgm_ops);
 set enable_seqscan=off;
@@ -3475,6 +3487,12 @@ select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu198
  qwertyu0988 | 0.333333
 (1 row)
 
+select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}';
+ count 
+-------
+  1000
+(1 row)
+
 create table test2(t text COLLATE "C");
 insert into test2 values ('abcdef');
 insert into test2 values ('quark');
index 34d0614edbca3cc97c0e282ca887dc3d32ec898d..806f3eb7049c1b18c8506f62be720c9caf467af7 100644 (file)
@@ -26,6 +26,7 @@ select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu098
 select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t;
 select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t;
 select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2;
+select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}';
 
 create index trgm_idx on test_trgm using gist (t gist_trgm_ops);
 set enable_seqscan=off;
@@ -36,6 +37,7 @@ select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu198
 explain (costs off)
 select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2;
 select t <-> 'q0987wertyu0988', t from test_trgm order by t <-> 'q0987wertyu0988' limit 2;
+select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}';
 
 drop index trgm_idx;
 create index trgm_idx on test_trgm using gin (t gin_trgm_ops);
@@ -44,6 +46,7 @@ set enable_seqscan=off;
 select t,similarity(t,'qwertyu0988') as sml from test_trgm where t % 'qwertyu0988' order by sml desc, t;
 select t,similarity(t,'gwertyu0988') as sml from test_trgm where t % 'gwertyu0988' order by sml desc, t;
 select t,similarity(t,'gwertyu1988') as sml from test_trgm where t % 'gwertyu1988' order by sml desc, t;
+select count(*) from test_trgm where t ~ '[qwerty]{2}-?[qwerty]{2}';
 
 create table test2(t text COLLATE "C");
 insert into test2 values ('abcdef');
index 9b8ff1062d3e9610bf84ca622337e02b6cd21bf4..0e9d5a62e44f9e8369c1ee298606ce52a590f675 100644 (file)
@@ -318,21 +318,22 @@ typedef struct
  *     arcs     - outgoing arcs of this state (List of TrgmArc)
  *     enterKeys - enter keys reachable from this state without reading any
  *                        predictable trigram (List of TrgmStateKey)
- *     fin              - flag indicating this state is final
- *     init     - flag indicating this state is initial
+ *     flags    - flag bits
  *     parent   - parent state, if this state has been merged into another
- *     children - child states (states that have been merged into this one)
+ *     tentParent - planned parent state, if considering a merge
  *     number   - number of this state (used at the packaging stage)
  */
+#define TSTATE_INIT            0x01    /* flag indicating this state is initial */
+#define TSTATE_FIN             0x02    /* flag indicating this state is final */
+
 typedef struct TrgmState
 {
        TrgmStateKey stateKey;          /* hashtable key: must be first field */
        List       *arcs;
        List       *enterKeys;
-       bool            fin;
-       bool            init;
+       int                     flags;
        struct TrgmState *parent;
-       List       *children;
+       struct TrgmState *tentParent;
        int                     number;
 } TrgmState;
 
@@ -599,7 +600,7 @@ createTrgmNFAInternal(regex_t *regex, TrgmPackedGraph **graph,
         * get from the initial state to the final state without reading any
         * predictable trigram.
         */
-       if (trgmNFA.initState->fin)
+       if (trgmNFA.initState->flags & TSTATE_FIN)
                return NULL;
 
        /*
@@ -925,7 +926,7 @@ transformGraph(TrgmNFA *trgmNFA)
        initkey.nstate = pg_reg_getinitialstate(trgmNFA->regex);
 
        initstate = getState(trgmNFA, &initkey);
-       initstate->init = true;
+       initstate->flags |= TSTATE_INIT;
        trgmNFA->initState = initstate;
 
        /*
@@ -943,7 +944,7 @@ transformGraph(TrgmNFA *trgmNFA)
                 * actual processing.
                 */
                if (trgmNFA->overflowed)
-                       state->fin = true;
+                       state->flags |= TSTATE_FIN;
                else
                        processState(trgmNFA, state);
 
@@ -968,7 +969,7 @@ processState(TrgmNFA *trgmNFA, TrgmState *state)
         * queue is empty.  But we can quit if the state gets marked final.
         */
        addKey(trgmNFA, state, &state->stateKey);
-       while (trgmNFA->keysQueue != NIL && !state->fin)
+       while (trgmNFA->keysQueue != NIL && !(state->flags & TSTATE_FIN))
        {
                TrgmStateKey *key = (TrgmStateKey *) linitial(trgmNFA->keysQueue);
 
@@ -980,7 +981,7 @@ processState(TrgmNFA *trgmNFA, TrgmState *state)
         * Add outgoing arcs only if state isn't final (we have no interest in
         * outgoing arcs if we already match)
         */
-       if (!state->fin)
+       if (!(state->flags & TSTATE_FIN))
                addArcs(trgmNFA, state);
 }
 
@@ -989,7 +990,7 @@ processState(TrgmNFA *trgmNFA, TrgmState *state)
  * whether this should result in any further enter keys being added.
  * If so, add those keys to keysQueue so that processState will handle them.
  *
- * If the enter key is for the NFA's final state, set state->fin = TRUE.
+ * If the enter key is for the NFA's final state, mark state as TSTATE_FIN.
  * This situation means that we can reach the final state from this expanded
  * state without reading any predictable trigram, so we must consider this
  * state as an accepting one.
@@ -1059,7 +1060,7 @@ addKey(TrgmNFA *trgmNFA, TrgmState *state, TrgmStateKey *key)
        /* If state is now known final, mark it and we're done */
        if (key->nstate == pg_reg_getfinalstate(trgmNFA->regex))
        {
-               state->fin = true;
+               state->flags |= TSTATE_FIN;
                return;
        }
 
@@ -1385,10 +1386,9 @@ getState(TrgmNFA *trgmNFA, TrgmStateKey *key)
                /* New state: initialize and queue it */
                state->arcs = NIL;
                state->enterKeys = NIL;
-               state->init = false;
-               state->fin = false;
+               state->flags = 0;
                state->parent = NULL;
-               state->children = NIL;
+               state->tentParent = NULL;
                state->number = -1;
 
                trgmNFA->queue = lappend(trgmNFA->queue, state);
@@ -1582,6 +1582,8 @@ selectColorTrigrams(TrgmNFA *trgmNFA)
                        TrgmArcInfo *arcInfo = (TrgmArcInfo *) lfirst(cell);
                        TrgmState  *source = arcInfo->source,
                                           *target = arcInfo->target;
+                       int                     source_flags,
+                                               target_flags;
 
                        /* examine parent states, if any merging has already happened */
                        while (source->parent)
@@ -1589,13 +1591,51 @@ selectColorTrigrams(TrgmNFA *trgmNFA)
                        while (target->parent)
                                target = target->parent;
 
-                       if ((source->init || target->init) &&
-                               (source->fin || target->fin))
+                       /* we must also consider merges we are planning right now */
+                       source_flags = source->flags;
+                       while (source->tentParent)
+                       {
+                               source = source->tentParent;
+                               source_flags |= source->flags;
+                       }
+                       target_flags = target->flags;
+                       while (target->tentParent)
+                       {
+                               target = target->tentParent;
+                               target_flags |= target->flags;
+                       }
+
+                       /* would fully-merged state have both INIT and FIN set? */
+                       if (((source_flags | target_flags) & (TSTATE_INIT | TSTATE_FIN)) ==
+                               (TSTATE_INIT | TSTATE_FIN))
                        {
                                canRemove = false;
                                break;
                        }
+
+                       /* ok so far, so remember planned merge */
+                       if (source != target)
+                               target->tentParent = source;
                }
+
+               /* We must clear all the tentParent fields before continuing */
+               foreach(cell, trgmInfo->arcs)
+               {
+                       TrgmArcInfo *arcInfo = (TrgmArcInfo *) lfirst(cell);
+                       TrgmState  *target = arcInfo->target;
+                       TrgmState  *ttarget;
+
+                       while (target->parent)
+                               target = target->parent;
+
+                       while ((ttarget = target->tentParent) != NULL)
+                       {
+                               target->tentParent = NULL;
+                               target = ttarget;
+                       }
+               }
+
+               /* Now, move on if we can't drop this trigram */
                if (!canRemove)
                        continue;
 
@@ -1611,7 +1651,12 @@ selectColorTrigrams(TrgmNFA *trgmNFA)
                        while (target->parent)
                                target = target->parent;
                        if (source != target)
+                       {
                                mergeStates(source, target);
+                               /* Assert we didn't merge initial and final states */
+                               Assert((source->flags & (TSTATE_INIT | TSTATE_FIN)) !=
+                                          (TSTATE_INIT | TSTATE_FIN));
+                       }
                }
 
                /* Mark trigram unexpanded, and update totals */
@@ -1754,27 +1799,15 @@ fillTrgm(trgm *ptrgm, trgm_mb_char s[3])
 static void
 mergeStates(TrgmState *state1, TrgmState *state2)
 {
-       ListCell   *cell;
-
        Assert(state1 != state2);
        Assert(!state1->parent);
        Assert(!state2->parent);
 
-       /* state1 absorbs state2's init/fin flags */
-       state1->init |= state2->init;
-       state1->fin |= state2->fin;
+       /* state1 absorbs state2's flags */
+       state1->flags |= state2->flags;
 
-       /* state2, and all its children, become children of state1 */
-       foreach(cell, state2->children)
-       {
-               TrgmState  *state = (TrgmState *) lfirst(cell);
-
-               state->parent = state1;
-       }
+       /* state2, and indirectly all its children, become children of state1 */
        state2->parent = state1;
-       state1->children = list_concat(state1->children, state2->children);
-       state1->children = lappend(state1->children, state2);
-       state2->children = NIL;
 }
 
 /*
@@ -1843,9 +1876,9 @@ packGraph(TrgmNFA *trgmNFA, MemoryContext rcontext)
 
                if (state->number < 0)
                {
-                       if (state->init)
+                       if (state->flags & TSTATE_INIT)
                                state->number = 0;
-                       else if (state->fin)
+                       else if (state->flags & TSTATE_FIN)
                                state->number = 1;
                        else
                        {
@@ -2109,9 +2142,9 @@ printTrgmNFA(TrgmNFA *trgmNFA)
                ListCell   *cell;
 
                appendStringInfo(&buf, "s%p", (void *) state);
-               if (state->fin)
+               if (state->flags & TSTATE_FIN)
                        appendStringInfoString(&buf, " [shape = doublecircle]");
-               if (state->init)
+               if (state->flags & TSTATE_INIT)
                        initstate = state;
                appendStringInfo(&buf, " [label = \"%d\"]", state->stateKey.nstate);
                appendStringInfoString(&buf, ";\n");