1 /*-------------------------------------------------------------------------
2 *
3 * tsearch2.c
4 * Backwards-compatibility package for old contrib/tsearch2 API
5 *
6 * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
7 *
8 *
9 * IDENTIFICATION
10 * contrib/tsearch2/tsearch2.c
11 *
12 *-------------------------------------------------------------------------
13 */
14 #include "postgres.h"
15
16 #include "catalog/namespace.h"
17 #include "catalog/pg_type.h"
18 #include "commands/trigger.h"
19 #include "tsearch/ts_utils.h"
20 #include "utils/builtins.h"
21 #include "utils/guc.h"
22 #include "utils/syscache.h"
23
24 PG_MODULE_MAGIC;
25
26 static Oid current_dictionary_oid = InvalidOid;
27 static Oid current_parser_oid = InvalidOid;
28
29 /* insert given value at argument position 0 */
30 #define INSERT_ARGUMENT0(argument, isnull) \
31 do { \
32 int i; \
33 for (i = fcinfo->nargs; i > 0; i--) \
34 { \
35 fcinfo->arg[i] = fcinfo->arg[i-1]; \
36 fcinfo->argnull[i] = fcinfo->argnull[i-1]; \
37 } \
38 fcinfo->arg[0] = (argument); \
39 fcinfo->argnull[0] = (isnull); \
40 fcinfo->nargs++; \
41 } while (0)
42
43 #define TextGetObjectId(infunction, text) \
44 DatumGetObjectId(DirectFunctionCall1(infunction, \
45 CStringGetDatum(text_to_cstring(text))))
46
47 #define UNSUPPORTED_FUNCTION(name) \
48 PG_FUNCTION_INFO_V1(name); \
49 Datum \
50 name(PG_FUNCTION_ARGS) \
51 { \
52 ereport(ERROR, \
53 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),\
54 errmsg("function %s is no longer supported", \
55 format_procedure(fcinfo->flinfo->fn_oid)), \
56 errhint("Switch to new tsearch functionality."))); \
57 /* keep compiler quiet */ \
58 PG_RETURN_NULL(); \
59 } \
60 extern int no_such_variable
61
62 static Oid GetCurrentDict(void);
63 static Oid GetCurrentParser(void);
64
65 PG_FUNCTION_INFO_V1(tsa_lexize_byname);
66 PG_FUNCTION_INFO_V1(tsa_lexize_bycurrent);
67 PG_FUNCTION_INFO_V1(tsa_set_curdict);
68 PG_FUNCTION_INFO_V1(tsa_set_curdict_byname);
69 PG_FUNCTION_INFO_V1(tsa_token_type_current);
70 PG_FUNCTION_INFO_V1(tsa_set_curprs);
71 PG_FUNCTION_INFO_V1(tsa_set_curprs_byname);
72 PG_FUNCTION_INFO_V1(tsa_parse_current);
73 PG_FUNCTION_INFO_V1(tsa_set_curcfg);
74 PG_FUNCTION_INFO_V1(tsa_set_curcfg_byname);
75 PG_FUNCTION_INFO_V1(tsa_to_tsvector_name);
76 PG_FUNCTION_INFO_V1(tsa_to_tsquery_name);
77 PG_FUNCTION_INFO_V1(tsa_plainto_tsquery_name);
78 PG_FUNCTION_INFO_V1(tsa_headline_byname);
79 PG_FUNCTION_INFO_V1(tsa_ts_stat);
80 PG_FUNCTION_INFO_V1(tsa_tsearch2);
81 PG_FUNCTION_INFO_V1(tsa_rewrite_accum);
82 PG_FUNCTION_INFO_V1(tsa_rewrite_finish);
83
84
85 /*
86 * List of unsupported functions
87 *
88 * The parser and dictionary functions are defined only so that the former
89 * contents of pg_ts_parser and pg_ts_dict can be loaded into the system,
90 * for ease of reference while creating the new tsearch configuration.
91 */
92
93 UNSUPPORTED_FUNCTION(tsa_dex_init);
94 UNSUPPORTED_FUNCTION(tsa_dex_lexize);
95
96 UNSUPPORTED_FUNCTION(tsa_snb_en_init);
97 UNSUPPORTED_FUNCTION(tsa_snb_lexize);
98 UNSUPPORTED_FUNCTION(tsa_snb_ru_init_koi8);
99 UNSUPPORTED_FUNCTION(tsa_snb_ru_init_utf8);
100 UNSUPPORTED_FUNCTION(tsa_snb_ru_init);
101
102 UNSUPPORTED_FUNCTION(tsa_spell_init);
103 UNSUPPORTED_FUNCTION(tsa_spell_lexize);
104
105 UNSUPPORTED_FUNCTION(tsa_syn_init);
106 UNSUPPORTED_FUNCTION(tsa_syn_lexize);
107
108 UNSUPPORTED_FUNCTION(tsa_thesaurus_init);
109 UNSUPPORTED_FUNCTION(tsa_thesaurus_lexize);
110
111 UNSUPPORTED_FUNCTION(tsa_prsd_start);
112 UNSUPPORTED_FUNCTION(tsa_prsd_getlexeme);
113 UNSUPPORTED_FUNCTION(tsa_prsd_end);
114 UNSUPPORTED_FUNCTION(tsa_prsd_lextype);
115 UNSUPPORTED_FUNCTION(tsa_prsd_headline);
116
117 UNSUPPORTED_FUNCTION(tsa_reset_tsearch);
118 UNSUPPORTED_FUNCTION(tsa_get_covers);
119
120
121 /*
122 * list of redefined functions
123 */
124
125 /* lexize(text, text) */
126 Datum
tsa_lexize_byname(PG_FUNCTION_ARGS)127 tsa_lexize_byname(PG_FUNCTION_ARGS)
128 {
129 text *dictname = PG_GETARG_TEXT_PP(0);
130 Datum arg1 = PG_GETARG_DATUM(1);
131
132 return DirectFunctionCall2(ts_lexize,
133 ObjectIdGetDatum(TextGetObjectId(regdictionaryin, dictname)),
134 arg1);
135 }
136
137 /* lexize(text) */
138 Datum
tsa_lexize_bycurrent(PG_FUNCTION_ARGS)139 tsa_lexize_bycurrent(PG_FUNCTION_ARGS)
140 {
141 Datum arg0 = PG_GETARG_DATUM(0);
142 Oid id = GetCurrentDict();
143
144 return DirectFunctionCall2(ts_lexize,
145 ObjectIdGetDatum(id),
146 arg0);
147 }
148
149 /* set_curdict(int) */
150 Datum
tsa_set_curdict(PG_FUNCTION_ARGS)151 tsa_set_curdict(PG_FUNCTION_ARGS)
152 {
153 Oid dict_oid = PG_GETARG_OID(0);
154
155 if (!SearchSysCacheExists(TSDICTOID,
156 ObjectIdGetDatum(dict_oid),
157 0, 0, 0))
158 elog(ERROR, "cache lookup failed for text search dictionary %u",
159 dict_oid);
160
161 current_dictionary_oid = dict_oid;
162
163 PG_RETURN_VOID();
164 }
165
166 /* set_curdict(text) */
167 Datum
tsa_set_curdict_byname(PG_FUNCTION_ARGS)168 tsa_set_curdict_byname(PG_FUNCTION_ARGS)
169 {
170 text *name = PG_GETARG_TEXT_PP(0);
171 Oid dict_oid;
172
173 dict_oid = get_ts_dict_oid(stringToQualifiedNameList(text_to_cstring(name)), false);
174
175 current_dictionary_oid = dict_oid;
176
177 PG_RETURN_VOID();
178 }
179
180 /* token_type() */
181 Datum
tsa_token_type_current(PG_FUNCTION_ARGS)182 tsa_token_type_current(PG_FUNCTION_ARGS)
183 {
184 INSERT_ARGUMENT0(ObjectIdGetDatum(GetCurrentParser()), false);
185 return ts_token_type_byid(fcinfo);
186 }
187
188 /* set_curprs(int) */
189 Datum
tsa_set_curprs(PG_FUNCTION_ARGS)190 tsa_set_curprs(PG_FUNCTION_ARGS)
191 {
192 Oid parser_oid = PG_GETARG_OID(0);
193
194 if (!SearchSysCacheExists(TSPARSEROID,
195 ObjectIdGetDatum(parser_oid),
196 0, 0, 0))
197 elog(ERROR, "cache lookup failed for text search parser %u",
198 parser_oid);
199
200 current_parser_oid = parser_oid;
201
202 PG_RETURN_VOID();
203 }
204
205 /* set_curprs(text) */
206 Datum
tsa_set_curprs_byname(PG_FUNCTION_ARGS)207 tsa_set_curprs_byname(PG_FUNCTION_ARGS)
208 {
209 text *name = PG_GETARG_TEXT_PP(0);
210 Oid parser_oid;
211
212 parser_oid = get_ts_parser_oid(stringToQualifiedNameList(text_to_cstring(name)), false);
213
214 current_parser_oid = parser_oid;
215
216 PG_RETURN_VOID();
217 }
218
219 /* parse(text) */
220 Datum
tsa_parse_current(PG_FUNCTION_ARGS)221 tsa_parse_current(PG_FUNCTION_ARGS)
222 {
223 INSERT_ARGUMENT0(ObjectIdGetDatum(GetCurrentParser()), false);
224 return ts_parse_byid(fcinfo);
225 }
226
227 /* set_curcfg(int) */
228 Datum
tsa_set_curcfg(PG_FUNCTION_ARGS)229 tsa_set_curcfg(PG_FUNCTION_ARGS)
230 {
231 Oid arg0 = PG_GETARG_OID(0);
232 char *name;
233
234 name = DatumGetCString(DirectFunctionCall1(regconfigout,
235 ObjectIdGetDatum(arg0)));
236
237 SetConfigOption("default_text_search_config", name,
238 PGC_USERSET, PGC_S_SESSION);
239
240 PG_RETURN_VOID();
241 }
242
243 /* set_curcfg(text) */
244 Datum
tsa_set_curcfg_byname(PG_FUNCTION_ARGS)245 tsa_set_curcfg_byname(PG_FUNCTION_ARGS)
246 {
247 text *arg0 = PG_GETARG_TEXT_PP(0);
248 char *name;
249
250 name = text_to_cstring(arg0);
251
252 SetConfigOption("default_text_search_config", name,
253 PGC_USERSET, PGC_S_SESSION);
254
255 PG_RETURN_VOID();
256 }
257
258 /* to_tsvector(text, text) */
259 Datum
tsa_to_tsvector_name(PG_FUNCTION_ARGS)260 tsa_to_tsvector_name(PG_FUNCTION_ARGS)
261 {
262 text *cfgname = PG_GETARG_TEXT_PP(0);
263 Datum arg1 = PG_GETARG_DATUM(1);
264 Oid config_oid;
265
266 config_oid = TextGetObjectId(regconfigin, cfgname);
267
268 return DirectFunctionCall2(to_tsvector_byid,
269 ObjectIdGetDatum(config_oid), arg1);
270 }
271
272 /* to_tsquery(text, text) */
273 Datum
tsa_to_tsquery_name(PG_FUNCTION_ARGS)274 tsa_to_tsquery_name(PG_FUNCTION_ARGS)
275 {
276 text *cfgname = PG_GETARG_TEXT_PP(0);
277 Datum arg1 = PG_GETARG_DATUM(1);
278 Oid config_oid;
279
280 config_oid = TextGetObjectId(regconfigin, cfgname);
281
282 return DirectFunctionCall2(to_tsquery_byid,
283 ObjectIdGetDatum(config_oid), arg1);
284 }
285
286
287 /* plainto_tsquery(text, text) */
288 Datum
tsa_plainto_tsquery_name(PG_FUNCTION_ARGS)289 tsa_plainto_tsquery_name(PG_FUNCTION_ARGS)
290 {
291 text *cfgname = PG_GETARG_TEXT_PP(0);
292 Datum arg1 = PG_GETARG_DATUM(1);
293 Oid config_oid;
294
295 config_oid = TextGetObjectId(regconfigin, cfgname);
296
297 return DirectFunctionCall2(plainto_tsquery_byid,
298 ObjectIdGetDatum(config_oid), arg1);
299 }
300
301 /* headline(text, text, tsquery [,text]) */
302 Datum
tsa_headline_byname(PG_FUNCTION_ARGS)303 tsa_headline_byname(PG_FUNCTION_ARGS)
304 {
305 Datum arg0 = PG_GETARG_DATUM(0);
306 Datum arg1 = PG_GETARG_DATUM(1);
307 Datum arg2 = PG_GETARG_DATUM(2);
308 Datum result;
309 Oid config_oid;
310
311 /* first parameter has to be converted to oid */
312 config_oid = DatumGetObjectId(DirectFunctionCall1(regconfigin,
313 CStringGetDatum(TextDatumGetCString(arg0))));
314
315 if (PG_NARGS() == 3)
316 result = DirectFunctionCall3(ts_headline_byid,
317 ObjectIdGetDatum(config_oid), arg1, arg2);
318 else
319 {
320 Datum arg3 = PG_GETARG_DATUM(3);
321
322 result = DirectFunctionCall4(ts_headline_byid_opt,
323 ObjectIdGetDatum(config_oid),
324 arg1, arg2, arg3);
325 }
326
327 return result;
328 }
329
330 /*
331 * tsearch2 version of update trigger
332 *
333 * We pass this on to the core trigger after inserting the default text
334 * search configuration name as the second argument. Note that this isn't
335 * a complete implementation of the original functionality; tsearch2 allowed
336 * transformation function names to be included in the list. However, that
337 * is deliberately removed as being a security risk.
338 */
339 Datum
tsa_tsearch2(PG_FUNCTION_ARGS)340 tsa_tsearch2(PG_FUNCTION_ARGS)
341 {
342 TriggerData *trigdata;
343 Trigger *trigger;
344 char **tgargs,
345 **tgargs_old;
346 int i;
347 Datum res;
348
349 /* Check call context */
350 if (!CALLED_AS_TRIGGER(fcinfo)) /* internal error */
351 elog(ERROR, "tsvector_update_trigger: not fired by trigger manager");
352
353 trigdata = (TriggerData *) fcinfo->context;
354 trigger = trigdata->tg_trigger;
355
356 if (trigger->tgnargs < 2)
357 elog(ERROR, "TSearch: format tsearch2(tsvector_field, text_field1,...)");
358
359 /* create space for configuration name */
360 tgargs = (char **) palloc((trigger->tgnargs + 1) * sizeof(char *));
361 tgargs[0] = trigger->tgargs[0];
362 for (i = 1; i < trigger->tgnargs; i++)
363 tgargs[i + 1] = trigger->tgargs[i];
364
365 tgargs[1] = pstrdup(GetConfigOptionByName("default_text_search_config",
366 NULL, false));
367 tgargs_old = trigger->tgargs;
368 trigger->tgargs = tgargs;
369 trigger->tgnargs++;
370
371 res = tsvector_update_trigger_byid(fcinfo);
372
373 /* restore old trigger data */
374 trigger->tgargs = tgargs_old;
375 trigger->tgnargs--;
376
377 pfree(tgargs[1]);
378 pfree(tgargs);
379
380 return res;
381 }
382
383
384 Datum
tsa_rewrite_accum(PG_FUNCTION_ARGS)385 tsa_rewrite_accum(PG_FUNCTION_ARGS)
386 {
387 TSQuery acc;
388 ArrayType *qa;
389 TSQuery q;
390 QTNode *qex = NULL,
391 *subs = NULL,
392 *acctree = NULL;
393 bool isfind = false;
394 Datum *elemsp;
395 int nelemsp;
396 MemoryContext aggcontext;
397 MemoryContext oldcontext;
398
399 if (!AggCheckCallContext(fcinfo, &aggcontext))
400 elog(ERROR, "tsa_rewrite_accum called in non-aggregate context");
401
402 if (PG_ARGISNULL(0) || PG_GETARG_POINTER(0) == NULL)
403 {
404 acc = (TSQuery) MemoryContextAlloc(aggcontext, HDRSIZETQ);
405 SET_VARSIZE(acc, HDRSIZETQ);
406 acc->size = 0;
407 }
408 else
409 acc = PG_GETARG_TSQUERY(0);
410
411 if (PG_ARGISNULL(1) || PG_GETARG_POINTER(1) == NULL)
412 PG_RETURN_TSQUERY(acc);
413 else
414 qa = PG_GETARG_ARRAYTYPE_P_COPY(1);
415
416 if (ARR_NDIM(qa) != 1)
417 elog(ERROR, "array must be one-dimensional, not %d dimensions",
418 ARR_NDIM(qa));
419 if (ArrayGetNItems(ARR_NDIM(qa), ARR_DIMS(qa)) != 3)
420 elog(ERROR, "array must have three elements");
421 if (ARR_ELEMTYPE(qa) != TSQUERYOID)
422 elog(ERROR, "array must contain tsquery elements");
423
424 deconstruct_array(qa, TSQUERYOID, -1, false, 'i', &elemsp, NULL, &nelemsp);
425
426 q = DatumGetTSQuery(elemsp[0]);
427 if (q->size == 0)
428 {
429 pfree(elemsp);
430 PG_RETURN_POINTER(acc);
431 }
432
433 if (!acc->size)
434 {
435 if (VARSIZE(acc) > HDRSIZETQ)
436 {
437 pfree(elemsp);
438 PG_RETURN_POINTER(acc);
439 }
440 else
441 acctree = QT2QTN(GETQUERY(q), GETOPERAND(q));
442 }
443 else
444 acctree = QT2QTN(GETQUERY(acc), GETOPERAND(acc));
445
446 QTNTernary(acctree);
447 QTNSort(acctree);
448
449 q = DatumGetTSQuery(elemsp[1]);
450 if (q->size == 0)
451 {
452 pfree(elemsp);
453 PG_RETURN_POINTER(acc);
454 }
455 qex = QT2QTN(GETQUERY(q), GETOPERAND(q));
456 QTNTernary(qex);
457 QTNSort(qex);
458
459 q = DatumGetTSQuery(elemsp[2]);
460 if (q->size)
461 subs = QT2QTN(GETQUERY(q), GETOPERAND(q));
462
463 acctree = findsubquery(acctree, qex, subs, &isfind);
464
465 if (isfind || !acc->size)
466 {
467 /* pfree( acc ); do not pfree(p), because nodeAgg.c will */
468 if (acctree)
469 {
470 QTNBinary(acctree);
471 oldcontext = MemoryContextSwitchTo(aggcontext);
472 acc = QTN2QT(acctree);
473 MemoryContextSwitchTo(oldcontext);
474 }
475 else
476 {
477 acc = (TSQuery) MemoryContextAlloc(aggcontext, HDRSIZETQ);
478 SET_VARSIZE(acc, HDRSIZETQ);
479 acc->size = 0;
480 }
481 }
482
483 pfree(elemsp);
484 QTNFree(qex);
485 QTNFree(subs);
486 QTNFree(acctree);
487
488 PG_RETURN_TSQUERY(acc);
489 }
490
491 Datum
tsa_rewrite_finish(PG_FUNCTION_ARGS)492 tsa_rewrite_finish(PG_FUNCTION_ARGS)
493 {
494 TSQuery acc = PG_GETARG_TSQUERY(0);
495 TSQuery rewrited;
496
497 if (acc == NULL || PG_ARGISNULL(0) || acc->size == 0)
498 {
499 rewrited = (TSQuery) palloc(HDRSIZETQ);
500 SET_VARSIZE(rewrited, HDRSIZETQ);
501 rewrited->size = 0;
502 }
503 else
504 {
505 rewrited = (TSQuery) palloc(VARSIZE(acc));
506 memcpy(rewrited, acc, VARSIZE(acc));
507 pfree(acc);
508 }
509
510 PG_RETURN_POINTER(rewrited);
511 }
512
513
514 /*
515 * Get Oid of current dictionary
516 */
517 static Oid
GetCurrentDict(void)518 GetCurrentDict(void)
519 {
520 if (current_dictionary_oid == InvalidOid)
521 ereport(ERROR,
522 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
523 errmsg("no current dictionary"),
524 errhint("Execute SELECT set_curdict(...).")));
525
526 return current_dictionary_oid;
527 }
528
529 /*
530 * Get Oid of current parser
531 *
532 * Here, it seems reasonable to select the "default" parser if none has been
533 * set.
534 */
535 static Oid
GetCurrentParser(void)536 GetCurrentParser(void)
537 {
538 if (current_parser_oid == InvalidOid)
539 current_parser_oid = get_ts_parser_oid(stringToQualifiedNameList("pg_catalog.default"), false);
540 return current_parser_oid;
541 }
542