Hello hackers,
Updated version of the patch in the attachment.
--
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 8075ea94e7..a661373d1b 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,
@@ -3917,7 +3949,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 4b44b85642..cbd11fded7 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 7b9dbfef0c..ae0b5bf775 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
*/
@@ -63,9 +69,9 @@ typedef enum
* *strval, *lenval and *weight are filled in when return value is PT_VAL
*
*/
-typedef ts_tokentype (*ts_tokenizer) (TSQueryParserState state, int8 *operator,
- int *lenval, char **strval,
- int16 *weight, bool *prefix);
+typedef ts_tokentype (*ts_tokenizer)(TSQueryParserState state, int8 *operator,
+ int *lenval, char **strval,
+ 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)
@@ -320,7 +437,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;
}
@@ -351,7 +468,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;
@@ -382,9 +499,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)
@@ -502,7 +619,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
{
@@ -523,9 +640,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')
@@ -542,7 +659,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;
@@ -551,7 +668,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);
@@ -643,17 +761,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)++;
}
@@ -673,7 +791,7 @@ cleanOpStack(TSQueryParserState state,
(*lenstack)--;
pushOperator(state, stack[*lenstack].op,
- stack[*lenstack].distance);
+ stack[*lenstack].operator_data);
}
}
@@ -693,7 +811,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 */
@@ -701,16 +819,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);
@@ -1069,7 +1187,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;
@@ -1096,8 +1215,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:
@@ -1107,10 +1231,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 */
@@ -1195,7 +1321,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);
@@ -1298,7 +1427,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 40d54ed030..ded27a6a7f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8725,6 +8725,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 d59e38c36b..2eacfa1dfd 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);