1 /*-------------------------------------------------------------------------
4 * operations over tsvector
6 * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
10 * $PostgreSQL: pgsql/src/backend/utils/adt/tsvector_op.c,v 1.26 2010/01/02 16:57:55 momjian Exp $
12 *-------------------------------------------------------------------------
17 #include "catalog/namespace.h"
18 #include "catalog/pg_type.h"
19 #include "commands/trigger.h"
20 #include "executor/spi.h"
22 #include "mb/pg_wchar.h"
23 #include "miscadmin.h"
24 #include "tsearch/ts_type.h"
25 #include "tsearch/ts_utils.h"
26 #include "utils/builtins.h"
27 #include "utils/lsyscache.h"
39 typedef struct StatEntry
41 uint32 ndoc; /* zero indicates that we already was here
42 * while walking throug the tree */
44 struct StatEntry *left;
45 struct StatEntry *right;
50 #define STATENTRYHDRSZ (offsetof(StatEntry, lexeme))
64 #define STATHDRSIZE (offsetof(TSVectorStat, data))
66 static Datum tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column);
70 * Check if datatype is the specified type or equivalent to it.
72 * Note: we could just do getBaseType() unconditionally, but since that's
73 * a relatively expensive catalog lookup that most users won't need, we
74 * try the straight comparison first.
77 is_expected_type(Oid typid, Oid expected_type)
79 if (typid == expected_type)
81 typid = getBaseType(typid);
82 if (typid == expected_type)
87 /* Check if datatype is TEXT or binary-equivalent to it */
89 is_text_type(Oid typid)
91 /* varchar(n) and char(n) are binary-compatible with text */
92 if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
94 /* Allow domains over these types, too */
95 typid = getBaseType(typid);
96 if (typid == TEXTOID || typid == VARCHAROID || typid == BPCHAROID)
103 * Order: haspos, len, word, for all positions (pos, weight)
106 silly_cmp_tsvector(const TSVector a, const TSVector b)
108 if (VARSIZE(a) < VARSIZE(b))
110 else if (VARSIZE(a) > VARSIZE(b))
112 else if (a->size < b->size)
114 else if (a->size > b->size)
118 WordEntry *aptr = ARRPTR(a);
119 WordEntry *bptr = ARRPTR(b);
124 for (i = 0; i < a->size; i++)
126 if (aptr->haspos != bptr->haspos)
128 return (aptr->haspos > bptr->haspos) ? -1 : 1;
130 else if ((res = tsCompareString(STRPTR(a) + aptr->pos, aptr->len, STRPTR(b) + bptr->pos, bptr->len, false)) != 0)
134 else if (aptr->haspos)
136 WordEntryPos *ap = POSDATAPTR(a, aptr);
137 WordEntryPos *bp = POSDATAPTR(b, bptr);
140 if (POSDATALEN(a, aptr) != POSDATALEN(b, bptr))
141 return (POSDATALEN(a, aptr) > POSDATALEN(b, bptr)) ? -1 : 1;
143 for (j = 0; j < POSDATALEN(a, aptr); j++)
145 if (WEP_GETPOS(*ap) != WEP_GETPOS(*bp))
147 return (WEP_GETPOS(*ap) > WEP_GETPOS(*bp)) ? -1 : 1;
149 else if (WEP_GETWEIGHT(*ap) != WEP_GETWEIGHT(*bp))
151 return (WEP_GETWEIGHT(*ap) > WEP_GETWEIGHT(*bp)) ? -1 : 1;
165 #define TSVECTORCMPFUNC( type, action, ret ) \
167 tsvector_##type(PG_FUNCTION_ARGS) \
169 TSVector a = PG_GETARG_TSVECTOR(0); \
170 TSVector b = PG_GETARG_TSVECTOR(1); \
171 int res = silly_cmp_tsvector(a, b); \
172 PG_FREE_IF_COPY(a,0); \
173 PG_FREE_IF_COPY(b,1); \
174 PG_RETURN_##ret( res action 0 ); \
176 /* keep compiler quiet - no extra ; */ \
177 extern int no_such_variable
179 TSVECTORCMPFUNC(lt, <, BOOL);
180 TSVECTORCMPFUNC(le, <=, BOOL);
181 TSVECTORCMPFUNC(eq, ==, BOOL);
182 TSVECTORCMPFUNC(ge, >=, BOOL);
183 TSVECTORCMPFUNC(gt, >, BOOL);
184 TSVECTORCMPFUNC(ne, !=, BOOL);
185 TSVECTORCMPFUNC(cmp, +, INT32);
188 tsvector_strip(PG_FUNCTION_ARGS)
190 TSVector in = PG_GETARG_TSVECTOR(0);
194 WordEntry *arrin = ARRPTR(in),
198 for (i = 0; i < in->size; i++)
201 len = CALCDATASIZE(in->size, len);
202 out = (TSVector) palloc0(len);
203 SET_VARSIZE(out, len);
204 out->size = in->size;
205 arrout = ARRPTR(out);
207 for (i = 0; i < in->size; i++)
209 memcpy(cur, STRPTR(in) + arrin[i].pos, arrin[i].len);
210 arrout[i].haspos = 0;
211 arrout[i].len = arrin[i].len;
212 arrout[i].pos = cur - STRPTR(out);
213 cur += arrout[i].len;
216 PG_FREE_IF_COPY(in, 0);
217 PG_RETURN_POINTER(out);
221 tsvector_length(PG_FUNCTION_ARGS)
223 TSVector in = PG_GETARG_TSVECTOR(0);
226 PG_FREE_IF_COPY(in, 0);
227 PG_RETURN_INT32(ret);
231 tsvector_setweight(PG_FUNCTION_ARGS)
233 TSVector in = PG_GETARG_TSVECTOR(0);
234 char cw = PG_GETARG_CHAR(1);
262 elog(ERROR, "unrecognized weight: %d", cw);
265 out = (TSVector) palloc(VARSIZE(in));
266 memcpy(out, in, VARSIZE(in));
271 if ((j = POSDATALEN(out, entry)) != 0)
273 p = POSDATAPTR(out, entry);
276 WEP_SETWEIGHT(*p, w);
283 PG_FREE_IF_COPY(in, 0);
284 PG_RETURN_POINTER(out);
287 #define compareEntry(pa, a, pb, b) \
288 tsCompareString((pa) + (a)->pos, (a)->len, \
289 (pb) + (b)->pos, (b)->len, \
293 * Add positions from src to dest after offsetting them by maxpos.
294 * Return the number added (might be less than expected due to overflow)
297 add_pos(TSVector src, WordEntry *srcptr,
298 TSVector dest, WordEntry *destptr,
301 uint16 *clen = &_POSVECPTR(dest, destptr)->npos;
303 uint16 slen = POSDATALEN(src, srcptr),
305 WordEntryPos *spos = POSDATAPTR(src, srcptr),
306 *dpos = POSDATAPTR(dest, destptr);
308 if (!destptr->haspos)
313 i < slen && *clen < MAXNUMPOS &&
314 (*clen == 0 || WEP_GETPOS(dpos[*clen - 1]) != MAXENTRYPOS - 1);
317 WEP_SETWEIGHT(dpos[*clen], WEP_GETWEIGHT(spos[i]));
318 WEP_SETPOS(dpos[*clen], LIMITPOS(WEP_GETPOS(spos[i]) + maxpos));
322 if (*clen != startlen)
324 return *clen - startlen;
329 tsvector_concat(PG_FUNCTION_ARGS)
331 TSVector in1 = PG_GETARG_TSVECTOR(0);
332 TSVector in2 = PG_GETARG_TSVECTOR(1);
352 if ((j = POSDATALEN(in1, ptr)) != 0)
354 p = POSDATAPTR(in1, ptr);
357 if (WEP_GETPOS(*p) > maxpos)
358 maxpos = WEP_GETPOS(*p);
371 /* conservative estimate of space needed */
372 out = (TSVector) palloc0(VARSIZE(in1) + VARSIZE(in2));
373 SET_VARSIZE(out, VARSIZE(in1) + VARSIZE(in2));
374 out->size = in1->size + in2->size;
380 int cmp = compareEntry(data1, ptr1, data2, ptr2);
384 ptr->haspos = ptr1->haspos;
385 ptr->len = ptr1->len;
386 memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len);
388 dataoff += ptr1->len;
391 dataoff = SHORTALIGN(dataoff);
392 memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
393 dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
402 ptr->haspos = ptr2->haspos;
403 ptr->len = ptr2->len;
404 memcpy(data + dataoff, data2 + ptr2->pos, ptr2->len);
406 dataoff += ptr2->len;
409 int addlen = add_pos(in2, ptr2, out, ptr, maxpos);
415 dataoff = SHORTALIGN(dataoff);
416 dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16);
426 ptr->haspos = ptr1->haspos | ptr2->haspos;
427 ptr->len = ptr1->len;
428 memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len);
430 dataoff += ptr1->len;
435 dataoff = SHORTALIGN(dataoff);
436 memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
437 dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
439 dataoff += add_pos(in2, ptr2, out, ptr, maxpos) * sizeof(WordEntryPos);
441 else /* must have ptr2->haspos */
443 int addlen = add_pos(in2, ptr2, out, ptr, maxpos);
449 dataoff = SHORTALIGN(dataoff);
450 dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16);
465 ptr->haspos = ptr1->haspos;
466 ptr->len = ptr1->len;
467 memcpy(data + dataoff, data1 + ptr1->pos, ptr1->len);
469 dataoff += ptr1->len;
472 dataoff = SHORTALIGN(dataoff);
473 memcpy(data + dataoff, _POSVECPTR(in1, ptr1), POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16));
474 dataoff += POSDATALEN(in1, ptr1) * sizeof(WordEntryPos) + sizeof(uint16);
484 ptr->haspos = ptr2->haspos;
485 ptr->len = ptr2->len;
486 memcpy(data + dataoff, data2 + ptr2->pos, ptr2->len);
488 dataoff += ptr2->len;
491 int addlen = add_pos(in2, ptr2, out, ptr, maxpos);
497 dataoff = SHORTALIGN(dataoff);
498 dataoff += addlen * sizeof(WordEntryPos) + sizeof(uint16);
508 * Instead of checking each offset individually, we check for overflow of
509 * pos fields once at the end.
511 if (dataoff > MAXSTRPOS)
513 (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
514 errmsg("string is too long for tsvector (%d bytes, max %d bytes)", dataoff, MAXSTRPOS)));
516 out->size = ptr - ARRPTR(out);
517 SET_VARSIZE(out, CALCDATASIZE(out->size, dataoff));
518 if (data != STRPTR(out))
519 memmove(STRPTR(out), data, dataoff);
521 PG_FREE_IF_COPY(in1, 0);
522 PG_FREE_IF_COPY(in2, 1);
523 PG_RETURN_POINTER(out);
527 * Compare two strings by tsvector rules.
528 * if isPrefix = true then it returns not-zero value if b has prefix a
531 tsCompareString(char *a, int lena, char *b, int lenb, bool prefix)
538 cmp = 0; /* emtry string is equal to any if a prefix
541 cmp = (lenb > 0) ? -1 : 0;
545 cmp = (lena > 0) ? 1 : 0;
549 cmp = memcmp(a, b, Min(lena, lenb));
553 if (cmp == 0 && lena > lenb)
556 * b argument is not beginning with argument a
561 else if ((cmp == 0) && (lena != lenb))
563 cmp = (lena < lenb) ? -1 : 1;
574 checkclass_str(CHKVAL *chkval, WordEntry *val, QueryOperand *item)
576 WordEntryPosVector *posvec;
580 posvec = (WordEntryPosVector *)
581 (chkval->values + SHORTALIGN(val->pos + val->len));
588 if (item->weight & (1 << WEP_GETWEIGHT(*ptr)))
596 * is there value 'val' in array or not ?
599 checkcondition_str(void *checkval, QueryOperand *val)
601 CHKVAL *chkval = (CHKVAL *) checkval;
602 WordEntry *StopLow = chkval->arrb;
603 WordEntry *StopHigh = chkval->arre;
604 WordEntry *StopMiddle = StopHigh;
608 /* Loop invariant: StopLow <= val < StopHigh */
609 while (StopLow < StopHigh)
611 StopMiddle = StopLow + (StopHigh - StopLow) / 2;
612 difference = tsCompareString(chkval->operand + val->distance, val->length,
613 chkval->values + StopMiddle->pos, StopMiddle->len,
618 res = (val->weight && StopMiddle->haspos) ?
619 checkclass_str(chkval, StopMiddle, val) : true;
622 else if (difference > 0)
623 StopLow = StopMiddle + 1;
625 StopHigh = StopMiddle;
628 if (res == false && val->prefix == true)
631 * there was a failed exact search, so we should scan further to find
634 if (StopLow >= StopHigh)
635 StopMiddle = StopHigh;
637 while (res == false && StopMiddle < chkval->arre &&
638 tsCompareString(chkval->operand + val->distance, val->length,
639 chkval->values + StopMiddle->pos, StopMiddle->len,
642 res = (val->weight && StopMiddle->haspos) ?
643 checkclass_str(chkval, StopMiddle, val) : true;
653 * check for boolean condition.
655 * if calcnot is false, NOT expressions are always evaluated to be true. This is used in ranking.
656 * checkval can be used to pass information to the callback. TS_execute doesn't
657 * do anything with it.
658 * chkcond is a callback function used to evaluate each VAL node in the query.
662 TS_execute(QueryItem *curitem, void *checkval, bool calcnot,
663 bool (*chkcond) (void *checkval, QueryOperand *val))
665 /* since this function recurses, it could be driven to stack overflow */
668 if (curitem->type == QI_VAL)
669 return chkcond(checkval, (QueryOperand *) curitem);
671 switch (curitem->qoperator.oper)
675 return !TS_execute(curitem + 1, checkval, calcnot, chkcond);
679 if (TS_execute(curitem + curitem->qoperator.left, checkval, calcnot, chkcond))
680 return TS_execute(curitem + 1, checkval, calcnot, chkcond);
685 if (TS_execute(curitem + curitem->qoperator.left, checkval, calcnot, chkcond))
688 return TS_execute(curitem + 1, checkval, calcnot, chkcond);
691 elog(ERROR, "unrecognized operator: %d", curitem->qoperator.oper);
694 /* not reachable, but keep compiler quiet */
702 ts_match_qv(PG_FUNCTION_ARGS)
704 PG_RETURN_DATUM(DirectFunctionCall2(ts_match_vq,
706 PG_GETARG_DATUM(0)));
710 ts_match_vq(PG_FUNCTION_ARGS)
712 TSVector val = PG_GETARG_TSVECTOR(0);
713 TSQuery query = PG_GETARG_TSQUERY(1);
717 if (!val->size || !query->size)
719 PG_FREE_IF_COPY(val, 0);
720 PG_FREE_IF_COPY(query, 1);
721 PG_RETURN_BOOL(false);
724 chkval.arrb = ARRPTR(val);
725 chkval.arre = chkval.arrb + val->size;
726 chkval.values = STRPTR(val);
727 chkval.operand = GETOPERAND(query);
735 PG_FREE_IF_COPY(val, 0);
736 PG_FREE_IF_COPY(query, 1);
737 PG_RETURN_BOOL(result);
741 ts_match_tt(PG_FUNCTION_ARGS)
747 vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector,
748 PG_GETARG_DATUM(0)));
749 query = DatumGetTSQuery(DirectFunctionCall1(plainto_tsquery,
750 PG_GETARG_DATUM(1)));
752 res = DatumGetBool(DirectFunctionCall2(ts_match_vq,
753 TSVectorGetDatum(vector),
754 TSQueryGetDatum(query)));
763 ts_match_tq(PG_FUNCTION_ARGS)
766 TSQuery query = PG_GETARG_TSQUERY(1);
769 vector = DatumGetTSVector(DirectFunctionCall1(to_tsvector,
770 PG_GETARG_DATUM(0)));
772 res = DatumGetBool(DirectFunctionCall2(ts_match_vq,
773 TSVectorGetDatum(vector),
774 TSQueryGetDatum(query)));
777 PG_FREE_IF_COPY(query, 1);
783 * ts_stat statistic function support
788 * Returns the number of positions in value 'wptr' within tsvector 'txt',
789 * that have a weight equal to one of the weights in 'weight' bitmask.
792 check_weight(TSVector txt, WordEntry *wptr, int8 weight)
794 int len = POSDATALEN(txt, wptr);
796 WordEntryPos *ptr = POSDATAPTR(txt, wptr);
800 if (weight & (1 << WEP_GETWEIGHT(*ptr)))
807 #define compareStatWord(a,e,t) \
808 tsCompareString((a)->lexeme, (a)->lenlexeme, \
809 STRPTR(t) + (e)->pos, (e)->len, \
813 insertStatEntry(MemoryContext persistentContext, TSVectorStat *stat, TSVector txt, uint32 off)
815 WordEntry *we = ARRPTR(txt) + off;
816 StatEntry *node = stat->root,
822 if (stat->weight == 0)
823 n = (we->haspos) ? POSDATALEN(txt, we) : 1;
825 n = (we->haspos) ? check_weight(txt, we, stat->weight) : 0;
828 return; /* nothing to insert */
832 res = compareStatWord(node, we, txt);
841 node = (res < 0) ? node->left : node->right;
846 if (depth > stat->maxdepth)
847 stat->maxdepth = depth;
851 node = MemoryContextAlloc(persistentContext, STATENTRYHDRSZ + we->len);
852 node->left = node->right = NULL;
855 node->lenlexeme = we->len;
856 memcpy(node->lexeme, STRPTR(txt) + we->pos, node->lenlexeme);
879 chooseNextStatEntry(MemoryContext persistentContext, TSVectorStat *stat, TSVector txt,
880 uint32 low, uint32 high, uint32 offset)
883 uint32 middle = (low + high) >> 1;
885 pos = (low + middle) >> 1;
886 if (low != middle && pos >= offset && pos - offset < txt->size)
887 insertStatEntry(persistentContext, stat, txt, pos - offset);
888 pos = (high + middle + 1) >> 1;
889 if (middle + 1 != high && pos >= offset && pos - offset < txt->size)
890 insertStatEntry(persistentContext, stat, txt, pos - offset);
893 chooseNextStatEntry(persistentContext, stat, txt, low, middle, offset);
894 if (high != middle + 1)
895 chooseNextStatEntry(persistentContext, stat, txt, middle + 1, high, offset);
899 * This is written like a custom aggregate function, because the
900 * original plan was to do just that. Unfortunately, an aggregate function
901 * can't return a set, so that plan was abandoned. If that limitation is
902 * lifted in the future, ts_stat could be a real aggregate function so that
903 * you could use it like this:
905 * SELECT ts_stat(vector_column) FROM vector_table;
907 * where vector_column is a tsvector-type column in vector_table.
910 static TSVectorStat *
911 ts_accum(MemoryContext persistentContext, TSVectorStat *stat, Datum data)
913 TSVector txt = DatumGetTSVector(data);
919 { /* Init in first */
920 stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat));
924 /* simple check of correctness */
925 if (txt == NULL || txt->size == 0)
927 if (txt && txt != (TSVector) DatumGetPointer(data))
933 for (; i > 0; i >>= 1)
937 offset = (nbit - txt->size) / 2;
939 insertStatEntry(persistentContext, stat, txt, (nbit >> 1) - offset);
940 chooseNextStatEntry(persistentContext, stat, txt, 0, nbit, offset);
946 ts_setup_firstcall(FunctionCallInfo fcinfo, FuncCallContext *funcctx,
950 MemoryContext oldcontext;
953 funcctx->user_fctx = (void *) stat;
955 oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
957 stat->stack = palloc0(sizeof(StatEntry *) * (stat->maxdepth + 1));
961 /* find leftmost value */
963 stat->stack[stat->stackpos] = NULL;
967 stat->stack[stat->stackpos] = node;
976 Assert(stat->stackpos <= stat->maxdepth);
978 tupdesc = CreateTemplateTupleDesc(3, false);
979 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "word",
981 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "ndoc",
983 TupleDescInitEntry(tupdesc, (AttrNumber) 3, "nentry",
985 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
986 funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
988 MemoryContextSwitchTo(oldcontext);
992 walkStatEntryTree(TSVectorStat *stat)
994 StatEntry *node = stat->stack[stat->stackpos];
1001 /* return entry itself: we already was at left sublink */
1004 else if (node->right && node->right != stat->stack[stat->stackpos + 1])
1006 /* go on right sublink */
1010 /* find most-left value */
1013 stat->stack[stat->stackpos] = node;
1022 Assert(stat->stackpos <= stat->maxdepth);
1026 /* we already return all left subtree, itself and right subtree */
1027 if (stat->stackpos == 0)
1031 return walkStatEntryTree(stat);
1038 ts_process_call(FuncCallContext *funcctx)
1043 st = (TSVectorStat *) funcctx->user_fctx;
1045 entry = walkStatEntryTree(st);
1055 values[0] = palloc(entry->lenlexeme + 1);
1056 memcpy(values[0], entry->lexeme, entry->lenlexeme);
1057 (values[0])[entry->lenlexeme] = '\0';
1058 sprintf(ndoc, "%d", entry->ndoc);
1060 sprintf(nentry, "%d", entry->nentry);
1063 tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
1064 result = HeapTupleGetDatum(tuple);
1068 /* mark entry as already visited */
1077 static TSVectorStat *
1078 ts_stat_sql(MemoryContext persistentContext, text *txt, text *ws)
1080 char *query = text_to_cstring(txt);
1087 if ((plan = SPI_prepare(query, 0, NULL)) == NULL)
1088 /* internal error */
1089 elog(ERROR, "SPI_prepare(\"%s\") failed", query);
1091 if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL)
1092 /* internal error */
1093 elog(ERROR, "SPI_cursor_open(\"%s\") failed", query);
1095 SPI_cursor_fetch(portal, true, 100);
1097 if (SPI_tuptable == NULL ||
1098 SPI_tuptable->tupdesc->natts != 1 ||
1099 !is_expected_type(SPI_gettypeid(SPI_tuptable->tupdesc, 1),
1102 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1103 errmsg("ts_stat query must return one tsvector column")));
1105 stat = MemoryContextAllocZero(persistentContext, sizeof(TSVectorStat));
1113 while (buf - VARDATA(ws) < VARSIZE(ws) - VARHDRSZ)
1115 if (pg_mblen(buf) == 1)
1121 stat->weight |= 1 << 3;
1125 stat->weight |= 1 << 2;
1129 stat->weight |= 1 << 1;
1139 buf += pg_mblen(buf);
1143 while (SPI_processed > 0)
1145 for (i = 0; i < SPI_processed; i++)
1147 Datum data = SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull);
1150 stat = ts_accum(persistentContext, stat, data);
1153 SPI_freetuptable(SPI_tuptable);
1154 SPI_cursor_fetch(portal, true, 100);
1157 SPI_freetuptable(SPI_tuptable);
1158 SPI_cursor_close(portal);
1166 ts_stat1(PG_FUNCTION_ARGS)
1168 FuncCallContext *funcctx;
1171 if (SRF_IS_FIRSTCALL())
1174 text *txt = PG_GETARG_TEXT_P(0);
1176 funcctx = SRF_FIRSTCALL_INIT();
1178 stat = ts_stat_sql(funcctx->multi_call_memory_ctx, txt, NULL);
1179 PG_FREE_IF_COPY(txt, 0);
1180 ts_setup_firstcall(fcinfo, funcctx, stat);
1184 funcctx = SRF_PERCALL_SETUP();
1185 if ((result = ts_process_call(funcctx)) != (Datum) 0)
1186 SRF_RETURN_NEXT(funcctx, result);
1187 SRF_RETURN_DONE(funcctx);
1191 ts_stat2(PG_FUNCTION_ARGS)
1193 FuncCallContext *funcctx;
1196 if (SRF_IS_FIRSTCALL())
1199 text *txt = PG_GETARG_TEXT_P(0);
1200 text *ws = PG_GETARG_TEXT_P(1);
1202 funcctx = SRF_FIRSTCALL_INIT();
1204 stat = ts_stat_sql(funcctx->multi_call_memory_ctx, txt, ws);
1205 PG_FREE_IF_COPY(txt, 0);
1206 PG_FREE_IF_COPY(ws, 1);
1207 ts_setup_firstcall(fcinfo, funcctx, stat);
1211 funcctx = SRF_PERCALL_SETUP();
1212 if ((result = ts_process_call(funcctx)) != (Datum) 0)
1213 SRF_RETURN_NEXT(funcctx, result);
1214 SRF_RETURN_DONE(funcctx);
1219 * Triggers for automatic update of a tsvector column from text column(s)
1221 * Trigger arguments are either
1222 * name of tsvector col, name of tsconfig to use, name(s) of text col(s)
1223 * name of tsvector col, name of regconfig col, name(s) of text col(s)
1224 * ie, tsconfig can either be specified by name, or indirectly as the
1225 * contents of a regconfig field in the row. If the name is used, it must
1226 * be explicitly schema-qualified.
1229 tsvector_update_trigger_byid(PG_FUNCTION_ARGS)
1231 return tsvector_update_trigger(fcinfo, false);
1235 tsvector_update_trigger_bycolumn(PG_FUNCTION_ARGS)
1237 return tsvector_update_trigger(fcinfo, true);
1241 tsvector_update_trigger(PG_FUNCTION_ARGS, bool config_column)
1243 TriggerData *trigdata;
1246 HeapTuple rettuple = NULL;
1247 int tsvector_attr_num,
1255 /* Check call context */
1256 if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */
1257 elog(ERROR, "tsvector_update_trigger: not fired by trigger manager");
1259 trigdata = (TriggerData *) fcinfo->context;
1260 if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
1261 elog(ERROR, "tsvector_update_trigger: can't process STATEMENT events");
1262 if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
1263 elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
1265 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
1266 rettuple = trigdata->tg_trigtuple;
1267 else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
1268 rettuple = trigdata->tg_newtuple;
1270 elog(ERROR, "tsvector_update_trigger: must be fired for INSERT or UPDATE");
1272 trigger = trigdata->tg_trigger;
1273 rel = trigdata->tg_relation;
1275 if (trigger->tgnargs < 3)
1276 elog(ERROR, "tsvector_update_trigger: arguments must be tsvector_field, ts_config, text_field1, ...)");
1278 /* Find the target tsvector column */
1279 tsvector_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[0]);
1280 if (tsvector_attr_num == SPI_ERROR_NOATTRIBUTE)
1282 (errcode(ERRCODE_UNDEFINED_COLUMN),
1283 errmsg("tsvector column \"%s\" does not exist",
1284 trigger->tgargs[0])));
1285 if (!is_expected_type(SPI_gettypeid(rel->rd_att, tsvector_attr_num),
1288 (errcode(ERRCODE_DATATYPE_MISMATCH),
1289 errmsg("column \"%s\" is not of tsvector type",
1290 trigger->tgargs[0])));
1292 /* Find the configuration to use */
1295 int config_attr_num;
1297 config_attr_num = SPI_fnumber(rel->rd_att, trigger->tgargs[1]);
1298 if (config_attr_num == SPI_ERROR_NOATTRIBUTE)
1300 (errcode(ERRCODE_UNDEFINED_COLUMN),
1301 errmsg("configuration column \"%s\" does not exist",
1302 trigger->tgargs[1])));
1303 if (!is_expected_type(SPI_gettypeid(rel->rd_att, config_attr_num),
1306 (errcode(ERRCODE_DATATYPE_MISMATCH),
1307 errmsg("column \"%s\" is not of regconfig type",
1308 trigger->tgargs[1])));
1310 datum = SPI_getbinval(rettuple, rel->rd_att, config_attr_num, &isnull);
1313 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
1314 errmsg("configuration column \"%s\" must not be null",
1315 trigger->tgargs[1])));
1316 cfgId = DatumGetObjectId(datum);
1322 names = stringToQualifiedNameList(trigger->tgargs[1]);
1323 /* require a schema so that results are not search path dependent */
1324 if (list_length(names) < 2)
1326 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1327 errmsg("text search configuration name \"%s\" must be schema-qualified",
1328 trigger->tgargs[1])));
1329 cfgId = TSConfigGetCfgid(names, false);
1332 /* initialize parse state */
1336 prs.words = (ParsedWord *) palloc(sizeof(ParsedWord) * prs.lenwords);
1338 /* find all words in indexable column(s) */
1339 for (i = 2; i < trigger->tgnargs; i++)
1343 numattr = SPI_fnumber(rel->rd_att, trigger->tgargs[i]);
1344 if (numattr == SPI_ERROR_NOATTRIBUTE)
1346 (errcode(ERRCODE_UNDEFINED_COLUMN),
1347 errmsg("column \"%s\" does not exist",
1348 trigger->tgargs[i])));
1349 if (!is_text_type(SPI_gettypeid(rel->rd_att, numattr)))
1351 (errcode(ERRCODE_DATATYPE_MISMATCH),
1352 errmsg("column \"%s\" is not of a character type",
1353 trigger->tgargs[i])));
1355 datum = SPI_getbinval(rettuple, rel->rd_att, numattr, &isnull);
1359 txt = DatumGetTextP(datum);
1361 parsetext(cfgId, &prs, VARDATA(txt), VARSIZE(txt) - VARHDRSZ);
1363 if (txt != (text *) DatumGetPointer(datum))
1367 /* make tsvector value */
1370 datum = PointerGetDatum(make_tsvector(&prs));
1371 rettuple = SPI_modifytuple(rel, rettuple, 1, &tsvector_attr_num,
1373 pfree(DatumGetPointer(datum));
1377 TSVector out = palloc(CALCDATASIZE(0, 0));
1379 SET_VARSIZE(out, CALCDATASIZE(0, 0));
1381 datum = PointerGetDatum(out);
1382 rettuple = SPI_modifytuple(rel, rettuple, 1, &tsvector_attr_num,
1387 if (rettuple == NULL) /* internal error */
1388 elog(ERROR, "tsvector_update_trigger: %d returned by SPI_modifytuple",
1391 return PointerGetDatum(rettuple);