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