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
51static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
52 HeapTuple tup, Relation relMap);
53static 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 */
64static Datum
65get_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 */
127static ObjectAddress
128makeParserDependencies(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 */
175ObjectAddress
176DefineTSParser(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 */
294void
295RemoveTSParserById(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 */
321static ObjectAddress
322makeDictionaryDependencies(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 */
356static void
357verify_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 */
411ObjectAddress
412DefineTSDictionary(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 */
507void
508RemoveTSDictionaryById(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 */
531ObjectAddress
532AlterTSDictionary(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 */
653static Datum
654get_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 */
696static ObjectAddress
697makeTSTemplateDependencies(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 */
735ObjectAddress
736DefineTSTemplate(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 */
829void
830RemoveTSTemplateById(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 */
856static HeapTuple
857GetTSConfigTuple(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 */
881static ObjectAddress
882makeConfigurationDependencies(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 */
968ObjectAddress
969DefineTSConfiguration(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 */
1136void
1137RemoveTSConfigurationById(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 */
1184ObjectAddress
1185AlterTSConfiguration(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 */
1232static int *
1233getTokenTypes(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 */
1287static void
1288MakeConfigurationMapping(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 */
1451static void
1452DropConfigurationMapping(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 */
1532text *
1533serialize_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 */
1577List *
1578deserialize_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