Hello hackers,
Nowadays, phrase operator in Postgres FTS supports only exact match of
the distance between two words. It is sufficient for a search of
simple/exact phrases, but in some cases exact distance is unknown and we
want to words be close enough. E.g. it may help to search phrases with
additional words in the middle of the phrase ("long, narrow, plastic
brush" vs "long brush")
Proposed patch adds ability to use ranges in phrase operator for
mentioned cases. Few examples:
'term1 <4,10> term2'::tsquery -- Distance between term1 and term2 is
-- at least 4 and no greater than 10
'term1 <,10> term2'::tsquery -- Distance between term1 and term2 is
-- no greater than 10
'term1 <4,> term2'::tsquery -- Distance between term1 and term2 is
-- at least 4
In addition, negative distance is supported and means reverse order of
the words. For example:
'term1 <4,10> term2'::tsquery = 'term2 <-10,-4> term1'::tsquery
'term1 <,10> term2'::tsquery = 'term2 <-10,> term1'::tsquery
'term1 <4,> term2'::tsquery = 'term2 <,-4> term1'::tsquery
Negative distance support introduced to use it for AROUND operator
mentioned in websearch_to_tsquery[1]. In web search query language
AROUND(N) does a search for words within given distance N in
both forward and backward direction and it can be represented as <-N,N>
range phrase operator.
[1]
https://www.postgresql.org/message-id/flat/fe931111ff7e9ad79196486ada79e...@postgrespro.ru
--
Aleksandr Parfenov
Postgres Professional: http://www.postgrespro.com
Russian Postgres Company
diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml
index 19f58511c8..2ccff5d16d 100644
--- a/doc/src/sgml/textsearch.sgml
+++ b/doc/src/sgml/textsearch.sgml
@@ -373,6 +373,38 @@ SELECT phraseto_tsquery('the cats ate the rats');
can be used to require that two patterns match the same word.
</para>
+ <para>
+ Range version of the FOLLOWED BY operator having the form
+ <literal><<replaceable>[N]</replaceable>,<replaceable>[M]</replaceable>></literal>,
+ where <replaceable>N</replaceable> and <replaceable>M</replaceable> are integers
+ representing for the minimum and maximum difference between the positions of the
+ matching lexemes. <replaceable>N</replaceable> can be omitted as a short version of
+ <literal><0,<replaceable>M</replaceable>></literal>. Likewise,
+ <replaceable>M</replaceable> can be omitted to represent a maximum distance
+ between the positions of the matching lexemes limited by maximum distance in
+ <literal>tsquery</literal>. Negative distance is supported to show reverse order
+ of the matching lexemes. Omitted borders are inverted in case of negative distance.
+ </para>
+<programlisting>
+SELECT 'big <1,2> cat'::tsquery;
+ tsquery
+-------------------
+ 'big' <1,2> 'cat'
+(1 row)
+
+SELECT 'big <,2> cat'::tsquery;
+ tsquery
+-------------------
+ 'big' <0,2> 'cat'
+(1 row)
+
+SELECT 'big <1,> cat'::tsquery;
+ tsquery
+-----------------------
+ 'big' <1,16384> 'cat'
+(1 row)
+</programlisting>
+
<para>
Parentheses can be used to control nesting of the <type>tsquery</type>
operators. Without parentheses, <literal>|</literal> binds least tightly,
@@ -3921,7 +3953,7 @@ Parser: "pg_catalog.default"
</listitem>
<listitem>
<para>The match distance in a <literal><<replaceable>N</replaceable>></literal>
- (FOLLOWED BY) <type>tsquery</type> operator cannot be more than
+ (FOLLOWED BY) <type>tsquery</type> operator cannot be less than -16,384 and more than
16,384</para>
</listitem>
<listitem>
diff --git a/src/backend/tsearch/to_tsany.c b/src/backend/tsearch/to_tsany.c
index 2474b723b4..6fc19e9c9b 100644
--- a/src/backend/tsearch/to_tsany.c
+++ b/src/backend/tsearch/to_tsany.c
@@ -487,12 +487,14 @@ pushval_morph(Datum opaque, TSQueryParserState state, char *strval, int lenval,
cntvar = 0,
cntpos = 0,
cnt = 0;
+ OperatorData operator_data;
MorphOpaque *data = (MorphOpaque *) DatumGetPointer(opaque);
prs.lenwords = 4;
prs.curwords = 0;
prs.pos = 0;
prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords);
+ OPERATOR_DATA_INITIALIZE(operator_data, 0);
parsetext(data->cfg_id, &prs, strval, lenval);
@@ -511,7 +513,10 @@ pushval_morph(Datum opaque, TSQueryParserState state, char *strval, int lenval,
/* put placeholders for each missing stop word */
pushStop(state);
if (cntpos)
- pushOperator(state, data->qoperator, 1);
+ {
+ OPERATOR_DATA_INITIALIZE(operator_data, 1);
+ pushOperator(state, data->qoperator, operator_data);
+ }
cntpos++;
pos++;
}
@@ -539,20 +544,21 @@ pushval_morph(Datum opaque, TSQueryParserState state, char *strval, int lenval,
((prs.words[count].flags & TSL_PREFIX) || prefix));
pfree(prs.words[count].word);
if (cnt)
- pushOperator(state, OP_AND, 0);
+ pushOperator(state, OP_AND, operator_data);
cnt++;
count++;
}
if (cntvar)
- pushOperator(state, OP_OR, 0);
+ pushOperator(state, OP_OR, operator_data);
cntvar++;
}
if (cntpos)
{
/* distance may be useful */
- pushOperator(state, data->qoperator, 1);
+ OPERATOR_DATA_INITIALIZE(operator_data, 1);
+ pushOperator(state, data->qoperator, operator_data);
}
cntpos++;
diff --git a/src/backend/utils/adt/tsquery.c b/src/backend/utils/adt/tsquery.c
index 793c0e5dd1..73fbc3d2ec 100644
--- a/src/backend/utils/adt/tsquery.c
+++ b/src/backend/utils/adt/tsquery.c
@@ -42,6 +42,12 @@ typedef enum
WAITFIRSTOPERAND = 3
} ts_parserstate;
+/* Contains additional information for tokens being parsed */
+typedef union TokenData {
+ int16 weight; /*weight for operand */
+ OperatorData operator_data; /* data for operator */
+} TokenData;
+
/*
* token types for parsing
*/
@@ -65,7 +71,7 @@ typedef enum
*/
typedef ts_tokentype (*ts_tokenizer)(TSQueryParserState state, int8 *operator,
int *lenval, char **strval,
- int16 *weight, bool *prefix);
+ TokenData *token_data, bool *prefix);
struct TSQueryParserStateData
{
@@ -152,18 +158,23 @@ get_modifiers(char *buf, int16 *weight, bool *prefix)
* The buffer should begin with '<' char
*/
static bool
-parse_phrase_operator(TSQueryParserState pstate, int16 *distance)
+parse_phrase_operator(TSQueryParserState pstate, OperatorData *operator_data)
{
enum
{
PHRASE_OPEN = 0,
- PHRASE_DIST,
+ PHRASE_DIST_FROM,
+ PHRASE_DIST_TO,
PHRASE_CLOSE,
PHRASE_FINISH
} state = PHRASE_OPEN;
char *ptr = pstate->buf;
char *endptr;
- long l = 1; /* default distance */
+ bool negative_distance = false;
+ int32 distance_from = 0;
+ int32 distance_to = MAXENTRYPOS;
+ bool distance_from_set = false;
+ bool distance_to_set = false;
while (*ptr)
{
@@ -172,37 +183,126 @@ parse_phrase_operator(TSQueryParserState pstate, int16 *distance)
case PHRASE_OPEN:
if (t_iseq(ptr, '<'))
{
- state = PHRASE_DIST;
+ state = PHRASE_DIST_FROM;
ptr++;
}
else
return false;
break;
- case PHRASE_DIST:
- if (t_iseq(ptr, '-'))
+ case PHRASE_DIST_FROM:
+ /* <-> */
+ if (t_iseq(ptr, '-') && !negative_distance)
+ {
+ /* make sure '-' doesn't mean negative number */
+ if (t_iseq(ptr + 1, '>'))
+ {
+ distance_from_set = true;
+ distance_to_set = true;
+ state = PHRASE_CLOSE;
+ ptr++;
+ distance_from = 1;
+ distance_to = 1;
+ }
+ else
+ {
+ negative_distance = true;
+ ptr++;
+ }
+ }
+ else if (t_iseq(ptr, ',') && !negative_distance)
{
- state = PHRASE_CLOSE;
ptr++;
- continue;
+ state = PHRASE_DIST_TO;
}
+ else if (t_isdigit(ptr)) /* <N...> */
+ {
+ errno = 0;
+ distance_from = strtol(ptr, &endptr, 10);
+ distance_from_set = true;
+ if (negative_distance)
+ {
+ distance_from = -distance_from;
+ negative_distance = false;
+ }
- if (!t_isdigit(ptr))
+ if (ptr == endptr)
+ {
+ return false;
+ }
+ else if (errno == ERANGE || distance_from < MINENTRYPOS || distance_from > MAXENTRYPOS)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("distance in phrase operator should not be less than %d and no greater than %d",
+ MINENTRYPOS, MAXENTRYPOS)));
+ }
+ else
+ {
+ ptr = endptr;
+ if (t_iseq(ptr, ','))
+ {
+ /* <N,...> */
+ ptr++;
+ state = PHRASE_DIST_TO;
+ }
+ else /* <N> is equal <N,N> */
+ {
+ distance_to_set = true;
+ distance_to = distance_from;
+ state = PHRASE_CLOSE;
+ }
+ }
+ }
+ else
+ {
return false;
+ }
+ break;
- errno = 0;
- l = strtol(ptr, &endptr, 10);
- if (ptr == endptr)
- return false;
- else if (errno == ERANGE || l < 0 || l > MAXENTRYPOS)
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("distance in phrase operator should not be greater than %d",
- MAXENTRYPOS)));
+ case PHRASE_DIST_TO:
+ if (t_iseq(ptr, '-') && !negative_distance)
+ {
+ negative_distance = true;
+ ptr++;
+ }
+ else if (t_isdigit(ptr))
+ {
+ errno = 0;
+ distance_to = strtol(ptr, &endptr, 10);
+ distance_to_set = true;
+ if (negative_distance)
+ {
+ distance_to = -distance_to;
+ negative_distance = false;
+ }
+
+ if (ptr == endptr)
+ {
+ return false;
+ }
+ else if (errno == ERANGE || distance_to < MINENTRYPOS || distance_to > MAXENTRYPOS)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("distance in phrase operator should not be less than %d and no greater than %d",
+ MINENTRYPOS, MAXENTRYPOS)));
+ }
+ else if (distance_from_set && distance_to < distance_from)
+ {
+ return false;
+ }
+ else
+ {
+ state = PHRASE_CLOSE;
+ ptr = endptr;
+ }
+ }
else
{
+ if (negative_distance)
+ return false;
state = PHRASE_CLOSE;
- ptr = endptr;
}
break;
@@ -213,11 +313,28 @@ parse_phrase_operator(TSQueryParserState pstate, int16 *distance)
ptr++;
}
else
+ {
return false;
+ }
break;
case PHRASE_FINISH:
- *distance = (int16) l;
+ if (!distance_from_set && !distance_to_set)
+ {
+ distance_from = MINENTRYPOS;
+ distance_to = MAXENTRYPOS;
+ distance_to_set = distance_from_set = true;
+ }
+ if (!distance_from_set)
+ {
+ distance_from = distance_to < 0 ? MINENTRYPOS : 0;
+ }
+ if (!distance_to_set)
+ {
+ distance_to = distance_from < 0 ? 0 : MAXENTRYPOS;
+ }
+ operator_data->distance_from = (int16) distance_from;
+ operator_data->distance_to = (int16) distance_to;
pstate->buf = ptr;
return true;
}
@@ -278,9 +395,9 @@ parse_or_operator(TSQueryParserState pstate)
static ts_tokentype
gettoken_query_standard(TSQueryParserState state, int8 *operator,
int *lenval, char **strval,
- int16 *weight, bool *prefix)
+ TokenData *token_data, bool *prefix)
{
- *weight = 0;
+ OPERATOR_DATA_INITIALIZE(token_data->operator_data, 0);
*prefix = false;
while (true)
@@ -317,7 +434,7 @@ gettoken_query_standard(TSQueryParserState state, int8 *operator,
if (gettoken_tsvector(state->valstate, strval, lenval,
NULL, NULL, &state->buf))
{
- state->buf = get_modifiers(state->buf, weight, prefix);
+ state->buf = get_modifiers(state->buf, &token_data->weight, prefix);
state->state = WAITOPERATOR;
return PT_VAL;
}
@@ -348,7 +465,7 @@ gettoken_query_standard(TSQueryParserState state, int8 *operator,
*operator = OP_OR;
return PT_OPR;
}
- else if (parse_phrase_operator(state, weight))
+ else if (parse_phrase_operator(state, &token_data->operator_data))
{
/* weight var is used as storage for distance */
state->state = WAITOPERAND;
@@ -379,9 +496,9 @@ gettoken_query_standard(TSQueryParserState state, int8 *operator,
static ts_tokentype
gettoken_query_websearch(TSQueryParserState state, int8 *operator,
int *lenval, char **strval,
- int16 *weight, bool *prefix)
+ TokenData *token_data, bool *prefix)
{
- *weight = 0;
+ OPERATOR_DATA_INITIALIZE(token_data->operator_data, 0);
*prefix = false;
while (true)
@@ -496,7 +613,7 @@ gettoken_query_websearch(TSQueryParserState state, int8 *operator,
{
/* put implicit <-> after an operand */
*operator = OP_PHRASE;
- *weight = 1;
+ OPERATOR_DATA_INITIALIZE(token_data->operator_data, 1);
}
else
{
@@ -517,9 +634,9 @@ gettoken_query_websearch(TSQueryParserState state, int8 *operator,
static ts_tokentype
gettoken_query_plain(TSQueryParserState state, int8 *operator,
int *lenval, char **strval,
- int16 *weight, bool *prefix)
+ TokenData *token_data, bool *prefix)
{
- *weight = 0;
+ OPERATOR_DATA_INITIALIZE(token_data->operator_data, 0);
*prefix = false;
if (*state->buf == '\0')
@@ -536,7 +653,7 @@ gettoken_query_plain(TSQueryParserState state, int8 *operator,
* Push an operator to state->polstr
*/
void
-pushOperator(TSQueryParserState state, int8 oper, int16 distance)
+pushOperator(TSQueryParserState state, int8 oper, OperatorData operator_data)
{
QueryOperator *tmp;
@@ -545,7 +662,8 @@ pushOperator(TSQueryParserState state, int8 oper, int16 distance)
tmp = (QueryOperator *) palloc0(sizeof(QueryOperator));
tmp->type = QI_OPR;
tmp->oper = oper;
- tmp->distance = (oper == OP_PHRASE) ? distance : 0;
+ if (oper == OP_PHRASE)
+ tmp->operator_data = operator_data;
/* left is filled in later with findoprnd */
state->polstr = lcons(tmp, state->polstr);
@@ -637,17 +755,17 @@ pushStop(TSQueryParserState state)
typedef struct OperatorElement
{
int8 op;
- int16 distance;
+ OperatorData operator_data;
} OperatorElement;
static void
-pushOpStack(OperatorElement *stack, int *lenstack, int8 op, int16 distance)
+pushOpStack(OperatorElement *stack, int *lenstack, int8 op, OperatorData operator_data)
{
if (*lenstack == STACKDEPTH) /* internal error */
elog(ERROR, "tsquery stack too small");
stack[*lenstack].op = op;
- stack[*lenstack].distance = distance;
+ stack[*lenstack].operator_data = operator_data;
(*lenstack)++;
}
@@ -667,7 +785,7 @@ cleanOpStack(TSQueryParserState state,
(*lenstack)--;
pushOperator(state, stack[*lenstack].op,
- stack[*lenstack].distance);
+ stack[*lenstack].operator_data);
}
}
@@ -687,7 +805,7 @@ makepol(TSQueryParserState state,
char *strval = NULL;
OperatorElement opstack[STACKDEPTH];
int lenstack = 0;
- int16 weight = 0;
+ TokenData token_data;
bool prefix;
/* since this function recurses, it could be driven to stack overflow */
@@ -695,16 +813,16 @@ makepol(TSQueryParserState state,
while ((type = state->gettoken(state, &operator,
&lenval, &strval,
- &weight, &prefix)) != PT_END)
+ &token_data, &prefix)) != PT_END)
{
switch (type)
{
case PT_VAL:
- pushval(opaque, state, strval, lenval, weight, prefix);
+ pushval(opaque, state, strval, lenval, token_data.weight, prefix);
break;
case PT_OPR:
cleanOpStack(state, opstack, &lenstack, operator);
- pushOpStack(opstack, &lenstack, operator, weight);
+ pushOpStack(opstack, &lenstack, operator, token_data.operator_data);
break;
case PT_OPEN:
makepol(state, pushval, opaque);
@@ -1063,7 +1181,8 @@ infix(INFIX *in, int parentPriority, bool rightPhraseOp)
{
int8 op = in->curpol->qoperator.oper;
int priority = QO_PRIORITY(in->curpol);
- int16 distance = in->curpol->qoperator.distance;
+ int16 distance_from = in->curpol->qoperator.operator_data.distance_from;
+ int16 distance_to = in->curpol->qoperator.operator_data.distance_to;
INFIX nrm;
bool needParenthesis = false;
@@ -1090,8 +1209,13 @@ infix(INFIX *in, int parentPriority, bool rightPhraseOp)
in->curpol = nrm.curpol;
infix(in, priority, false);
- /* print operator & right operand */
- RESIZEBUF(in, 3 + (2 + 10 /* distance */ ) + (nrm.cur - nrm.buf));
+ /*
+ * print operator & right operand
+ * Required size is calculated as 3 for " <op> ", 2 for "<,>"
+ * and 11 for each distance in phrase operator with minus sign.
+ * (nrm.cur - nrm.buf) is length of the operand
+ */
+ RESIZEBUF(in, 3 + (2 + 22) + (nrm.cur - nrm.buf));
switch (op)
{
case OP_OR:
@@ -1101,10 +1225,12 @@ infix(INFIX *in, int parentPriority, bool rightPhraseOp)
sprintf(in->cur, " & %s", nrm.buf);
break;
case OP_PHRASE:
- if (distance != 1)
- sprintf(in->cur, " <%d> %s", distance, nrm.buf);
- else
+ if (distance_from == 1 && distance_to == 1)
sprintf(in->cur, " <-> %s", nrm.buf);
+ else if (distance_from == distance_to)
+ sprintf(in->cur, " <%d> %s", distance_from, nrm.buf);
+ else
+ sprintf(in->cur, " <%d,%d> %s", distance_from, distance_to, nrm.buf);
break;
default:
/* OP_NOT is handled in above if-branch */
@@ -1189,7 +1315,10 @@ tsquerysend(PG_FUNCTION_ARGS)
case QI_OPR:
pq_sendint8(&buf, item->qoperator.oper);
if (item->qoperator.oper == OP_PHRASE)
- pq_sendint16(&buf, item->qoperator.distance);
+ {
+ pq_sendint16(&buf, item->qoperator.operator_data.distance_from);
+ pq_sendint16(&buf, item->qoperator.operator_data.distance_to);
+ }
break;
default:
elog(ERROR, "unrecognized tsquery node type: %d", item->type);
@@ -1292,7 +1421,10 @@ tsqueryrecv(PG_FUNCTION_ARGS)
item->qoperator.oper = oper;
if (oper == OP_PHRASE)
- item->qoperator.distance = (int16) pq_getmsgint(buf, sizeof(int16));
+ {
+ item->qoperator.operator_data.distance_from = (int16) pq_getmsgint(buf, sizeof(int16));
+ item->qoperator.operator_data.distance_to = (int16) pq_getmsgint(buf, sizeof(int16));
+ }
}
else
elog(ERROR, "unrecognized tsquery node type: %d", item->type);
diff --git a/src/backend/utils/adt/tsquery_cleanup.c b/src/backend/utils/adt/tsquery_cleanup.c
index c146376e66..d4e00d1f04 100644
--- a/src/backend/utils/adt/tsquery_cleanup.c
+++ b/src/backend/utils/adt/tsquery_cleanup.c
@@ -234,13 +234,15 @@ clean_NOT(QueryItem *ptr, int *len)
* '((x <-> a) | a) <-> y' will become 'x <2> y'.
*/
static NODE *
-clean_stopword_intree(NODE *node, int *ladd, int *radd)
+clean_stopword_intree(NODE *node, int *ladd_from, int *radd_from,
+ int *ladd_to, int *radd_to)
{
/* since this function recurses, it could be driven to stack overflow. */
check_stack_depth();
/* default output parameters indicate no change in parent distance */
- *ladd = *radd = 0;
+ *ladd_from = *radd_from = 0;
+ *ladd_to = *radd_to = 0;
if (node->valnode->type == QI_VAL)
return node;
@@ -255,7 +257,8 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
if (node->valnode->qoperator.oper == OP_NOT)
{
/* NOT doesn't change pattern width, so just report child distances */
- node->right = clean_stopword_intree(node->right, ladd, radd);
+ node->right = clean_stopword_intree(node->right, ladd_from, radd_from,
+ ladd_to, radd_to);
if (!node->right)
{
freetree(node);
@@ -266,19 +269,28 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
{
NODE *res = node;
bool isphrase;
- int ndistance,
- lladd,
- lradd,
- rladd,
- rradd;
+ int ndistance_from,
+ ndistance_to,
+ lladd_from,
+ lradd_from,
+ lladd_to,
+ lradd_to,
+ rladd_from,
+ rradd_from,
+ rladd_to,
+ rradd_to;
/* First, recurse */
- node->left = clean_stopword_intree(node->left, &lladd, &lradd);
- node->right = clean_stopword_intree(node->right, &rladd, &rradd);
+ node->left = clean_stopword_intree(node->left,
+ &lladd_from, &lradd_from, &lladd_to, &lradd_to);
+ node->right = clean_stopword_intree(node->right,
+ &rladd_from, &rradd_from, &rladd_to, &rradd_to);
/* Check if current node is OP_PHRASE, get its distance */
isphrase = (node->valnode->qoperator.oper == OP_PHRASE);
- ndistance = isphrase ? node->valnode->qoperator.distance : 0;
+ ndistance_from = isphrase ? node->valnode->qoperator.operator_data.distance_from : 0;
+ ndistance_to = isphrase ? node->valnode->qoperator.operator_data.distance_to : 0;
+
if (node->left == NULL && node->right == NULL)
{
@@ -293,9 +305,15 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
* corresponds to what TS_execute will do in non-stopword cases.
*/
if (isphrase)
- *ladd = *radd = lladd + ndistance + rladd;
+ {
+ *ladd_from = *radd_from = lladd_from + ndistance_from + rladd_from;
+ *ladd_to = *radd_to = lladd_to + ndistance_to + rladd_to;
+ }
else
- *ladd = *radd = Max(lladd, rladd);
+ {
+ *ladd_from = *radd_from = Max(lladd_from, rladd_from);
+ *ladd_to = *radd_to = Max(lladd_to, rladd_to);
+ }
freetree(node);
return NULL;
}
@@ -306,14 +324,18 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
if (isphrase)
{
/* operator's own distance must propagate to left */
- *ladd = lladd + ndistance + rladd;
- *radd = rradd;
+ *ladd_from = lladd_from + ndistance_from + rladd_from;
+ *ladd_to = lladd_to + ndistance_to + rladd_to;
+ *radd_from = rradd_from;
+ *radd_to = rradd_to;
}
else
{
/* at non-phrase op, just forget the left subnode entirely */
- *ladd = rladd;
- *radd = rradd;
+ *ladd_from = rladd_from;
+ *ladd_to = rladd_to;
+ *radd_from = rradd_from;
+ *radd_to = rradd_to;
}
res = node->right;
pfree(node);
@@ -325,14 +347,18 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
if (isphrase)
{
/* operator's own distance must propagate to right */
- *ladd = lladd;
- *radd = lradd + ndistance + rradd;
+ *ladd_from = lladd_from;
+ *ladd_to = lladd_to;
+ *radd_from = lradd_from + ndistance_from + rradd_from;
+ *radd_to = lradd_to + ndistance_to + rradd_to;
}
else
{
/* at non-phrase op, just forget the right subnode entirely */
- *ladd = lladd;
- *radd = lradd;
+ *ladd_from = lladd_from;
+ *ladd_to = lladd_to;
+ *radd_from = lradd_from;
+ *radd_to = lradd_to;
}
res = node->left;
pfree(node);
@@ -340,10 +366,15 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
else if (isphrase)
{
/* Absorb appropriate corrections at this level */
- node->valnode->qoperator.distance += lradd + rladd;
+ node->valnode->qoperator.operator_data.distance_from +=
+ lradd_from + rladd_from;
+ node->valnode->qoperator.operator_data.distance_to +=
+ lradd_to + rladd_to;
/* Propagate up any unaccounted-for corrections */
- *ladd = lladd;
- *radd = rradd;
+ *ladd_from = lladd_from;
+ *ladd_to = lladd_to;
+ *radd_from = rradd_from;
+ *radd_to = rradd_to;
}
else
{
@@ -390,8 +421,10 @@ cleanup_tsquery_stopwords(TSQuery in)
commonlen,
i;
NODE *root;
- int ladd,
- radd;
+ int ladd_from,
+ radd_from;
+ int ladd_to,
+ radd_to;
TSQuery out;
QueryItem *items;
char *operands;
@@ -400,7 +433,7 @@ cleanup_tsquery_stopwords(TSQuery in)
return in;
/* eliminate stop words */
- root = clean_stopword_intree(maketree(GETQUERY(in)), &ladd, &radd);
+ root = clean_stopword_intree(maketree(GETQUERY(in)), &ladd_from, &radd_from, &ladd_to, &radd_to);
if (root == NULL)
{
ereport(NOTICE,
diff --git a/src/backend/utils/adt/tsquery_op.c b/src/backend/utils/adt/tsquery_op.c
index 07bc609972..bcf44c0244 100644
--- a/src/backend/utils/adt/tsquery_op.c
+++ b/src/backend/utils/adt/tsquery_op.c
@@ -28,7 +28,8 @@ tsquery_numnode(PG_FUNCTION_ARGS)
}
static QTNode *
-join_tsqueries(TSQuery a, TSQuery b, int8 operator, uint16 distance)
+join_tsqueries(TSQuery a, TSQuery b, int8 operator,
+ uint16 distance_from, uint16 distance_to)
{
QTNode *res = (QTNode *) palloc0(sizeof(QTNode));
@@ -38,7 +39,10 @@ join_tsqueries(TSQuery a, TSQuery b, int8 operator, uint16 distance)
res->valnode->type = QI_OPR;
res->valnode->qoperator.oper = operator;
if (operator == OP_PHRASE)
- res->valnode->qoperator.distance = distance;
+ {
+ res->valnode->qoperator.operator_data.distance_from = distance_from;
+ res->valnode->qoperator.operator_data.distance_to = distance_to;
+ }
res->child = (QTNode **) palloc0(sizeof(QTNode *) * 2);
res->child[0] = QT2QTN(GETQUERY(b), GETOPERAND(b));
@@ -67,7 +71,7 @@ tsquery_and(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(a);
}
- res = join_tsqueries(a, b, OP_AND, 0);
+ res = join_tsqueries(a, b, OP_AND, 0, 0);
query = QTN2QT(res);
@@ -97,7 +101,7 @@ tsquery_or(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(a);
}
- res = join_tsqueries(a, b, OP_OR, 0);
+ res = join_tsqueries(a, b, OP_OR, 0, 0);
query = QTN2QT(res);
@@ -109,48 +113,68 @@ tsquery_or(PG_FUNCTION_ARGS)
}
Datum
-tsquery_phrase_distance(PG_FUNCTION_ARGS)
+tsquery_phrase_distance_range(PG_FUNCTION_ARGS)
{
TSQuery a = PG_GETARG_TSQUERY_COPY(0);
TSQuery b = PG_GETARG_TSQUERY_COPY(1);
+ int32 distance_from = PG_GETARG_INT32(2);
+ int32 distance_to = PG_GETARG_INT32(3);
QTNode *res;
TSQuery query;
- int32 distance = PG_GETARG_INT32(2);
- if (distance < 0 || distance > MAXENTRYPOS)
+ if (distance_from > MAXENTRYPOS || distance_to > MAXENTRYPOS)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("distance in phrase operator should be less than %d",
+ MAXENTRYPOS)));
+
+ if (distance_from > distance_to)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("distance in phrase operator should be non-negative and less than %d",
- MAXENTRYPOS)));
+ errmsg("Lower bound of range should be less or equal to upper bound")));
+
if (a->size == 0)
{
PG_FREE_IF_COPY(a, 1);
PG_RETURN_POINTER(b);
}
- else if (b->size == 0)
+ if (b->size == 0)
{
PG_FREE_IF_COPY(b, 1);
PG_RETURN_POINTER(a);
}
- res = join_tsqueries(a, b, OP_PHRASE, (uint16) distance);
+ res = join_tsqueries(a, b, OP_PHRASE,
+ (int16) distance_from, (int16) distance_to);
query = QTN2QT(res);
QTNFree(res);
- PG_FREE_IF_COPY(a, 0);
+ PG_FREE_IF_COPY(a, 1);
PG_FREE_IF_COPY(b, 1);
PG_RETURN_TSQUERY(query);
}
+Datum
+tsquery_phrase_distance(PG_FUNCTION_ARGS)
+{
+ PG_RETURN_POINTER(DirectFunctionCall4(
+ tsquery_phrase_distance_range,
+ PG_GETARG_DATUM(0),
+ PG_GETARG_DATUM(1),
+ PG_GETARG_DATUM(2),
+ PG_GETARG_DATUM(2)));
+}
+
Datum
tsquery_phrase(PG_FUNCTION_ARGS)
{
- PG_RETURN_POINTER(DirectFunctionCall3(
- tsquery_phrase_distance,
+ PG_RETURN_POINTER(DirectFunctionCall4(
+ tsquery_phrase_distance_range,
PG_GETARG_DATUM(0),
PG_GETARG_DATUM(1),
+ Int32GetDatum(1),
Int32GetDatum(1)));
}
diff --git a/src/backend/utils/adt/tsquery_util.c b/src/backend/utils/adt/tsquery_util.c
index cd310b87d5..c9e21d5be4 100644
--- a/src/backend/utils/adt/tsquery_util.c
+++ b/src/backend/utils/adt/tsquery_util.c
@@ -121,8 +121,16 @@ QTNodeCompare(QTNode *an, QTNode *bn)
return res;
}
- if (ao->oper == OP_PHRASE && ao->distance != bo->distance)
- return (ao->distance > bo->distance) ? -1 : 1;
+ if (ao->oper == OP_PHRASE)
+ {
+ if (ao->operator_data.distance_from != bo->operator_data.distance_from)
+ return (ao->operator_data.distance_from > bo->operator_data.distance_from) ? -1 : 1;
+
+ if (ao->operator_data.distance_to != bo->operator_data.distance_to)
+ return (ao->operator_data.distance_to > bo->operator_data.distance_to) ? -1 : 1;
+
+ return 0;
+ }
return 0;
}
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index 258fe47a24..4672bf3a9b 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -1430,6 +1430,97 @@ checkcondition_str(void *checkval, QueryOperand *val, ExecPhraseData *data)
#define TSPO_R_ONLY 0x02 /* emit positions appearing only in R */
#define TSPO_BOTH 0x04 /* emit positions appearing in both L&R */
+static bool
+TS_phrase_output_range(ExecPhraseData *data,
+ ExecPhraseData *Ldata,
+ ExecPhraseData *Rdata,
+ int from,
+ int to,
+ int max_npos)
+{
+ int Lindex = 0;
+ int Rindex = 0;
+
+ if ((Ldata->npos == 0 && Ldata->negate && Rdata->npos != 0) ||
+ (Rdata->npos == 0 && Rdata->negate && Ldata->npos != 0))
+ {
+ if (data != NULL)
+ {
+ int npos;
+ ExecPhraseData *LRdata;
+
+ if (Ldata->npos == 0)
+ {
+ npos = Rdata->npos;
+ LRdata = Rdata;
+ }
+ else
+ {
+ npos = Ldata->npos;
+ LRdata = Ldata;
+ }
+
+ for (Rindex = 0; Rindex < npos; Rindex++)
+ {
+ int end = WEP_GETPOS(LRdata->pos[Rindex]);
+ int start = end - LRdata->width;
+ if (data->pos == NULL)
+ {
+ data->pos = (WordEntryPos *)palloc(max_npos * sizeof(WordEntryPos));
+ data->allocated = true;
+ }
+ data->pos[data->npos++] = end;
+ data->width = end - start;
+ }
+ }
+ return true;
+ }
+
+ for (Lindex = 0; Lindex < Ldata->npos; Lindex++)
+ {
+ int Lend = WEP_GETPOS(Ldata->pos[Lindex]);
+ int Lstart = Lend - Ldata->width;
+
+ for (Rindex = 0; Rindex < Rdata->npos; Rindex++)
+ {
+ int Rend = WEP_GETPOS(Rdata->pos[Rindex]);
+ int Rstart = Rend - Rdata->width;
+
+ int from_pos = from < 0 ? Lstart + from : Lend + from + Rdata->width;
+ int to_pos = to < 0 ? Lstart + to : Lend + to + Rdata->width;
+
+ bool negate = Ldata->negate || Rdata->negate;
+ bool inside = from_pos <= Rend && Rend <= to_pos;
+
+ if ((!negate && inside) || (negate && !inside))
+ {
+ if (data != NULL)
+ {
+ if (data->pos == NULL)
+ {
+ data->pos = (WordEntryPos *)palloc(max_npos * sizeof(WordEntryPos));
+ data->allocated = true;
+ }
+ data->pos[data->npos++] = Max(Lend, Rend);
+ data->width = Max(Lend, Rend) - Min(Lstart, Rstart);
+ }
+ else
+ {
+ return true;
+ }
+ }
+ }
+ }
+
+ if (data && data->npos > 0)
+ {
+ Assert(data->npos <= max_npos);
+ return true;
+ }
+
+ return false;
+}
+
static bool
TS_phrase_output(ExecPhraseData *data,
ExecPhraseData *Ldata,
@@ -1649,15 +1740,10 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
if (curitem->qoperator.oper == OP_PHRASE)
{
- /*
- * Compute Loffset and Roffset suitable for phrase match, and
- * compute overall width of whole phrase match.
- */
- Loffset = curitem->qoperator.distance + Rdata.width;
- Roffset = 0;
- if (data)
- data->width = curitem->qoperator.distance +
- Ldata.width + Rdata.width;
+ return TS_phrase_output_range(data, &Ldata, &Rdata,
+ curitem->qoperator.operator_data.distance_from,
+ curitem->qoperator.operator_data.distance_to,
+ Ldata.npos + Rdata.npos);
}
else
{
@@ -1665,6 +1751,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
* For OP_AND, set output width and alignment like OP_OR (see
* comment below)
*/
+ Assert(curitem->qoperator.oper == OP_AND);
maxwidth = Max(Ldata.width, Rdata.width);
Loffset = maxwidth - Ldata.width;
Roffset = maxwidth - Rdata.width;
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f643f564a6..67e366a496 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8722,6 +8722,9 @@
{ oid => '5004', descr => 'phrase-concatenate with distance',
proname => 'tsquery_phrase', prorettype => 'tsquery',
proargtypes => 'tsquery tsquery int4', prosrc => 'tsquery_phrase_distance' },
+{ oid => '5017', descr => 'phrase-concatenate with distance',
+ proname => 'tsquery_phrase', prorettype => 'tsquery',
+ proargtypes => 'tsquery tsquery int4 int4', prosrc => 'tsquery_phrase_distance_range' },
{ oid => '3671',
proname => 'tsquery_not', prorettype => 'tsquery', proargtypes => 'tsquery',
prosrc => 'tsquery_not' },
diff --git a/src/include/tsearch/ts_type.h b/src/include/tsearch/ts_type.h
index ccf5701aa3..d610a91772 100644
--- a/src/include/tsearch/ts_type.h
+++ b/src/include/tsearch/ts_type.h
@@ -82,6 +82,7 @@ typedef struct
#define WEP_SETWEIGHT(x,v) ( (x) = ( (v) << 14 ) | ( (x) & 0x3fff ) )
#define WEP_SETPOS(x,v) ( (x) = ( (x) & 0xc000 ) | ( (v) & 0x3fff ) )
+#define MINENTRYPOS (-(1<<14))
#define MAXENTRYPOS (1<<14)
#define MAXNUMPOS (256)
#define LIMITPOS(x) ( ( (x) >= MAXENTRYPOS ) ? (MAXENTRYPOS-1) : (x) )
@@ -176,11 +177,19 @@ extern const int tsearch_op_priority[OP_COUNT];
/* get QueryOperator priority */
#define QO_PRIORITY(x) OP_PRIORITY(((QueryOperator *) (x))->oper)
+/* Additional data for operators */
+typedef struct OperatorData {
+ int16 distance_from;
+ int16 distance_to;
+} OperatorData;
+
+#define OPERATOR_DATA_INITIALIZE(x,v) { (x).distance_from = (x).distance_to = (v); }
+
typedef struct
{
QueryItemType type;
int8 oper; /* see above */
- int16 distance; /* distance between agrs for OP_PHRASE */
+ OperatorData operator_data; /* data for operator */
uint32 left; /* pointer to left operand. Right operand is
* item + 1, left operand is placed
* item+item->left */
diff --git a/src/include/tsearch/ts_utils.h b/src/include/tsearch/ts_utils.h
index 73e969fe9c..280a51e2ba 100644
--- a/src/include/tsearch/ts_utils.h
+++ b/src/include/tsearch/ts_utils.h
@@ -70,7 +70,7 @@ extern TSQuery parse_tsquery(char *buf,
extern void pushValue(TSQueryParserState state,
char *strval, int lenval, int16 weight, bool prefix);
extern void pushStop(TSQueryParserState state);
-extern void pushOperator(TSQueryParserState state, int8 oper, int16 distance);
+extern void pushOperator(TSQueryParserState state, int8 oper, OperatorData operator_data);
/*
* parse plain text and lexize words
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
index 6272e70e09..df6964c9d6 100644
--- a/src/test/regress/expected/tstypes.out
+++ b/src/test/regress/expected/tstypes.out
@@ -464,12 +464,138 @@ SELECT 'a & g' <-> 'b <-> d'::tsquery;
( 'a' & 'g' ) <-> ( 'b' <-> 'd' )
(1 row)
+SELECT tsquery_phrase('a <3> g', 'b & d');
+ tsquery_phrase
+-------------------------------
+ 'a' <3> 'g' <-> ( 'b' & 'd' )
+(1 row)
+
SELECT tsquery_phrase('a <3> g', 'b & d', 10);
tsquery_phrase
--------------------------------
'a' <3> 'g' <10> ( 'b' & 'd' )
(1 row)
+SELECT tsquery_phrase('a <3> g', 'b & d', -10);
+ tsquery_phrase
+---------------------------------
+ 'a' <3> 'g' <-10> ( 'b' & 'd' )
+(1 row)
+
+SELECT tsquery_phrase('a <3> g', 'b & d', 10, 12);
+ tsquery_phrase
+-----------------------------------
+ 'a' <3> 'g' <10,12> ( 'b' & 'd' )
+(1 row)
+
+SELECT tsquery_phrase('a <3> g', 'b & d', 10, -5);
+ERROR: Lower bound of range should be less or equal to upper bound
+SELECT tsquery_phrase('a <3> g', 'b & d', -10, 5);
+ tsquery_phrase
+-----------------------------------
+ 'a' <3> 'g' <-10,5> ( 'b' & 'd' )
+(1 row)
+
+SELECT tsquery_phrase('a <3> g', 'b & d', -10, -3);
+ tsquery_phrase
+------------------------------------
+ 'a' <3> 'g' <-10,-3> ( 'b' & 'd' )
+(1 row)
+
+SELECT 'a <-1000> b'::tsquery;
+ tsquery
+-----------------
+ 'a' <-1000> 'b'
+(1 row)
+
+SELECT 'a <,-1000> b'::tsquery;
+ tsquery
+------------------------
+ 'a' <-16384,-1000> 'b'
+(1 row)
+
+SELECT 'a <-1000,> b'::tsquery;
+ tsquery
+-------------------
+ 'a' <-1000,0> 'b'
+(1 row)
+
+SELECT 'a <1000> b'::tsquery;
+ tsquery
+----------------
+ 'a' <1000> 'b'
+(1 row)
+
+SELECT 'a <,1000> b'::tsquery;
+ tsquery
+------------------
+ 'a' <0,1000> 'b'
+(1 row)
+
+SELECT 'a <1000,> b'::tsquery;
+ tsquery
+----------------------
+ 'a' <1000,16384> 'b'
+(1 row)
+
+SELECT 'a <-10000000> b'::tsquery;
+ERROR: distance in phrase operator should not be less than -16384 and no greater than 16384
+LINE 1: SELECT 'a <-10000000> b'::tsquery;
+ ^
+SELECT 'a <,-10000000> b'::tsquery;
+ERROR: distance in phrase operator should not be less than -16384 and no greater than 16384
+LINE 1: SELECT 'a <,-10000000> b'::tsquery;
+ ^
+SELECT 'a <-10000000,> b'::tsquery;
+ERROR: distance in phrase operator should not be less than -16384 and no greater than 16384
+LINE 1: SELECT 'a <-10000000,> b'::tsquery;
+ ^
+SELECT 'a <10000000> b'::tsquery;
+ERROR: distance in phrase operator should not be less than -16384 and no greater than 16384
+LINE 1: SELECT 'a <10000000> b'::tsquery;
+ ^
+SELECT 'a <,10000000> b'::tsquery;
+ERROR: distance in phrase operator should not be less than -16384 and no greater than 16384
+LINE 1: SELECT 'a <,10000000> b'::tsquery;
+ ^
+SELECT 'a <10000000,> b'::tsquery;
+ERROR: distance in phrase operator should not be less than -16384 and no greater than 16384
+LINE 1: SELECT 'a <10000000,> b'::tsquery;
+ ^
+SELECT 'a <--> b'::tsquery;
+ERROR: syntax error in tsquery: "a <--> b"
+LINE 1: SELECT 'a <--> b'::tsquery;
+ ^
+SELECT 'a <--1> b'::tsquery;
+ERROR: syntax error in tsquery: "a <--1> b"
+LINE 1: SELECT 'a <--1> b'::tsquery;
+ ^
+SELECT 'a <,> b'::tsquery;
+ tsquery
+------------------------
+ 'a' <-16384,16384> 'b'
+(1 row)
+
+SELECT 'a <-,> b'::tsquery;
+ERROR: syntax error in tsquery: "a <-,> b"
+LINE 1: SELECT 'a <-,> b'::tsquery;
+ ^
+SELECT 'a <,-> b'::tsquery;
+ERROR: syntax error in tsquery: "a <,-> b"
+LINE 1: SELECT 'a <,-> b'::tsquery;
+ ^
+SELECT 'a <--1,> b'::tsquery;
+ERROR: syntax error in tsquery: "a <--1,> b"
+LINE 1: SELECT 'a <--1,> b'::tsquery;
+ ^
+SELECT 'a <,--1> b'::tsquery;
+ERROR: syntax error in tsquery: "a <,--1> b"
+LINE 1: SELECT 'a <,--1> b'::tsquery;
+ ^
+SELECT 'a <> b'::tsquery;
+ERROR: syntax error in tsquery: "a <> b"
+LINE 1: SELECT 'a <> b'::tsquery;
+ ^
-- tsvector-tsquery operations
SELECT 'a b:89 ca:23A,64b d:34c'::tsvector @@ 'd:AC & ca' as "true";
true
@@ -1020,6 +1146,186 @@ SELECT 'a:1 b:3'::tsvector @@ 'a <0> a:*'::tsquery AS "true";
t
(1 row)
+SELECT 'a:1 b:2'::tsvector @@ 'a <1,2> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <1,2> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ 'a <1,2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ 'a <,2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ 'a <2,> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ '!a <1,2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ '!a <2,> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ '!a <2,> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ '!a <,2> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ '!a <,2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <1,2> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ 'a <2,> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <2,> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'a:1 b:4'::tsvector @@ 'a <,2> !b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'a:1 b:3'::tsvector @@ 'a <,2> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:2'::tsvector @@ 'a <-2,-1> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ 'a <-2,-1> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ 'a <-2,-1> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ 'a <-2,> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ 'a <,-2> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ '!a <-2,-1> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ '!a <,-2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ '!a <,-2> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ '!a <-2,> b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ '!a <-2,> b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ 'a <-2,-1> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ 'a <,-2> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ 'a <,-2> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
+SELECT 'b:1 a:4'::tsvector @@ 'a <-2,> !b'::tsquery AS "true";
+ true
+------
+ t
+(1 row)
+
+SELECT 'b:1 a:3'::tsvector @@ 'a <-2,> !b'::tsquery AS "false";
+ false
+-------
+ f
+(1 row)
+
-- tsvector editing operations
SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector);
strip
diff --git a/src/test/regress/sql/tstypes.sql b/src/test/regress/sql/tstypes.sql
index 0a40ec9350..73e79a981f 100644
--- a/src/test/regress/sql/tstypes.sql
+++ b/src/test/regress/sql/tstypes.sql
@@ -84,7 +84,34 @@ SELECT 'a' <-> 'b & d'::tsquery;
SELECT 'a & g' <-> 'b & d'::tsquery;
SELECT 'a & g' <-> 'b | d'::tsquery;
SELECT 'a & g' <-> 'b <-> d'::tsquery;
+SELECT tsquery_phrase('a <3> g', 'b & d');
SELECT tsquery_phrase('a <3> g', 'b & d', 10);
+SELECT tsquery_phrase('a <3> g', 'b & d', -10);
+SELECT tsquery_phrase('a <3> g', 'b & d', 10, 12);
+SELECT tsquery_phrase('a <3> g', 'b & d', 10, -5);
+SELECT tsquery_phrase('a <3> g', 'b & d', -10, 5);
+SELECT tsquery_phrase('a <3> g', 'b & d', -10, -3);
+
+SELECT 'a <-1000> b'::tsquery;
+SELECT 'a <,-1000> b'::tsquery;
+SELECT 'a <-1000,> b'::tsquery;
+SELECT 'a <1000> b'::tsquery;
+SELECT 'a <,1000> b'::tsquery;
+SELECT 'a <1000,> b'::tsquery;
+SELECT 'a <-10000000> b'::tsquery;
+SELECT 'a <,-10000000> b'::tsquery;
+SELECT 'a <-10000000,> b'::tsquery;
+SELECT 'a <10000000> b'::tsquery;
+SELECT 'a <,10000000> b'::tsquery;
+SELECT 'a <10000000,> b'::tsquery;
+SELECT 'a <--> b'::tsquery;
+SELECT 'a <--1> b'::tsquery;
+SELECT 'a <,> b'::tsquery;
+SELECT 'a <-,> b'::tsquery;
+SELECT 'a <,-> b'::tsquery;
+SELECT 'a <--1,> b'::tsquery;
+SELECT 'a <,--1> b'::tsquery;
+SELECT 'a <> b'::tsquery;
-- tsvector-tsquery operations
@@ -192,6 +219,39 @@ SELECT 'a:1 b:3'::tsvector @@ 'a <2> b'::tsquery AS "true";
SELECT 'a:1 b:3'::tsvector @@ 'a <3> b'::tsquery AS "false";
SELECT 'a:1 b:3'::tsvector @@ 'a <0> a:*'::tsquery AS "true";
+SELECT 'a:1 b:2'::tsvector @@ 'a <1,2> b'::tsquery AS "true";
+SELECT 'a:1 b:3'::tsvector @@ 'a <1,2> b'::tsquery AS "true";
+SELECT 'a:1 b:4'::tsvector @@ 'a <1,2> b'::tsquery AS "false";
+SELECT 'a:1 b:4'::tsvector @@ 'a <,2> b'::tsquery AS "false";
+SELECT 'a:1 b:4'::tsvector @@ 'a <2,> b'::tsquery AS "true";
+SELECT 'a:1 b:3'::tsvector @@ '!a <1,2> b'::tsquery AS "false";
+SELECT 'a:1 b:4'::tsvector @@ '!a <2,> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ '!a <2,> b'::tsquery AS "false";
+SELECT 'a:1 b:4'::tsvector @@ '!a <,2> b'::tsquery AS "true";
+SELECT 'a:1 b:3'::tsvector @@ '!a <,2> b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <1,2> !b'::tsquery AS "false";
+SELECT 'a:1 b:4'::tsvector @@ 'a <2,> !b'::tsquery AS "false";
+SELECT 'a:1 b:3'::tsvector @@ 'a <2,> !b'::tsquery AS "false";
+SELECT 'a:1 b:4'::tsvector @@ 'a <,2> !b'::tsquery AS "true";
+SELECT 'a:1 b:3'::tsvector @@ 'a <,2> !b'::tsquery AS "false";
+
+SELECT 'b:1 a:2'::tsvector @@ 'a <-2,-1> b'::tsquery AS "true";
+SELECT 'b:1 a:3'::tsvector @@ 'a <-2,-1> b'::tsquery AS "true";
+SELECT 'b:1 a:4'::tsvector @@ 'a <-2,-1> b'::tsquery AS "false";
+SELECT 'b:1 a:4'::tsvector @@ 'a <-2,> b'::tsquery AS "false";
+SELECT 'b:1 a:4'::tsvector @@ 'a <,-2> b'::tsquery AS "true";
+SELECT 'b:1 a:3'::tsvector @@ '!a <-2,-1> b'::tsquery AS "false";
+SELECT 'b:1 a:4'::tsvector @@ '!a <,-2> b'::tsquery AS "false";
+SELECT 'b:1 a:3'::tsvector @@ '!a <,-2> b'::tsquery AS "false";
+SELECT 'b:1 a:4'::tsvector @@ '!a <-2,> b'::tsquery AS "true";
+SELECT 'b:1 a:3'::tsvector @@ '!a <-2,> b'::tsquery AS "false";
+SELECT 'b:1 a:3'::tsvector @@ 'a <-2,-1> !b'::tsquery AS "false";
+SELECT 'b:1 a:4'::tsvector @@ 'a <,-2> !b'::tsquery AS "false";
+SELECT 'b:1 a:3'::tsvector @@ 'a <,-2> !b'::tsquery AS "false";
+SELECT 'b:1 a:4'::tsvector @@ 'a <-2,> !b'::tsquery AS "true";
+SELECT 'b:1 a:3'::tsvector @@ 'a <-2,> !b'::tsquery AS "false";
+
+
-- tsvector editing operations
SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector);