Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * wparser.c
4 : * Standard interface to word parser
5 : *
6 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/tsearch/wparser.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "catalog/namespace.h"
17 : #include "commands/defrem.h"
18 : #include "funcapi.h"
19 : #include "tsearch/ts_cache.h"
20 : #include "tsearch/ts_utils.h"
21 : #include "utils/fmgrprotos.h"
22 : #include "utils/jsonfuncs.h"
23 : #include "utils/varlena.h"
24 :
25 : /******sql-level interface******/
26 :
27 : typedef struct
28 : {
29 : int cur;
30 : LexDescr *list;
31 : } TSTokenTypeStorage;
32 :
33 : /* state for ts_headline_json_* */
34 : typedef struct HeadlineJsonState
35 : {
36 : HeadlineParsedText *prs;
37 : TSConfigCacheEntry *cfg;
38 : TSParserCacheEntry *prsobj;
39 : TSQuery query;
40 : List *prsoptions;
41 : bool transformed;
42 : } HeadlineJsonState;
43 :
44 : static text *headline_json_value(void *_state, char *elem_value, int elem_len);
45 :
46 : static void
47 265 : tt_setup_firstcall(FuncCallContext *funcctx, FunctionCallInfo fcinfo,
48 : Oid prsid)
49 : {
50 : TupleDesc tupdesc;
51 : MemoryContext oldcontext;
52 : TSTokenTypeStorage *st;
53 265 : TSParserCacheEntry *prs = lookup_ts_parser_cache(prsid);
54 :
55 265 : if (!OidIsValid(prs->lextypeOid))
56 0 : elog(ERROR, "method lextype isn't defined for text search parser %u",
57 : prsid);
58 :
59 265 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
60 :
61 265 : st = palloc_object(TSTokenTypeStorage);
62 265 : st->cur = 0;
63 : /* lextype takes one dummy argument */
64 265 : st->list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
65 : (Datum) 0));
66 265 : funcctx->user_fctx = st;
67 :
68 265 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
69 0 : elog(ERROR, "return type must be a row type");
70 265 : funcctx->tuple_desc = tupdesc;
71 265 : funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
72 :
73 265 : MemoryContextSwitchTo(oldcontext);
74 265 : }
75 :
76 : static Datum
77 6360 : tt_process_call(FuncCallContext *funcctx)
78 : {
79 : TSTokenTypeStorage *st;
80 :
81 6360 : st = (TSTokenTypeStorage *) funcctx->user_fctx;
82 6360 : if (st->list && st->list[st->cur].lexid)
83 : {
84 : Datum result;
85 : char *values[3];
86 : char txtid[16];
87 : HeapTuple tuple;
88 :
89 6095 : sprintf(txtid, "%d", st->list[st->cur].lexid);
90 6095 : values[0] = txtid;
91 6095 : values[1] = st->list[st->cur].alias;
92 6095 : values[2] = st->list[st->cur].descr;
93 :
94 6095 : tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
95 6095 : result = HeapTupleGetDatum(tuple);
96 :
97 6095 : pfree(values[1]);
98 6095 : pfree(values[2]);
99 6095 : st->cur++;
100 6095 : return result;
101 : }
102 265 : return (Datum) 0;
103 : }
104 :
105 : Datum
106 6288 : ts_token_type_byid(PG_FUNCTION_ARGS)
107 : {
108 : FuncCallContext *funcctx;
109 : Datum result;
110 :
111 6288 : if (SRF_IS_FIRSTCALL())
112 : {
113 262 : funcctx = SRF_FIRSTCALL_INIT();
114 262 : tt_setup_firstcall(funcctx, fcinfo, PG_GETARG_OID(0));
115 : }
116 :
117 6288 : funcctx = SRF_PERCALL_SETUP();
118 :
119 6288 : if ((result = tt_process_call(funcctx)) != (Datum) 0)
120 6026 : SRF_RETURN_NEXT(funcctx, result);
121 262 : SRF_RETURN_DONE(funcctx);
122 : }
123 :
124 : Datum
125 72 : ts_token_type_byname(PG_FUNCTION_ARGS)
126 : {
127 : FuncCallContext *funcctx;
128 : Datum result;
129 :
130 72 : if (SRF_IS_FIRSTCALL())
131 : {
132 3 : text *prsname = PG_GETARG_TEXT_PP(0);
133 : Oid prsId;
134 :
135 3 : funcctx = SRF_FIRSTCALL_INIT();
136 3 : prsId = get_ts_parser_oid(textToQualifiedNameList(prsname), false);
137 3 : tt_setup_firstcall(funcctx, fcinfo, prsId);
138 : }
139 :
140 72 : funcctx = SRF_PERCALL_SETUP();
141 :
142 72 : if ((result = tt_process_call(funcctx)) != (Datum) 0)
143 69 : SRF_RETURN_NEXT(funcctx, result);
144 3 : SRF_RETURN_DONE(funcctx);
145 : }
146 :
147 : typedef struct
148 : {
149 : int type;
150 : char *lexeme;
151 : } LexemeEntry;
152 :
153 : typedef struct
154 : {
155 : int cur;
156 : int len;
157 : LexemeEntry *list;
158 : } PrsStorage;
159 :
160 :
161 : static void
162 22 : prs_setup_firstcall(FuncCallContext *funcctx, FunctionCallInfo fcinfo,
163 : Oid prsid, text *txt)
164 : {
165 : TupleDesc tupdesc;
166 : MemoryContext oldcontext;
167 : PrsStorage *st;
168 22 : TSParserCacheEntry *prs = lookup_ts_parser_cache(prsid);
169 22 : char *lex = NULL;
170 22 : int llen = 0,
171 22 : type = 0;
172 : void *prsdata;
173 :
174 22 : oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
175 :
176 22 : st = palloc_object(PrsStorage);
177 22 : st->cur = 0;
178 22 : st->len = 16;
179 22 : st->list = palloc_array(LexemeEntry, st->len);
180 :
181 22 : prsdata = DatumGetPointer(FunctionCall2(&prs->prsstart,
182 : PointerGetDatum(VARDATA_ANY(txt)),
183 : Int32GetDatum(VARSIZE_ANY_EXHDR(txt))));
184 :
185 541 : while ((type = DatumGetInt32(FunctionCall3(&prs->prstoken,
186 : PointerGetDatum(prsdata),
187 : PointerGetDatum(&lex),
188 541 : PointerGetDatum(&llen)))) != 0)
189 : {
190 519 : if (st->cur >= st->len)
191 : {
192 12 : st->len = 2 * st->len;
193 12 : st->list = (LexemeEntry *) repalloc(st->list, sizeof(LexemeEntry) * st->len);
194 : }
195 519 : st->list[st->cur].lexeme = palloc(llen + 1);
196 519 : memcpy(st->list[st->cur].lexeme, lex, llen);
197 519 : st->list[st->cur].lexeme[llen] = '\0';
198 519 : st->list[st->cur].type = type;
199 519 : st->cur++;
200 : }
201 :
202 22 : FunctionCall1(&prs->prsend, PointerGetDatum(prsdata));
203 :
204 22 : st->len = st->cur;
205 22 : st->cur = 0;
206 :
207 22 : funcctx->user_fctx = st;
208 22 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
209 0 : elog(ERROR, "return type must be a row type");
210 22 : funcctx->tuple_desc = tupdesc;
211 22 : funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
212 22 : MemoryContextSwitchTo(oldcontext);
213 22 : }
214 :
215 : static Datum
216 541 : prs_process_call(FuncCallContext *funcctx)
217 : {
218 : PrsStorage *st;
219 :
220 541 : st = (PrsStorage *) funcctx->user_fctx;
221 541 : if (st->cur < st->len)
222 : {
223 : Datum result;
224 : char *values[2];
225 : char tid[16];
226 : HeapTuple tuple;
227 :
228 519 : values[0] = tid;
229 519 : sprintf(tid, "%d", st->list[st->cur].type);
230 519 : values[1] = st->list[st->cur].lexeme;
231 519 : tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
232 519 : result = HeapTupleGetDatum(tuple);
233 :
234 519 : pfree(values[1]);
235 519 : st->cur++;
236 519 : return result;
237 : }
238 22 : return (Datum) 0;
239 : }
240 :
241 : Datum
242 105 : ts_parse_byid(PG_FUNCTION_ARGS)
243 : {
244 : FuncCallContext *funcctx;
245 : Datum result;
246 :
247 105 : if (SRF_IS_FIRSTCALL())
248 : {
249 18 : text *txt = PG_GETARG_TEXT_PP(1);
250 :
251 18 : funcctx = SRF_FIRSTCALL_INIT();
252 18 : prs_setup_firstcall(funcctx, fcinfo, PG_GETARG_OID(0), txt);
253 18 : PG_FREE_IF_COPY(txt, 1);
254 : }
255 :
256 105 : funcctx = SRF_PERCALL_SETUP();
257 :
258 105 : if ((result = prs_process_call(funcctx)) != (Datum) 0)
259 87 : SRF_RETURN_NEXT(funcctx, result);
260 18 : SRF_RETURN_DONE(funcctx);
261 : }
262 :
263 : Datum
264 436 : ts_parse_byname(PG_FUNCTION_ARGS)
265 : {
266 : FuncCallContext *funcctx;
267 : Datum result;
268 :
269 436 : if (SRF_IS_FIRSTCALL())
270 : {
271 4 : text *prsname = PG_GETARG_TEXT_PP(0);
272 4 : text *txt = PG_GETARG_TEXT_PP(1);
273 : Oid prsId;
274 :
275 4 : funcctx = SRF_FIRSTCALL_INIT();
276 4 : prsId = get_ts_parser_oid(textToQualifiedNameList(prsname), false);
277 4 : prs_setup_firstcall(funcctx, fcinfo, prsId, txt);
278 : }
279 :
280 436 : funcctx = SRF_PERCALL_SETUP();
281 :
282 436 : if ((result = prs_process_call(funcctx)) != (Datum) 0)
283 432 : SRF_RETURN_NEXT(funcctx, result);
284 4 : SRF_RETURN_DONE(funcctx);
285 : }
286 :
287 : Datum
288 73 : ts_headline_byid_opt(PG_FUNCTION_ARGS)
289 : {
290 73 : Oid tsconfig = PG_GETARG_OID(0);
291 73 : text *in = PG_GETARG_TEXT_PP(1);
292 73 : TSQuery query = PG_GETARG_TSQUERY(2);
293 73 : text *opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_PP(3) : NULL;
294 : HeadlineParsedText prs;
295 : List *prsoptions;
296 : text *out;
297 : TSConfigCacheEntry *cfg;
298 : TSParserCacheEntry *prsobj;
299 :
300 73 : cfg = lookup_ts_config_cache(tsconfig);
301 73 : prsobj = lookup_ts_parser_cache(cfg->prsId);
302 :
303 73 : if (!OidIsValid(prsobj->headlineOid))
304 0 : ereport(ERROR,
305 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
306 : errmsg("text search parser does not support headline creation")));
307 :
308 73 : memset(&prs, 0, sizeof(HeadlineParsedText));
309 73 : prs.lenwords = 32;
310 73 : prs.words = palloc_array(HeadlineWordEntry, prs.lenwords);
311 :
312 73 : hlparsetext(cfg->cfgId, &prs, query,
313 73 : VARDATA_ANY(in), VARSIZE_ANY_EXHDR(in));
314 :
315 73 : if (opt)
316 33 : prsoptions = deserialize_deflist(PointerGetDatum(opt));
317 : else
318 40 : prsoptions = NIL;
319 :
320 73 : FunctionCall3(&(prsobj->prsheadline),
321 : PointerGetDatum(&prs),
322 : PointerGetDatum(prsoptions),
323 : PointerGetDatum(query));
324 :
325 73 : out = generateHeadline(&prs);
326 :
327 73 : PG_FREE_IF_COPY(in, 1);
328 73 : PG_FREE_IF_COPY(query, 2);
329 73 : if (opt)
330 33 : PG_FREE_IF_COPY(opt, 3);
331 73 : pfree(prs.words);
332 73 : pfree(prs.startsel);
333 73 : pfree(prs.stopsel);
334 :
335 73 : PG_RETURN_POINTER(out);
336 : }
337 :
338 : Datum
339 40 : ts_headline_byid(PG_FUNCTION_ARGS)
340 : {
341 40 : PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_byid_opt,
342 : PG_GETARG_DATUM(0),
343 : PG_GETARG_DATUM(1),
344 : PG_GETARG_DATUM(2)));
345 : }
346 :
347 : Datum
348 0 : ts_headline(PG_FUNCTION_ARGS)
349 : {
350 0 : PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_byid_opt,
351 : ObjectIdGetDatum(getTSCurrentConfig(true)),
352 : PG_GETARG_DATUM(0),
353 : PG_GETARG_DATUM(1)));
354 : }
355 :
356 : Datum
357 0 : ts_headline_opt(PG_FUNCTION_ARGS)
358 : {
359 0 : PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_byid_opt,
360 : ObjectIdGetDatum(getTSCurrentConfig(true)),
361 : PG_GETARG_DATUM(0),
362 : PG_GETARG_DATUM(1),
363 : PG_GETARG_DATUM(2)));
364 : }
365 :
366 : Datum
367 21 : ts_headline_jsonb_byid_opt(PG_FUNCTION_ARGS)
368 : {
369 21 : Oid tsconfig = PG_GETARG_OID(0);
370 21 : Jsonb *jb = PG_GETARG_JSONB_P(1);
371 21 : TSQuery query = PG_GETARG_TSQUERY(2);
372 21 : text *opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_P(3) : NULL;
373 : Jsonb *out;
374 21 : JsonTransformStringValuesAction action = (JsonTransformStringValuesAction) headline_json_value;
375 : HeadlineParsedText prs;
376 21 : HeadlineJsonState *state = palloc0_object(HeadlineJsonState);
377 :
378 21 : memset(&prs, 0, sizeof(HeadlineParsedText));
379 21 : prs.lenwords = 32;
380 21 : prs.words = palloc_array(HeadlineWordEntry, prs.lenwords);
381 :
382 21 : state->prs = &prs;
383 21 : state->cfg = lookup_ts_config_cache(tsconfig);
384 21 : state->prsobj = lookup_ts_parser_cache(state->cfg->prsId);
385 21 : state->query = query;
386 21 : if (opt)
387 6 : state->prsoptions = deserialize_deflist(PointerGetDatum(opt));
388 : else
389 15 : state->prsoptions = NIL;
390 :
391 21 : if (!OidIsValid(state->prsobj->headlineOid))
392 0 : ereport(ERROR,
393 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
394 : errmsg("text search parser does not support headline creation")));
395 :
396 21 : out = transform_jsonb_string_values(jb, state, action);
397 :
398 21 : PG_FREE_IF_COPY(jb, 1);
399 21 : PG_FREE_IF_COPY(query, 2);
400 21 : if (opt)
401 6 : PG_FREE_IF_COPY(opt, 3);
402 :
403 21 : pfree(prs.words);
404 :
405 21 : if (state->transformed)
406 : {
407 12 : pfree(prs.startsel);
408 12 : pfree(prs.stopsel);
409 : }
410 :
411 21 : PG_RETURN_JSONB_P(out);
412 : }
413 :
414 : Datum
415 12 : ts_headline_jsonb(PG_FUNCTION_ARGS)
416 : {
417 12 : PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_jsonb_byid_opt,
418 : ObjectIdGetDatum(getTSCurrentConfig(true)),
419 : PG_GETARG_DATUM(0),
420 : PG_GETARG_DATUM(1)));
421 : }
422 :
423 : Datum
424 3 : ts_headline_jsonb_byid(PG_FUNCTION_ARGS)
425 : {
426 3 : PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_jsonb_byid_opt,
427 : PG_GETARG_DATUM(0),
428 : PG_GETARG_DATUM(1),
429 : PG_GETARG_DATUM(2)));
430 : }
431 :
432 : Datum
433 3 : ts_headline_jsonb_opt(PG_FUNCTION_ARGS)
434 : {
435 3 : PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_jsonb_byid_opt,
436 : ObjectIdGetDatum(getTSCurrentConfig(true)),
437 : PG_GETARG_DATUM(0),
438 : PG_GETARG_DATUM(1),
439 : PG_GETARG_DATUM(2)));
440 : }
441 :
442 : Datum
443 21 : ts_headline_json_byid_opt(PG_FUNCTION_ARGS)
444 : {
445 21 : Oid tsconfig = PG_GETARG_OID(0);
446 21 : text *json = PG_GETARG_TEXT_P(1);
447 21 : TSQuery query = PG_GETARG_TSQUERY(2);
448 21 : text *opt = (PG_NARGS() > 3 && PG_GETARG_POINTER(3)) ? PG_GETARG_TEXT_P(3) : NULL;
449 : text *out;
450 21 : JsonTransformStringValuesAction action = (JsonTransformStringValuesAction) headline_json_value;
451 :
452 : HeadlineParsedText prs;
453 21 : HeadlineJsonState *state = palloc0_object(HeadlineJsonState);
454 :
455 21 : memset(&prs, 0, sizeof(HeadlineParsedText));
456 21 : prs.lenwords = 32;
457 21 : prs.words = palloc_array(HeadlineWordEntry, prs.lenwords);
458 :
459 21 : state->prs = &prs;
460 21 : state->cfg = lookup_ts_config_cache(tsconfig);
461 21 : state->prsobj = lookup_ts_parser_cache(state->cfg->prsId);
462 21 : state->query = query;
463 21 : if (opt)
464 6 : state->prsoptions = deserialize_deflist(PointerGetDatum(opt));
465 : else
466 15 : state->prsoptions = NIL;
467 :
468 21 : if (!OidIsValid(state->prsobj->headlineOid))
469 0 : ereport(ERROR,
470 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
471 : errmsg("text search parser does not support headline creation")));
472 :
473 21 : out = transform_json_string_values(json, state, action);
474 :
475 21 : PG_FREE_IF_COPY(json, 1);
476 21 : PG_FREE_IF_COPY(query, 2);
477 21 : if (opt)
478 6 : PG_FREE_IF_COPY(opt, 3);
479 21 : pfree(prs.words);
480 :
481 21 : if (state->transformed)
482 : {
483 12 : pfree(prs.startsel);
484 12 : pfree(prs.stopsel);
485 : }
486 :
487 21 : PG_RETURN_TEXT_P(out);
488 : }
489 :
490 : Datum
491 12 : ts_headline_json(PG_FUNCTION_ARGS)
492 : {
493 12 : PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_json_byid_opt,
494 : ObjectIdGetDatum(getTSCurrentConfig(true)),
495 : PG_GETARG_DATUM(0),
496 : PG_GETARG_DATUM(1)));
497 : }
498 :
499 : Datum
500 3 : ts_headline_json_byid(PG_FUNCTION_ARGS)
501 : {
502 3 : PG_RETURN_DATUM(DirectFunctionCall3(ts_headline_json_byid_opt,
503 : PG_GETARG_DATUM(0),
504 : PG_GETARG_DATUM(1),
505 : PG_GETARG_DATUM(2)));
506 : }
507 :
508 : Datum
509 3 : ts_headline_json_opt(PG_FUNCTION_ARGS)
510 : {
511 3 : PG_RETURN_DATUM(DirectFunctionCall4(ts_headline_json_byid_opt,
512 : ObjectIdGetDatum(getTSCurrentConfig(true)),
513 : PG_GETARG_DATUM(0),
514 : PG_GETARG_DATUM(1),
515 : PG_GETARG_DATUM(2)));
516 : }
517 :
518 :
519 : /*
520 : * Return headline in text from, generated from a json(b) element
521 : */
522 : static text *
523 114 : headline_json_value(void *_state, char *elem_value, int elem_len)
524 : {
525 114 : HeadlineJsonState *state = (HeadlineJsonState *) _state;
526 :
527 114 : HeadlineParsedText *prs = state->prs;
528 114 : TSConfigCacheEntry *cfg = state->cfg;
529 114 : TSParserCacheEntry *prsobj = state->prsobj;
530 114 : TSQuery query = state->query;
531 114 : List *prsoptions = state->prsoptions;
532 :
533 114 : prs->curwords = 0;
534 114 : hlparsetext(cfg->cfgId, prs, query, elem_value, elem_len);
535 114 : FunctionCall3(&(prsobj->prsheadline),
536 : PointerGetDatum(prs),
537 : PointerGetDatum(prsoptions),
538 : PointerGetDatum(query));
539 :
540 114 : state->transformed = true;
541 114 : return generateHeadline(prs);
542 : }
|