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