diff --git a/src/backend/tsearch/ts_selfuncs.c b/src/backend/tsearch/ts_selfuncs.c
index 046f543..375cb5c 100644
--- a/src/backend/tsearch/ts_selfuncs.c
+++ b/src/backend/tsearch/ts_selfuncs.c
@@ -396,6 +396,7 @@ tsquery_opr_selec(QueryItem *item, char *operand,
 				break;
 
 			case OP_PHRASE:
+			case OP_AROUND:
 			case OP_AND:
 				s1 = tsquery_opr_selec(item + 1, operand,
 									   lookup, length, minfreq);
diff --git a/src/backend/utils/adt/tsginidx.c b/src/backend/utils/adt/tsginidx.c
index aba456e..1c852bd 100644
--- a/src/backend/utils/adt/tsginidx.c
+++ b/src/backend/utils/adt/tsginidx.c
@@ -239,10 +239,11 @@ TS_execute_ternary(GinChkVal *gcv, QueryItem *curitem, bool in_phrase)
 			return !result;
 
 		case OP_PHRASE:
+		case OP_AROUND:
 
 			/*
 			 * GIN doesn't contain any information about positions, so treat
-			 * OP_PHRASE as OP_AND with recheck requirement
+			 * OP_PHRASE and OP_AROUND as OP_AND with recheck requirement
 			 */
 			*(gcv->need_recheck) = true;
 			/* Pass down in_phrase == true in case there's a NOT below */
diff --git a/src/backend/utils/adt/tsquery.c b/src/backend/utils/adt/tsquery.c
index 5cdfe4d..9c04391 100644
--- a/src/backend/utils/adt/tsquery.c
+++ b/src/backend/utils/adt/tsquery.c
@@ -29,7 +29,8 @@ const int	tsearch_op_priority[OP_COUNT] =
 	4,							/* OP_NOT */
 	2,							/* OP_AND */
 	1,							/* OP_OR */
-	3							/* OP_PHRASE */
+	3,							/* OP_PHRASE */
+	3							/* OP_AROUND */
 };
 
 struct TSQueryParserStateData
@@ -58,10 +59,10 @@ struct TSQueryParserStateData
 };
 
 /* parser's states */
-#define WAITOPERAND 1
-#define WAITOPERATOR	2
-#define WAITFIRSTOPERAND 3
-#define WAITSINGLEOPERAND 4
+#define WAITOPERAND			1
+#define WAITOPERATOR		2
+#define WAITFIRSTOPERAND	3
+#define WAITSINGLEOPERAND	4
 
 /*
  * subroutine to parse the modifiers (weight and prefix flag currently)
@@ -211,6 +212,65 @@ typedef enum
 } ts_tokentype;
 
 /*
+ * Checks if 'str' starts with a 'prefix'
+ */
+static bool
+has_prefix(char * str, char * prefix)
+{
+	if (strlen(prefix) > strlen(str))
+	{
+		return false;
+	}
+	return strstr(str, prefix) == str;
+}
+
+/*
+ * Parse around operator. The operator
+ * have the following form:
+ *
+ *		a AROUND(X) b (distance is no greater than X)
+ *
+ * The buffer should begins with "AROUND(" prefix
+ */
+static char *
+parse_around_operator(char *buf, int16 *distance)
+{
+	char	   *ptr = buf;
+	char	   *endptr;
+	long		l = 1;
+
+	Assert(has_prefix(ptr, "AROUND("));
+
+	ptr += strlen("AROUND(");
+
+	while (t_isspace(ptr))
+		ptr++;
+
+	l = strtol(ptr, &endptr, 10);
+	if (ptr == endptr)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Invalid AROUND(X) operator!")));
+	else if (errno == ERANGE || l > MAXENTRYPOS)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("distance in AROUND operator should not be greater than %d",
+						MAXENTRYPOS)));
+
+	ptr = endptr;
+	*distance = l;
+	while (t_isspace(ptr))
+		ptr++;
+
+	if (!t_iseq(ptr, ')'))
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Missing ')' in AROUND(X) operator")));
+
+	return ++ptr;
+}
+
+/*
  * get token from query string
  *
  * *operator is filled in with OP_* when return values is PT_OPR,
@@ -301,6 +361,16 @@ gettoken_query(TSQueryParserState state,
 						return PT_ERR;
 					return PT_OPR;
 				}
+				else if (has_prefix(state->buf, "AROUND("))
+				{
+					state->state = WAITOPERAND;
+					*operator = OP_AROUND;
+					/* weight var is used as storage for distance */
+					state->buf = parse_around_operator(state->buf, weight);
+					if (*weight < 0)
+						return PT_ERR;
+					return PT_OPR;
+				}
 				else if (t_iseq(state->buf, ')'))
 				{
 					(state->buf)++;
@@ -336,12 +406,13 @@ pushOperator(TSQueryParserState state, int8 oper, int16 distance)
 {
 	QueryOperator *tmp;
 
-	Assert(oper == OP_NOT || oper == OP_AND || oper == OP_OR || oper == OP_PHRASE);
+	Assert(oper == OP_NOT || oper == OP_AND || oper == OP_OR
+		|| oper == OP_PHRASE || oper == OP_AROUND);
 
 	tmp = (QueryOperator *) palloc0(sizeof(QueryOperator));
 	tmp->type = QI_OPR;
 	tmp->oper = oper;
-	tmp->distance = (oper == OP_PHRASE) ? distance : 0;
+	tmp->distance = (oper == OP_PHRASE || oper == OP_AROUND) ? distance : 0;
 	/* left is filled in later with findoprnd */
 
 	state->polstr = lcons(tmp, state->polstr);
@@ -555,7 +626,8 @@ findoprnd_recurse(QueryItem *ptr, uint32 *pos, int nnodes, bool *needcleanup)
 
 			Assert(curitem->oper == OP_AND ||
 				   curitem->oper == OP_OR ||
-				   curitem->oper == OP_PHRASE);
+				   curitem->oper == OP_PHRASE ||
+				   curitem->oper == OP_AROUND);
 
 			(*pos)++;
 
@@ -884,6 +956,9 @@ infix(INFIX *in, int parentPriority, bool rightPhraseOp)
 				else
 					sprintf(in->cur, " <-> %s", nrm.buf);
 				break;
+			case OP_AROUND:
+				snprintf(in->cur, 256, " AROUND(%d) %s", distance, nrm.buf);
+				break;
 			default:
 				/* OP_NOT is handled in above if-branch */
 				elog(ERROR, "unrecognized operator type: %d", op);
@@ -966,7 +1041,8 @@ tsquerysend(PG_FUNCTION_ARGS)
 				break;
 			case QI_OPR:
 				pq_sendint8(&buf, item->qoperator.oper);
-				if (item->qoperator.oper == OP_PHRASE)
+				if (item->qoperator.oper == OP_PHRASE
+					|| item->qoperator.oper == OP_AROUND)
 					pq_sendint16(&buf, item->qoperator.distance);
 				break;
 			default:
@@ -1062,14 +1138,15 @@ tsqueryrecv(PG_FUNCTION_ARGS)
 			int8		oper;
 
 			oper = (int8) pq_getmsgint(buf, sizeof(int8));
-			if (oper != OP_NOT && oper != OP_OR && oper != OP_AND && oper != OP_PHRASE)
+			if (oper != OP_NOT && oper != OP_OR && oper != OP_AND
+				&& oper != OP_PHRASE && oper != OP_AROUND)
 				elog(ERROR, "invalid tsquery: unrecognized operator type %d",
 					 (int) oper);
 			if (i == size - 1)
 				elog(ERROR, "invalid pointer to right operand");
 
 			item->qoperator.oper = oper;
-			if (oper == OP_PHRASE)
+			if (oper == OP_PHRASE || oper == OP_AROUND)
 				item->qoperator.distance = (int16) pq_getmsgint(buf, sizeof(int16));
 		}
 		else
diff --git a/src/backend/utils/adt/tsquery_cleanup.c b/src/backend/utils/adt/tsquery_cleanup.c
index 350171c..e679bb5 100644
--- a/src/backend/utils/adt/tsquery_cleanup.c
+++ b/src/backend/utils/adt/tsquery_cleanup.c
@@ -161,7 +161,8 @@ clean_NOT_intree(NODE *node)
 		NODE	   *res = node;
 
 		Assert(node->valnode->qoperator.oper == OP_AND ||
-			   node->valnode->qoperator.oper == OP_PHRASE);
+			   node->valnode->qoperator.oper == OP_PHRASE ||
+			   node->valnode->qoperator.oper == OP_AROUND);
 
 		node->left = clean_NOT_intree(node->left);
 		node->right = clean_NOT_intree(node->right);
@@ -277,7 +278,8 @@ clean_stopword_intree(NODE *node, int *ladd, int *radd)
 		node->right = clean_stopword_intree(node->right, &rladd, &rradd);
 
 		/* Check if current node is OP_PHRASE, get its distance */
-		isphrase = (node->valnode->qoperator.oper == OP_PHRASE);
+		isphrase = (node->valnode->qoperator.oper == OP_PHRASE ||
+					node->valnode->qoperator.oper == OP_AROUND);
 		ndistance = isphrase ? node->valnode->qoperator.distance : 0;
 
 		if (node->left == NULL && node->right == NULL)
diff --git a/src/backend/utils/adt/tsquery_op.c b/src/backend/utils/adt/tsquery_op.c
index 755c3e9..7cf9b8a 100644
--- a/src/backend/utils/adt/tsquery_op.c
+++ b/src/backend/utils/adt/tsquery_op.c
@@ -37,7 +37,7 @@ join_tsqueries(TSQuery a, TSQuery b, int8 operator, uint16 distance)
 	res->valnode = (QueryItem *) palloc0(sizeof(QueryItem));
 	res->valnode->type = QI_OPR;
 	res->valnode->qoperator.oper = operator;
-	if (operator == OP_PHRASE)
+	if (operator == OP_PHRASE || operator == OP_AROUND)
 		res->valnode->qoperator.distance = distance;
 
 	res->child = (QTNode **) palloc0(sizeof(QTNode *) * 2);
diff --git a/src/backend/utils/adt/tsquery_util.c b/src/backend/utils/adt/tsquery_util.c
index 971bb81..548a846 100644
--- a/src/backend/utils/adt/tsquery_util.c
+++ b/src/backend/utils/adt/tsquery_util.c
@@ -121,7 +121,7 @@ QTNodeCompare(QTNode *an, QTNode *bn)
 					return res;
 		}
 
-		if (ao->oper == OP_PHRASE && ao->distance != bo->distance)
+		if ((ao->oper == OP_PHRASE || ao->oper == OP_AROUND) && ao->distance != bo->distance)
 			return (ao->distance > bo->distance) ? -1 : 1;
 
 		return 0;
@@ -171,7 +171,8 @@ QTNSort(QTNode *in)
 
 	for (i = 0; i < in->nchild; i++)
 		QTNSort(in->child[i]);
-	if (in->nchild > 1 && in->valnode->qoperator.oper != OP_PHRASE)
+	if (in->nchild > 1 && in->valnode->qoperator.oper != OP_PHRASE
+		&& in->valnode->qoperator.oper != OP_AROUND)
 		qsort((void *) in->child, in->nchild, sizeof(QTNode *), cmpQTN);
 }
 
diff --git a/src/backend/utils/adt/tsrank.c b/src/backend/utils/adt/tsrank.c
index 4577bcc..b62b14d 100644
--- a/src/backend/utils/adt/tsrank.c
+++ b/src/backend/utils/adt/tsrank.c
@@ -366,7 +366,8 @@ calc_rank(const float *w, TSVector t, TSQuery q, int32 method)
 
 	/* XXX: What about NOT? */
 	res = (item->type == QI_OPR && (item->qoperator.oper == OP_AND ||
-									item->qoperator.oper == OP_PHRASE)) ?
+									item->qoperator.oper == OP_PHRASE ||
+									item->qoperator.oper == OP_AROUND)) ?
 		calc_rank_and(w, t, q) :
 		calc_rank_or(w, t, q);
 
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index 8225202..ce7ee25 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -1429,6 +1429,7 @@ checkcondition_str(void *checkval, QueryOperand *val, ExecPhraseData *data)
 #define TSPO_L_ONLY		0x01	/* emit positions appearing only in L */
 #define TSPO_R_ONLY		0x02	/* emit positions appearing only in R */
 #define TSPO_BOTH		0x04	/* emit positions appearing in both L&R */
+#define TSPO_NOT_EXAC	0x08	/* not exact distance for AROUND(X) */
 
 static bool
 TS_phrase_output(ExecPhraseData *data,
@@ -1473,8 +1474,18 @@ TS_phrase_output(ExecPhraseData *data,
 			Rpos = INT_MAX;
 		}
 
+		/* Processing OP_AROUND */
+		if ((emit & TSPO_NOT_EXAC) &&
+			Lpos - Rpos >= 0 &&
+			Lpos - Rpos <= (Loffset + Roffset) * 2 - Rdata->width + Ldata->width)
+		{
+			if (emit & TSPO_BOTH)
+				output_pos = Rpos;
+			Lindex++;
+			Rindex++;
+		}
 		/* Merge-join the two input lists */
-		if (Lpos < Rpos)
+		else if (Lpos < Rpos)
 		{
 			/* Lpos is not matched in Rdata, should we output it? */
 			if (emit & TSPO_L_ONLY)
@@ -1625,6 +1636,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
 			}
 
 		case OP_PHRASE:
+		case OP_AROUND:
 		case OP_AND:
 			memset(&Ldata, 0, sizeof(Ldata));
 			memset(&Rdata, 0, sizeof(Rdata));
@@ -1647,7 +1659,7 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
 				(Rdata.npos == 0 && !Rdata.negate))
 				return (flags & TS_EXEC_PHRASE_NO_POS) ? true : false;
 
-			if (curitem->qoperator.oper == OP_PHRASE)
+			if (curitem->qoperator.oper == OP_PHRASE || curitem->qoperator.oper == OP_AROUND)
 			{
 				/*
 				 * Compute Loffset and Roffset suitable for phrase match, and
@@ -1703,7 +1715,8 @@ TS_phrase_execute(QueryItem *curitem, void *arg, uint32 flags,
 			{
 				/* straight AND */
 				return TS_phrase_output(data, &Ldata, &Rdata,
-										TSPO_BOTH,
+										TSPO_BOTH |
+										(curitem->qoperator.oper == OP_AROUND ? TSPO_NOT_EXAC : 0),
 										Loffset, Roffset,
 										Min(Ldata.npos, Rdata.npos));
 			}
@@ -1843,6 +1856,7 @@ TS_execute(QueryItem *curitem, void *arg, uint32 flags,
 				return TS_execute(curitem + 1, arg, flags, chkcond);
 
 		case OP_PHRASE:
+		case OP_AROUND:
 			return TS_phrase_execute(curitem, arg, flags, chkcond, NULL);
 
 		default:
@@ -1882,9 +1896,10 @@ tsquery_requires_match(QueryItem *curitem)
 			return false;
 
 		case OP_PHRASE:
+		case OP_AROUND:
 
 			/*
-			 * Treat OP_PHRASE as OP_AND here
+			 * Treat OP_PHRASE and OP_AROUND as OP_AND here
 			 */
 		case OP_AND:
 			/* If either side requires a match, we're good */
diff --git a/src/include/tsearch/ts_type.h b/src/include/tsearch/ts_type.h
index 30d7c4b..ee1a184 100644
--- a/src/include/tsearch/ts_type.h
+++ b/src/include/tsearch/ts_type.h
@@ -166,8 +166,9 @@ typedef struct
 #define OP_NOT			1
 #define OP_AND			2
 #define OP_OR			3
-#define OP_PHRASE		4		/* highest code, tsquery_cleanup.c */
-#define OP_COUNT		4
+#define OP_PHRASE		4
+#define OP_AROUND		5		/* highest code, tsquery_cleanup.c */
+#define OP_COUNT		5
 
 extern const int tsearch_op_priority[OP_COUNT];
 
