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);