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>&lt;<replaceable>[N]</replaceable>,<replaceable>[M]</replaceable>&gt;</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>&lt;0,<replaceable>M</replaceable>&gt;</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 &lt;1,2&gt; cat'::tsquery;
+      tsquery      
+-------------------
+ 'big' &lt;1,2&gt; 'cat'
+(1 row)
+
+SELECT 'big &lt;,2&gt; cat'::tsquery;
+      tsquery      
+-------------------
+ 'big' &lt;0,2&gt; 'cat'
+(1 row)
+
+SELECT 'big &lt;1,&gt; cat'::tsquery;
+        tsquery        
+-----------------------
+ 'big' &lt;1,16384&gt; '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>&lt;<replaceable>N</replaceable>&gt;</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);

Reply via email to