diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index e08bf60..fe93058 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -9086,16 +9086,28 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple setweight - setweight(tsvector, "char") + setweight(tsin tsvector, w "char") tsvector - assign weight to each element of tsvector + assign weight w to each element of tsin setweight('fat:2,4 cat:3 rat:5B'::tsvector, 'A') 'cat':3A 'fat':2A,4A 'rat':5A + setweight_by_filter + + setweight(tsin tsvector, w "char", lexarr "text"[]) + + tsvector + assign weight w to elements of tsin that are listed in lexemes array lexarr + setweight('fat:2,4 cat:3 rat:5B'::tsvector, 'A', '{cat,rat}') + 'cat':3A 'fat':2,4 'rat':5A + + + + strip strip(tsvector) @@ -9108,6 +9120,108 @@ CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow', 'green', 'blue', 'purple + delete_str + + delete(tsin tsvector, lexeme text) + + tsvector + remove given lexeme from tsin + delete('fat:2,4 cat:3 rat:5A'::tsvector, 'fat') + 'cat':3 'rat':5A + + + + + delete_arr + + delete(tsin tsvector, lexarr text[]) + + tsvector + remove any occurrence of lexemes in lexarr from tsin + delete('fat:2,4 cat:3 rat:5A'::tsvector, ARRAY['fat','rat']) + 'cat':3 + + + + + delete_tsvector + + delete(tsin tsvector, tsv_filter tsvector) + + tsvector + + Delete lexemes and/or positions of tsv_filter from tsin. + When lexeme in tsv_filter has no positions function will delete any occurence of same lexeme in tsin. When tsv_filter lexeme have positions function will delete them from positions of matching lexeme in tsin. If after such removal resulting positions set is empty then function will delete that lexeme from resulting tsvector. + + delete('fat:2,4 cat:3 rat:5A'::tsvector, 'fat:2,6 rat'::tsvector) + 'cat':3 'fat':4 + + + + + unnest + + unnest(tsvector) + + setof anyelement + expand a tsvector to a set of rows. Each row has following columns: lexeme, postings, weights. + unnest('fat:2,4 cat:3 rat:5A'::tsvector) + cat {3} {A} +fat {2,4} {D,D} +rat {5} {A} +(3 rows) + + + + + to_array + + to_array(tsvector) + + text[] + convert tsvector to array of lexemes + to_array('fat:2,4 cat:3 rat:5A'::tsvector) + {cat,fat,rat} + + + + + array_to_tsvector + + to_tsvector(text[]) + + tsvector + convert array of lexemes to tsvector + to_tsvector('{fat,cat,rat}'::text[]) + 'fat' 'cat' 'rat' + + + + + filter + + filter(tsin tsvector, weights "char"[]) + + tsvector + Select only elements with given weights from tsin + filter('fat:2,4 cat:3b rat:5A'::tsvector, '{a,b}') + 'cat':3B 'rat':5A + + + + + shift + + shift(tsin tsvector, offset int16) + + tsvector + Shift all positions in tsin by given offset + shift('fat:2,4 cat:3b rat:5A'::tsvector, 10) + 'cat':13B 'fat':12,14 'rat':15A + + + + to_tsquery to_tsquery( config regconfig , query text) diff --git a/doc/src/sgml/textsearch.sgml b/doc/src/sgml/textsearch.sgml index d66b4d5..32033fa 100644 --- a/doc/src/sgml/textsearch.sgml +++ b/doc/src/sgml/textsearch.sgml @@ -1326,6 +1326,10 @@ FROM (SELECT id, body, q, ts_rank_cd(ti, q) AS rank + + Full list of tsvector-related functions available in . + + diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c index e822ba8..adc2128 100644 --- a/src/backend/utils/adt/tsvector_op.c +++ b/src/backend/utils/adt/tsvector_op.c @@ -14,6 +14,7 @@ #include "postgres.h" +#include "access/htup_details.h" #include "catalog/namespace.h" #include "catalog/pg_type.h" #include "commands/trigger.h" @@ -65,6 +66,7 @@ typedef struct #define STATHDRSIZE (offsetof(TSVectorStat, data)) static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column); +static int tsvector_bsearch(TSVector tsin, char *lexin, int lexin_len); /* * Order: haspos, len, word, for all positions (pos, weight) @@ -194,6 +196,83 @@ tsvector_length(PG_FUNCTION_ARGS) PG_RETURN_INT32(ret); } +/* + * setweight(tsin tsvector, w "char", lexarr "text"[]) + * + * Assign weight w to elements of tsin that are listed in lexarr. + */ +Datum +tsvector_setweight_by_filter(PG_FUNCTION_ARGS) +{ + TSVector in = PG_GETARG_TSVECTOR(0); + char cw = PG_GETARG_CHAR(1); + ArrayType *lexarr = NULL; + TSVector out; + int i, + j, + nlex = 0, + lex_len, + w = 0, + lex_pos; + WordEntry *entry; + WordEntryPos *p; + Datum *dlexemes; + bool *nulls; + char *lex; + + switch (cw) + { + case 'A': case 'a': + w = 3; + break; + case 'B': case 'b': + w = 2; + break; + case 'C': case 'c': + w = 1; + break; + case 'D': case 'd': + w = 0; + break; + default: + /* internal error */ + elog(ERROR, "unrecognized weight: %d", cw); + } + + out = (TSVector) palloc(VARSIZE(in)); + memcpy(out, in, VARSIZE(in)); + entry = ARRPTR(out); + + lexarr = PG_GETARG_ARRAYTYPE_P(2); + deconstruct_array(lexarr, TEXTOID, -1, false, 'i', + &dlexemes, &nulls, &nlex); + + /* + * Assuming that lexarr is significantly shorter than tsvector + * we can iterate through lexarr performing binary search + * of each lexeme from lexarr in tsvector. + */ + for (i = 0; i < nlex; i++) + { + lex = VARDATA(dlexemes[i]); + lex_len = VARSIZE_ANY_EXHDR(dlexemes[i]); + lex_pos = tsvector_bsearch(out, lex, lex_len); + + if (lex_pos >= 0 && (j = POSDATALEN(out, entry + lex_pos)) != 0 ) + { + p = POSDATAPTR(out, entry + lex_pos); + while (j--) + { + WEP_SETWEIGHT(*p, w); + p++; + } + } + } + + PG_FREE_IF_COPY(in, 0); + PG_RETURN_POINTER(out); +} + Datum tsvector_setweight(PG_FUNCTION_ARGS) { @@ -291,6 +370,635 @@ add_pos(TSVector src, WordEntry *srcptr, return *clen - startlen; } +/* + * Perform binary search of given lexeme in TSVector. + * Returns lexeme position in TSVector's entry array or -1 if lexeme wasn't + * found. + */ +static int +tsvector_bsearch(const TSVector tsv, char *lexeme, int lexeme_len) +{ + WordEntry *arrin = ARRPTR(tsv); + int StopLow = 0, + StopHigh = tsv->size, + StopMiddle, + cmp; + + while (StopLow < StopHigh) + { + StopMiddle = (StopLow + StopHigh)/2; + + cmp = tsCompareString(lexeme, lexeme_len, + STRPTR(tsv) + arrin[StopMiddle].pos, + arrin[StopMiddle].len, + false); + + if (cmp < 0) + StopHigh = StopMiddle; + else if (cmp > 0) + StopLow = StopMiddle + 1; + else /* found it */ + return StopMiddle; + } + + return -1; +} + +static int +compareint(const void *va, const void *vb) +{ + int32 a = *((const int32 *) va); + int32 b = *((const int32 *) vb); + + if (a == b) + return 0; + return (a > b) ? 1 : -1; +} + +/* + * Internal routine to delete lexemes from TSVector by array of offsets. + * + * int *indices_to_delete -- array of lexeme offsets to delete + * int indices_count -- size of that array + * + * Returns new TSVector without given lexemes along with their positions + * and weights. + */ +static TSVector +_tsvector_delete(TSVector tsv, int *indices_to_delete, int indices_count) +{ + TSVector tsout; + WordEntry *arrin = ARRPTR(tsv), + *arrout; + char *data = STRPTR(tsv), + *dataout; + int i, j, k, + curoff; + + /* + * Here we overestimates tsout size, since we don't know exact size + * occupied by positions and weights. We will set exact size later + * after a pass through TSVector. + */ + tsout = (TSVector) palloc0(VARSIZE(tsv)); + arrout = ARRPTR(tsout); + tsout->size = tsv->size - indices_count; + + /* Sort our filter array to simplify membership check later. */ + qsort(indices_to_delete, indices_count, sizeof(int), compareint); + + /* + * Copy tsv to tsout skipping lexemes that enlisted in indices_to_delete. + */ + curoff = 0; + dataout = STRPTR(tsout); + for (i = j = k = 0; i < tsv->size; i++) + { + /* + * Here we should check whether current i is present in indices_to_delete + * or not. Since indices_to_delete is already sorted we can advance + * it index only when we have match. + */ + if (k < indices_count && i == indices_to_delete[k]){ + k++; + continue; + } + + /* Copy lexeme, it's positions and weights */ + memcpy(dataout + curoff, data + arrin[i].pos, arrin[i].len); + arrout[j].haspos = arrin[i].haspos; + arrout[j].len = arrin[i].len; + arrout[j].pos = curoff; + curoff += arrin[i].len; + if (arrin[i].haspos) + { + int len = POSDATALEN(tsv, arrin+i) * sizeof(WordEntryPos) + sizeof(uint16); + curoff = SHORTALIGN(curoff); + memcpy(dataout + curoff, (STRPTR(tsv) + SHORTALIGN(arrin[i].pos + arrin[i].len)), len); + curoff += len; + } + + j++; + } + + /* + * After the pass through TSVector k should equals exactly to indices_count. + * If it isn't then the caller provided us with indices outside of [0, tsv->size) + * range and estimation of tsout's size is wrong. + */ + Assert(k == indices_count); + + SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, curoff)); + return tsout; +} + +/* + * Delete given lexeme from tsvector. + * Implementation of user-level delete(tsvector, text). + */ +Datum +tsvector_delete_str(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + char *lexin = VARDATA(PG_GETARG_TEXT_P(1)); + int lexin_len = VARSIZE_ANY_EXHDR(PG_GETARG_TEXT_P(1)), + skip_index; + + if ((skip_index = tsvector_bsearch(tsin, lexin, lexin_len)) == -1) + PG_RETURN_POINTER(tsin); + + tsout = _tsvector_delete(tsin, &skip_index, 1); + + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(tsout); +} + +/* + * Delete given array of lexemes from tsvector. + * Implementation of user-level delete(tsvector, text[]). + */ +Datum +tsvector_delete_arr(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + ArrayType *lexarr = PG_GETARG_ARRAYTYPE_P(1); + int i, nlex, + skip_count, + *skip_indices; + Datum *dlexemes; + bool *nulls; + + deconstruct_array(lexarr, TEXTOID, -1, false, 'i', + &dlexemes, &nulls, &nlex); + + /* + * In typical use case array of lexemes to delete is relatively small. + * So here we optimizing things for that scenario: iterate through lexarr + * performing binary search of each lexeme from lexarr in tsvector. + */ + skip_indices = palloc0(nlex*sizeof(int)); + for (i = skip_count = 0; i < nlex; i++) + { + char *lex = VARDATA(dlexemes[i]); + int lex_len = VARSIZE_ANY_EXHDR(dlexemes[i]); + int lex_pos = tsvector_bsearch(tsin, lex, lex_len); + + if (lex_pos >= 0) + skip_indices[skip_count++] = lex_pos; + } + + tsout = _tsvector_delete(tsin, skip_indices, skip_count); + + pfree(skip_indices); + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(tsout); +} + +/* + * Delete lexemes and/or positions of second tsvector from first tsvector. + * Implementation of user-level delete(tsvector, tsvector). + * + * When lexeme in second tsvector (ts2) has no positions function will + * delete any occurence of same lexeme in first tsvector (ts1). + * When ts2 lexeme have positions function will delete them from positions + * of matching lexeme in ts1. If after such removal resulting positions set is + * empty then function will delete that lexeme from resulting tsvector. + */ +Datum +tsvector_delete_tsvector(PG_FUNCTION_ARGS) +{ + TSVector ts1 = PG_GETARG_TSVECTOR(0), + ts2 = PG_GETARG_TSVECTOR(1), + tsout; + WordEntry *arr1 = ARRPTR(ts1), + *arr2 = ARRPTR(ts2), + *arrout; + char *data1 = STRPTR(ts1), + *data2 = STRPTR(ts2), + *dataout; + int i_out, i1, i2, + curoff = 0, + match_count = 0; + + /* + * As in the tsvector_delete_arr() we optimize things for case with ts2 + * significantly smaller than ts1, so at first we find all occurences of + * ts2 lexemes in ts1. + */ + int *matched_indices = palloc0(ts2->size*sizeof(int)); + int *matched_indices_ts2 = palloc0(ts2->size*sizeof(int)); + for (i2 = 0; i2 < ts2->size; i2++) + { + char *lex = data2 + arr2[i2].pos; + int lex_len = arr2[i2].len; + int lex_pos = tsvector_bsearch(ts1, lex, lex_len); + int m; + + /* + * Since ts2 can contain lexemes, that are not present in ts1 + * we need to store both indices. + * Also both those arrays should be sorted because of lexemes sorting + * in tsvector. + */ + if (lex_pos >= 0){ + m = match_count++; + matched_indices[m] = lex_pos; + matched_indices_ts2[m] = i2; + } + } + + /* + * In contrast to tsvector_delete_arr() and tsvector_delete_str() + * here we should have logic about deleting ts2 positions info from + * ts1 position. So here the same logic as in _tsvector_delete(), + * but also with positions handling inside main loop. + */ + tsout = (TSVector) palloc0(VARSIZE(ts1)); + tsout->size = ts1->size; + arrout = ARRPTR(tsout); + dataout = STRPTR(tsout); + + /* + * Here we use indices starting with i for iterating through tsvectors and + * indices starting from j for iterating through positions. + */ + for (i_out = i1 = i2 = 0; i1 < ts1->size; i1++) + { + if (i2 < match_count && i1 == matched_indices[i2]) + { + /* + * Lexeme matched. If filtering vector has some positions on that + * lexeme than we should delete them from filtered vector. + */ + if (arr2[matched_indices_ts2[i2]].haspos && arr1[i1].haspos) + { + WordEntryPosVector *posv1 = _POSVECPTR(ts1, arr1 + i1); + WordEntryPosVector *posv2 = _POSVECPTR(ts2, arr2 + matched_indices_ts2[i2]); + int *pos_out = palloc0(posv1->npos*sizeof(int)); + int j1, j2, j_out; + + /* + * Substract (as a set) ts2 lexeme positions from ts1 lexeme positions. + * Here we used the fact that both positions arrays already sorted. + */ + for(j1 = j2 = j_out = 0; j1 < posv1->npos; j1++) + { + while (j2 < posv2->npos && + WEP_GETPOS(posv1->pos[j1]) > WEP_GETPOS(posv2->pos[j2])) + j2++; + + if (WEP_GETPOS(posv1->pos[j1]) < WEP_GETPOS(posv2->pos[j2])) + pos_out[j_out++] = posv1->pos[j1]; + } + + /* + * If result is not empty than we can copy lexeme and positions. + */ + if (j_out > 0) + { + WordEntryPosVector *posv_out; + int k; + + memcpy(dataout + curoff, data1 + arr1[i1].pos, arr1[i1].len); + arrout[i_out].haspos = arr1[i1].haspos; + arrout[i_out].len = arr1[i1].len; + arrout[i_out].pos = curoff; + + curoff += arr1[i1].len; + + posv_out = _POSVECPTR(tsout, arrout + i_out); + posv_out->npos = j_out; + for (k = 0; k < posv_out->npos; k++) + posv_out->pos[k] = pos_out[k]; + curoff = SHORTALIGN(curoff); + curoff += j_out * sizeof(WordEntryPos) + sizeof(uint16); + + i_out++; + } + + pfree(pos_out); + } + + i2++; + } + else + { + /* + * Not a match, we can copy lexeme and positions unchanged. + */ + memcpy(dataout + curoff, data1 + arr1[i1].pos, arr1[i1].len); + arrout[i_out].haspos = arr1[i1].haspos; + arrout[i_out].len = arr1[i1].len; + arrout[i_out].pos = curoff; + curoff += arr1[i1].len; + if (arr1[i1].haspos) + { + int len = POSDATALEN(ts1, arr1 + i1) * sizeof(WordEntryPos) + sizeof(uint16); + curoff = SHORTALIGN(curoff); + memcpy(dataout + curoff, + STRPTR(ts1) + SHORTALIGN(arr1[i1].pos + arr1[i1].len), len); + curoff += len; + } + + i_out++; + } + } + + tsout->size = i_out; + if (dataout != STRPTR(tsout)) + memmove(STRPTR(tsout), dataout, curoff); + + SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, curoff)); + + pfree(matched_indices); + pfree(matched_indices_ts2); + PG_FREE_IF_COPY(ts1, 0); + PG_FREE_IF_COPY(ts2, 0); + PG_RETURN_POINTER(tsout); +} + +/* + * Expand tsvector as table with following columns: + * lexeme: lexeme text + * positions: integer array of lexeme positions + * weights: char array of weights corresponding to positions + */ +Datum +tsvector_unnest(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + TSVector tsin = PG_GETARG_TSVECTOR(0); + WordEntry *arrin = ARRPTR(tsin); + char *data = STRPTR(tsin); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(3, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "lexeme", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "positions", + INT2ARRAYOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "weights", + TEXTARRAYOID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + + if (funcctx->call_cntr < tsin->size) + { + HeapTuple tuple; + int j, + i = funcctx->call_cntr; + bool nulls[] = {false, false, false}; + Datum values[3]; + + values[0] = PointerGetDatum(cstring_to_text_with_len(data + arrin[i].pos, arrin[i].len)); + + if (arrin[i].haspos) + { + WordEntryPosVector *posv; + Datum *positions; + Datum *weights; + char weight; + + /* + * Internally tsvector stores position and weight in the same + * uint16 (2 bits for weight, 14 for position). Here we extract that + * in two separate arrays. + */ + posv = _POSVECPTR(tsin, arrin + i); + positions = palloc(posv->npos * sizeof(Datum)); + weights = palloc(posv->npos * sizeof(Datum)); + for (j = 0; j < posv->npos; j++) + { + positions[j] = Int16GetDatum(WEP_GETPOS(posv->pos[j])); + weight = (WEP_GETWEIGHT(posv->pos[j]) >> 2) ? + 'D' : 'D' - WEP_GETWEIGHT(posv->pos[j]); + weights[j] = PointerGetDatum(cstring_to_text_with_len(&weight, 1)); + } + + values[1] = PointerGetDatum( + construct_array(positions, posv->npos, INT2OID, 2, true, 's')); + values[2] = PointerGetDatum( + construct_array(weights, posv->npos, TEXTOID, -1, false, 'i')); + } + else + { + values[1] = PointerGetDatum(construct_array(NULL, 0, INT2OID, 2, true, 's')); + values[2] = PointerGetDatum(construct_array(NULL, 0, TEXTOID, -1, false, 'i')); + } + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + PG_FREE_IF_COPY(tsin, 0); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + else + SRF_RETURN_DONE(funcctx); +} + +/* + * Convert tsvector to array of lexemes. + */ +Datum +tsvector_to_array(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0); + WordEntry *arrin = ARRPTR(tsin); + Datum elements[tsin->size]; + int i; + ArrayType *array; + + for (i = 0; i < tsin->size; i++) + { + elements[i] = PointerGetDatum( + cstring_to_text_with_len(STRPTR(tsin) + arrin[i].pos, arrin[i].len)); + } + array = construct_array(elements, tsin->size, TEXTOID, -1, false, 'i'); + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(array); +} + +/* + * Build tsvector from array of lexemes. + */ +Datum +array_to_tsvector(PG_FUNCTION_ARGS) +{ + ArrayType *v = PG_GETARG_ARRAYTYPE_P(0); + TSVector tsout; + Datum *dlexemes; + WordEntry *arrout; + bool *nulls; + int nitems, + i, + tslen, + datalen = 0; + char *cur; + + deconstruct_array(v, TEXTOID, -1, false, 'i', &dlexemes, &nulls, &nitems); + + for (i = 0; i < nitems; i++) + datalen += VARSIZE_ANY_EXHDR(dlexemes[i]); + + tslen = CALCDATASIZE(nitems, datalen); + tsout = (TSVector) palloc0(tslen); + SET_VARSIZE(tsout, tslen); + tsout->size = nitems; + arrout = ARRPTR(tsout); + cur = STRPTR(tsout); + + for (i = 0; i < nitems; i++) + { + char *lex = VARDATA(dlexemes[i]); + int lex_len = VARSIZE_ANY_EXHDR(dlexemes[i]); + + memcpy(cur, lex, lex_len); + arrout[i].haspos = 0; + arrout[i].len = lex_len; + arrout[i].pos = cur - STRPTR(tsout); + cur += lex_len; + } + + PG_FREE_IF_COPY(v, 0); + PG_RETURN_POINTER(tsout); +} + +/* + * Leave only elements with given weights from tsvector. + */ +Datum +tsvector_filter(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + ArrayType *weights = PG_GETARG_ARRAYTYPE_P(1); + WordEntry *arrin = ARRPTR(tsin), + *arrout; + char *datain = STRPTR(tsin), + *dataout; + Datum *dweights; + bool *nulls; + int nweigths; + int i, j; + char mask = 0, + cur_pos = 0; + + deconstruct_array(weights, CHAROID, 1, true, 'c', + &dweights, &nulls, &nweigths); + + for (i = 0; i < nweigths; i++) + { + char cw = DatumGetChar(dweights[i]); + switch (cw) + { + case 'A': case 'a': + mask = mask | 8; + break; + case 'B': case 'b': + mask = mask | 4; + break; + case 'C': case 'c': + mask = mask | 2; + break; + case 'D': case 'd': + mask = mask | 1; + break; + default: + /* internal error */ + elog(ERROR, "unrecognized weight: %c", cw); + } + } + + tsout = (TSVector) palloc0(VARSIZE(tsin)); + tsout->size = tsin->size; + arrout = ARRPTR(tsout); + dataout = STRPTR(tsout); + + for (i = j = 0; i < tsin->size; i++) + { + WordEntryPosVector *posvin, + *posvout; + int npos = 0; + int k; + + if (!arrin[i].haspos) + continue; + + posvin = _POSVECPTR(tsin, arrin + i); + posvout = (WordEntryPosVector *)(dataout + SHORTALIGN(cur_pos + arrin[i].len)); + + for (k = 0; k < posvin->npos; k++) + { + if (mask & (1 << WEP_GETWEIGHT(posvin->pos[k]))) + posvout->pos[npos++] = posvin->pos[k]; + } + + if (!npos) /* no satisfactory positions found, so skip that lexeme */ + continue; + + arrout[j].haspos = true; + arrout[j].len = arrin[i].len; + arrout[j].pos = cur_pos; + + memcpy(dataout + cur_pos, datain + arrin[i].pos, arrin[i].len); + posvout->npos = npos; + cur_pos += SHORTALIGN(arrin[i].len); + cur_pos += POSDATALEN(tsout, arrout+j) * sizeof(WordEntryPos) + sizeof(uint16); + j++; + } + + tsout->size = j; + if (dataout != STRPTR(tsout)) + memmove(STRPTR(tsout), dataout, cur_pos); + + SET_VARSIZE(tsout, CALCDATASIZE(tsout->size, cur_pos)); + + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(tsout); +} + +/* + * Shift all positions in tsvector by given value. + */ +Datum +tsvector_shift(PG_FUNCTION_ARGS) +{ + TSVector tsin = PG_GETARG_TSVECTOR(0), + tsout; + int16 offset = PG_GETARG_INT16(1); + WordEntry *arrout; + int i; + + tsout = (TSVector) palloc0(VARSIZE(tsin)); + memcpy(tsout, tsin, VARSIZE(tsin)); + arrout = ARRPTR(tsout); + + for (i = 0; i < tsout->size; i++) + { + int j, newpos; + WordEntryPosVector *posvout = _POSVECPTR(tsout, arrout + i); + + for (j = 0; j < posvout->npos; j++) + { + newpos = LIMITPOS(WEP_GETPOS(posvout->pos[j]) + offset); + WEP_SETPOS(posvout->pos[j], newpos > 0 ? newpos : 0); + } + } + + PG_FREE_IF_COPY(tsin, 0); + PG_RETURN_POINTER(tsout); +} Datum tsvector_concat(PG_FUNCTION_ARGS) diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index d8640db..84604dc 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -4575,8 +4575,26 @@ DESCR("number of lexemes"); DATA(insert OID = 3623 ( strip PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 3614 "3614" _null_ _null_ _null_ _null_ _null_ tsvector_strip _null_ _null_ _null_ )); DESCR("strip position information"); DATA(insert OID = 3624 ( setweight PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 18" _null_ _null_ _null_ _null_ _null_ tsvector_setweight _null_ _null_ _null_ )); -DESCR("set weight of lexeme's entries"); -DATA(insert OID = 3625 ( tsvector_concat PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 3614" _null_ _null_ _null_ _null_ _null_ tsvector_concat _null_ _null_ _null_ )); +DESCR("set given weight for whole tsvector"); +DATA(insert OID = 3320 ( setweight PGNSP PGUID 12 1 0 0 0 f f f f t f i s 3 0 3614 "3614 18 1009" _null_ _null_ _null_ _null_ _null_ tsvector_setweight_by_filter _null_ _null_ _null_ )); +DESCR("set given weight for given lexemes"); +DATA(insert OID = 3625 ( tsvector_concat PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 3614" _null_ _null_ _null_ _null_ _null_ tsvector_concat _null_ _null_ _null_ )); +DATA(insert OID = 3321 ( delete PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 25" _null_ _null_ _null_ _null_ _null_ tsvector_delete_str _null_ _null_ _null_ )); +DESCR("delete lexeme"); +DATA(insert OID = 3323 ( delete PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 1009" _null_ _null_ _null_ _null_ _null_ tsvector_delete_arr _null_ _null_ _null_ )); +DESCR("delete given lexemes"); +DATA(insert OID = 3324 ( delete PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 3614" _null_ _null_ _null_ _null_ _null_ tsvector_delete_tsvector _null_ _null_ _null_ )); +DESCR("delete lexemes that given as tsvector"); +DATA(insert OID = 3322 ( unnest PGNSP PGUID 12 1 10 0 0 f f f f t t i s 1 0 2249 "3614" "{3614,25,1005,1009}" "{i,o,o,o}" "{tsvector,lexeme,positions,weights}" _null_ _null_ tsvector_unnest _null_ _null_ _null_ )); +DESCR("expand tsvector to set of rows"); +DATA(insert OID = 3317 ( to_array PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 1009 "3614" _null_ _null_ _null_ _null_ _null_ tsvector_to_array _null_ _null_ _null_ )); +DESCR("convert to lexeme's array"); +DATA(insert OID = 3318 ( to_tsvector PGNSP PGUID 12 1 0 0 0 f f f f t f i s 1 0 3614 "1009" _null_ _null_ _null_ _null_ _null_ array_to_tsvector _null_ _null_ _null_ )); +DESCR("build tsvector from lexeme's array"); +DATA(insert OID = 3319 ( filter PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 1002" _null_ _null_ _null_ _null_ _null_ tsvector_filter _null_ _null_ _null_ )); +DESCR("returns tsvector that contain only postings with given weights"); +DATA(insert OID = 3325 ( shift PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 3614 "3614 23" _null_ _null_ _null_ _null_ _null_ tsvector_shift _null_ _null_ _null_ )); +DESCR("shift all positions in tsvector by given value"); DATA(insert OID = 3634 ( ts_match_vq PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3614 3615" _null_ _null_ _null_ _null_ _null_ ts_match_vq _null_ _null_ _null_ )); DATA(insert OID = 3635 ( ts_match_qv PGNSP PGUID 12 1 0 0 0 f f f f t f i s 2 0 16 "3615 3614" _null_ _null_ _null_ _null_ _null_ ts_match_qv _null_ _null_ _null_ )); diff --git a/src/include/tsearch/ts_type.h b/src/include/tsearch/ts_type.h index 281cdd6..f72a718 100644 --- a/src/include/tsearch/ts_type.h +++ b/src/include/tsearch/ts_type.h @@ -141,7 +141,16 @@ extern Datum tsvector_cmp(PG_FUNCTION_ARGS); extern Datum tsvector_length(PG_FUNCTION_ARGS); extern Datum tsvector_strip(PG_FUNCTION_ARGS); extern Datum tsvector_setweight(PG_FUNCTION_ARGS); +extern Datum tsvector_setweight_by_filter(PG_FUNCTION_ARGS); extern Datum tsvector_concat(PG_FUNCTION_ARGS); +extern Datum tsvector_delete_str(PG_FUNCTION_ARGS); +extern Datum tsvector_delete_arr(PG_FUNCTION_ARGS); +extern Datum tsvector_delete_tsvector(PG_FUNCTION_ARGS); +extern Datum tsvector_unnest(PG_FUNCTION_ARGS); +extern Datum tsvector_to_array(PG_FUNCTION_ARGS); +extern Datum array_to_tsvector(PG_FUNCTION_ARGS); +extern Datum tsvector_filter(PG_FUNCTION_ARGS); +extern Datum tsvector_shift(PG_FUNCTION_ARGS); extern Datum tsvector_update_trigger_byid(PG_FUNCTION_ARGS); extern Datum tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS); diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out index 6284fb6..9c55664 100644 --- a/src/test/regress/expected/tstypes.out +++ b/src/test/regress/expected/tstypes.out @@ -83,18 +83,6 @@ SELECT 'a:3A b:2a'::tsvector || 'ba:1234 a:1B'; 'a':3A,4B 'b':2A 'ba':1237 (1 row) -SELECT setweight('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd zxc:81,567,222A'::tsvector, 'c'); - setweight ----------------------------------------------------------- - 'a':1C,3C 'asd':1C 'w':5C,6C,12C,13C 'zxc':81C,222C,567C -(1 row) - -SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector); - strip ---------------- - 'a' 'asd' 'w' -(1 row) - --Base tsquery test SELECT '1'::tsquery; tsquery @@ -625,3 +613,276 @@ SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a & s'); 0.1 (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 +--------------- + 'a' 'asd' 'w' +(1 row) + +SELECT strip('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); + strip +---------------------------------------------- + 'base' 'hidden' 'rebel' 'spaceship' 'strike' +(1 row) + +SELECT strip('base hidden rebel spaceship strike'::tsvector); + strip +---------------------------------------------- + 'base' 'hidden' 'rebel' 'spaceship' 'strike' +(1 row) + +SELECT delete(to_tsvector('english', 'Rebel spaceships, striking from a hidden base'), 'spaceship'); + delete +------------------------------------------ + 'base':7 'hidden':6 'rebel':1 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base'); + delete +-------------------------------------------------------------- + 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bas'); + delete +----------------------------------------------------------------------- + 'base':7 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bases'); + delete +----------------------------------------------------------------------- + 'base':7 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship'); + delete +------------------------------------------ + 'base':7 'hidden':6 'rebel':1 'strike':3 +(1 row) + +SELECT delete('base hidden rebel spaceship strike'::tsvector, 'spaceship'); + delete +---------------------------------- + 'base' 'hidden' 'rebel' 'strike' +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','rebel']); + delete +-------------------------------- + 'base':7 'hidden':6 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceships','rebel']); + delete +------------------------------------------------------------- + 'base':7 'hidden':6 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceshi','rebel']); + delete +------------------------------------------------------------- + 'base':7 'hidden':6 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','leya','rebel']); + delete +-------------------------------- + 'base':7 'hidden':6 'strike':3 +(1 row) + +SELECT delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel']); + delete +-------------------------- + 'base' 'hidden' 'strike' +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base'::tsvector); + delete +-------------------------------------------------------------- + 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base:7,9'::tsvector); + delete +----------------------------------------------------------------------- + 'base':8 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base:7,8,9'::tsvector); + delete +-------------------------------------------------------------- + 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base:1,7,8,9,10'::tsvector); + delete +-------------------------------------------------------------- + 'hidden':6 'rebel':1 'spaceship':2,33A,34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship'::tsvector); + delete +---------------------------------------------- + 'base':7,8,9 'hidden':6 'rebel':1 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship:2,33'::tsvector); + delete +--------------------------------------------------------------------- + 'base':7,8,9 'hidden':6 'rebel':1 'spaceship':34B,35C,36 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship:2,33,34,35,36'::tsvector); + delete +---------------------------------------------- + 'base':7,8,9 'hidden':6 'rebel':1 'strike':3 +(1 row) + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship:2B,33C,34B,35C,36B'::tsvector); + delete +---------------------------------------------- + 'base':7,8,9 'hidden':6 'rebel':1 'strike':3 +(1 row) + +SELECT delete('base hidden rebel spaceship strike'::tsvector, 'spaceship:2 rebel'::tsvector); + delete +-------------------------- + 'base' 'hidden' 'strike' +(1 row) + +SELECT delete('cat:3 fat:2,4 rat:5A'::tsvector, 'aaa fat:2,6'::tsvector); + delete +-------------------------- + 'cat':3 'fat':4 'rat':5A +(1 row) + +SELECT unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); + unnest +--------------------------------------------- + (base,{7},{D}) + (hidden,{6},{D}) + (rebel,{1},{D}) + (spaceship,"{2,33,34,35,36}","{D,A,B,C,D}") + (strike,{3},{D}) +(5 rows) + +SELECT unnest('base hidden rebel spaceship strike'::tsvector); + unnest +------------------- + (base,{},{}) + (hidden,{},{}) + (rebel,{},{}) + (spaceship,{},{}) + (strike,{},{}) +(5 rows) + +SELECT * FROM unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); + lexeme | positions | weights +-----------+-----------------+------------- + base | {7} | {D} + hidden | {6} | {D} + rebel | {1} | {D} + spaceship | {2,33,34,35,36} | {D,A,B,C,D} + strike | {3} | {D} +(5 rows) + +SELECT * FROM unnest('base hidden rebel spaceship strike'::tsvector); + lexeme | positions | weights +-----------+-----------+--------- + base | {} | {} + hidden | {} | {} + rebel | {} | {} + spaceship | {} | {} + strike | {} | {} +(5 rows) + +SELECT lexeme, positions[1] from unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); + lexeme | positions +-----------+----------- + base | 7 + hidden | 6 + rebel | 1 + spaceship | 2 + strike | 3 +(5 rows) + +SELECT to_array('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); + to_array +-------------------------------------- + {base,hidden,rebel,spaceship,strike} +(1 row) + +SELECT to_array('base hidden rebel spaceship strike'::tsvector); + to_array +-------------------------------------- + {base,hidden,rebel,spaceship,strike} +(1 row) + +SELECT to_tsvector(ARRAY['base','hidden','rebel','spaceship','strike']); + to_tsvector +---------------------------------------------- + 'base' 'hidden' 'rebel' 'spaceship' 'strike' +(1 row) + +SELECT setweight('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd zxc:81,567,222A'::tsvector, 'c'); + setweight +---------------------------------------------------------- + 'a':1C,3C 'asd':1C 'w':5C,6C,12C,13C 'zxc':81C,222C,567C +(1 row) + +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c'); + setweight +---------------------------------------------------------- + 'a':1C,3C 'asd':1C 'w':5C,6C,12C,13C 'zxc':81C,222C,567C +(1 row) + +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}'); + setweight +------------------------------------------------------ + 'a':1C,3C 'asd':1C 'w':5,6,12B,13A 'zxc':81,222A,567 +(1 row) + +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}'); + setweight +------------------------------------------------------ + 'a':1C,3C 'asd':1C 'w':5,6,12B,13A 'zxc':81,222A,567 +(1 row) + +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a,zxc}'); + setweight +-------------------------------------------------------- + 'a':1C,3C 'asd':1C 'w':5,6,12B,13A 'zxc':81C,222C,567C +(1 row) + +SELECT setweight('a asd w:5,6,12B,13A zxc'::tsvector, 'c', '{a,zxc}'); + setweight +--------------------------------- + 'a' 'asd' 'w':5,6,12B,13A 'zxc' +(1 row) + +SELECT filter('base:7A empir:17 evil:15 first:11 galact:16 hidden:6A rebel:1A spaceship:2A strike:3A victori:12 won:9'::tsvector, '{a}'); + filter +------------------------------------------------------------- + 'base':7A 'hidden':6A 'rebel':1A 'spaceship':2A 'strike':3A +(1 row) + +SELECT filter('base hidden rebel spaceship strike'::tsvector, '{a}'); + filter +-------- + +(1 row) + +SELECT shift('fat:2,4 cat:3 rat:5A'::tsvector, 10); + shift +-------------------------------- + 'cat':13 'fat':12,14 'rat':15A +(1 row) + +SELECT shift('fat:2,4 cat:3 rat:5A'::tsvector, -3); + shift +---------------------------- + 'cat':0 'fat':0,1 'rat':2A +(1 row) + diff --git a/src/test/regress/sql/tstypes.sql b/src/test/regress/sql/tstypes.sql index fd7c702..52a1923 100644 --- a/src/test/regress/sql/tstypes.sql +++ b/src/test/regress/sql/tstypes.sql @@ -14,8 +14,6 @@ SELECT $$'\\as' ab\c ab\\c AB\\\c ab\\\\c$$::tsvector; SELECT tsvectorin(tsvectorout($$'\\as' ab\c ab\\c AB\\\c ab\\\\c$$::tsvector)); SELECT '''w'':4A,3B,2C,1D,5 a:8'; SELECT 'a:3A b:2a'::tsvector || 'ba:1234 a:1B'; -SELECT setweight('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd zxc:81,567,222A'::tsvector, 'c'); -SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector); --Base tsquery test SELECT '1'::tsquery; @@ -115,3 +113,58 @@ SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a | s'); SELECT ts_rank_cd(' a:1 s:2C d g'::tsvector, 'a & s'); SELECT ts_rank_cd(' a:1 s:2B d g'::tsvector, 'a & s'); SELECT ts_rank_cd(' a:1 s:2 d g'::tsvector, 'a & s'); + +-- tsvector editing operations + +SELECT strip('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd'::tsvector); +SELECT strip('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); +SELECT strip('base hidden rebel spaceship strike'::tsvector); + +SELECT delete(to_tsvector('english', 'Rebel spaceships, striking from a hidden base'), 'spaceship'); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base'); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bas'); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'bases'); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship'); +SELECT delete('base hidden rebel spaceship strike'::tsvector, 'spaceship'); + +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','rebel']); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceships','rebel']); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceshi','rebel']); +SELECT delete('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, ARRAY['spaceship','leya','rebel']); +SELECT delete('base hidden rebel spaceship strike'::tsvector, ARRAY['spaceship','leya','rebel']); + +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base:7,9'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base:7,8,9'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'base:1,7,8,9,10'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship:2,33'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship:2,33,34,35,36'::tsvector); +SELECT delete('base:7,8,9 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector, 'spaceship:2B,33C,34B,35C,36B'::tsvector); +SELECT delete('base hidden rebel spaceship strike'::tsvector, 'spaceship:2 rebel'::tsvector); +SELECT delete('cat:3 fat:2,4 rat:5A'::tsvector, 'aaa fat:2,6'::tsvector); + +SELECT unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); +SELECT unnest('base hidden rebel spaceship strike'::tsvector); +SELECT * FROM unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); +SELECT * FROM unnest('base hidden rebel spaceship strike'::tsvector); +SELECT lexeme, positions[1] from unnest('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); + +SELECT to_array('base:7 hidden:6 rebel:1 spaceship:2,33A,34B,35C,36D strike:3'::tsvector); +SELECT to_array('base hidden rebel spaceship strike'::tsvector); + +SELECT to_tsvector(ARRAY['base','hidden','rebel','spaceship','strike']); + +SELECT setweight('w:12B w:13* w:12,5,6 a:1,3* a:3 w asd:1dc asd zxc:81,567,222A'::tsvector, 'c'); +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c'); +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}'); +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a}'); +SELECT setweight('a:1,3A asd:1C w:5,6,12B,13A zxc:81,222A,567'::tsvector, 'c', '{a,zxc}'); +SELECT setweight('a asd w:5,6,12B,13A zxc'::tsvector, 'c', '{a,zxc}'); + +SELECT filter('base:7A empir:17 evil:15 first:11 galact:16 hidden:6A rebel:1A spaceship:2A strike:3A victori:12 won:9'::tsvector, '{a}'); +SELECT filter('base hidden rebel spaceship strike'::tsvector, '{a}'); + +SELECT shift('fat:2,4 cat:3 rat:5A'::tsvector, 10); +SELECT shift('fat:2,4 cat:3 rat:5A'::tsvector, -3); +