1 /*-------------------------------------------------------------------------
2  *
3  * tsearchcmds.c
4  *
5  *	  Routines for tsearch manipulation commands
6  *
7  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
8  * Portions Copyright (c) 1994, Regents of the University of California
9  *
10  *
11  * IDENTIFICATION
12  *	  src/backend/commands/tsearchcmds.c
13  *
14  *-------------------------------------------------------------------------
15  */
16 #include "postgres.h"
17 
18 #include <ctype.h>
19 
20 #include "access/genam.h"
21 #include "access/htup_details.h"
22 #include "access/table.h"
23 #include "access/xact.h"
24 #include "catalog/catalog.h"
25 #include "catalog/dependency.h"
26 #include "catalog/indexing.h"
27 #include "catalog/objectaccess.h"
28 #include "catalog/pg_namespace.h"
29 #include "catalog/pg_proc.h"
30 #include "catalog/pg_ts_config.h"
31 #include "catalog/pg_ts_config_map.h"
32 #include "catalog/pg_ts_dict.h"
33 #include "catalog/pg_ts_parser.h"
34 #include "catalog/pg_ts_template.h"
35 #include "catalog/pg_type.h"
36 #include "commands/alter.h"
37 #include "commands/defrem.h"
38 #include "commands/event_trigger.h"
39 #include "common/string.h"
40 #include "miscadmin.h"
41 #include "nodes/makefuncs.h"
42 #include "parser/parse_func.h"
43 #include "tsearch/ts_cache.h"
44 #include "tsearch/ts_utils.h"
45 #include "utils/builtins.h"
46 #include "utils/fmgroids.h"
47 #include "utils/lsyscache.h"
48 #include "utils/rel.h"
49 #include "utils/syscache.h"
50 
51 
52 static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
53 									 HeapTuple tup, Relation relMap);
54 static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
55 									 HeapTuple tup, Relation relMap);
56 static DefElem *buildDefItem(const char *name, const char *val,
57 							 bool was_quoted);
58 
59 
60 /* --------------------- TS Parser commands ------------------------ */
61 
62 /*
63  * lookup a parser support function and return its OID (as a Datum)
64  *
65  * attnum is the pg_ts_parser column the function will go into
66  */
67 static Datum
get_ts_parser_func(DefElem * defel,int attnum)68 get_ts_parser_func(DefElem *defel, int attnum)
69 {
70 	List	   *funcName = defGetQualifiedName(defel);
71 	Oid			typeId[3];
72 	Oid			retTypeId;
73 	int			nargs;
74 	Oid			procOid;
75 
76 	retTypeId = INTERNALOID;	/* correct for most */
77 	typeId[0] = INTERNALOID;
78 	switch (attnum)
79 	{
80 		case Anum_pg_ts_parser_prsstart:
81 			nargs = 2;
82 			typeId[1] = INT4OID;
83 			break;
84 		case Anum_pg_ts_parser_prstoken:
85 			nargs = 3;
86 			typeId[1] = INTERNALOID;
87 			typeId[2] = INTERNALOID;
88 			break;
89 		case Anum_pg_ts_parser_prsend:
90 			nargs = 1;
91 			retTypeId = VOIDOID;
92 			break;
93 		case Anum_pg_ts_parser_prsheadline:
94 			nargs = 3;
95 			typeId[1] = INTERNALOID;
96 			typeId[2] = TSQUERYOID;
97 			break;
98 		case Anum_pg_ts_parser_prslextype:
99 			nargs = 1;
100 
101 			/*
102 			 * Note: because the lextype method returns type internal, it must
103 			 * have an internal-type argument for security reasons.  The
104 			 * argument is not actually used, but is just passed as a zero.
105 			 */
106 			break;
107 		default:
108 			/* should not be here */
109 			elog(ERROR, "unrecognized attribute for text search parser: %d",
110 				 attnum);
111 			nargs = 0;			/* keep compiler quiet */
112 	}
113 
114 	procOid = LookupFuncName(funcName, nargs, typeId, false);
115 	if (get_func_rettype(procOid) != retTypeId)
116 		ereport(ERROR,
117 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
118 				 errmsg("function %s should return type %s",
119 						func_signature_string(funcName, nargs, NIL, typeId),
120 						format_type_be(retTypeId))));
121 
122 	return ObjectIdGetDatum(procOid);
123 }
124 
125 /*
126  * make pg_depend entries for a new pg_ts_parser entry
127  *
128  * Return value is the address of said new entry.
129  */
130 static ObjectAddress
makeParserDependencies(HeapTuple tuple)131 makeParserDependencies(HeapTuple tuple)
132 {
133 	Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
134 	ObjectAddress myself,
135 				referenced;
136 
137 	myself.classId = TSParserRelationId;
138 	myself.objectId = prs->oid;
139 	myself.objectSubId = 0;
140 
141 	/* dependency on namespace */
142 	referenced.classId = NamespaceRelationId;
143 	referenced.objectId = prs->prsnamespace;
144 	referenced.objectSubId = 0;
145 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
146 
147 	/* dependency on extension */
148 	recordDependencyOnCurrentExtension(&myself, false);
149 
150 	/* dependencies on functions */
151 	referenced.classId = ProcedureRelationId;
152 	referenced.objectSubId = 0;
153 
154 	referenced.objectId = prs->prsstart;
155 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
156 
157 	referenced.objectId = prs->prstoken;
158 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
159 
160 	referenced.objectId = prs->prsend;
161 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
162 
163 	referenced.objectId = prs->prslextype;
164 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
165 
166 	if (OidIsValid(prs->prsheadline))
167 	{
168 		referenced.objectId = prs->prsheadline;
169 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
170 	}
171 
172 	return myself;
173 }
174 
175 /*
176  * CREATE TEXT SEARCH PARSER
177  */
178 ObjectAddress
DefineTSParser(List * names,List * parameters)179 DefineTSParser(List *names, List *parameters)
180 {
181 	char	   *prsname;
182 	ListCell   *pl;
183 	Relation	prsRel;
184 	HeapTuple	tup;
185 	Datum		values[Natts_pg_ts_parser];
186 	bool		nulls[Natts_pg_ts_parser];
187 	NameData	pname;
188 	Oid			prsOid;
189 	Oid			namespaceoid;
190 	ObjectAddress address;
191 
192 	if (!superuser())
193 		ereport(ERROR,
194 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
195 				 errmsg("must be superuser to create text search parsers")));
196 
197 	prsRel = table_open(TSParserRelationId, RowExclusiveLock);
198 
199 	/* Convert list of names to a name and namespace */
200 	namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
201 
202 	/* initialize tuple fields with name/namespace */
203 	memset(values, 0, sizeof(values));
204 	memset(nulls, false, sizeof(nulls));
205 
206 	prsOid = GetNewOidWithIndex(prsRel, TSParserOidIndexId,
207 								Anum_pg_ts_parser_oid);
208 	values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(prsOid);
209 	namestrcpy(&pname, prsname);
210 	values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
211 	values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
212 
213 	/*
214 	 * loop over the definition list and extract the information we need.
215 	 */
216 	foreach(pl, parameters)
217 	{
218 		DefElem    *defel = (DefElem *) lfirst(pl);
219 
220 		if (strcmp(defel->defname, "start") == 0)
221 		{
222 			values[Anum_pg_ts_parser_prsstart - 1] =
223 				get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
224 		}
225 		else if (strcmp(defel->defname, "gettoken") == 0)
226 		{
227 			values[Anum_pg_ts_parser_prstoken - 1] =
228 				get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
229 		}
230 		else if (strcmp(defel->defname, "end") == 0)
231 		{
232 			values[Anum_pg_ts_parser_prsend - 1] =
233 				get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
234 		}
235 		else if (strcmp(defel->defname, "headline") == 0)
236 		{
237 			values[Anum_pg_ts_parser_prsheadline - 1] =
238 				get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
239 		}
240 		else if (strcmp(defel->defname, "lextypes") == 0)
241 		{
242 			values[Anum_pg_ts_parser_prslextype - 1] =
243 				get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
244 		}
245 		else
246 			ereport(ERROR,
247 					(errcode(ERRCODE_SYNTAX_ERROR),
248 					 errmsg("text search parser parameter \"%s\" not recognized",
249 							defel->defname)));
250 	}
251 
252 	/*
253 	 * Validation
254 	 */
255 	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
256 		ereport(ERROR,
257 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
258 				 errmsg("text search parser start method is required")));
259 
260 	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
261 		ereport(ERROR,
262 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
263 				 errmsg("text search parser gettoken method is required")));
264 
265 	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
266 		ereport(ERROR,
267 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
268 				 errmsg("text search parser end method is required")));
269 
270 	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
271 		ereport(ERROR,
272 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
273 				 errmsg("text search parser lextypes method is required")));
274 
275 	/*
276 	 * Looks good, insert
277 	 */
278 	tup = heap_form_tuple(prsRel->rd_att, values, nulls);
279 
280 	CatalogTupleInsert(prsRel, tup);
281 
282 	address = makeParserDependencies(tup);
283 
284 	/* Post creation hook for new text search parser */
285 	InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
286 
287 	heap_freetuple(tup);
288 
289 	table_close(prsRel, RowExclusiveLock);
290 
291 	return address;
292 }
293 
294 /*
295  * Guts of TS parser deletion.
296  */
297 void
RemoveTSParserById(Oid prsId)298 RemoveTSParserById(Oid prsId)
299 {
300 	Relation	relation;
301 	HeapTuple	tup;
302 
303 	relation = table_open(TSParserRelationId, RowExclusiveLock);
304 
305 	tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
306 
307 	if (!HeapTupleIsValid(tup))
308 		elog(ERROR, "cache lookup failed for text search parser %u", prsId);
309 
310 	CatalogTupleDelete(relation, &tup->t_self);
311 
312 	ReleaseSysCache(tup);
313 
314 	table_close(relation, RowExclusiveLock);
315 }
316 
317 /* ---------------------- TS Dictionary commands -----------------------*/
318 
319 /*
320  * make pg_depend entries for a new pg_ts_dict entry
321  *
322  * Return value is address of the new entry
323  */
324 static ObjectAddress
makeDictionaryDependencies(HeapTuple tuple)325 makeDictionaryDependencies(HeapTuple tuple)
326 {
327 	Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
328 	ObjectAddress myself,
329 				referenced;
330 
331 	myself.classId = TSDictionaryRelationId;
332 	myself.objectId = dict->oid;
333 	myself.objectSubId = 0;
334 
335 	/* dependency on namespace */
336 	referenced.classId = NamespaceRelationId;
337 	referenced.objectId = dict->dictnamespace;
338 	referenced.objectSubId = 0;
339 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
340 
341 	/* dependency on owner */
342 	recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
343 
344 	/* dependency on extension */
345 	recordDependencyOnCurrentExtension(&myself, false);
346 
347 	/* dependency on template */
348 	referenced.classId = TSTemplateRelationId;
349 	referenced.objectId = dict->dicttemplate;
350 	referenced.objectSubId = 0;
351 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
352 
353 	return myself;
354 }
355 
356 /*
357  * verify that a template's init method accepts a proposed option list
358  */
359 static void
verify_dictoptions(Oid tmplId,List * dictoptions)360 verify_dictoptions(Oid tmplId, List *dictoptions)
361 {
362 	HeapTuple	tup;
363 	Form_pg_ts_template tform;
364 	Oid			initmethod;
365 
366 	/*
367 	 * Suppress this test when running in a standalone backend.  This is a
368 	 * hack to allow initdb to create prefab dictionaries that might not
369 	 * actually be usable in template1's encoding (due to using external files
370 	 * that can't be translated into template1's encoding).  We want to create
371 	 * them anyway, since they might be usable later in other databases.
372 	 */
373 	if (!IsUnderPostmaster)
374 		return;
375 
376 	tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
377 	if (!HeapTupleIsValid(tup)) /* should not happen */
378 		elog(ERROR, "cache lookup failed for text search template %u",
379 			 tmplId);
380 	tform = (Form_pg_ts_template) GETSTRUCT(tup);
381 
382 	initmethod = tform->tmplinit;
383 
384 	if (!OidIsValid(initmethod))
385 	{
386 		/* If there is no init method, disallow any options */
387 		if (dictoptions)
388 			ereport(ERROR,
389 					(errcode(ERRCODE_SYNTAX_ERROR),
390 					 errmsg("text search template \"%s\" does not accept options",
391 							NameStr(tform->tmplname))));
392 	}
393 	else
394 	{
395 		/*
396 		 * Copy the options just in case init method thinks it can scribble on
397 		 * them ...
398 		 */
399 		dictoptions = copyObject(dictoptions);
400 
401 		/*
402 		 * Call the init method and see if it complains.  We don't worry about
403 		 * it leaking memory, since our command will soon be over anyway.
404 		 */
405 		(void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
406 	}
407 
408 	ReleaseSysCache(tup);
409 }
410 
411 /*
412  * CREATE TEXT SEARCH DICTIONARY
413  */
414 ObjectAddress
DefineTSDictionary(List * names,List * parameters)415 DefineTSDictionary(List *names, List *parameters)
416 {
417 	ListCell   *pl;
418 	Relation	dictRel;
419 	HeapTuple	tup;
420 	Datum		values[Natts_pg_ts_dict];
421 	bool		nulls[Natts_pg_ts_dict];
422 	NameData	dname;
423 	Oid			templId = InvalidOid;
424 	List	   *dictoptions = NIL;
425 	Oid			dictOid;
426 	Oid			namespaceoid;
427 	AclResult	aclresult;
428 	char	   *dictname;
429 	ObjectAddress address;
430 
431 	/* Convert list of names to a name and namespace */
432 	namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
433 
434 	/* Check we have creation rights in target namespace */
435 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
436 	if (aclresult != ACLCHECK_OK)
437 		aclcheck_error(aclresult, OBJECT_SCHEMA,
438 					   get_namespace_name(namespaceoid));
439 
440 	/*
441 	 * loop over the definition list and extract the information we need.
442 	 */
443 	foreach(pl, parameters)
444 	{
445 		DefElem    *defel = (DefElem *) lfirst(pl);
446 
447 		if (strcmp(defel->defname, "template") == 0)
448 		{
449 			templId = get_ts_template_oid(defGetQualifiedName(defel), false);
450 		}
451 		else
452 		{
453 			/* Assume it's an option for the dictionary itself */
454 			dictoptions = lappend(dictoptions, defel);
455 		}
456 	}
457 
458 	/*
459 	 * Validation
460 	 */
461 	if (!OidIsValid(templId))
462 		ereport(ERROR,
463 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
464 				 errmsg("text search template is required")));
465 
466 	verify_dictoptions(templId, dictoptions);
467 
468 
469 	dictRel = table_open(TSDictionaryRelationId, RowExclusiveLock);
470 
471 	/*
472 	 * Looks good, insert
473 	 */
474 	memset(values, 0, sizeof(values));
475 	memset(nulls, false, sizeof(nulls));
476 
477 	dictOid = GetNewOidWithIndex(dictRel, TSDictionaryOidIndexId,
478 								 Anum_pg_ts_dict_oid);
479 	values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(dictOid);
480 	namestrcpy(&dname, dictname);
481 	values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
482 	values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
483 	values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
484 	values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
485 	if (dictoptions)
486 		values[Anum_pg_ts_dict_dictinitoption - 1] =
487 			PointerGetDatum(serialize_deflist(dictoptions));
488 	else
489 		nulls[Anum_pg_ts_dict_dictinitoption - 1] = true;
490 
491 	tup = heap_form_tuple(dictRel->rd_att, values, nulls);
492 
493 	CatalogTupleInsert(dictRel, tup);
494 
495 	address = makeDictionaryDependencies(tup);
496 
497 	/* Post creation hook for new text search dictionary */
498 	InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
499 
500 	heap_freetuple(tup);
501 
502 	table_close(dictRel, RowExclusiveLock);
503 
504 	return address;
505 }
506 
507 /*
508  * Guts of TS dictionary deletion.
509  */
510 void
RemoveTSDictionaryById(Oid dictId)511 RemoveTSDictionaryById(Oid dictId)
512 {
513 	Relation	relation;
514 	HeapTuple	tup;
515 
516 	relation = table_open(TSDictionaryRelationId, RowExclusiveLock);
517 
518 	tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
519 
520 	if (!HeapTupleIsValid(tup))
521 		elog(ERROR, "cache lookup failed for text search dictionary %u",
522 			 dictId);
523 
524 	CatalogTupleDelete(relation, &tup->t_self);
525 
526 	ReleaseSysCache(tup);
527 
528 	table_close(relation, RowExclusiveLock);
529 }
530 
531 /*
532  * ALTER TEXT SEARCH DICTIONARY
533  */
534 ObjectAddress
AlterTSDictionary(AlterTSDictionaryStmt * stmt)535 AlterTSDictionary(AlterTSDictionaryStmt *stmt)
536 {
537 	HeapTuple	tup,
538 				newtup;
539 	Relation	rel;
540 	Oid			dictId;
541 	ListCell   *pl;
542 	List	   *dictoptions;
543 	Datum		opt;
544 	bool		isnull;
545 	Datum		repl_val[Natts_pg_ts_dict];
546 	bool		repl_null[Natts_pg_ts_dict];
547 	bool		repl_repl[Natts_pg_ts_dict];
548 	ObjectAddress address;
549 
550 	dictId = get_ts_dict_oid(stmt->dictname, false);
551 
552 	rel = table_open(TSDictionaryRelationId, RowExclusiveLock);
553 
554 	tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
555 
556 	if (!HeapTupleIsValid(tup))
557 		elog(ERROR, "cache lookup failed for text search dictionary %u",
558 			 dictId);
559 
560 	/* must be owner */
561 	if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
562 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSDICTIONARY,
563 					   NameListToString(stmt->dictname));
564 
565 	/* deserialize the existing set of options */
566 	opt = SysCacheGetAttr(TSDICTOID, tup,
567 						  Anum_pg_ts_dict_dictinitoption,
568 						  &isnull);
569 	if (isnull)
570 		dictoptions = NIL;
571 	else
572 		dictoptions = deserialize_deflist(opt);
573 
574 	/*
575 	 * Modify the options list as per specified changes
576 	 */
577 	foreach(pl, stmt->options)
578 	{
579 		DefElem    *defel = (DefElem *) lfirst(pl);
580 		ListCell   *cell;
581 
582 		/*
583 		 * Remove any matches ...
584 		 */
585 		foreach(cell, dictoptions)
586 		{
587 			DefElem    *oldel = (DefElem *) lfirst(cell);
588 
589 			if (strcmp(oldel->defname, defel->defname) == 0)
590 				dictoptions = foreach_delete_current(dictoptions, cell);
591 		}
592 
593 		/*
594 		 * and add new value if it's got one
595 		 */
596 		if (defel->arg)
597 			dictoptions = lappend(dictoptions, defel);
598 	}
599 
600 	/*
601 	 * Validate
602 	 */
603 	verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
604 					   dictoptions);
605 
606 	/*
607 	 * Looks good, update
608 	 */
609 	memset(repl_val, 0, sizeof(repl_val));
610 	memset(repl_null, false, sizeof(repl_null));
611 	memset(repl_repl, false, sizeof(repl_repl));
612 
613 	if (dictoptions)
614 		repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
615 			PointerGetDatum(serialize_deflist(dictoptions));
616 	else
617 		repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true;
618 	repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;
619 
620 	newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
621 							   repl_val, repl_null, repl_repl);
622 
623 	CatalogTupleUpdate(rel, &newtup->t_self, newtup);
624 
625 	InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
626 
627 	ObjectAddressSet(address, TSDictionaryRelationId, dictId);
628 
629 	/*
630 	 * NOTE: because we only support altering the options, not the template,
631 	 * there is no need to update dependencies.  This might have to change if
632 	 * the options ever reference inside-the-database objects.
633 	 */
634 
635 	heap_freetuple(newtup);
636 	ReleaseSysCache(tup);
637 
638 	table_close(rel, RowExclusiveLock);
639 
640 	return address;
641 }
642 
643 /* ---------------------- TS Template commands -----------------------*/
644 
645 /*
646  * lookup a template support function and return its OID (as a Datum)
647  *
648  * attnum is the pg_ts_template column the function will go into
649  */
650 static Datum
get_ts_template_func(DefElem * defel,int attnum)651 get_ts_template_func(DefElem *defel, int attnum)
652 {
653 	List	   *funcName = defGetQualifiedName(defel);
654 	Oid			typeId[4];
655 	Oid			retTypeId;
656 	int			nargs;
657 	Oid			procOid;
658 
659 	retTypeId = INTERNALOID;
660 	typeId[0] = INTERNALOID;
661 	typeId[1] = INTERNALOID;
662 	typeId[2] = INTERNALOID;
663 	typeId[3] = INTERNALOID;
664 	switch (attnum)
665 	{
666 		case Anum_pg_ts_template_tmplinit:
667 			nargs = 1;
668 			break;
669 		case Anum_pg_ts_template_tmpllexize:
670 			nargs = 4;
671 			break;
672 		default:
673 			/* should not be here */
674 			elog(ERROR, "unrecognized attribute for text search template: %d",
675 				 attnum);
676 			nargs = 0;			/* keep compiler quiet */
677 	}
678 
679 	procOid = LookupFuncName(funcName, nargs, typeId, false);
680 	if (get_func_rettype(procOid) != retTypeId)
681 		ereport(ERROR,
682 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
683 				 errmsg("function %s should return type %s",
684 						func_signature_string(funcName, nargs, NIL, typeId),
685 						format_type_be(retTypeId))));
686 
687 	return ObjectIdGetDatum(procOid);
688 }
689 
690 /*
691  * make pg_depend entries for a new pg_ts_template entry
692  */
693 static ObjectAddress
makeTSTemplateDependencies(HeapTuple tuple)694 makeTSTemplateDependencies(HeapTuple tuple)
695 {
696 	Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
697 	ObjectAddress myself,
698 				referenced;
699 
700 	myself.classId = TSTemplateRelationId;
701 	myself.objectId = tmpl->oid;
702 	myself.objectSubId = 0;
703 
704 	/* dependency on namespace */
705 	referenced.classId = NamespaceRelationId;
706 	referenced.objectId = tmpl->tmplnamespace;
707 	referenced.objectSubId = 0;
708 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
709 
710 	/* dependency on extension */
711 	recordDependencyOnCurrentExtension(&myself, false);
712 
713 	/* dependencies on functions */
714 	referenced.classId = ProcedureRelationId;
715 	referenced.objectSubId = 0;
716 
717 	referenced.objectId = tmpl->tmpllexize;
718 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
719 
720 	if (OidIsValid(tmpl->tmplinit))
721 	{
722 		referenced.objectId = tmpl->tmplinit;
723 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
724 	}
725 
726 	return myself;
727 }
728 
729 /*
730  * CREATE TEXT SEARCH TEMPLATE
731  */
732 ObjectAddress
DefineTSTemplate(List * names,List * parameters)733 DefineTSTemplate(List *names, List *parameters)
734 {
735 	ListCell   *pl;
736 	Relation	tmplRel;
737 	HeapTuple	tup;
738 	Datum		values[Natts_pg_ts_template];
739 	bool		nulls[Natts_pg_ts_template];
740 	NameData	dname;
741 	int			i;
742 	Oid			tmplOid;
743 	Oid			namespaceoid;
744 	char	   *tmplname;
745 	ObjectAddress address;
746 
747 	if (!superuser())
748 		ereport(ERROR,
749 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
750 				 errmsg("must be superuser to create text search templates")));
751 
752 	/* Convert list of names to a name and namespace */
753 	namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
754 
755 	tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock);
756 
757 	for (i = 0; i < Natts_pg_ts_template; i++)
758 	{
759 		nulls[i] = false;
760 		values[i] = ObjectIdGetDatum(InvalidOid);
761 	}
762 
763 	tmplOid = GetNewOidWithIndex(tmplRel, TSTemplateOidIndexId,
764 								 Anum_pg_ts_dict_oid);
765 	values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(tmplOid);
766 	namestrcpy(&dname, tmplname);
767 	values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
768 	values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
769 
770 	/*
771 	 * loop over the definition list and extract the information we need.
772 	 */
773 	foreach(pl, parameters)
774 	{
775 		DefElem    *defel = (DefElem *) lfirst(pl);
776 
777 		if (strcmp(defel->defname, "init") == 0)
778 		{
779 			values[Anum_pg_ts_template_tmplinit - 1] =
780 				get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
781 			nulls[Anum_pg_ts_template_tmplinit - 1] = false;
782 		}
783 		else if (strcmp(defel->defname, "lexize") == 0)
784 		{
785 			values[Anum_pg_ts_template_tmpllexize - 1] =
786 				get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
787 			nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
788 		}
789 		else
790 			ereport(ERROR,
791 					(errcode(ERRCODE_SYNTAX_ERROR),
792 					 errmsg("text search template parameter \"%s\" not recognized",
793 							defel->defname)));
794 	}
795 
796 	/*
797 	 * Validation
798 	 */
799 	if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
800 		ereport(ERROR,
801 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
802 				 errmsg("text search template lexize method is required")));
803 
804 	/*
805 	 * Looks good, insert
806 	 */
807 	tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
808 
809 	CatalogTupleInsert(tmplRel, tup);
810 
811 	address = makeTSTemplateDependencies(tup);
812 
813 	/* Post creation hook for new text search template */
814 	InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
815 
816 	heap_freetuple(tup);
817 
818 	table_close(tmplRel, RowExclusiveLock);
819 
820 	return address;
821 }
822 
823 /*
824  * Guts of TS template deletion.
825  */
826 void
RemoveTSTemplateById(Oid tmplId)827 RemoveTSTemplateById(Oid tmplId)
828 {
829 	Relation	relation;
830 	HeapTuple	tup;
831 
832 	relation = table_open(TSTemplateRelationId, RowExclusiveLock);
833 
834 	tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
835 
836 	if (!HeapTupleIsValid(tup))
837 		elog(ERROR, "cache lookup failed for text search template %u",
838 			 tmplId);
839 
840 	CatalogTupleDelete(relation, &tup->t_self);
841 
842 	ReleaseSysCache(tup);
843 
844 	table_close(relation, RowExclusiveLock);
845 }
846 
847 /* ---------------------- TS Configuration commands -----------------------*/
848 
849 /*
850  * Finds syscache tuple of configuration.
851  * Returns NULL if no such cfg.
852  */
853 static HeapTuple
GetTSConfigTuple(List * names)854 GetTSConfigTuple(List *names)
855 {
856 	HeapTuple	tup;
857 	Oid			cfgId;
858 
859 	cfgId = get_ts_config_oid(names, true);
860 	if (!OidIsValid(cfgId))
861 		return NULL;
862 
863 	tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
864 
865 	if (!HeapTupleIsValid(tup)) /* should not happen */
866 		elog(ERROR, "cache lookup failed for text search configuration %u",
867 			 cfgId);
868 
869 	return tup;
870 }
871 
872 /*
873  * make pg_depend entries for a new or updated pg_ts_config entry
874  *
875  * Pass opened pg_ts_config_map relation if there might be any config map
876  * entries for the config.
877  */
878 static ObjectAddress
makeConfigurationDependencies(HeapTuple tuple,bool removeOld,Relation mapRel)879 makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
880 							  Relation mapRel)
881 {
882 	Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
883 	ObjectAddresses *addrs;
884 	ObjectAddress myself,
885 				referenced;
886 
887 	myself.classId = TSConfigRelationId;
888 	myself.objectId = cfg->oid;
889 	myself.objectSubId = 0;
890 
891 	/* for ALTER case, first flush old dependencies, except extension deps */
892 	if (removeOld)
893 	{
894 		deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
895 		deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
896 	}
897 
898 	/*
899 	 * We use an ObjectAddresses list to remove possible duplicate
900 	 * dependencies from the config map info.  The pg_ts_config items
901 	 * shouldn't be duplicates, but might as well fold them all into one call.
902 	 */
903 	addrs = new_object_addresses();
904 
905 	/* dependency on namespace */
906 	referenced.classId = NamespaceRelationId;
907 	referenced.objectId = cfg->cfgnamespace;
908 	referenced.objectSubId = 0;
909 	add_exact_object_address(&referenced, addrs);
910 
911 	/* dependency on owner */
912 	recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
913 
914 	/* dependency on extension */
915 	recordDependencyOnCurrentExtension(&myself, removeOld);
916 
917 	/* dependency on parser */
918 	referenced.classId = TSParserRelationId;
919 	referenced.objectId = cfg->cfgparser;
920 	referenced.objectSubId = 0;
921 	add_exact_object_address(&referenced, addrs);
922 
923 	/* dependencies on dictionaries listed in config map */
924 	if (mapRel)
925 	{
926 		ScanKeyData skey;
927 		SysScanDesc scan;
928 		HeapTuple	maptup;
929 
930 		/* CCI to ensure we can see effects of caller's changes */
931 		CommandCounterIncrement();
932 
933 		ScanKeyInit(&skey,
934 					Anum_pg_ts_config_map_mapcfg,
935 					BTEqualStrategyNumber, F_OIDEQ,
936 					ObjectIdGetDatum(myself.objectId));
937 
938 		scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
939 								  NULL, 1, &skey);
940 
941 		while (HeapTupleIsValid((maptup = systable_getnext(scan))))
942 		{
943 			Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
944 
945 			referenced.classId = TSDictionaryRelationId;
946 			referenced.objectId = cfgmap->mapdict;
947 			referenced.objectSubId = 0;
948 			add_exact_object_address(&referenced, addrs);
949 		}
950 
951 		systable_endscan(scan);
952 	}
953 
954 	/* Record 'em (this includes duplicate elimination) */
955 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
956 
957 	free_object_addresses(addrs);
958 
959 	return myself;
960 }
961 
962 /*
963  * CREATE TEXT SEARCH CONFIGURATION
964  */
965 ObjectAddress
DefineTSConfiguration(List * names,List * parameters,ObjectAddress * copied)966 DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
967 {
968 	Relation	cfgRel;
969 	Relation	mapRel = NULL;
970 	HeapTuple	tup;
971 	Datum		values[Natts_pg_ts_config];
972 	bool		nulls[Natts_pg_ts_config];
973 	AclResult	aclresult;
974 	Oid			namespaceoid;
975 	char	   *cfgname;
976 	NameData	cname;
977 	Oid			sourceOid = InvalidOid;
978 	Oid			prsOid = InvalidOid;
979 	Oid			cfgOid;
980 	ListCell   *pl;
981 	ObjectAddress address;
982 
983 	/* Convert list of names to a name and namespace */
984 	namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
985 
986 	/* Check we have creation rights in target namespace */
987 	aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
988 	if (aclresult != ACLCHECK_OK)
989 		aclcheck_error(aclresult, OBJECT_SCHEMA,
990 					   get_namespace_name(namespaceoid));
991 
992 	/*
993 	 * loop over the definition list and extract the information we need.
994 	 */
995 	foreach(pl, parameters)
996 	{
997 		DefElem    *defel = (DefElem *) lfirst(pl);
998 
999 		if (strcmp(defel->defname, "parser") == 0)
1000 			prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
1001 		else if (strcmp(defel->defname, "copy") == 0)
1002 			sourceOid = get_ts_config_oid(defGetQualifiedName(defel), false);
1003 		else
1004 			ereport(ERROR,
1005 					(errcode(ERRCODE_SYNTAX_ERROR),
1006 					 errmsg("text search configuration parameter \"%s\" not recognized",
1007 							defel->defname)));
1008 	}
1009 
1010 	if (OidIsValid(sourceOid) && OidIsValid(prsOid))
1011 		ereport(ERROR,
1012 				(errcode(ERRCODE_SYNTAX_ERROR),
1013 				 errmsg("cannot specify both PARSER and COPY options")));
1014 
1015 	/* make copied tsconfig available to callers */
1016 	if (copied && OidIsValid(sourceOid))
1017 	{
1018 		ObjectAddressSet(*copied,
1019 						 TSConfigRelationId,
1020 						 sourceOid);
1021 	}
1022 
1023 	/*
1024 	 * Look up source config if given.
1025 	 */
1026 	if (OidIsValid(sourceOid))
1027 	{
1028 		Form_pg_ts_config cfg;
1029 
1030 		tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid));
1031 		if (!HeapTupleIsValid(tup))
1032 			elog(ERROR, "cache lookup failed for text search configuration %u",
1033 				 sourceOid);
1034 
1035 		cfg = (Form_pg_ts_config) GETSTRUCT(tup);
1036 
1037 		/* use source's parser */
1038 		prsOid = cfg->cfgparser;
1039 
1040 		ReleaseSysCache(tup);
1041 	}
1042 
1043 	/*
1044 	 * Validation
1045 	 */
1046 	if (!OidIsValid(prsOid))
1047 		ereport(ERROR,
1048 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1049 				 errmsg("text search parser is required")));
1050 
1051 	cfgRel = table_open(TSConfigRelationId, RowExclusiveLock);
1052 
1053 	/*
1054 	 * Looks good, build tuple and insert
1055 	 */
1056 	memset(values, 0, sizeof(values));
1057 	memset(nulls, false, sizeof(nulls));
1058 
1059 	cfgOid = GetNewOidWithIndex(cfgRel, TSConfigOidIndexId,
1060 								Anum_pg_ts_config_oid);
1061 	values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(cfgOid);
1062 	namestrcpy(&cname, cfgname);
1063 	values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
1064 	values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
1065 	values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
1066 	values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
1067 
1068 	tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
1069 
1070 	CatalogTupleInsert(cfgRel, tup);
1071 
1072 	if (OidIsValid(sourceOid))
1073 	{
1074 		/*
1075 		 * Copy token-dicts map from source config
1076 		 */
1077 		ScanKeyData skey;
1078 		SysScanDesc scan;
1079 		HeapTuple	maptup;
1080 
1081 		mapRel = table_open(TSConfigMapRelationId, RowExclusiveLock);
1082 
1083 		ScanKeyInit(&skey,
1084 					Anum_pg_ts_config_map_mapcfg,
1085 					BTEqualStrategyNumber, F_OIDEQ,
1086 					ObjectIdGetDatum(sourceOid));
1087 
1088 		scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1089 								  NULL, 1, &skey);
1090 
1091 		while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1092 		{
1093 			Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1094 			HeapTuple	newmaptup;
1095 			Datum		mapvalues[Natts_pg_ts_config_map];
1096 			bool		mapnulls[Natts_pg_ts_config_map];
1097 
1098 			memset(mapvalues, 0, sizeof(mapvalues));
1099 			memset(mapnulls, false, sizeof(mapnulls));
1100 
1101 			mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
1102 			mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
1103 			mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
1104 			mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
1105 
1106 			newmaptup = heap_form_tuple(mapRel->rd_att, mapvalues, mapnulls);
1107 
1108 			CatalogTupleInsert(mapRel, newmaptup);
1109 
1110 			heap_freetuple(newmaptup);
1111 		}
1112 
1113 		systable_endscan(scan);
1114 	}
1115 
1116 	address = makeConfigurationDependencies(tup, false, mapRel);
1117 
1118 	/* Post creation hook for new text search configuration */
1119 	InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
1120 
1121 	heap_freetuple(tup);
1122 
1123 	if (mapRel)
1124 		table_close(mapRel, RowExclusiveLock);
1125 	table_close(cfgRel, RowExclusiveLock);
1126 
1127 	return address;
1128 }
1129 
1130 /*
1131  * Guts of TS configuration deletion.
1132  */
1133 void
RemoveTSConfigurationById(Oid cfgId)1134 RemoveTSConfigurationById(Oid cfgId)
1135 {
1136 	Relation	relCfg,
1137 				relMap;
1138 	HeapTuple	tup;
1139 	ScanKeyData skey;
1140 	SysScanDesc scan;
1141 
1142 	/* Remove the pg_ts_config entry */
1143 	relCfg = table_open(TSConfigRelationId, RowExclusiveLock);
1144 
1145 	tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
1146 
1147 	if (!HeapTupleIsValid(tup))
1148 		elog(ERROR, "cache lookup failed for text search dictionary %u",
1149 			 cfgId);
1150 
1151 	CatalogTupleDelete(relCfg, &tup->t_self);
1152 
1153 	ReleaseSysCache(tup);
1154 
1155 	table_close(relCfg, RowExclusiveLock);
1156 
1157 	/* Remove any pg_ts_config_map entries */
1158 	relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
1159 
1160 	ScanKeyInit(&skey,
1161 				Anum_pg_ts_config_map_mapcfg,
1162 				BTEqualStrategyNumber, F_OIDEQ,
1163 				ObjectIdGetDatum(cfgId));
1164 
1165 	scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1166 							  NULL, 1, &skey);
1167 
1168 	while (HeapTupleIsValid((tup = systable_getnext(scan))))
1169 	{
1170 		CatalogTupleDelete(relMap, &tup->t_self);
1171 	}
1172 
1173 	systable_endscan(scan);
1174 
1175 	table_close(relMap, RowExclusiveLock);
1176 }
1177 
1178 /*
1179  * ALTER TEXT SEARCH CONFIGURATION - main entry point
1180  */
1181 ObjectAddress
AlterTSConfiguration(AlterTSConfigurationStmt * stmt)1182 AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
1183 {
1184 	HeapTuple	tup;
1185 	Oid			cfgId;
1186 	Relation	relMap;
1187 	ObjectAddress address;
1188 
1189 	/* Find the configuration */
1190 	tup = GetTSConfigTuple(stmt->cfgname);
1191 	if (!HeapTupleIsValid(tup))
1192 		ereport(ERROR,
1193 				(errcode(ERRCODE_UNDEFINED_OBJECT),
1194 				 errmsg("text search configuration \"%s\" does not exist",
1195 						NameListToString(stmt->cfgname))));
1196 
1197 	cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid;
1198 
1199 	/* must be owner */
1200 	if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1201 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSCONFIGURATION,
1202 					   NameListToString(stmt->cfgname));
1203 
1204 	relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
1205 
1206 	/* Add or drop mappings */
1207 	if (stmt->dicts)
1208 		MakeConfigurationMapping(stmt, tup, relMap);
1209 	else if (stmt->tokentype)
1210 		DropConfigurationMapping(stmt, tup, relMap);
1211 
1212 	/* Update dependencies */
1213 	makeConfigurationDependencies(tup, true, relMap);
1214 
1215 	InvokeObjectPostAlterHook(TSConfigRelationId, cfgId, 0);
1216 
1217 	ObjectAddressSet(address, TSConfigRelationId, cfgId);
1218 
1219 	table_close(relMap, RowExclusiveLock);
1220 
1221 	ReleaseSysCache(tup);
1222 
1223 	return address;
1224 }
1225 
1226 /*
1227  * Translate a list of token type names to an array of token type numbers
1228  */
1229 static int *
getTokenTypes(Oid prsId,List * tokennames)1230 getTokenTypes(Oid prsId, List *tokennames)
1231 {
1232 	TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
1233 	LexDescr   *list;
1234 	int		   *res,
1235 				i,
1236 				ntoken;
1237 	ListCell   *tn;
1238 
1239 	ntoken = list_length(tokennames);
1240 	if (ntoken == 0)
1241 		return NULL;
1242 	res = (int *) palloc(sizeof(int) * ntoken);
1243 
1244 	if (!OidIsValid(prs->lextypeOid))
1245 		elog(ERROR, "method lextype isn't defined for text search parser %u",
1246 			 prsId);
1247 
1248 	/* lextype takes one dummy argument */
1249 	list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
1250 														 (Datum) 0));
1251 
1252 	i = 0;
1253 	foreach(tn, tokennames)
1254 	{
1255 		Value	   *val = (Value *) lfirst(tn);
1256 		bool		found = false;
1257 		int			j;
1258 
1259 		j = 0;
1260 		while (list && list[j].lexid)
1261 		{
1262 			if (strcmp(strVal(val), list[j].alias) == 0)
1263 			{
1264 				res[i] = list[j].lexid;
1265 				found = true;
1266 				break;
1267 			}
1268 			j++;
1269 		}
1270 		if (!found)
1271 			ereport(ERROR,
1272 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1273 					 errmsg("token type \"%s\" does not exist",
1274 							strVal(val))));
1275 		i++;
1276 	}
1277 
1278 	return res;
1279 }
1280 
1281 /*
1282  * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
1283  */
1284 static void
MakeConfigurationMapping(AlterTSConfigurationStmt * stmt,HeapTuple tup,Relation relMap)1285 MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
1286 						 HeapTuple tup, Relation relMap)
1287 {
1288 	Form_pg_ts_config tsform;
1289 	Oid			cfgId;
1290 	ScanKeyData skey[2];
1291 	SysScanDesc scan;
1292 	HeapTuple	maptup;
1293 	int			i;
1294 	int			j;
1295 	Oid			prsId;
1296 	int		   *tokens,
1297 				ntoken;
1298 	Oid		   *dictIds;
1299 	int			ndict;
1300 	ListCell   *c;
1301 
1302 	tsform = (Form_pg_ts_config) GETSTRUCT(tup);
1303 	cfgId = tsform->oid;
1304 	prsId = tsform->cfgparser;
1305 
1306 	tokens = getTokenTypes(prsId, stmt->tokentype);
1307 	ntoken = list_length(stmt->tokentype);
1308 
1309 	if (stmt->override)
1310 	{
1311 		/*
1312 		 * delete maps for tokens if they exist and command was ALTER
1313 		 */
1314 		for (i = 0; i < ntoken; i++)
1315 		{
1316 			ScanKeyInit(&skey[0],
1317 						Anum_pg_ts_config_map_mapcfg,
1318 						BTEqualStrategyNumber, F_OIDEQ,
1319 						ObjectIdGetDatum(cfgId));
1320 			ScanKeyInit(&skey[1],
1321 						Anum_pg_ts_config_map_maptokentype,
1322 						BTEqualStrategyNumber, F_INT4EQ,
1323 						Int32GetDatum(tokens[i]));
1324 
1325 			scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1326 									  NULL, 2, skey);
1327 
1328 			while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1329 			{
1330 				CatalogTupleDelete(relMap, &maptup->t_self);
1331 			}
1332 
1333 			systable_endscan(scan);
1334 		}
1335 	}
1336 
1337 	/*
1338 	 * Convert list of dictionary names to array of dict OIDs
1339 	 */
1340 	ndict = list_length(stmt->dicts);
1341 	dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
1342 	i = 0;
1343 	foreach(c, stmt->dicts)
1344 	{
1345 		List	   *names = (List *) lfirst(c);
1346 
1347 		dictIds[i] = get_ts_dict_oid(names, false);
1348 		i++;
1349 	}
1350 
1351 	if (stmt->replace)
1352 	{
1353 		/*
1354 		 * Replace a specific dictionary in existing entries
1355 		 */
1356 		Oid			dictOld = dictIds[0],
1357 					dictNew = dictIds[1];
1358 
1359 		ScanKeyInit(&skey[0],
1360 					Anum_pg_ts_config_map_mapcfg,
1361 					BTEqualStrategyNumber, F_OIDEQ,
1362 					ObjectIdGetDatum(cfgId));
1363 
1364 		scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1365 								  NULL, 1, skey);
1366 
1367 		while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1368 		{
1369 			Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1370 
1371 			/*
1372 			 * check if it's one of target token types
1373 			 */
1374 			if (tokens)
1375 			{
1376 				bool		tokmatch = false;
1377 
1378 				for (j = 0; j < ntoken; j++)
1379 				{
1380 					if (cfgmap->maptokentype == tokens[j])
1381 					{
1382 						tokmatch = true;
1383 						break;
1384 					}
1385 				}
1386 				if (!tokmatch)
1387 					continue;
1388 			}
1389 
1390 			/*
1391 			 * replace dictionary if match
1392 			 */
1393 			if (cfgmap->mapdict == dictOld)
1394 			{
1395 				Datum		repl_val[Natts_pg_ts_config_map];
1396 				bool		repl_null[Natts_pg_ts_config_map];
1397 				bool		repl_repl[Natts_pg_ts_config_map];
1398 				HeapTuple	newtup;
1399 
1400 				memset(repl_val, 0, sizeof(repl_val));
1401 				memset(repl_null, false, sizeof(repl_null));
1402 				memset(repl_repl, false, sizeof(repl_repl));
1403 
1404 				repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
1405 				repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
1406 
1407 				newtup = heap_modify_tuple(maptup,
1408 										   RelationGetDescr(relMap),
1409 										   repl_val, repl_null, repl_repl);
1410 				CatalogTupleUpdate(relMap, &newtup->t_self, newtup);
1411 			}
1412 		}
1413 
1414 		systable_endscan(scan);
1415 	}
1416 	else
1417 	{
1418 		/*
1419 		 * Insertion of new entries
1420 		 */
1421 		for (i = 0; i < ntoken; i++)
1422 		{
1423 			for (j = 0; j < ndict; j++)
1424 			{
1425 				Datum		values[Natts_pg_ts_config_map];
1426 				bool		nulls[Natts_pg_ts_config_map];
1427 
1428 				memset(nulls, false, sizeof(nulls));
1429 				values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
1430 				values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]);
1431 				values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
1432 				values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
1433 
1434 				tup = heap_form_tuple(relMap->rd_att, values, nulls);
1435 				CatalogTupleInsert(relMap, tup);
1436 
1437 				heap_freetuple(tup);
1438 			}
1439 		}
1440 	}
1441 
1442 	EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
1443 }
1444 
1445 /*
1446  * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
1447  */
1448 static void
DropConfigurationMapping(AlterTSConfigurationStmt * stmt,HeapTuple tup,Relation relMap)1449 DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
1450 						 HeapTuple tup, Relation relMap)
1451 {
1452 	Form_pg_ts_config tsform;
1453 	Oid			cfgId;
1454 	ScanKeyData skey[2];
1455 	SysScanDesc scan;
1456 	HeapTuple	maptup;
1457 	int			i;
1458 	Oid			prsId;
1459 	int		   *tokens;
1460 	ListCell   *c;
1461 
1462 	tsform = (Form_pg_ts_config) GETSTRUCT(tup);
1463 	cfgId = tsform->oid;
1464 	prsId = tsform->cfgparser;
1465 
1466 	tokens = getTokenTypes(prsId, stmt->tokentype);
1467 
1468 	i = 0;
1469 	foreach(c, stmt->tokentype)
1470 	{
1471 		Value	   *val = (Value *) lfirst(c);
1472 		bool		found = false;
1473 
1474 		ScanKeyInit(&skey[0],
1475 					Anum_pg_ts_config_map_mapcfg,
1476 					BTEqualStrategyNumber, F_OIDEQ,
1477 					ObjectIdGetDatum(cfgId));
1478 		ScanKeyInit(&skey[1],
1479 					Anum_pg_ts_config_map_maptokentype,
1480 					BTEqualStrategyNumber, F_INT4EQ,
1481 					Int32GetDatum(tokens[i]));
1482 
1483 		scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1484 								  NULL, 2, skey);
1485 
1486 		while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1487 		{
1488 			CatalogTupleDelete(relMap, &maptup->t_self);
1489 			found = true;
1490 		}
1491 
1492 		systable_endscan(scan);
1493 
1494 		if (!found)
1495 		{
1496 			if (!stmt->missing_ok)
1497 			{
1498 				ereport(ERROR,
1499 						(errcode(ERRCODE_UNDEFINED_OBJECT),
1500 						 errmsg("mapping for token type \"%s\" does not exist",
1501 								strVal(val))));
1502 			}
1503 			else
1504 			{
1505 				ereport(NOTICE,
1506 						(errmsg("mapping for token type \"%s\" does not exist, skipping",
1507 								strVal(val))));
1508 			}
1509 		}
1510 
1511 		i++;
1512 	}
1513 
1514 	EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
1515 }
1516 
1517 
1518 /*
1519  * Serialize dictionary options, producing a TEXT datum from a List of DefElem
1520  *
1521  * This is used to form the value stored in pg_ts_dict.dictinitoption.
1522  * For the convenience of pg_dump, the output is formatted exactly as it
1523  * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
1524  * same options.
1525  */
1526 text *
serialize_deflist(List * deflist)1527 serialize_deflist(List *deflist)
1528 {
1529 	text	   *result;
1530 	StringInfoData buf;
1531 	ListCell   *l;
1532 
1533 	initStringInfo(&buf);
1534 
1535 	foreach(l, deflist)
1536 	{
1537 		DefElem    *defel = (DefElem *) lfirst(l);
1538 		char	   *val = defGetString(defel);
1539 
1540 		appendStringInfo(&buf, "%s = ",
1541 						 quote_identifier(defel->defname));
1542 
1543 		/*
1544 		 * If the value is a T_Integer or T_Float, emit it without quotes,
1545 		 * otherwise with quotes.  This is essential to allow correct
1546 		 * reconstruction of the node type as well as the value.
1547 		 */
1548 		if (IsA(defel->arg, Integer) || IsA(defel->arg, Float))
1549 			appendStringInfoString(&buf, val);
1550 		else
1551 		{
1552 			/* If backslashes appear, force E syntax to quote them safely */
1553 			if (strchr(val, '\\'))
1554 				appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
1555 			appendStringInfoChar(&buf, '\'');
1556 			while (*val)
1557 			{
1558 				char		ch = *val++;
1559 
1560 				if (SQL_STR_DOUBLE(ch, true))
1561 					appendStringInfoChar(&buf, ch);
1562 				appendStringInfoChar(&buf, ch);
1563 			}
1564 			appendStringInfoChar(&buf, '\'');
1565 		}
1566 		if (lnext(deflist, l) != NULL)
1567 			appendStringInfoString(&buf, ", ");
1568 	}
1569 
1570 	result = cstring_to_text_with_len(buf.data, buf.len);
1571 	pfree(buf.data);
1572 	return result;
1573 }
1574 
1575 /*
1576  * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
1577  *
1578  * This is also used for prsheadline options, so for backward compatibility
1579  * we need to accept a few things serialize_deflist() will never emit:
1580  * in particular, unquoted and double-quoted strings.
1581  */
1582 List *
deserialize_deflist(Datum txt)1583 deserialize_deflist(Datum txt)
1584 {
1585 	text	   *in = DatumGetTextPP(txt);	/* in case it's toasted */
1586 	List	   *result = NIL;
1587 	int			len = VARSIZE_ANY_EXHDR(in);
1588 	char	   *ptr,
1589 			   *endptr,
1590 			   *workspace,
1591 			   *wsptr = NULL,
1592 			   *startvalue = NULL;
1593 	typedef enum
1594 	{
1595 		CS_WAITKEY,
1596 		CS_INKEY,
1597 		CS_INQKEY,
1598 		CS_WAITEQ,
1599 		CS_WAITVALUE,
1600 		CS_INSQVALUE,
1601 		CS_INDQVALUE,
1602 		CS_INWVALUE
1603 	} ds_state;
1604 	ds_state	state = CS_WAITKEY;
1605 
1606 	workspace = (char *) palloc(len + 1);	/* certainly enough room */
1607 	ptr = VARDATA_ANY(in);
1608 	endptr = ptr + len;
1609 	for (; ptr < endptr; ptr++)
1610 	{
1611 		switch (state)
1612 		{
1613 			case CS_WAITKEY:
1614 				if (isspace((unsigned char) *ptr) || *ptr == ',')
1615 					continue;
1616 				if (*ptr == '"')
1617 				{
1618 					wsptr = workspace;
1619 					state = CS_INQKEY;
1620 				}
1621 				else
1622 				{
1623 					wsptr = workspace;
1624 					*wsptr++ = *ptr;
1625 					state = CS_INKEY;
1626 				}
1627 				break;
1628 			case CS_INKEY:
1629 				if (isspace((unsigned char) *ptr))
1630 				{
1631 					*wsptr++ = '\0';
1632 					state = CS_WAITEQ;
1633 				}
1634 				else if (*ptr == '=')
1635 				{
1636 					*wsptr++ = '\0';
1637 					state = CS_WAITVALUE;
1638 				}
1639 				else
1640 				{
1641 					*wsptr++ = *ptr;
1642 				}
1643 				break;
1644 			case CS_INQKEY:
1645 				if (*ptr == '"')
1646 				{
1647 					if (ptr + 1 < endptr && ptr[1] == '"')
1648 					{
1649 						/* copy only one of the two quotes */
1650 						*wsptr++ = *ptr++;
1651 					}
1652 					else
1653 					{
1654 						*wsptr++ = '\0';
1655 						state = CS_WAITEQ;
1656 					}
1657 				}
1658 				else
1659 				{
1660 					*wsptr++ = *ptr;
1661 				}
1662 				break;
1663 			case CS_WAITEQ:
1664 				if (*ptr == '=')
1665 					state = CS_WAITVALUE;
1666 				else if (!isspace((unsigned char) *ptr))
1667 					ereport(ERROR,
1668 							(errcode(ERRCODE_SYNTAX_ERROR),
1669 							 errmsg("invalid parameter list format: \"%s\"",
1670 									text_to_cstring(in))));
1671 				break;
1672 			case CS_WAITVALUE:
1673 				if (*ptr == '\'')
1674 				{
1675 					startvalue = wsptr;
1676 					state = CS_INSQVALUE;
1677 				}
1678 				else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
1679 				{
1680 					ptr++;
1681 					startvalue = wsptr;
1682 					state = CS_INSQVALUE;
1683 				}
1684 				else if (*ptr == '"')
1685 				{
1686 					startvalue = wsptr;
1687 					state = CS_INDQVALUE;
1688 				}
1689 				else if (!isspace((unsigned char) *ptr))
1690 				{
1691 					startvalue = wsptr;
1692 					*wsptr++ = *ptr;
1693 					state = CS_INWVALUE;
1694 				}
1695 				break;
1696 			case CS_INSQVALUE:
1697 				if (*ptr == '\'')
1698 				{
1699 					if (ptr + 1 < endptr && ptr[1] == '\'')
1700 					{
1701 						/* copy only one of the two quotes */
1702 						*wsptr++ = *ptr++;
1703 					}
1704 					else
1705 					{
1706 						*wsptr++ = '\0';
1707 						result = lappend(result,
1708 										 buildDefItem(workspace,
1709 													  startvalue,
1710 													  true));
1711 						state = CS_WAITKEY;
1712 					}
1713 				}
1714 				else if (*ptr == '\\')
1715 				{
1716 					if (ptr + 1 < endptr && ptr[1] == '\\')
1717 					{
1718 						/* copy only one of the two backslashes */
1719 						*wsptr++ = *ptr++;
1720 					}
1721 					else
1722 						*wsptr++ = *ptr;
1723 				}
1724 				else
1725 				{
1726 					*wsptr++ = *ptr;
1727 				}
1728 				break;
1729 			case CS_INDQVALUE:
1730 				if (*ptr == '"')
1731 				{
1732 					if (ptr + 1 < endptr && ptr[1] == '"')
1733 					{
1734 						/* copy only one of the two quotes */
1735 						*wsptr++ = *ptr++;
1736 					}
1737 					else
1738 					{
1739 						*wsptr++ = '\0';
1740 						result = lappend(result,
1741 										 buildDefItem(workspace,
1742 													  startvalue,
1743 													  true));
1744 						state = CS_WAITKEY;
1745 					}
1746 				}
1747 				else
1748 				{
1749 					*wsptr++ = *ptr;
1750 				}
1751 				break;
1752 			case CS_INWVALUE:
1753 				if (*ptr == ',' || isspace((unsigned char) *ptr))
1754 				{
1755 					*wsptr++ = '\0';
1756 					result = lappend(result,
1757 									 buildDefItem(workspace,
1758 												  startvalue,
1759 												  false));
1760 					state = CS_WAITKEY;
1761 				}
1762 				else
1763 				{
1764 					*wsptr++ = *ptr;
1765 				}
1766 				break;
1767 			default:
1768 				elog(ERROR, "unrecognized deserialize_deflist state: %d",
1769 					 state);
1770 		}
1771 	}
1772 
1773 	if (state == CS_INWVALUE)
1774 	{
1775 		*wsptr++ = '\0';
1776 		result = lappend(result,
1777 						 buildDefItem(workspace,
1778 									  startvalue,
1779 									  false));
1780 	}
1781 	else if (state != CS_WAITKEY)
1782 		ereport(ERROR,
1783 				(errcode(ERRCODE_SYNTAX_ERROR),
1784 				 errmsg("invalid parameter list format: \"%s\"",
1785 						text_to_cstring(in))));
1786 
1787 	pfree(workspace);
1788 
1789 	return result;
1790 }
1791 
1792 /*
1793  * Build one DefElem for deserialize_deflist
1794  */
1795 static DefElem *
buildDefItem(const char * name,const char * val,bool was_quoted)1796 buildDefItem(const char *name, const char *val, bool was_quoted)
1797 {
1798 	/* If input was quoted, always emit as string */
1799 	if (!was_quoted && val[0] != '\0')
1800 	{
1801 		int			v;
1802 		char	   *endptr;
1803 
1804 		/* Try to parse as an integer */
1805 		errno = 0;
1806 		v = strtoint(val, &endptr, 10);
1807 		if (errno == 0 && *endptr == '\0')
1808 			return makeDefElem(pstrdup(name),
1809 							   (Node *) makeInteger(v),
1810 							   -1);
1811 		/* Nope, how about as a float? */
1812 		errno = 0;
1813 		(void) strtod(val, &endptr);
1814 		if (errno == 0 && *endptr == '\0')
1815 			return makeDefElem(pstrdup(name),
1816 							   (Node *) makeFloat(pstrdup(val)),
1817 							   -1);
1818 	}
1819 	/* Just make it a string */
1820 	return makeDefElem(pstrdup(name),
1821 					   (Node *) makeString(pstrdup(val)),
1822 					   -1);
1823 }
1824