typedefstruct StatEntry
{
uint32 ndoc; /* zero indicates that we were already here
* while walking through the tree */
uint32 nentry; struct StatEntry *left; struct StatEntry *right;
uint32 lenlexeme; char lexeme[FLEXIBLE_ARRAY_MEMBER];
} StatEntry;
Datum
tsvector_strip(PG_FUNCTION_ARGS)
{
TSVector in = PG_GETARG_TSVECTOR(0);
TSVector out; int i,
len = 0;
WordEntry *arrin = ARRPTR(in),
*arrout; char *cur;
for (i = 0; i < in->size; i++)
len += arrin[i].len;
len = CALCDATASIZE(in->size, len);
out = (TSVector) palloc0(len);
SET_VARSIZE(out, len);
out->size = in->size;
arrout = ARRPTR(out);
cur = STRPTR(out); for (i = 0; i < in->size; i++)
{
memcpy(cur, STRPTR(in) + arrin[i].pos, arrin[i].len);
arrout[i].haspos = 0;
arrout[i].len = arrin[i].len;
arrout[i].pos = cur - STRPTR(out);
cur += arrout[i].len;
}
PG_FREE_IF_COPY(in, 0);
PG_RETURN_POINTER(out);
}
Datum
tsvector_length(PG_FUNCTION_ARGS)
{
TSVector in = PG_GETARG_TSVECTOR(0);
int32 ret = in->size;
PG_FREE_IF_COPY(in, 0);
PG_RETURN_INT32(ret);
}
Datum
tsvector_setweight(PG_FUNCTION_ARGS)
{
TSVector in = PG_GETARG_TSVECTOR(0); char cw = PG_GETARG_CHAR(1);
TSVector out; int i,
j;
WordEntry *entry;
WordEntryPos *p; int w = 0;
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);
i = out->size; while (i--)
{ if ((j = POSDATALEN(out, entry)) != 0)
{
p = POSDATAPTR(out, entry); while (j--)
{
WEP_SETWEIGHT(*p, w);
p++;
}
}
entry++;
}
/* *Assumingthatlexemesarrayissignificantlyshorterthantsvectorwe *caniteratethroughlexemesperformingbinarysearchofeachlexeme *fromlexemesintsvector.
*/ for (i = 0; i < nlexemes; i++)
{ char *lex; int lex_len,
lex_pos;
/* Ignore null array elements, they surely don't match */ if (nulls[i]) continue;
if (cmp < 0)
StopHigh = StopMiddle; elseif (cmp > 0)
StopLow = StopMiddle + 1; else/* found it */ return StopMiddle;
}
return -1;
}
/* *qsortcomparatorfunctions
*/
staticint
compare_int(constvoid *va, constvoid *vb)
{ int a = *((constint *) va); int b = *((constint *) vb);
return pg_cmp_s32(a, b);
}
staticint
compare_text_lexemes(constvoid *va, constvoid *vb)
{
Datum a = *((const Datum *) va);
Datum b = *((const Datum *) vb); char *alex = VARDATA_ANY(a); int alex_len = VARSIZE_ANY_EXHDR(a); char *blex = VARDATA_ANY(b); int blex_len = VARSIZE_ANY_EXHDR(b);
/* *InternalroutinetodeletelexemesfromTSVectorbyarrayofoffsets. * *int*indices_to_delete--arrayoflexemeoffsetstodelete(modifiedhere!) *intindices_count--sizeofthatarray * *ReturnsnewTSVectorwithoutgivenlexemesalongwiththeirpositions *andweights.
*/ static TSVector
tsvector_delete_by_indices(TSVector tsv, int *indices_to_delete, int indices_count)
{
TSVector tsout;
WordEntry *arrin = ARRPTR(tsv),
*arrout; char *data = STRPTR(tsv),
*dataout; int i, /* index in arrin */
j, /* index in arrout */
k, /* index in indices_to_delete */
curoff; /* index in dataout area */
/* *Rejectnullsandzerolengthstrings(maybeweshouldjustignorethem, *instead?)
*/ for (i = 0; i < nitems; i++)
{ if (nulls[i])
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("lexeme array may not contain nulls")));
if (VARSIZE(dlexemes[i]) - VARHDRSZ == 0)
ereport(ERROR,
(errcode(ERRCODE_ZERO_LENGTH_CHARACTER_STRING),
errmsg("lexeme array may not contain empty strings")));
}
/* Sort and de-dup, because this is required for a valid tsvector. */ if (nitems > 1)
{
qsort(dlexemes, nitems, sizeof(Datum), compare_text_lexemes);
nitems = qunique(dlexemes, nitems, sizeof(Datum),
compare_text_lexemes);
}
/* Calculate space needed for surviving lexemes. */ for (i = 0; i < nitems; i++)
datalen += VARSIZE(dlexemes[i]) - VARHDRSZ;
tslen = CALCDATASIZE(nitems, datalen);
/* Allocate and fill tsvector. */
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(dlexemes[i]) - VARHDRSZ;
memcpy(cur, lex, lex_len);
arrout[i].haspos = 0;
arrout[i].len = lex_len;
arrout[i].pos = cur - STRPTR(tsout);
cur += lex_len;
}
/* Get max position in in1; we'll need this to offset in2's positions */
ptr = ARRPTR(in1);
i = in1->size; while (i--)
{ if ((j = POSDATALEN(in1, ptr)) != 0)
{
p = POSDATAPTR(in1, ptr); while (j--)
{ if (WEP_GETPOS(*p) > maxpos)
maxpos = WEP_GETPOS(*p);
p++;
}
}
ptr++;
}
/* *Insteadofcheckingeachoffsetindividually,wecheckforoverflowof *posfieldsonceattheend.
*/ if (dataoff > MAXSTRPOS)
ereport(ERROR,
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
errmsg("string is too long for tsvector (%d bytes, max %d bytes)", dataoff, MAXSTRPOS)));
if (prefix)
{ if (cmp == 0 && lena > lenb)
cmp = 1; /* a is longer, so not a prefix of b */
} elseif (cmp == 0 && lena != lenb)
{
cmp = (lena < lenb) ? -1 : 1;
}
}
/* Is there a position with a matching weight? */ while (posvec_iter < posvec->pos + posvec->npos)
{ /* If true, append this position to the data->pos */ if (val->weight & (1 << WEP_GETWEIGHT(*posvec_iter)))
{
*dptr = WEP_GETPOS(*posvec_iter);
dptr++;
}
/* Is there a position with a matching weight? */ while (posvec_iter < posvec->pos + posvec->npos)
{ if (val->weight & (1 << WEP_GETWEIGHT(*posvec_iter)))
{
result = TS_YES; break; /* no need to go further */
}
posvec_iter++;
}
} elseif (data)
{
data->npos = posvec->npos;
data->pos = posvec->pos;
data->allocated = false;
result = TS_YES;
} else
{ /* simplest case: no weight check, positions not needed */
result = TS_YES;
}
} else
{ /* *Positioninfoislacking,soifthecallerrequiresit,wecanonly *saythatmaybethereisamatch. * *Notice,however,thatwe*don't*checkval->weighthere. *Historically,strippedtsvectorsareconsideredtomatchqueries *whetherornotthequeryhasaweightrestriction;that'salittle *dubiousbutwe'llpreservethebehavior.
*/ if (data)
result = TS_MAYBE; else
result = TS_YES;
}
/* adjust start position for corner case */ if (StopLow >= StopHigh)
StopMiddle = StopHigh;
/* we don't try to re-use any data from the initial match */ if (data)
{ if (data->allocated)
pfree(data->pos);
data->pos = NULL;
data->allocated = false;
data->npos = 0;
}
res = TS_NO;
/* don't leak storage from individual matches */ if (data->allocated)
pfree(data->pos);
data->pos = NULL;
data->allocated = false; /* it's important to reset data->npos before next loop */
data->npos = 0;
} else
{ /* Don't need positions, just handle YES/MAYBE */ if (subres == TS_YES || res == TS_NO)
res = subres;
}
}
StopMiddle++;
}
if (data && npos > 0)
{ /* Sort and make unique array of found positions */
data->pos = allpos;
qsort(data->pos, npos, sizeof(WordEntryPos), compareWordEntryPos);
data->npos = qunique(data->pos, npos, sizeof(WordEntryPos),
compareWordEntryPos);
data->allocated = true;
res = TS_YES;
}
}
return res;
}
/* *Computeoutputpositionlistforatsqueryoperatorinphrasemode. * *MergethepositionlistsinLdataandRdataasspecifiedby"emit", *returningtheresultlistinto*data.Theinputpositionlistsmustbe *sortedandunique,andtheoutputwillbeaswell. * *data:pointertoinitially-all-zeroesoutputstruct,orNULL *Ldata,Rdata:inputpositionlists *emit:bitmaskofTSPO_XXXflags *Loffset:offsettobeaddedtoLdatapositionsbeforecomparing/outputting *Roffset:offsettobeaddedtoRdatapositionsbeforecomparing/outputting *max_npos:maximumpossiblerequiredsizeofoutputpositionarray * *LoffsetandRoffsetshouldnotbenegative,elsewerisktryingtooutput *negativepositions,whichwon'tfitintoWordEntryPos. * *Theresultisboolean(TS_YESorTS_NO),butforthecaller'sconvenience *wereturnitasTSTernaryValue. * *ReturnsTS_YESifanypositionswereemittedto*data;orifdataisNULL, *returnsTS_YESifanypositionswouldhavebeenemitted.
*/ #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 */
static TSTernaryValue
TS_phrase_output(ExecPhraseData *data,
ExecPhraseData *Ldata,
ExecPhraseData *Rdata, int emit, int Loffset, int Roffset, int max_npos)
{ int Lindex,
Rindex;
/* Loop until both inputs are exhausted */
Lindex = Rindex = 0; while (Lindex < Ldata->npos || Rindex < Rdata->npos)
{ int Lpos,
Rpos; int output_pos = 0;
/* *Fetchcurrentvaluestocompare.WEP_GETPOS()isneededbecause *ExecPhraseData->datacanpointtoatsvector'sWordEntryPosVector.
*/ if (Lindex < Ldata->npos)
Lpos = WEP_GETPOS(Ldata->pos[Lindex]) + Loffset; else
{ /* L array exhausted, so we're done if R_ONLY isn't set */ if (!(emit & TSPO_R_ONLY)) break;
Lpos = INT_MAX;
} if (Rindex < Rdata->npos)
Rpos = WEP_GETPOS(Rdata->pos[Rindex]) + Roffset; else
{ /* R array exhausted, so we're done if L_ONLY isn't set */ if (!(emit & TSPO_L_ONLY)) break;
Rpos = INT_MAX;
}
/* Merge-join the two input lists */ if (Lpos < Rpos)
{ /* Lpos is not matched in Rdata, should we output it? */ if (emit & TSPO_L_ONLY)
output_pos = Lpos;
Lindex++;
} elseif (Lpos == Rpos)
{ /* Lpos and Rpos match ... should we output it? */ if (emit & TSPO_BOTH)
output_pos = Rpos;
Lindex++;
Rindex++;
} else/* Lpos > Rpos */
{ /* Rpos is not matched in Ldata, should we output it? */ if (emit & TSPO_R_ONLY)
output_pos = Rpos;
Rindex++;
}
if (output_pos > 0)
{ if (data)
{ /* Store position, first allocating output array if needed */ if (data->pos == NULL)
{
data->pos = (WordEntryPos *)
palloc(max_npos * sizeof(WordEntryPos));
data->allocated = true;
}
data->pos[data->npos++] = output_pos;
} else
{ /* *Exactpositionsnotneeded,soreturnTS_YESassoonaswe *knowthereisatleastone.
*/ return TS_YES;
}
}
}
if (data && data->npos > 0)
{ /* Let's assert we didn't overrun the array */
Assert(data->npos <= max_npos); return TS_YES;
} return TS_NO;
}
/* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
/* ... and let's check for query cancel while we're at it */
CHECK_FOR_INTERRUPTS();
/* Default locations result is empty */
*locations = NIL;
if (curitem->type == QI_VAL)
{
data = palloc0_object(ExecPhraseData); if (chkcond(arg, (QueryOperand *) curitem, data) == TS_YES)
{
*locations = list_make1(data); returntrue;
}
pfree(data); returnfalse;
}
switch (curitem->qoperator.oper)
{ case OP_NOT: if (!TS_execute_locations_recurse(curitem + 1, arg, chkcond,
&llocations)) returntrue; /* we don't pass back any locations */ returnfalse;
case OP_AND: if (!TS_execute_locations_recurse(curitem + curitem->qoperator.left,
arg, chkcond,
&llocations)) returnfalse; if (!TS_execute_locations_recurse(curitem + 1,
arg, chkcond,
&rlocations)) returnfalse;
*locations = list_concat(llocations, rlocations); returntrue;
case OP_PHRASE: /* We can hand this off to TS_phrase_execute */
data = palloc0_object(ExecPhraseData); if (TS_phrase_execute(curitem, arg, TS_EXEC_EMPTY, chkcond,
data) == TS_YES)
{ if (!data->negate)
*locations = list_make1(data); returntrue;
}
pfree(data); returnfalse;
/* not reachable, but keep compiler quiet */ returnfalse;
}
/* *Detectwhetheratsquerybooleanexpressionrequiresanypositivematches *tovaluesshowninthetsquery. * *ThisisneededtoknowwhetheraGINindexsearchrequiresfullindexscan. *Forexample,'x&!y'requiresamatchofx,soit'ssufficienttoscan *entriesforx;but'x|!y'couldmatchrowscontainingneitherxnory.
*/ bool
tsquery_requires_match(QueryItem *curitem)
{ /* since this function recurses, it could be driven to stack overflow */
check_stack_depth();
/* *TreatOP_PHRASEasOP_ANDhere
*/ case OP_AND: /* If either side requires a match, we're good */ if (tsquery_requires_match(curitem + curitem->qoperator.left)) returntrue; else return tsquery_requires_match(curitem + 1);
case OP_OR: /* Both sides must require a match */ if (tsquery_requires_match(curitem + curitem->qoperator.left)) return tsquery_requires_match(curitem + 1); else returnfalse;
node = stat->root; /* find leftmost value */ if (node == NULL)
stat->stack[stat->stackpos] = NULL; else for (;;)
{
stat->stack[stat->stackpos] = node; if (node->left)
{
stat->stackpos++;
node = node->left;
} else break;
}
Assert(stat->stackpos <= stat->maxdepth);
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
funcctx->tuple_desc = tupdesc;
funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
if (node->ndoc != 0)
{ /* return entry itself: we already was at left sublink */ return node;
} elseif (node->right && node->right != stat->stack[stat->stackpos + 1])
{ /* go on right sublink */
stat->stackpos++;
node = node->right;
/* find most-left value */ for (;;)
{
stat->stack[stat->stackpos] = node; if (node->left)
{
stat->stackpos++;
node = node->left;
} else break;
}
Assert(stat->stackpos <= stat->maxdepth);
} else
{ /* we already return all left subtree, itself and right subtree */ if (stat->stackpos == 0) return NULL;
Datum
tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS)
{ return tsvector_update_trigger(fcinfo, true);
}
static Datum
tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
{
TriggerData *trigdata;
Trigger *trigger;
Relation rel;
HeapTuple rettuple = NULL; int tsvector_attr_num,
i;
ParsedText prs;
Datum datum; bool isnull;
text *txt;
Oid cfgId; bool update_needed;
/* Check call context */ if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */
elog(ERROR, "tsvector_update_trigger: not fired by trigger manager");
trigdata = (TriggerData *) fcinfo->context; if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
elog(ERROR, "tsvector_update_trigger: must be fired for row"); if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
rettuple = trigdata->tg_trigtuple;
update_needed = true;
} elseif (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
{
rettuple = trigdata->tg_newtuple;
update_needed = false; /* computed below */
} else
elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE");
if (trigger->tgnargs < 3)
elog(ERROR, "tsvector_update_trigger: arguments must be tsvector_field, ts_config, text_field1, ...)");
/* Find the target tsvector column */
tsvector_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[0]); if (tsvector_attr_num == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("tsvector column \"%s\" does not exist",
trigger->tgargs[0]))); /* This will effectively reject system columns, so no separate test: */ if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, tsvector_attr_num),
TSVECTOROID))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" is not of tsvector type",
trigger->tgargs[0])));
/* Find the configuration to use */ if (config_column)
{ int config_attr_num;
config_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[1]); if (config_attr_num == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("configuration column \"%s\" does not exist",
trigger->tgargs[1]))); if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, config_attr_num),
REGCONFIGOID))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" is not of regconfig type",
trigger->tgargs[1])));
datum = SPI_getbinval(rettuple, rel->rd_att, config_attr_num, &isnull); if (isnull)
ereport(ERROR,
(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
errmsg("configuration column \"%s\" must not be null",
trigger->tgargs[1])));
cfgId = DatumGetObjectId(datum);
} else
{
List *names;
names = stringToQualifiedNameList(trigger->tgargs[1], NULL); /* require a schema so that results are not search path dependent */ if (list_length(names) < 2)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("text search configuration name \"%s\" must be schema-qualified",
trigger->tgargs[1])));
cfgId = get_ts_config_oid(names, false);
}
/* find all words in indexable column(s) */ for (i = 2; i < trigger->tgnargs; i++)
{ int numattr;
numattr = SPI_fnumber(rel->rd_att, trigger->tgargs[i]); if (numattr == SPI_ERROR_NOATTRIBUTE)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_COLUMN),
errmsg("column \"%s\" does not exist",
trigger->tgargs[i]))); if (!IsBinaryCoercible(SPI_gettypeid(rel->rd_att, numattr), TEXTOID))
ereport(ERROR,
(errcode(ERRCODE_DATATYPE_MISMATCH),
errmsg("column \"%s\" is not of a character type",
trigger->tgargs[i])));
if (bms_is_member(numattr - FirstLowInvalidHeapAttributeNumber, trigdata->tg_updatedcols))
update_needed = true;
datum = SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull); if (isnull) continue;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.