Coverage Report

Created: 2026-10-16 06:48

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