Re: BUG #16388: Different results when bitmap scan enabled/disabled - Mailing list pgsql-bugs

From Tom Lane
Subject Re: BUG #16388: Different results when bitmap scan enabled/disabled
Date
Msg-id 16254.1587852873@sss.pgh.pa.us
Whole thread Raw
In response to Re: BUG #16388: Different results when bitmap scan enabled/disabled  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-bugs
I wrote:
> PG Bug reporting form <noreply@postgresql.org> writes:
>> When Postgres uses a bitmap heap scan to evaluate a tsquery that includes !,
>> it is giving me different (and incorrect) results compared to when it
>> performs a seqscan.
>> Can anybody shed some light on this?

> It's a bug, without a doubt.

Here's a proposed patch that fixes it as I suggested yesterday, by
converting TS_execute and TS_phrase_execute to use honest ternary
results instead of trying to get by with bool.  There's also a minor
bug fix in TS_execute_ternary; that doesn't show up with the one-row
example Charles posted, but it does show up with larger indexes.
I had worried that it might be necessary to change the exposed API
of TS_execute(), but in the event it seems we need not.

Possibly the most controversial aspect of this patch is that I changed
the test data file tsearch.data, by attaching lexeme position info
to a couple dozen rows.  Without that, the test_tsvector test table is
next to useless for testing phrase search, since it has no rows with
position data.  This does result in minor changes in one existing test
case for ts_stat(), but I see little wrong with that.

Also, the new test cases in tstypes.sql are mostly to ensure that
this patch didn't cause any changes in the behavior of phrase match;
they all pass on the existing code.

(Charles, if you want to test this locally to verify it fixes your
problem, the code changes should apply to v11; though there are a
couple of hunks in the regression test files that will not apply
because they change test stanzas that are new in HEAD.)

            regards, tom lane

diff --git a/src/backend/utils/adt/tsginidx.c b/src/backend/utils/adt/tsginidx.c
index c96f1f7..2d65616 100644
--- a/src/backend/utils/adt/tsginidx.c
+++ b/src/backend/utils/adt/tsginidx.c
@@ -210,6 +210,11 @@ checkcondition_gin(void *checkval, QueryOperand *val, ExecPhraseData *data)

 /*
  * Evaluate tsquery boolean expression using ternary logic.
+ *
+ * Note: the reason we can't use TS_execute() for this is that its API
+ * for the checkcondition callback doesn't allow a MAYBE result to be
+ * returned, but we might have MAYBEs in the gcv->check array.
+ * Perhaps we should change that API.
  */
 static GinTernaryValue
 TS_execute_ternary(GinChkVal *gcv, QueryItem *curitem, bool in_phrase)
@@ -230,9 +235,19 @@ TS_execute_ternary(GinChkVal *gcv, QueryItem *curitem, bool in_phrase)
     switch (curitem->qoperator.oper)
     {
         case OP_NOT:
-            /* In phrase search, always return MAYBE since we lack positions */
+
+            /*
+             * Below a phrase search, force NOT's result to MAYBE.  We cannot
+             * invert a TRUE result from the subexpression to FALSE, since
+             * TRUE only says that the subexpression matches somewhere, not
+             * that it matches everywhere, so there might be positions where
+             * the NOT will match.  We could invert FALSE to TRUE, but there's
+             * little point in distinguishing TRUE from MAYBE, since a recheck
+             * will have been forced already.
+             */
             if (in_phrase)
                 return GIN_MAYBE;
+
             result = TS_execute_ternary(gcv, curitem + 1, in_phrase);
             if (result == GIN_MAYBE)
                 return result;
@@ -242,7 +257,8 @@ TS_execute_ternary(GinChkVal *gcv, QueryItem *curitem, bool in_phrase)

             /*
              * GIN doesn't contain any information about positions, so treat
-             * OP_PHRASE as OP_AND with recheck requirement
+             * OP_PHRASE as OP_AND with recheck requirement, and always
+             * reporting MAYBE not TRUE.
              */
             *(gcv->need_recheck) = true;
             /* Pass down in_phrase == true in case there's a NOT below */
@@ -258,7 +274,8 @@ TS_execute_ternary(GinChkVal *gcv, QueryItem *curitem, bool in_phrase)
             val2 = TS_execute_ternary(gcv, curitem + 1, in_phrase);
             if (val2 == GIN_FALSE)
                 return GIN_FALSE;
-            if (val1 == GIN_TRUE && val2 == GIN_TRUE)
+            if (val1 == GIN_TRUE && val2 == GIN_TRUE &&
+                curitem->qoperator.oper != OP_PHRASE)
                 return GIN_TRUE;
             else
                 return GIN_MAYBE;
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index 2452587..96d8031 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -67,8 +67,21 @@ typedef struct
     StatEntry  *root;
 } TSVectorStat;

-static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column);
+/* TS_execute requires ternary logic to handle NOT with phrase matches */
+typedef enum
+{
+    TS_NO,                        /* definitely no match */
+    TS_YES,                        /* definitely does match */
+    TS_MAYBE                    /* can't verify match for lack of pos data */
+} TSTernaryValue;
+
+
+static TSTernaryValue TS_execute_recurse(QueryItem *curitem, void *arg,
+                                         uint32 flags,
+                                         TSExecuteCallback chkcond);
 static int    tsvector_bsearch(const TSVector tsv, char *lexeme, int lexeme_len);
+static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column);
+

 /*
  * Order: haspos, len, word, for all positions (pos, weight)
@@ -1374,14 +1387,17 @@ checkcondition_str(void *checkval, QueryOperand *val, ExecPhraseData *data)
  * Loffset and Roffset should not be negative, else we risk trying to output
  * negative positions, which won't fit into WordEntryPos.
  *
- * Returns true if any positions were emitted to *data; or if data is NULL,
- * returns true if any positions would have been emitted.
+ * The result is boolean (TS_YES or TS_NO), but for the caller's convenience
+ * we return it as TSTernaryValue.
+ *
+ * Returns TS_YES if any positions were emitted to *data; or if data is NULL,
+ * returns TS_YES if any positions would have been emitted.
  */
 #define TSPO_L_ONLY        0x01    /* emit positions appearing only in L */
 #define TSPO_R_ONLY        0x02    /* emit positions appearing only in R */
 #define TSPO_BOTH        0x04    /* emit positions appearing in both L&R */

-static bool
+static TSTernaryValue
 TS_phrase_output(ExecPhraseData *data,
                  ExecPhraseData *Ldata,
                  ExecPhraseData *Rdata,
@@ -1464,10 +1480,10 @@ TS_phrase_output(ExecPhraseData *data,
             else
             {
                 /*
-                 * Exact positions not needed, so return true as soon as we
+                 * Exact positions not needed, so return TS_YES as soon as we
                  * know there is at least one.
                  */
-                return true;
+                return TS_YES;
             }
         }
     }
@@ -1476,9 +1492,9 @@ TS_phrase_output(ExecPhraseData *data,
     {
         /* Let's assert we didn't overrun the array */
         Assert(data->npos <= max_npos);
-        return true;
+        return TS_YES;
     }
-    return false;
+    return TS_NO;
 }

 /*
@@ -1496,17 +1512,16 @@ TS_phrase_output(ExecPhraseData *data,
  * This is OK because an outside call always starts from an OP_PHRASE node.
  *
  * The detailed semantics of the match data, given that the function returned
- * "true" (successful match, or possible match), are:
+ * TS_YES (successful match), are:
  *
  * npos > 0, negate = false:
  *     query is matched at specified position(s) (and only those positions)
  * npos > 0, negate = true:
  *     query is matched at all positions *except* specified position(s)
- * npos = 0, negate = false:
- *     query is possibly matched, matching position(s) are unknown
- *     (this should only be returned when TS_EXEC_PHRASE_NO_POS flag is set)
  * npos = 0, negate = true:
  *     query is matched at all positions
+ * npos = 0, negate = false:
+ *     disallowed (this should result in TS_NO or TS_MAYBE, as appropriate)
  *
  * Successful matches also return a "width" value which is the match width in
  * lexemes, less one.  Hence, "width" is zero for simple one-lexeme matches,
@@ -1515,18 +1530,22 @@ TS_phrase_output(ExecPhraseData *data,
  * the starts.  (This unintuitive rule is needed to avoid possibly generating
  * negative positions, which wouldn't fit into the WordEntryPos arrays.)
  *
- * When the function returns "false" (no match), it must return npos = 0,
+ * If the TSExecuteCallback function reports that an operand is present
+ * but fails to provide position(s) for it, we will return TS_MAYBE when
+ * it is possible but not certain that the query is matched.
+ *
+ * When the function returns TS_NO or TS_MAYBE, it must return npos = 0,
  * negate = false (which is the state initialized by the caller); but the
  * "width" output in such cases is undefined.
  */
-static bool
+static TSTernaryValue
 TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
                   TSExecuteCallback chkcond,
                   ExecPhraseData *data)
 {
     ExecPhraseData Ldata,
                 Rdata;
-    bool        lmatch,
+    TSTernaryValue lmatch,
                 rmatch;
     int            Loffset,
                 Roffset,
@@ -1536,67 +1555,80 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
     check_stack_depth();

     if (curitem->type == QI_VAL)
-        return chkcond(arg, (QueryOperand *) curitem, data);
+    {
+        if (!chkcond(arg, (QueryOperand *) curitem, data))
+            return TS_NO;
+        if (data->npos > 0 || data->negate)
+            return TS_YES;
+        /* If we have no position data, we must return TS_MAYBE */
+        return TS_MAYBE;
+    }

     switch (curitem->qoperator.oper)
     {
         case OP_NOT:

             /*
-             * Because a "true" result with no specific positions is taken as
-             * uncertain, we need no special care here for !TS_EXEC_CALC_NOT.
-             * If it's a false positive, the right things happen anyway.
-             *
-             * Also, we need not touch data->width, since a NOT operation does
-             * not change the match width.
+             * We need not touch data->width, since a NOT operation does not
+             * change the match width.
              */
-            if (TS_phrase_execute(curitem + 1, arg, flags, chkcond, data))
+            if (!(flags & TS_EXEC_CALC_NOT))
             {
-                if (data->npos > 0)
-                {
-                    /* we have some positions, invert negate flag */
-                    data->negate = !data->negate;
-                    return true;
-                }
-                else if (data->negate)
-                {
-                    /* change "match everywhere" to "match nowhere" */
-                    data->negate = false;
-                    return false;
-                }
-                /* match positions are, and remain, uncertain */
-                return true;
-            }
-            else
-            {
-                /* change "match nowhere" to "match everywhere" */
+                /* without CALC_NOT, report NOT as "match everywhere" */
                 Assert(data->npos == 0 && !data->negate);
                 data->negate = true;
-                return true;
+                return TS_YES;
             }
+            switch (TS_phrase_execute(curitem + 1, arg, flags, chkcond, data))
+            {
+                case TS_NO:
+                    /* change "match nowhere" to "match everywhere" */
+                    Assert(data->npos == 0 && !data->negate);
+                    data->negate = true;
+                    return TS_YES;
+                case TS_YES:
+                    if (data->npos > 0)
+                    {
+                        /* we have some positions, invert negate flag */
+                        data->negate = !data->negate;
+                        return TS_YES;
+                    }
+                    else if (data->negate)
+                    {
+                        /* change "match everywhere" to "match nowhere" */
+                        data->negate = false;
+                        return TS_NO;
+                    }
+                    /* Should not get here if result was TS_YES */
+                    Assert(false);
+                    break;
+                case TS_MAYBE:
+                    /* match positions are, and remain, uncertain */
+                    return TS_MAYBE;
+            }
+            break;

         case OP_PHRASE:
         case OP_AND:
             memset(&Ldata, 0, sizeof(Ldata));
             memset(&Rdata, 0, sizeof(Rdata));

-            if (!TS_phrase_execute(curitem + curitem->qoperator.left,
-                                   arg, flags, chkcond, &Ldata))
-                return false;
+            lmatch = TS_phrase_execute(curitem + curitem->qoperator.left,
+                                       arg, flags, chkcond, &Ldata);
+            if (lmatch == TS_NO)
+                return TS_NO;

-            if (!TS_phrase_execute(curitem + 1,
-                                   arg, flags, chkcond, &Rdata))
-                return false;
+            rmatch = TS_phrase_execute(curitem + 1,
+                                       arg, flags, chkcond, &Rdata);
+            if (rmatch == TS_NO)
+                return TS_NO;

             /*
              * If either operand has no position information, then we can't
-             * return position data, only a "possible match" result. "Possible
-             * match" answers are only wanted when TS_EXEC_PHRASE_NO_POS flag
-             * is set, otherwise return false.
+             * return reliable position data, only a MAYBE result.
              */
-            if ((Ldata.npos == 0 && !Ldata.negate) ||
-                (Rdata.npos == 0 && !Rdata.negate))
-                return (flags & TS_EXEC_PHRASE_NO_POS) ? true : false;
+            if (lmatch == TS_MAYBE || rmatch == TS_MAYBE)
+                return TS_MAYBE;

             if (curitem->qoperator.oper == OP_PHRASE)
             {
@@ -1632,7 +1664,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
                                         Ldata.npos + Rdata.npos);
                 if (data)
                     data->negate = true;
-                return true;
+                return TS_YES;
             }
             else if (Ldata.negate)
             {
@@ -1668,27 +1700,24 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
             rmatch = TS_phrase_execute(curitem + 1,
                                        arg, flags, chkcond, &Rdata);

-            if (!lmatch && !rmatch)
-                return false;
+            if (lmatch == TS_NO && rmatch == TS_NO)
+                return TS_NO;

             /*
-             * If a valid operand has no position information, then we can't
-             * return position data, only a "possible match" result. "Possible
-             * match" answers are only wanted when TS_EXEC_PHRASE_NO_POS flag
-             * is set, otherwise return false.
+             * If either operand has no position information, then we can't
+             * return reliable position data, only a MAYBE result.
              */
-            if ((lmatch && Ldata.npos == 0 && !Ldata.negate) ||
-                (rmatch && Rdata.npos == 0 && !Rdata.negate))
-                return (flags & TS_EXEC_PHRASE_NO_POS) ? true : false;
+            if (lmatch == TS_MAYBE || rmatch == TS_MAYBE)
+                return TS_MAYBE;

             /*
              * Cope with undefined output width from failed submatch.  (This
              * takes less code than trying to ensure that all failure returns
              * set data->width to zero.)
              */
-            if (!lmatch)
+            if (lmatch == TS_NO)
                 Ldata.width = 0;
-            if (!rmatch)
+            if (rmatch == TS_NO)
                 Rdata.width = 0;

             /*
@@ -1710,7 +1739,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
                                         Loffset, Roffset,
                                         Min(Ldata.npos, Rdata.npos));
                 data->negate = true;
-                return true;
+                return TS_YES;
             }
             else if (Ldata.negate)
             {
@@ -1720,7 +1749,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
                                         Loffset, Roffset,
                                         Ldata.npos);
                 data->negate = true;
-                return true;
+                return TS_YES;
             }
             else if (Rdata.negate)
             {
@@ -1730,7 +1759,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
                                         Loffset, Roffset,
                                         Rdata.npos);
                 data->negate = true;
-                return true;
+                return TS_YES;
             }
             else
             {
@@ -1746,7 +1775,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
     }

     /* not reachable, but keep compiler quiet */
-    return false;
+    return TS_NO;
 }


@@ -1757,51 +1786,113 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
  * arg: opaque value to pass through to callback function
  * flags: bitmask of flag bits shown in ts_utils.h
  * chkcond: callback function to check whether a primitive value is present
- *
- * The logic here deals only with operators above any phrase operator, for
- * which we do not need to worry about lexeme positions.  As soon as we hit an
- * OP_PHRASE operator, we pass it off to TS_phrase_execute which does worry.
  */
 bool
 TS_execute(QueryItem *curitem, void *arg, uint32 flags,
            TSExecuteCallback chkcond)
 {
+    /*
+     * If we get TS_MAYBE from the recursion, return true.  We could only see
+     * that result if the caller passed TS_EXEC_PHRASE_NO_POS, so there's no
+     * need to check again.
+     */
+    return TS_execute_recurse(curitem, arg, flags, chkcond) != TS_NO;
+}
+
+/*
+ * TS_execute recursion for operators above any phrase operator.  Here we do
+ * not need to worry about lexeme positions.  As soon as we hit an OP_PHRASE
+ * operator, we pass it off to TS_phrase_execute which does worry.
+ */
+static TSTernaryValue
+TS_execute_recurse(QueryItem *curitem, void *arg, uint32 flags,
+                   TSExecuteCallback chkcond)
+{
+    TSTernaryValue lmatch;
+
     /* since this function recurses, it could be driven to stack overflow */
     check_stack_depth();

     if (curitem->type == QI_VAL)
         return chkcond(arg, (QueryOperand *) curitem,
-                       NULL /* we don't need position info */ );
+                       NULL /* don't need position info */ ) ? TS_YES : TS_NO;

     switch (curitem->qoperator.oper)
     {
         case OP_NOT:
-            if (flags & TS_EXEC_CALC_NOT)
-                return !TS_execute(curitem + 1, arg, flags, chkcond);
-            else
-                return true;
+            if (!(flags & TS_EXEC_CALC_NOT))
+                return TS_YES;
+            switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond))
+            {
+                case TS_NO:
+                    return TS_YES;
+                case TS_YES:
+                    return TS_NO;
+                case TS_MAYBE:
+                    return TS_MAYBE;
+            }
+            break;

         case OP_AND:
-            if (TS_execute(curitem + curitem->qoperator.left, arg, flags, chkcond))
-                return TS_execute(curitem + 1, arg, flags, chkcond);
-            else
-                return false;
+            lmatch = TS_execute_recurse(curitem + curitem->qoperator.left, arg,
+                                        flags, chkcond);
+            if (lmatch == TS_NO)
+                return TS_NO;
+            switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond))
+            {
+                case TS_NO:
+                    return TS_NO;
+                case TS_YES:
+                    return lmatch;
+                case TS_MAYBE:
+                    return TS_MAYBE;
+            }
+            break;

         case OP_OR:
-            if (TS_execute(curitem + curitem->qoperator.left, arg, flags, chkcond))
-                return true;
-            else
-                return TS_execute(curitem + 1, arg, flags, chkcond);
+            lmatch = TS_execute_recurse(curitem + curitem->qoperator.left, arg,
+                                        flags, chkcond);
+            if (lmatch == TS_YES)
+                return TS_YES;
+            switch (TS_execute_recurse(curitem + 1, arg, flags, chkcond))
+            {
+                case TS_NO:
+                    return lmatch;
+                case TS_YES:
+                    return TS_YES;
+                case TS_MAYBE:
+                    return TS_MAYBE;
+            }
+            break;

         case OP_PHRASE:
-            return TS_phrase_execute(curitem, arg, flags, chkcond, NULL);
+
+            /*
+             * If we get a MAYBE result, and the caller doesn't want that,
+             * convert it to NO.  It would be more consistent, perhaps, to
+             * return the result of TS_phrase_execute() verbatim and then
+             * convert MAYBE results at the top of the recursion.  But
+             * converting at the topmost phrase operator gives results that
+             * are bug-compatible with the old implementation, so do it like
+             * this for now.
+             */
+            switch (TS_phrase_execute(curitem, arg, flags, chkcond, NULL))
+            {
+                case TS_NO:
+                    return TS_NO;
+                case TS_YES:
+                    return TS_YES;
+                case TS_MAYBE:
+                    return (flags & TS_EXEC_PHRASE_NO_POS) ? TS_MAYBE : TS_NO;
+            }
+            break;

         default:
             elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
     }

     /* not reachable, but keep compiler quiet */
-    return false;
+    return TS_NO;
 }

 /*
diff --git a/src/test/regress/data/tsearch.data b/src/test/regress/data/tsearch.data
index 29a26f2..a60f39b 100644
--- a/src/test/regress/data/tsearch.data
+++ b/src/test/regress/data/tsearch.data
@@ -486,23 +486,23 @@
 \n    fu uq co qk jl cg ld lg wo vr gc bd rj r3 yd rz iu ew d6 io to sh y7 jp db dn qn qm si xg qr ls jo lr wy rk wn
m7qu bb es op qp ru en ta e3 in hy dy hl vc gc gt jw ke 2t wh rk lj hg oy e6 yo ev em fz rw pq re dg qk ku oi qz k4 qv
lirq n8 ec 4d yb wb e1 iw id o6 ir do ux pi ep 
 \n    a2 wu jd ef dc mn 5e qp pl xd ag ay
 \n    yv o9 al a5 uq qg jw pi z2 jt cd q5 3m zl ez vu rg jl rz yf ix sj fp d0 tq ff ha hs zw om ni m0 xg c8 a7 ki qw
ccei xg j3 tt tu il p6 ix tp tx ib sp hg p0 fc pj su qu jv sj lk qp ws qs gm 1x mx lv wj qu l1 dt wh wv un aq fg rg e6
uoar ie up it sw tx f6 o2 h3 qc qa ho vj u3 kd zy n6 my ww vc lr em w2 se rt o4 yc a1 te 
-\n    qs dv pa ty iz uw qh jj z3 tn jc eg e4 qq w7 ut r3 uu kb up g1 fo iv if fd gd sc qm qe xg ia wb he ky hu tv rw
qurr es p3 ue s2 as i0 dt qt hz jm j0 gy ci fi hw nv ea kk vf rx 68 ti rp wl oi vp at om io uk pt qh qj dl cf lr cx wq
kuki w2 yh af ul sp yc it 
-\n    ub yb dc ty gm dm go nv we ql by la o1 ju o3 jx fm aj wa rg e4 vi a6 r4 xo tz oe ip pv dk tq a0 tf fg tg i4 pz
sdry ky mg hy g7 eu qy yi rw qp eg yw hw sm uc i7 dw fx s3 sf zo m9 xs vn rf ci nz kr qt 9y pj lk ee pz ef rk e0 fx uf
azfc qg jr oy lq cg qp um ad wc zn bw n6 my xr mp tu en o3 iq ir ro 
-\n    da a3 d1 jr dz ca ql nu q3 cf o6 nr mt lk yr rs lp w7 a5 pj ys ym r8 ey to fs dz im ih sw qx qv zn gl j1 xe lc
zcvw 6a mh b9 qt rm re oo qp p4 tk ix p7 og tz yr sp aa hk ih lx qd mx 4n kk vf el oo td ae yo fj uf pr hl qj qk wr qc
qvkf yz my wq hn zs dr ee u8 rv et ru ie ag tw 
-\n    gt ph z0 zl mu ui av zm om ui vh qr he qr es fl ws w6 nc ra rk kp ol wm yu it
-\n    dd df jq jd ux ql el 3r ya uu iu ee eh g3 sj us ib pp qc jv hd bh zt uo d8 b3 xu bc rq te uh ex tt eb il qu pc
gesj qp ih xf 3r gr yh qx tu wl wn sz up ay it ab jt qz v7 wn li za 6o w3 fn yk eu ie gz ro 
-\n    yv o9 qf eg eh mh jh rh r3 rv ix y3 a0 sr qc qq qr wr qe bx ki m8 mk qi lm uk eb ai ur e2 xd nc ca eo mb ed uv
rsup ya of hn lw wz qz et qh wm zr rc o3 r2 
-\n    ss qg qj ph qk q2 cs z4 bi qc cj q8 qn w7 rj ys ea r4 uy om rc ii fp sj ej yz el qx qv zn gk q9 hs m2 ii d7 nk
c8j4 qq dl gg pp ei qo yc od fo eh fp ta hy ok tv uu us dp qf sb zg ks sg n3 wh x2 nr cs wk kh wk wf ew 7h 7k oy t6 gi
rgyp s9 ya e9 pb tc dt dh hk h2 pt qh h7 wz n1 qv kd pm cc xr kp yk tm ge 
-\n    gr qa ft tt gn qs pw pu ca ph ls cg cn zf bz q7 z0 c3 qn gv w7 rg ut e8 ii er ip sg y3 oy ek ht gd qx g0 qv db
sujn qq lz uu jo ru an wi kn sq nh qr qy yx eo qi xz y9 ru pd au p7 dq he ut ok fd jz ui hc j9 l3 hb xd jq gy kh wf xs
slrs aq ez y4 ts um yi e0 gh dk py v5 qk ql ko jq wc nk v0 wb qv br iu wm 6p sr gk yp pu 
-\n    o0 pa pf z3 jt jy z7 cm ne w8 yz fd fg zn qq ll vg wr wb ia xx yj ty eh e1 so ts tc s4 i0 tn wo wp wa op va wk
x3vg qx rs sn au f3 tz sq hn rr o2 fv un k2 vj ey dj 
-\n    iy ra ij ty a4 un rf qg dm jr kj uv we cv gk wy z8 oc wp mo jl mz ev ch rv tu ax y2 g3 oy y6 im uq qv hp qb hd
iylp nk w2 bb ho ep tr os en sm p8 p9 hy ss ui gm qi oo vn ae qd w6 ps dn wd wg ro mr yt ol oz rg s7 u5 tl yd rr ax f0
cqku qx ze n4 wn kt ca jy bg yc zs yw rz w1 eu rm r1 
-\n    hv fu ca q8 mt la r3 pl yh to sy yh tv x4 tg yp ov wn ze sp
-\n    o9 qa az gm qd pw hq pd ga qj cd q3 jk pd du c2 zk xf t8 eq om es rc ua y3 pu ig qx se qv db st qn ii lx qe wt
xknx ku br qe qr qt rm eu xf xb rn qu qi ep qo rr ex xk p5 ym fi uq to ux ix ai hj gn zi oq qf kd wf xn kr w8 rl kk mq
rprf u1 s7 oa fh e7 yp e0 pr ql sx ck ag kg kt mp eb rl em ee w6 du rm yz if ep 
-\n    qs pi am 6a ut r4 ii sd ua ib y6 pa kt pb wm qq dz qt qp y0 he p8 ue tb qu or qo wh 40 8j t4 sl rf iu gh hh qc
iqbf rl wm mf oh ew is dp 
-\n    da ps a7 jr z4 q3 bu xt ip jx q6 np z7 bp lg bz ye wo ig bb ww rf om uu ef r8 ey pt ta pn y7 gg th dn pg qb ri
qq42 qw zw b9 b0 xb qt qy yl wh xk ft at yw i7 sp de pz fn qy si m0 ik wf sg cq ql hk er eg lc ek na wc iw ir rk ua e0
akiu sw ap uj av ab hl uv zc qa wc jw vj qv vk ay kg xw on rw 1b wn rl eb vm eq h4 yt oz eu uz 
-\n    a2 qa rd cp qh ub vg ws u0 4g bp da yo ev kz eq uu ee ef yk pr sj sk fe oi lt uy j2 gn io vk ns 27 ln wu ve yr
l2qu qi ry il ul tj eg ux i5 yr tx ph oj gq or zp wa qs gy iz v0 qt wj ic ca lh yb np ej sl td l8 yi iw s8 e8 ys yd sq
aldt pt tg te yb eu tq r1 ir fe 
-\n    tr h9 go qj b1 wu q7 zh el tg mb ys ed ii er r8 xs pe r0 sw db ov m7 d3 re no nu zr pq ji wr lc or qw ee yy qr
rnrm re qi te ea qo yb yn y0 uz uq iz tl yr i0 fm wp qs qd he wk kl ew as hl ez oo ox ie s0 f4 dl wq wl ww lr qc qv vk
mtmy kn ep em yt sy am so rp sp 
-\n    ak ft qg bg ji q6 bk xi tf mo ur pj yk ua qv wq gc qe ke ef eb tk uq i6 oh iv gb qs rx el yo rr pe wz wx ho xq
tcmo yk du r1 tq oc hc te 
-\n    ra ub qj jh jt dx ql q6 da tf r3 ew iu sg tp yl el gc 6n tt ry pd ye ff lz kt yp fl yf dl rn
-\n    o9 hb h9 qd dh qg q1 qj jy se q5 wt nr qv ge c5 el 6y uo rv ax pe et r0 fe y6 dx qx ha qq lo we zy v1 wy dl vr
waqr rm qi qp yn tz pg ph de p0 do qp wp wf bw xh ky xz wh hl to ek rd sv rj rq re h1 qg qh kl f1 zm 18 ez xe vm en 5j
o3rn fw fe it 
-\n    db c2 bb o0 w8 kl kc y4 qx zm pk cw id ve mh lp rq jb fl x1 qi wd lx f3 cy bq dd ye fn ig
+\n    qs:1 dv:2 pa:3 ty:4 iz:5 uw:6 qh:7 jj:8 z3:9 tn:10 jc:11 eg:12 e4:13 qq:14 w7:15 ut:16 r3:17 uu:18 kb:19 up:20
g1:21fo:22 iv:23 if:24 fd:25 gd:26 sc:27 qm:28 qe:29 xg:30 ia:31 wb:32 he:33 ky:34 hu:35 tv:36 rw:37 qu:38 rr:39 es:40
p3:41ue:42 s2:43 as:44 i0:45 dt:46 qt:47 hz:48 jm:49 j0:50 gy:51 ci:52 fi:53 hw:54 nv:55 ea:56 kk:57 vf:58 rx:59 68:60
ti:61rp:62 wl:63 oi:64 vp:65 at:66 om:67 io:68 uk:69 pt:70 qh:71 qj:72 dl:73 cf:74 lr:75 cx:76 wq:77 ku:78 ki:79 w2:80
yh:81af:82 ul:83 sp:84 yc:85 
+\n    ub:1 yb:2 dc:3 ty:4 gm:5 dm:6 go:7 nv:8 we:9 ql:10 by:11 la:12 o1:13 ju:14 o3:15 jx:16 fm:17 aj:18 wa:19 rg:20
e4:21vi:22 a6:23 r4:24 xo:25 tz:26 oe:27 ip:28 pv:29 dk:30 tq:31 a0:32 tf:33 fg:34 tg:35 i4:36 pz:37 sd:38 ry:39 ky:40
mg:41hy:42 g7:43 eu:44 qy:45 yi:46 rw:47 qp:48 eg:49 yw:50 hw:51 sm:52 uc:53 i7:54 dw:55 fx:56 s3:57 sf:58 zo:59 m9:60
xs:61vn:62 rf:63 ci:64 nz:65 kr:66 qt:67 9y:68 pj:69 lk:70 ee:71 pz:72 ef:73 rk:74 e0:75 fx:76 uf:77 az:78 fc:79 qg:80
jr:81oy:82 lq:83 cg:84 qp:85 um:86 ad:87 wc:88 zn:89 bw:90 n6:91 my:92 xr:93 mp:94 tu:95 en:96 o3:97 iq:98 ir:99 
+\n    da:1 a3:2 d1:3 jr:4 dz:5 ca:6 ql:7 nu:8 q3:9 cf:10 o6:11 nr:12 mt:13 lk:14 yr:15 rs:16 lp:17 w7:18 a5:19 pj:20
ys:21ym:22 r8:23 ey:24 to:25 fs:26 dz:27 im:28 ih:29 sw:30 qx:31 qv:32 zn:33 gl:34 j1:35 xe:36 lc:37 zc:38 vw:39 6a:40
mh:41b9:42 qt:43 rm:44 re:45 oo:46 qp:47 p4:48 tk:49 ix:50 p7:51 og:52 tz:53 yr:54 sp:55 aa:56 hk:57 ih:58 lx:59 qd:60
mx:614n:62 kk:63 vf:64 el:65 oo:66 td:67 ae:68 yo:69 fj:70 uf:71 pr:72 hl:73 qj:74 qk:75 wr:76 qc:77 qv:78 kf:79 yz:80
my:81wq:82 hn:83 zs:84 dr:85 ee:86 u8:87 rv:88 et:89 ru:90 ie:91 ag:92 
+\n    gt:1 ph:2 z0:3 zl:4 mu:5 ui:6 av:7 zm:8 om:9 ui:10 vh:11 qr:12 he:13 qr:14 es:15 fl:16 ws:17 w6:18 nc:19 ra:20
rk:21kp:22 ol:23 wm:24 yu:25 
+\n    dd:1 df:2 jq:3 jd:4 ux:5 ql:6 el:7 3r:8 ya:9 uu:10 iu:11 ee:12 eh:13 g3:14 sj:15 us:16 ib:17 pp:18 qc:19 jv:20
hd:21bh:22 zt:23 uo:24 d8:25 b3:26 xu:27 bc:28 rq:29 te:30 uh:31 ex:32 tt:33 eb:34 il:35 qu:36 pc:37 ge:38 sj:39 qp:40
ih:41xf:42 3r:43 gr:44 yh:45 qx:46 tu:47 wl:48 wn:49 sz:50 up:51 ay:52 it:53 ab:54 jt:55 qz:56 v7:57 wn:58 li:59 za:60
6o:61w3:62 fn:63 yk:64 eu:65 ie:66 gz:67 
+\n    yv:1 o9:2 qf:3 eg:4 eh:5 mh:6 jh:7 rh:8 r3:9 rv:10 ix:11 y3:12 a0:13 sr:14 qc:15 qq:16 qr:17 wr:18 qe:19 bx:20
ki:21m8:22 mk:23 qi:24 lm:25 uk:26 eb:27 ai:28 ur:29 e2:30 xd:31 nc:32 ca:33 eo:34 mb:35 ed:36 uv:37 rs:38 up:39 ya:40
of:41hn:42 lw:43 wz:44 qz:45 et:46 qh:47 wm:48 zr:49 rc:50 o3:51 
+\n    ss:1 qg:2 qj:3 ph:4 qk:5 q2:6 cs:7 z4:8 bi:9 qc:10 cj:11 q8:12 qn:13 w7:14 rj:15 ys:16 ea:17 r4:18 uy:19 om:20
rc:21ii:22 fp:23 sj:24 ej:25 yz:26 el:27 qx:28 qv:29 zn:30 gk:31 q9:32 hs:33 m2:34 ii:35 d7:36 nk:37 c8:38 j4:39 qq:40
dl:41gg:42 pp:43 ei:44 qo:45 yc:46 od:47 fo:48 eh:49 fp:50 ta:51 hy:52 ok:53 tv:54 uu:55 us:56 dp:57 qf:58 sb:59 zg:60
ks:61sg:62 n3:63 wh:64 x2:65 nr:66 cs:67 wk:68 kh:69 wk:70 wf:71 ew:72 7h:73 7k:74 oy:75 t6:76 gi:77 rg:78 yp:79 s9:80
ya:81e9:82 pb:83 tc:84 dt:85 dh:86 hk:87 h2:88 pt:89 qh:90 h7:91 wz:92 n1:93 qv:94 kd:95 pm:96 cc:97 xr:98 kp:99 yk:100
tm:101
+\n    gr:1 qa:2 ft:3 tt:4 gn:5 qs:6 pw:7 pu:8 ca:9 ph:10 ls:11 cg:12 cn:13 zf:14 bz:15 q7:16 z0:17 c3:18 qn:19 gv:20
w7:21rg:22 ut:23 e8:24 ii:25 er:26 ip:27 sg:28 y3:29 oy:30 ek:31 ht:32 gd:33 qx:34 g0:35 qv:36 db:37 su:38 jn:39 qq:40
lz:41uu:42 jo:43 ru:44 an:45 wi:46 kn:47 sq:48 nh:49 qr:50 qy:51 yx:52 eo:53 qi:54 xz:55 y9:56 ru:57 pd:58 au:59 p7:60
dq:61he:62 ut:63 ok:64 fd:65 jz:66 ui:67 hc:68 j9:69 l3:70 hb:71 xd:72 jq:73 gy:74 kh:75 wf:76 xs:77 sl:78 rs:79 aq:80
ez:81y4:82 ts:83 um:84 yi:85 e0:86 gh:87 dk:88 py:89 v5:90 qk:91 ql:92 ko:93 jq:94 wc:95 nk:96 v0:97 wb:98 qv:99 br:100
iu:101wm:102 6p:103 sr:104 gk:105 yp:106 
+\n    o0:1 pa:2 pf:3 z3:4 jt:5 jy:6 z7:7 cm:8 ne:9 w8:10 yz:11 fd:12 fg:13 zn:14 qq:15 ll:16 vg:17 wr:18 wb:19 ia:20
xx:21yj:22 ty:23 eh:24 e1:25 so:26 ts:27 tc:28 s4:29 i0:30 tn:31 wo:32 wp:33 wa:34 op:35 va:36 wk:37 x3:38 vg:39 qx:40
rs:41sn:42 au:43 f3:44 tz:45 sq:46 hn:47 rr:48 o2:49 fv:50 un:51 k2:52 vj:53 ey:54 
+\n    iy:1 ra:2 ij:3 ty:4 a4:5 un:6 rf:7 qg:8 dm:9 jr:10 kj:11 uv:12 we:13 cv:14 gk:15 wy:16 z8:17 oc:18 wp:19 mo:20
jl:21mz:22 ev:23 ch:24 rv:25 tu:26 ax:27 y2:28 g3:29 oy:30 y6:31 im:32 uq:33 qv:34 hp:35 qb:36 hd:37 iy:38 lp:39 nk:40
w2:41bb:42 ho:43 ep:44 tr:45 os:46 en:47 sm:48 p8:49 p9:50 hy:51 ss:52 ui:53 gm:54 qi:55 oo:56 vn:57 ae:58 qd:59 w6:60
ps:61dn:62 wd:63 wg:64 ro:65 mr:66 yt:67 ol:68 oz:69 rg:70 s7:71 u5:72 tl:73 yd:74 rr:75 ax:76 f0:77 cq:78 ku:79 qx:80
ze:81n4:82 wn:83 kt:84 ca:85 jy:86 bg:87 yc:88 zs:89 yw:90 rz:91 w1:92 eu:93 rm:94 
+\n    hv:1 fu:2 ca:3 q8:4 mt:5 la:6 r3:7 pl:8 yh:9 to:10 sy:11 yh:12 tv:13 x4:14 tg:15 yp:16 ov:17 wn:18 ze:19
+\n    o9:1 qa:2 az:3 gm:4 qd:5 pw:6 hq:7 pd:8 ga:9 qj:10 cd:11 q3:12 jk:13 pd:14 du:15 c2:16 zk:17 xf:18 t8:19 eq:20
om:21es:22 rc:23 ua:24 y3:25 pu:26 ig:27 qx:28 se:29 qv:30 db:31 st:32 qn:33 ii:34 lx:35 qe:36 wt:37 xk:38 nx:39 ku:40
br:41qe:42 qr:43 qt:44 rm:45 eu:46 xf:47 xb:48 rn:49 qu:50 qi:51 ep:52 qo:53 rr:54 ex:55 xk:56 p5:57 ym:58 fi:59 uq:60
to:61ux:62 ix:63 ai:64 hj:65 gn:66 zi:67 oq:68 qf:69 kd:70 wf:71 xn:72 kr:73 w8:74 rl:75 kk:76 mq:77 rp:78 rf:79 u1:80
s7:81oa:82 fh:83 e7:84 yp:85 e0:86 pr:87 ql:88 sx:89 ck:90 ag:91 kg:92 kt:93 mp:94 eb:95 rl:96 em:97 ee:98 w6:99 du:100
rm:101yz:102 if:103 
+\n    qs:1 pi:2 am:3 6a:4 ut:5 r4:6 ii:7 sd:8 ua:9 ib:10 y6:11 pa:12 kt:13 pb:14 wm:15 qq:16 dz:17 qt:18 qp:19 y0:20
he:21p8:22 ue:23 tb:24 qu:25 or:26 qo:27 wh:28 40:29 8j:30 t4:31 sl:32 rf:33 iu:34 gh:35 hh:36 qc:37 iq:38 bf:39 rl:40
wm:41mf:42 oh:43 ew:44 is:45 
+\n    da:1 ps:2 a7:3 jr:4 z4:5 q3:6 bu:7 xt:8 ip:9 jx:10 q6:11 np:12 z7:13 bp:14 lg:15 bz:16 ye:17 wo:18 ig:19 bb:20
ww:21rf:22 om:23 uu:24 ef:25 r8:26 ey:27 pt:28 ta:29 pn:30 y7:31 gg:32 th:33 dn:34 pg:35 qb:36 ri:37 qq:38 42:39 qw:40
zw:41b9:42 b0:43 xb:44 qt:45 qy:46 yl:47 wh:48 xk:49 ft:50 at:51 yw:52 i7:53 sp:54 de:55 pz:56 fn:57 qy:58 si:59 m0:60
ik:61wf:62 sg:63 cq:64 ql:65 hk:66 er:67 eg:68 lc:69 ek:70 na:71 wc:72 iw:73 ir:74 rk:75 ua:76 e0:77 ak:78 iu:79 sw:80
ap:81uj:82 av:83 ab:84 hl:85 uv:86 zc:87 qa:88 wc:89 jw:90 vj:91 qv:92 vk:93 ay:94 kg:95 xw:96 on:97 rw:98 1b:99 wn:100
rl:101eb:102 vm:103 eq:104 h4:105 yt:106 oz:107 eu:108 
+\n    a2:1 qa:2 rd:3 cp:4 qh:5 ub:6 vg:7 ws:8 u0:9 4g:10 bp:11 da:12 yo:13 ev:14 kz:15 eq:16 uu:17 ee:18 ef:19 yk:20
pr:21sj:22 sk:23 fe:24 oi:25 lt:26 uy:27 j2:28 gn:29 io:30 vk:31 ns:32 27:33 ln:34 wu:35 ve:36 yr:37 l2:38 qu:39 qi:40
ry:41il:42 ul:43 tj:44 eg:45 ux:46 i5:47 yr:48 tx:49 ph:50 oj:51 gq:52 or:53 zp:54 wa:55 qs:56 gy:57 iz:58 v0:59 qt:60
wj:61ic:62 ca:63 lh:64 yb:65 np:66 ej:67 sl:68 td:69 l8:70 yi:71 iw:72 s8:73 e8:74 ys:75 yd:76 sq:77 al:78 dt:79 pt:80
tg:81te:82 yb:83 eu:84 tq:85 r1:86 ir:87 
+\n    tr:1 h9:2 go:3 qj:4 b1:5 wu:6 q7:7 zh:8 el:9 tg:10 mb:11 ys:12 ed:13 ii:14 er:15 r8:16 xs:17 pe:18 r0:19 sw:20
db:21ov:22 m7:23 d3:24 re:25 no:26 nu:27 zr:28 pq:29 ji:30 wr:31 lc:32 or:33 qw:34 ee:35 yy:36 qr:37 rn:38 rm:39 re:40
qi:41te:42 ea:43 qo:44 yb:45 yn:46 y0:47 uz:48 uq:49 iz:50 tl:51 yr:52 i0:53 fm:54 wp:55 qs:56 qd:57 he:58 wk:59 kl:60
ew:61as:62 hl:63 ez:64 oo:65 ox:66 ie:67 s0:68 f4:69 dl:70 wq:71 wl:72 ww:73 lr:74 qc:75 qv:76 vk:77 mt:78 my:79 kn:80
ep:81em:82 yt:83 sy:84 am:85 so:86 rp:87 
+\n    ak:1 ft:2 qg:3 bg:4 ji:5 q6:6 bk:7 xi:8 tf:9 mo:10 ur:11 pj:12 yk:13 ua:14 qv:15 wq:16 gc:17 qe:18 ke:19 ef:20
eb:21tk:22 uq:23 i6:24 oh:25 iv:26 gb:27 qs:28 rx:29 el:30 yo:31 rr:32 pe:33 wz:34 wx:35 ho:36 xq:37 tc:38 mo:39 yk:40
du:41r1:42 tq:43 oc:44 hc:45 
+\n    ra:1 ub:2 qj:3 jh:4 jt:5 dx:6 ql:7 q6:8 da:9 tf:10 r3:11 ew:12 iu:13 sg:14 tp:15 yl:16 el:17 gc:18 6n:19 tt:20
ry:21pd:22 ye:23 ff:24 lz:25 kt:26 yp:27 fl:28 yf:29 dl:30 
+\n    o9:1 hb:2 h9:3 qd:4 dh:5 qg:6 q1:7 qj:8 jy:9 se:10 q5:11 wt:12 nr:13 qv:14 ge:15 c5:16 el:17 6y:18 uo:19 rv:20
ax:21pe:22 et:23 r0:24 fe:25 y6:26 dx:27 qx:28 ha:29 qq:30 lo:31 we:32 zy:33 v1:34 wy:35 dl:36 vr:37 wa:38 qr:39 rm:40
qi:41qp:42 yn:43 tz:44 pg:45 ph:46 de:47 p0:48 do:49 qp:50 wp:51 wf:52 bw:53 xh:54 ky:55 xz:56 wh:57 hl:58 to:59 ek:60
rd:61sv:62 rj:63 rq:64 re:65 h1:66 qg:67 qh:68 kl:69 f1:70 zm:71 18:72 ez:73 xe:74 vm:75 en:76 5j:77 o3:78 rn:79 fw:80
fe:81
+\n    db:1 c2:2 bb:3 o0:4 w8:5 kl:6 kc:7 y4:8 qx:9 zm:10 pk:11 cw:12 id:13 ve:14 mh:15 lp:16 rq:17 jb:18 fl:19 x1:20
qi:21wd:22 lx:23 f3:24 cy:25 bq:26 dd:27 ye:28 fn:29 
diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out
index d6e0ba6..7105c67 100644
--- a/src/test/regress/expected/tsearch.out
+++ b/src/test/regress/expected/tsearch.out
@@ -116,6 +116,66 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
    508
 (1 row)

+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+ count
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+ count
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+ count
+-------
+   432
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+ count
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+ count
+-------
+   507
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+ count
+-------
+   508
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';
+ count
+-------
+   507
+(1 row)
+
 create index wowidx on test_tsvector using gist (a);
 SET enable_seqscan=OFF;
 SET enable_indexscan=ON;
@@ -188,6 +248,66 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
    508
 (1 row)

+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+ count
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+ count
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+ count
+-------
+   432
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+ count
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+ count
+-------
+   507
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+ count
+-------
+   508
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';
+ count
+-------
+   507
+(1 row)
+
 SET enable_indexscan=OFF;
 SET enable_bitmapscan=ON;
 explain (costs off) SELECT count(*) FROM test_tsvector WHERE a @@ 'wr|qh';
@@ -260,6 +380,66 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
    508
 (1 row)

+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+ count
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+ count
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+ count
+-------
+   432
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+ count
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+ count
+-------
+   507
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+ count
+-------
+   508
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';
+ count
+-------
+   507
+(1 row)
+
 -- Test siglen parameter of GiST tsvector_ops
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
 ERROR:  unrecognized parameter "foo"
@@ -355,6 +535,66 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
    508
 (1 row)

+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+ count
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+ count
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+ count
+-------
+   432
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+ count
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+ count
+-------
+   507
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+ count
+-------
+   508
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';
+ count
+-------
+   507
+(1 row)
+
 DROP INDEX wowidx2;
 CREATE INDEX wowidx ON test_tsvector USING gist (a tsvector_ops(siglen=484));
 \d test_tsvector
@@ -436,6 +676,66 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
    508
 (1 row)

+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+ count
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+ count
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+ count
+-------
+   432
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+ count
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+ count
+-------
+   507
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+ count
+-------
+   508
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';
+ count
+-------
+   507
+(1 row)
+
 RESET enable_seqscan;
 RESET enable_indexscan;
 RESET enable_bitmapscan;
@@ -513,6 +813,66 @@ SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
    508
 (1 row)

+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+ count
+-------
+     0
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+ count
+-------
+     3
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+ count
+-------
+   432
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+ count
+-------
+     1
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+ count
+-------
+     6
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+ count
+-------
+   507
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+ count
+-------
+   508
+(1 row)
+
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';
+ count
+-------
+   507
+(1 row)
+
 -- Test optimization of non-empty GIN_SEARCH_MODE_ALL queries
 EXPLAIN (COSTS OFF)
 SELECT count(*) FROM test_tsvector WHERE a @@ '!qh';
@@ -555,14 +915,14 @@ SELECT * FROM ts_stat('SELECT a FROM test_tsvector') ORDER BY ndoc DESC, nentry
 ------+------+--------
  qq   |  108 |    108
  qt   |  102 |    102
- qe   |  100 |    100
- qh   |   98 |     98
+ qe   |  100 |    101
+ qh   |   98 |     99
  qw   |   98 |     98
  qa   |   97 |     97
  ql   |   94 |     94
  qs   |   94 |     94
+ qr   |   92 |     93
  qi   |   92 |     92
- qr   |   92 |     92
 (10 rows)

 SELECT * FROM ts_stat('SELECT a FROM test_tsvector', 'AB') ORDER BY ndoc DESC, nentry DESC, word;
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
index 2c838dd..dda2b07 100644
--- a/src/test/regress/expected/tstypes.out
+++ b/src/test/regress/expected/tstypes.out
@@ -769,12 +769,90 @@ select to_tsvector('simple', 'z q') @@ '(!x | y <-> z) <-> q' AS "true";
  t
 (1 row)

+select to_tsvector('simple', 'x y q') @@ '(!x | y) <-> y <-> q' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(!x | !y) <-> y <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(x | !y) <-> y <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q') @@ '(x | !!z) <-> y <-> q' AS "true";
+ true
+------
+ t
+(1 row)
+
 select to_tsvector('simple', 'x y q y') @@ '!x <-> y' AS "true";
  true
 ------
  t
 (1 row)

+select to_tsvector('simple', 'x y q y') @@ '!x <-> !y' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !!y' AS "true";
+ true
+------
+ t
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!(x <-> y)' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select to_tsvector('simple', 'x y q y') @@ '!(x <2> y)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> y' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !y' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !!y' AS "false";
+ false
+-------
+ f
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <-> y)' AS "true";
+ true
+------
+ t
+(1 row)
+
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <2> y)' AS "true";
+ true
+------
+ t
+(1 row)
+
 select to_tsvector('simple', 'x y q y') @@ '!foo' AS "true";
  true
 ------
diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql
index 22c84a3..e53e44f 100644
--- a/src/test/regress/sql/tsearch.sql
+++ b/src/test/regress/sql/tsearch.sql
@@ -51,6 +51,16 @@ SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
 SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
 SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
 SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';

 create index wowidx on test_tsvector using gist (a);

@@ -70,6 +80,16 @@ SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
 SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
 SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
 SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';

 SET enable_indexscan=OFF;
 SET enable_bitmapscan=ON;
@@ -86,6 +106,16 @@ SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
 SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
 SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
 SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';

 -- Test siglen parameter of GiST tsvector_ops
 CREATE INDEX wowidx1 ON test_tsvector USING gist (a tsvector_ops(foo=1));
@@ -112,6 +142,16 @@ SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
 SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
 SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
 SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';

 DROP INDEX wowidx2;

@@ -131,6 +171,16 @@ SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
 SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
 SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
 SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';

 RESET enable_seqscan;
 RESET enable_indexscan;
@@ -155,6 +205,16 @@ SELECT count(*) FROM test_tsvector WHERE a @@ 'w:*|q:*';
 SELECT count(*) FROM test_tsvector WHERE a @@ any ('{wr,qh}');
 SELECT count(*) FROM test_tsvector WHERE a @@ 'no_such_lexeme';
 SELECT count(*) FROM test_tsvector WHERE a @@ '!no_such_lexeme';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ 'qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!pl <-> !yh';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!yh <-> pl';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!qe <2> qt';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(pl <-> yh)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(yh <-> pl)';
+SELECT count(*) FROM test_tsvector WHERE a @@ '!(qe <2> qt)';

 -- Test optimization of non-empty GIN_SEARCH_MODE_ALL queries
 EXPLAIN (COSTS OFF)
diff --git a/src/test/regress/sql/tstypes.sql b/src/test/regress/sql/tstypes.sql
index 94ee177..81db2e8 100644
--- a/src/test/regress/sql/tstypes.sql
+++ b/src/test/regress/sql/tstypes.sql
@@ -147,7 +147,20 @@ select to_tsvector('simple', 'y y q') @@ '(x | y <-> !z) <-> q' AS "true";
 select to_tsvector('simple', 'x q') @@ '(x | y <-> !z) <-> q' AS "true";
 select to_tsvector('simple', 'x q') @@ '(!x | y <-> z) <-> q' AS "false";
 select to_tsvector('simple', 'z q') @@ '(!x | y <-> z) <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(!x | y) <-> y <-> q' AS "false";
+select to_tsvector('simple', 'x y q') @@ '(!x | !y) <-> y <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(x | !y) <-> y <-> q' AS "true";
+select to_tsvector('simple', 'x y q') @@ '(x | !!z) <-> y <-> q' AS "true";
 select to_tsvector('simple', 'x y q y') @@ '!x <-> y' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !y' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!x <-> !!y' AS "true";
+select to_tsvector('simple', 'x y q y') @@ '!(x <-> y)' AS "false";
+select to_tsvector('simple', 'x y q y') @@ '!(x <2> y)' AS "true";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> y' AS "false";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !y' AS "false";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!x <-> !!y' AS "false";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <-> y)' AS "true";
+select strip(to_tsvector('simple', 'x y q y')) @@ '!(x <2> y)' AS "true";
 select to_tsvector('simple', 'x y q y') @@ '!foo' AS "true";
 select to_tsvector('simple', '') @@ '!foo' AS "true";


pgsql-bugs by date:

Previous
From: Tom Lane
Date:
Subject: Re: BUG #16390: Regression between 12.2 and 11.6 on a recursive query : very slow and overestimation of rows
Next
From: "Yang Yingjun(YFPOIT)"
Date:
Subject: BUG #16384: having trouble while installation