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