Refactor the handling of the various DropStmt variants so that when multiple
[PostgreSQL.git] / src / backend / commands / tsearchcmds.c
blob407a7334c27cef9ee20ccc4d806e5a3a4cc1dedb
1 /*-------------------------------------------------------------------------
3 * tsearchcmds.c
5 * Routines for tsearch manipulation commands
7 * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
11 * IDENTIFICATION
12 * $PostgreSQL$
14 *-------------------------------------------------------------------------
16 #include "postgres.h"
18 #include <ctype.h>
20 #include "access/heapam.h"
21 #include "access/genam.h"
22 #include "access/xact.h"
23 #include "catalog/dependency.h"
24 #include "catalog/indexing.h"
25 #include "catalog/namespace.h"
26 #include "catalog/pg_namespace.h"
27 #include "catalog/pg_proc.h"
28 #include "catalog/pg_ts_config.h"
29 #include "catalog/pg_ts_config_map.h"
30 #include "catalog/pg_ts_dict.h"
31 #include "catalog/pg_ts_parser.h"
32 #include "catalog/pg_ts_template.h"
33 #include "catalog/pg_type.h"
34 #include "commands/defrem.h"
35 #include "miscadmin.h"
36 #include "nodes/makefuncs.h"
37 #include "parser/parse_func.h"
38 #include "tsearch/ts_cache.h"
39 #include "tsearch/ts_public.h"
40 #include "tsearch/ts_utils.h"
41 #include "utils/acl.h"
42 #include "utils/builtins.h"
43 #include "utils/catcache.h"
44 #include "utils/fmgroids.h"
45 #include "utils/lsyscache.h"
46 #include "utils/syscache.h"
47 #include "utils/tqual.h"
50 static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
51 HeapTuple tup, Relation relMap);
52 static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
53 HeapTuple tup, Relation relMap);
56 /* --------------------- TS Parser commands ------------------------ */
59 * lookup a parser support function and return its OID (as a Datum)
61 * attnum is the pg_ts_parser column the function will go into
63 static Datum
64 get_ts_parser_func(DefElem *defel, int attnum)
66 List *funcName = defGetQualifiedName(defel);
67 Oid typeId[3];
68 Oid retTypeId;
69 int nargs;
70 Oid procOid;
72 retTypeId = INTERNALOID; /* correct for most */
73 typeId[0] = INTERNALOID;
74 switch (attnum)
76 case Anum_pg_ts_parser_prsstart:
77 nargs = 2;
78 typeId[1] = INT4OID;
79 break;
80 case Anum_pg_ts_parser_prstoken:
81 nargs = 3;
82 typeId[1] = INTERNALOID;
83 typeId[2] = INTERNALOID;
84 break;
85 case Anum_pg_ts_parser_prsend:
86 nargs = 1;
87 retTypeId = VOIDOID;
88 break;
89 case Anum_pg_ts_parser_prsheadline:
90 nargs = 3;
91 typeId[1] = INTERNALOID;
92 typeId[2] = TSQUERYOID;
93 break;
94 case Anum_pg_ts_parser_prslextype:
95 nargs = 1;
96 break;
97 default:
98 /* should not be here */
99 elog(ERROR, "unrecognized attribute for text search parser: %d",
100 attnum);
101 nargs = 0; /* keep compiler quiet */
104 procOid = LookupFuncName(funcName, nargs, typeId, false);
105 if (get_func_rettype(procOid) != retTypeId)
106 ereport(ERROR,
107 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
108 errmsg("function %s should return type %s",
109 func_signature_string(funcName, nargs, typeId),
110 format_type_be(retTypeId))));
112 return ObjectIdGetDatum(procOid);
116 * make pg_depend entries for a new pg_ts_parser entry
118 static void
119 makeParserDependencies(HeapTuple tuple)
121 Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
122 ObjectAddress myself,
123 referenced;
125 myself.classId = TSParserRelationId;
126 myself.objectId = HeapTupleGetOid(tuple);
127 myself.objectSubId = 0;
129 /* dependency on namespace */
130 referenced.classId = NamespaceRelationId;
131 referenced.objectId = prs->prsnamespace;
132 referenced.objectSubId = 0;
133 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
135 /* dependencies on functions */
136 referenced.classId = ProcedureRelationId;
137 referenced.objectSubId = 0;
139 referenced.objectId = prs->prsstart;
140 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
142 referenced.objectId = prs->prstoken;
143 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
145 referenced.objectId = prs->prsend;
146 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
148 referenced.objectId = prs->prslextype;
149 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
151 if (OidIsValid(prs->prsheadline))
153 referenced.objectId = prs->prsheadline;
154 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
159 * CREATE TEXT SEARCH PARSER
161 void
162 DefineTSParser(List *names, List *parameters)
164 char *prsname;
165 ListCell *pl;
166 Relation prsRel;
167 HeapTuple tup;
168 Datum values[Natts_pg_ts_parser];
169 char nulls[Natts_pg_ts_parser];
170 NameData pname;
171 Oid prsOid;
172 Oid namespaceoid;
174 if (!superuser())
175 ereport(ERROR,
176 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
177 errmsg("must be superuser to create text search parsers")));
179 /* Convert list of names to a name and namespace */
180 namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
182 /* initialize tuple fields with name/namespace */
183 memset(values, 0, sizeof(values));
184 memset(nulls, ' ', sizeof(nulls));
186 namestrcpy(&pname, prsname);
187 values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
188 values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
191 * loop over the definition list and extract the information we need.
193 foreach(pl, parameters)
195 DefElem *defel = (DefElem *) lfirst(pl);
197 if (pg_strcasecmp(defel->defname, "start") == 0)
199 values[Anum_pg_ts_parser_prsstart - 1] =
200 get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
202 else if (pg_strcasecmp(defel->defname, "gettoken") == 0)
204 values[Anum_pg_ts_parser_prstoken - 1] =
205 get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
207 else if (pg_strcasecmp(defel->defname, "end") == 0)
209 values[Anum_pg_ts_parser_prsend - 1] =
210 get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
212 else if (pg_strcasecmp(defel->defname, "headline") == 0)
214 values[Anum_pg_ts_parser_prsheadline - 1] =
215 get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
217 else if (pg_strcasecmp(defel->defname, "lextypes") == 0)
219 values[Anum_pg_ts_parser_prslextype - 1] =
220 get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
222 else
223 ereport(ERROR,
224 (errcode(ERRCODE_SYNTAX_ERROR),
225 errmsg("text search parser parameter \"%s\" not recognized",
226 defel->defname)));
230 * Validation
232 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
233 ereport(ERROR,
234 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
235 errmsg("text search parser start method is required")));
237 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
238 ereport(ERROR,
239 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
240 errmsg("text search parser gettoken method is required")));
242 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
243 ereport(ERROR,
244 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
245 errmsg("text search parser end method is required")));
247 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
248 ereport(ERROR,
249 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
250 errmsg("text search parser lextypes method is required")));
253 * Looks good, insert
255 prsRel = heap_open(TSParserRelationId, RowExclusiveLock);
257 tup = heap_formtuple(prsRel->rd_att, values, nulls);
259 prsOid = simple_heap_insert(prsRel, tup);
261 CatalogUpdateIndexes(prsRel, tup);
263 makeParserDependencies(tup);
265 heap_freetuple(tup);
267 heap_close(prsRel, RowExclusiveLock);
271 * DROP TEXT SEARCH PARSER
273 void
274 RemoveTSParsers(DropStmt *drop)
276 ObjectAddresses *objects;
277 ListCell *cell;
279 if (!superuser())
280 ereport(ERROR,
281 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
282 errmsg("must be superuser to drop text search parsers")));
285 * First we identify all the objects, then we delete them in a single
286 * performMultipleDeletions() call. This is to avoid unwanted
287 * DROP RESTRICT errors if one of the objects depends on another.
289 objects = new_object_addresses();
291 foreach(cell, drop->objects)
293 List *names = (List *) lfirst(cell);
294 Oid prsOid;
295 ObjectAddress object;
297 prsOid = TSParserGetPrsid(names, true);
299 if (!OidIsValid(prsOid))
301 if (!drop->missing_ok)
303 ereport(ERROR,
304 (errcode(ERRCODE_UNDEFINED_OBJECT),
305 errmsg("text search parser \"%s\" does not exist",
306 NameListToString(names))));
308 else
310 ereport(NOTICE,
311 (errmsg("text search parser \"%s\" does not exist, skipping",
312 NameListToString(names))));
314 continue;
317 object.classId = TSParserRelationId;
318 object.objectId = prsOid;
319 object.objectSubId = 0;
321 add_exact_object_address(&object, objects);
324 performMultipleDeletions(objects, drop->behavior);
326 free_object_addresses(objects);
330 * Guts of TS parser deletion.
332 void
333 RemoveTSParserById(Oid prsId)
335 Relation relation;
336 HeapTuple tup;
338 relation = heap_open(TSParserRelationId, RowExclusiveLock);
340 tup = SearchSysCache(TSPARSEROID,
341 ObjectIdGetDatum(prsId),
342 0, 0, 0);
344 if (!HeapTupleIsValid(tup))
345 elog(ERROR, "cache lookup failed for text search parser %u", prsId);
347 simple_heap_delete(relation, &tup->t_self);
349 ReleaseSysCache(tup);
351 heap_close(relation, RowExclusiveLock);
355 * ALTER TEXT SEARCH PARSER RENAME
357 void
358 RenameTSParser(List *oldname, const char *newname)
360 HeapTuple tup;
361 Relation rel;
362 Oid prsId;
363 Oid namespaceOid;
365 if (!superuser())
366 ereport(ERROR,
367 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
368 errmsg("must be superuser to rename text search parsers")));
370 rel = heap_open(TSParserRelationId, RowExclusiveLock);
372 prsId = TSParserGetPrsid(oldname, false);
374 tup = SearchSysCacheCopy(TSPARSEROID,
375 ObjectIdGetDatum(prsId),
376 0, 0, 0);
378 if (!HeapTupleIsValid(tup)) /* should not happen */
379 elog(ERROR, "cache lookup failed for text search parser %u", prsId);
381 namespaceOid = ((Form_pg_ts_parser) GETSTRUCT(tup))->prsnamespace;
383 if (SearchSysCacheExists(TSPARSERNAMENSP,
384 PointerGetDatum(newname),
385 ObjectIdGetDatum(namespaceOid),
386 0, 0))
387 ereport(ERROR,
388 (errcode(ERRCODE_DUPLICATE_OBJECT),
389 errmsg("text search parser \"%s\" already exists",
390 newname)));
392 namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname);
393 simple_heap_update(rel, &tup->t_self, tup);
394 CatalogUpdateIndexes(rel, tup);
396 heap_close(rel, NoLock);
397 heap_freetuple(tup);
400 /* ---------------------- TS Dictionary commands -----------------------*/
403 * make pg_depend entries for a new pg_ts_dict entry
405 static void
406 makeDictionaryDependencies(HeapTuple tuple)
408 Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
409 ObjectAddress myself,
410 referenced;
412 myself.classId = TSDictionaryRelationId;
413 myself.objectId = HeapTupleGetOid(tuple);
414 myself.objectSubId = 0;
416 /* dependency on namespace */
417 referenced.classId = NamespaceRelationId;
418 referenced.objectId = dict->dictnamespace;
419 referenced.objectSubId = 0;
420 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
422 /* dependency on owner */
423 recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
425 /* dependency on template */
426 referenced.classId = TSTemplateRelationId;
427 referenced.objectId = dict->dicttemplate;
428 referenced.objectSubId = 0;
429 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
433 * verify that a template's init method accepts a proposed option list
435 static void
436 verify_dictoptions(Oid tmplId, List *dictoptions)
438 HeapTuple tup;
439 Form_pg_ts_template tform;
440 Oid initmethod;
443 * Suppress this test when running in a standalone backend. This is a
444 * hack to allow initdb to create prefab dictionaries that might not
445 * actually be usable in template1's encoding (due to using external files
446 * that can't be translated into template1's encoding). We want to create
447 * them anyway, since they might be usable later in other databases.
449 if (!IsUnderPostmaster)
450 return;
452 tup = SearchSysCache(TSTEMPLATEOID,
453 ObjectIdGetDatum(tmplId),
454 0, 0, 0);
455 if (!HeapTupleIsValid(tup)) /* should not happen */
456 elog(ERROR, "cache lookup failed for text search template %u",
457 tmplId);
458 tform = (Form_pg_ts_template) GETSTRUCT(tup);
460 initmethod = tform->tmplinit;
462 if (!OidIsValid(initmethod))
464 /* If there is no init method, disallow any options */
465 if (dictoptions)
466 ereport(ERROR,
467 (errcode(ERRCODE_SYNTAX_ERROR),
468 errmsg("text search template \"%s\" does not accept options",
469 NameStr(tform->tmplname))));
471 else
474 * Copy the options just in case init method thinks it can scribble on
475 * them ...
477 dictoptions = copyObject(dictoptions);
480 * Call the init method and see if it complains. We don't worry about
481 * it leaking memory, since our command will soon be over anyway.
483 (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
486 ReleaseSysCache(tup);
490 * CREATE TEXT SEARCH DICTIONARY
492 void
493 DefineTSDictionary(List *names, List *parameters)
495 ListCell *pl;
496 Relation dictRel;
497 HeapTuple tup;
498 Datum values[Natts_pg_ts_dict];
499 char nulls[Natts_pg_ts_dict];
500 NameData dname;
501 Oid templId = InvalidOid;
502 List *dictoptions = NIL;
503 Oid dictOid;
504 Oid namespaceoid;
505 AclResult aclresult;
506 char *dictname;
508 /* Convert list of names to a name and namespace */
509 namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
511 /* Check we have creation rights in target namespace */
512 aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
513 if (aclresult != ACLCHECK_OK)
514 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
515 get_namespace_name(namespaceoid));
518 * loop over the definition list and extract the information we need.
520 foreach(pl, parameters)
522 DefElem *defel = (DefElem *) lfirst(pl);
524 if (pg_strcasecmp(defel->defname, "template") == 0)
526 templId = TSTemplateGetTmplid(defGetQualifiedName(defel), false);
528 else
530 /* Assume it's an option for the dictionary itself */
531 dictoptions = lappend(dictoptions, defel);
536 * Validation
538 if (!OidIsValid(templId))
539 ereport(ERROR,
540 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
541 errmsg("text search template is required")));
543 verify_dictoptions(templId, dictoptions);
546 * Looks good, insert
548 memset(values, 0, sizeof(values));
549 memset(nulls, ' ', sizeof(nulls));
551 namestrcpy(&dname, dictname);
552 values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
553 values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
554 values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
555 values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
556 if (dictoptions)
557 values[Anum_pg_ts_dict_dictinitoption - 1] =
558 PointerGetDatum(serialize_deflist(dictoptions));
559 else
560 nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
562 dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
564 tup = heap_formtuple(dictRel->rd_att, values, nulls);
566 dictOid = simple_heap_insert(dictRel, tup);
568 CatalogUpdateIndexes(dictRel, tup);
570 makeDictionaryDependencies(tup);
572 heap_freetuple(tup);
574 heap_close(dictRel, RowExclusiveLock);
578 * ALTER TEXT SEARCH DICTIONARY RENAME
580 void
581 RenameTSDictionary(List *oldname, const char *newname)
583 HeapTuple tup;
584 Relation rel;
585 Oid dictId;
586 Oid namespaceOid;
587 AclResult aclresult;
589 rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
591 dictId = TSDictionaryGetDictid(oldname, false);
593 tup = SearchSysCacheCopy(TSDICTOID,
594 ObjectIdGetDatum(dictId),
595 0, 0, 0);
597 if (!HeapTupleIsValid(tup)) /* should not happen */
598 elog(ERROR, "cache lookup failed for text search dictionary %u",
599 dictId);
601 namespaceOid = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
603 if (SearchSysCacheExists(TSDICTNAMENSP,
604 PointerGetDatum(newname),
605 ObjectIdGetDatum(namespaceOid),
606 0, 0))
607 ereport(ERROR,
608 (errcode(ERRCODE_DUPLICATE_OBJECT),
609 errmsg("text search dictionary \"%s\" already exists",
610 newname)));
612 /* must be owner */
613 if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
614 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
615 NameListToString(oldname));
617 /* must have CREATE privilege on namespace */
618 aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
619 if (aclresult != ACLCHECK_OK)
620 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
621 get_namespace_name(namespaceOid));
623 namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname);
624 simple_heap_update(rel, &tup->t_self, tup);
625 CatalogUpdateIndexes(rel, tup);
627 heap_close(rel, NoLock);
628 heap_freetuple(tup);
632 * DROP TEXT SEARCH DICTIONARY
634 void
635 RemoveTSDictionaries(DropStmt *drop)
637 ObjectAddresses *objects;
638 ListCell *cell;
641 * First we identify all the objects, then we delete them in a single
642 * performMultipleDeletions() call. This is to avoid unwanted
643 * DROP RESTRICT errors if one of the objects depends on another.
645 objects = new_object_addresses();
647 foreach(cell, drop->objects)
649 List *names = (List *) lfirst(cell);
650 Oid dictOid;
651 ObjectAddress object;
652 HeapTuple tup;
653 Oid namespaceId;
655 dictOid = TSDictionaryGetDictid(names, true);
657 if (!OidIsValid(dictOid))
659 if (!drop->missing_ok)
661 ereport(ERROR,
662 (errcode(ERRCODE_UNDEFINED_OBJECT),
663 errmsg("text search dictionary \"%s\" does not exist",
664 NameListToString(names))));
666 else
668 ereport(NOTICE,
669 (errmsg("text search dictionary \"%s\" does not exist, skipping",
670 NameListToString(names))));
672 continue;
675 tup = SearchSysCache(TSDICTOID,
676 ObjectIdGetDatum(dictOid),
677 0, 0, 0);
678 if (!HeapTupleIsValid(tup)) /* should not happen */
679 elog(ERROR, "cache lookup failed for text search dictionary %u",
680 dictOid);
682 /* Permission check: must own dictionary or its namespace */
683 namespaceId = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
684 if (!pg_ts_dict_ownercheck(dictOid, GetUserId()) &&
685 !pg_namespace_ownercheck(namespaceId, GetUserId()))
686 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
687 NameListToString(names));
689 object.classId = TSDictionaryRelationId;
690 object.objectId = dictOid;
691 object.objectSubId = 0;
693 add_exact_object_address(&object, objects);
695 ReleaseSysCache(tup);
698 performMultipleDeletions(objects, drop->behavior);
700 free_object_addresses(objects);
704 * Guts of TS dictionary deletion.
706 void
707 RemoveTSDictionaryById(Oid dictId)
709 Relation relation;
710 HeapTuple tup;
712 relation = heap_open(TSDictionaryRelationId, RowExclusiveLock);
714 tup = SearchSysCache(TSDICTOID,
715 ObjectIdGetDatum(dictId),
716 0, 0, 0);
718 if (!HeapTupleIsValid(tup))
719 elog(ERROR, "cache lookup failed for text search dictionary %u",
720 dictId);
722 simple_heap_delete(relation, &tup->t_self);
724 ReleaseSysCache(tup);
726 heap_close(relation, RowExclusiveLock);
730 * ALTER TEXT SEARCH DICTIONARY
732 void
733 AlterTSDictionary(AlterTSDictionaryStmt *stmt)
735 HeapTuple tup,
736 newtup;
737 Relation rel;
738 Oid dictId;
739 ListCell *pl;
740 List *dictoptions;
741 Datum opt;
742 bool isnull;
743 Datum repl_val[Natts_pg_ts_dict];
744 char repl_null[Natts_pg_ts_dict];
745 char repl_repl[Natts_pg_ts_dict];
747 dictId = TSDictionaryGetDictid(stmt->dictname, false);
749 rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
751 tup = SearchSysCache(TSDICTOID,
752 ObjectIdGetDatum(dictId),
753 0, 0, 0);
755 if (!HeapTupleIsValid(tup))
756 elog(ERROR, "cache lookup failed for text search dictionary %u",
757 dictId);
759 /* must be owner */
760 if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
761 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
762 NameListToString(stmt->dictname));
764 /* deserialize the existing set of options */
765 opt = SysCacheGetAttr(TSDICTOID, tup,
766 Anum_pg_ts_dict_dictinitoption,
767 &isnull);
768 if (isnull)
769 dictoptions = NIL;
770 else
771 dictoptions = deserialize_deflist(opt);
774 * Modify the options list as per specified changes
776 foreach(pl, stmt->options)
778 DefElem *defel = (DefElem *) lfirst(pl);
779 ListCell *cell;
780 ListCell *prev;
781 ListCell *next;
784 * Remove any matches ...
786 prev = NULL;
787 for (cell = list_head(dictoptions); cell; cell = next)
789 DefElem *oldel = (DefElem *) lfirst(cell);
791 next = lnext(cell);
792 if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
793 dictoptions = list_delete_cell(dictoptions, cell, prev);
794 else
795 prev = cell;
799 * and add new value if it's got one
801 if (defel->arg)
802 dictoptions = lappend(dictoptions, defel);
806 * Validate
808 verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
809 dictoptions);
812 * Looks good, update
814 memset(repl_val, 0, sizeof(repl_val));
815 memset(repl_null, ' ', sizeof(repl_null));
816 memset(repl_repl, ' ', sizeof(repl_repl));
818 if (dictoptions)
819 repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
820 PointerGetDatum(serialize_deflist(dictoptions));
821 else
822 repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
823 repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
825 newtup = heap_modifytuple(tup, RelationGetDescr(rel),
826 repl_val, repl_null, repl_repl);
828 simple_heap_update(rel, &newtup->t_self, newtup);
830 CatalogUpdateIndexes(rel, newtup);
833 * NOTE: because we only support altering the options, not the template,
834 * there is no need to update dependencies. This might have to change if
835 * the options ever reference inside-the-database objects.
838 heap_freetuple(newtup);
839 ReleaseSysCache(tup);
841 heap_close(rel, RowExclusiveLock);
845 * ALTER TEXT SEARCH DICTIONARY OWNER
847 void
848 AlterTSDictionaryOwner(List *name, Oid newOwnerId)
850 HeapTuple tup;
851 Relation rel;
852 Oid dictId;
853 Oid namespaceOid;
854 AclResult aclresult;
855 Form_pg_ts_dict form;
857 rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
859 dictId = TSDictionaryGetDictid(name, false);
861 tup = SearchSysCacheCopy(TSDICTOID,
862 ObjectIdGetDatum(dictId),
863 0, 0, 0);
865 if (!HeapTupleIsValid(tup)) /* should not happen */
866 elog(ERROR, "cache lookup failed for text search dictionary %u",
867 dictId);
869 form = (Form_pg_ts_dict) GETSTRUCT(tup);
870 namespaceOid = form->dictnamespace;
872 if (form->dictowner != newOwnerId)
874 /* Superusers can always do it */
875 if (!superuser())
877 /* must be owner */
878 if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
879 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
880 NameListToString(name));
882 /* Must be able to become new owner */
883 check_is_member_of_role(GetUserId(), newOwnerId);
885 /* New owner must have CREATE privilege on namespace */
886 aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
887 if (aclresult != ACLCHECK_OK)
888 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
889 get_namespace_name(namespaceOid));
892 form->dictowner = newOwnerId;
894 simple_heap_update(rel, &tup->t_self, tup);
895 CatalogUpdateIndexes(rel, tup);
897 /* Update owner dependency reference */
898 changeDependencyOnOwner(TSDictionaryRelationId, HeapTupleGetOid(tup),
899 newOwnerId);
902 heap_close(rel, NoLock);
903 heap_freetuple(tup);
906 /* ---------------------- TS Template commands -----------------------*/
909 * lookup a template support function and return its OID (as a Datum)
911 * attnum is the pg_ts_template column the function will go into
913 static Datum
914 get_ts_template_func(DefElem *defel, int attnum)
916 List *funcName = defGetQualifiedName(defel);
917 Oid typeId[4];
918 Oid retTypeId;
919 int nargs;
920 Oid procOid;
922 retTypeId = INTERNALOID;
923 typeId[0] = INTERNALOID;
924 typeId[1] = INTERNALOID;
925 typeId[2] = INTERNALOID;
926 typeId[3] = INTERNALOID;
927 switch (attnum)
929 case Anum_pg_ts_template_tmplinit:
930 nargs = 1;
931 break;
932 case Anum_pg_ts_template_tmpllexize:
933 nargs = 4;
934 break;
935 default:
936 /* should not be here */
937 elog(ERROR, "unrecognized attribute for text search template: %d",
938 attnum);
939 nargs = 0; /* keep compiler quiet */
942 procOid = LookupFuncName(funcName, nargs, typeId, false);
943 if (get_func_rettype(procOid) != retTypeId)
944 ereport(ERROR,
945 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
946 errmsg("function %s should return type %s",
947 func_signature_string(funcName, nargs, typeId),
948 format_type_be(retTypeId))));
950 return ObjectIdGetDatum(procOid);
954 * make pg_depend entries for a new pg_ts_template entry
956 static void
957 makeTSTemplateDependencies(HeapTuple tuple)
959 Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
960 ObjectAddress myself,
961 referenced;
963 myself.classId = TSTemplateRelationId;
964 myself.objectId = HeapTupleGetOid(tuple);
965 myself.objectSubId = 0;
967 /* dependency on namespace */
968 referenced.classId = NamespaceRelationId;
969 referenced.objectId = tmpl->tmplnamespace;
970 referenced.objectSubId = 0;
971 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
973 /* dependencies on functions */
974 referenced.classId = ProcedureRelationId;
975 referenced.objectSubId = 0;
977 referenced.objectId = tmpl->tmpllexize;
978 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
980 if (OidIsValid(tmpl->tmplinit))
982 referenced.objectId = tmpl->tmplinit;
983 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
988 * CREATE TEXT SEARCH TEMPLATE
990 void
991 DefineTSTemplate(List *names, List *parameters)
993 ListCell *pl;
994 Relation tmplRel;
995 HeapTuple tup;
996 Datum values[Natts_pg_ts_template];
997 char nulls[Natts_pg_ts_template];
998 NameData dname;
999 int i;
1000 Oid dictOid;
1001 Oid namespaceoid;
1002 char *tmplname;
1004 if (!superuser())
1005 ereport(ERROR,
1006 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1007 errmsg("must be superuser to create text search templates")));
1009 /* Convert list of names to a name and namespace */
1010 namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
1012 for (i = 0; i < Natts_pg_ts_template; i++)
1014 nulls[i] = ' ';
1015 values[i] = ObjectIdGetDatum(InvalidOid);
1018 namestrcpy(&dname, tmplname);
1019 values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
1020 values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
1023 * loop over the definition list and extract the information we need.
1025 foreach(pl, parameters)
1027 DefElem *defel = (DefElem *) lfirst(pl);
1029 if (pg_strcasecmp(defel->defname, "init") == 0)
1031 values[Anum_pg_ts_template_tmplinit - 1] =
1032 get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
1033 nulls[Anum_pg_ts_template_tmplinit - 1] = ' ';
1035 else if (pg_strcasecmp(defel->defname, "lexize") == 0)
1037 values[Anum_pg_ts_template_tmpllexize - 1] =
1038 get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
1039 nulls[Anum_pg_ts_template_tmpllexize - 1] = ' ';
1041 else
1042 ereport(ERROR,
1043 (errcode(ERRCODE_SYNTAX_ERROR),
1044 errmsg("text search template parameter \"%s\" not recognized",
1045 defel->defname)));
1049 * Validation
1051 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
1052 ereport(ERROR,
1053 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1054 errmsg("text search template lexize method is required")));
1057 * Looks good, insert
1060 tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1062 tup = heap_formtuple(tmplRel->rd_att, values, nulls);
1064 dictOid = simple_heap_insert(tmplRel, tup);
1066 CatalogUpdateIndexes(tmplRel, tup);
1068 makeTSTemplateDependencies(tup);
1070 heap_freetuple(tup);
1072 heap_close(tmplRel, RowExclusiveLock);
1076 * ALTER TEXT SEARCH TEMPLATE RENAME
1078 void
1079 RenameTSTemplate(List *oldname, const char *newname)
1081 HeapTuple tup;
1082 Relation rel;
1083 Oid tmplId;
1084 Oid namespaceOid;
1086 if (!superuser())
1087 ereport(ERROR,
1088 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1089 errmsg("must be superuser to rename text search templates")));
1091 rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1093 tmplId = TSTemplateGetTmplid(oldname, false);
1095 tup = SearchSysCacheCopy(TSTEMPLATEOID,
1096 ObjectIdGetDatum(tmplId),
1097 0, 0, 0);
1099 if (!HeapTupleIsValid(tup)) /* should not happen */
1100 elog(ERROR, "cache lookup failed for text search template %u",
1101 tmplId);
1103 namespaceOid = ((Form_pg_ts_template) GETSTRUCT(tup))->tmplnamespace;
1105 if (SearchSysCacheExists(TSTEMPLATENAMENSP,
1106 PointerGetDatum(newname),
1107 ObjectIdGetDatum(namespaceOid),
1108 0, 0))
1109 ereport(ERROR,
1110 (errcode(ERRCODE_DUPLICATE_OBJECT),
1111 errmsg("text search template \"%s\" already exists",
1112 newname)));
1114 namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname);
1115 simple_heap_update(rel, &tup->t_self, tup);
1116 CatalogUpdateIndexes(rel, tup);
1118 heap_close(rel, NoLock);
1119 heap_freetuple(tup);
1123 * DROP TEXT SEARCH TEMPLATE
1125 void
1126 RemoveTSTemplates(DropStmt *drop)
1128 ObjectAddresses *objects;
1129 ListCell *cell;
1131 if (!superuser())
1132 ereport(ERROR,
1133 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1134 errmsg("must be superuser to drop text search templates")));
1137 * First we identify all the objects, then we delete them in a single
1138 * performMultipleDeletions() call. This is to avoid unwanted
1139 * DROP RESTRICT errors if one of the objects depends on another.
1141 objects = new_object_addresses();
1143 foreach(cell, drop->objects)
1145 List *names = (List *) lfirst(cell);
1146 Oid tmplOid;
1147 ObjectAddress object;
1149 tmplOid = TSTemplateGetTmplid(names, true);
1151 if (!OidIsValid(tmplOid))
1153 if (!drop->missing_ok)
1155 ereport(ERROR,
1156 (errcode(ERRCODE_UNDEFINED_OBJECT),
1157 errmsg("text search template \"%s\" does not exist",
1158 NameListToString(names))));
1160 else
1162 ereport(NOTICE,
1163 (errmsg("text search template \"%s\" does not exist, skipping",
1164 NameListToString(names))));
1166 continue;
1169 object.classId = TSTemplateRelationId;
1170 object.objectId = tmplOid;
1171 object.objectSubId = 0;
1173 add_exact_object_address(&object, objects);
1176 performMultipleDeletions(objects, drop->behavior);
1178 free_object_addresses(objects);
1182 * Guts of TS template deletion.
1184 void
1185 RemoveTSTemplateById(Oid tmplId)
1187 Relation relation;
1188 HeapTuple tup;
1190 relation = heap_open(TSTemplateRelationId, RowExclusiveLock);
1192 tup = SearchSysCache(TSTEMPLATEOID,
1193 ObjectIdGetDatum(tmplId),
1194 0, 0, 0);
1196 if (!HeapTupleIsValid(tup))
1197 elog(ERROR, "cache lookup failed for text search template %u",
1198 tmplId);
1200 simple_heap_delete(relation, &tup->t_self);
1202 ReleaseSysCache(tup);
1204 heap_close(relation, RowExclusiveLock);
1207 /* ---------------------- TS Configuration commands -----------------------*/
1210 * Finds syscache tuple of configuration.
1211 * Returns NULL if no such cfg.
1213 static HeapTuple
1214 GetTSConfigTuple(List *names)
1216 HeapTuple tup;
1217 Oid cfgId;
1219 cfgId = TSConfigGetCfgid(names, true);
1220 if (!OidIsValid(cfgId))
1221 return NULL;
1223 tup = SearchSysCache(TSCONFIGOID,
1224 ObjectIdGetDatum(cfgId),
1225 0, 0, 0);
1227 if (!HeapTupleIsValid(tup)) /* should not happen */
1228 elog(ERROR, "cache lookup failed for text search configuration %u",
1229 cfgId);
1231 return tup;
1235 * make pg_depend entries for a new or updated pg_ts_config entry
1237 * Pass opened pg_ts_config_map relation if there might be any config map
1238 * entries for the config.
1240 static void
1241 makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
1242 Relation mapRel)
1244 Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
1245 ObjectAddresses *addrs;
1246 ObjectAddress myself,
1247 referenced;
1249 myself.classId = TSConfigRelationId;
1250 myself.objectId = HeapTupleGetOid(tuple);
1251 myself.objectSubId = 0;
1253 /* for ALTER case, first flush old dependencies */
1254 if (removeOld)
1256 deleteDependencyRecordsFor(myself.classId, myself.objectId);
1257 deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
1261 * We use an ObjectAddresses list to remove possible duplicate
1262 * dependencies from the config map info. The pg_ts_config items
1263 * shouldn't be duplicates, but might as well fold them all into one call.
1265 addrs = new_object_addresses();
1267 /* dependency on namespace */
1268 referenced.classId = NamespaceRelationId;
1269 referenced.objectId = cfg->cfgnamespace;
1270 referenced.objectSubId = 0;
1271 add_exact_object_address(&referenced, addrs);
1273 /* dependency on owner */
1274 recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
1276 /* dependency on parser */
1277 referenced.classId = TSParserRelationId;
1278 referenced.objectId = cfg->cfgparser;
1279 referenced.objectSubId = 0;
1280 add_exact_object_address(&referenced, addrs);
1282 /* dependencies on dictionaries listed in config map */
1283 if (mapRel)
1285 ScanKeyData skey;
1286 SysScanDesc scan;
1287 HeapTuple maptup;
1289 /* CCI to ensure we can see effects of caller's changes */
1290 CommandCounterIncrement();
1292 ScanKeyInit(&skey,
1293 Anum_pg_ts_config_map_mapcfg,
1294 BTEqualStrategyNumber, F_OIDEQ,
1295 ObjectIdGetDatum(myself.objectId));
1297 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1298 SnapshotNow, 1, &skey);
1300 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1302 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1304 referenced.classId = TSDictionaryRelationId;
1305 referenced.objectId = cfgmap->mapdict;
1306 referenced.objectSubId = 0;
1307 add_exact_object_address(&referenced, addrs);
1310 systable_endscan(scan);
1313 /* Record 'em (this includes duplicate elimination) */
1314 record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
1316 free_object_addresses(addrs);
1320 * CREATE TEXT SEARCH CONFIGURATION
1322 void
1323 DefineTSConfiguration(List *names, List *parameters)
1325 Relation cfgRel;
1326 Relation mapRel = NULL;
1327 HeapTuple tup;
1328 Datum values[Natts_pg_ts_config];
1329 char nulls[Natts_pg_ts_config];
1330 AclResult aclresult;
1331 Oid namespaceoid;
1332 char *cfgname;
1333 NameData cname;
1334 Oid sourceOid = InvalidOid;
1335 Oid prsOid = InvalidOid;
1336 Oid cfgOid;
1337 ListCell *pl;
1339 /* Convert list of names to a name and namespace */
1340 namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
1342 /* Check we have creation rights in target namespace */
1343 aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
1344 if (aclresult != ACLCHECK_OK)
1345 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1346 get_namespace_name(namespaceoid));
1349 * loop over the definition list and extract the information we need.
1351 foreach(pl, parameters)
1353 DefElem *defel = (DefElem *) lfirst(pl);
1355 if (pg_strcasecmp(defel->defname, "parser") == 0)
1356 prsOid = TSParserGetPrsid(defGetQualifiedName(defel), false);
1357 else if (pg_strcasecmp(defel->defname, "copy") == 0)
1358 sourceOid = TSConfigGetCfgid(defGetQualifiedName(defel), false);
1359 else
1360 ereport(ERROR,
1361 (errcode(ERRCODE_SYNTAX_ERROR),
1362 errmsg("text search configuration parameter \"%s\" not recognized",
1363 defel->defname)));
1366 if (OidIsValid(sourceOid) && OidIsValid(prsOid))
1367 ereport(ERROR,
1368 (errcode(ERRCODE_SYNTAX_ERROR),
1369 errmsg("cannot specify both PARSER and COPY options")));
1372 * Look up source config if given.
1374 if (OidIsValid(sourceOid))
1376 Form_pg_ts_config cfg;
1378 tup = SearchSysCache(TSCONFIGOID,
1379 ObjectIdGetDatum(sourceOid),
1380 0, 0, 0);
1381 if (!HeapTupleIsValid(tup))
1382 elog(ERROR, "cache lookup failed for text search configuration %u",
1383 sourceOid);
1385 cfg = (Form_pg_ts_config) GETSTRUCT(tup);
1387 /* use source's parser */
1388 prsOid = cfg->cfgparser;
1390 ReleaseSysCache(tup);
1394 * Validation
1396 if (!OidIsValid(prsOid))
1397 ereport(ERROR,
1398 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1399 errmsg("text search parser is required")));
1402 * Looks good, build tuple and insert
1404 memset(values, 0, sizeof(values));
1405 memset(nulls, ' ', sizeof(nulls));
1407 namestrcpy(&cname, cfgname);
1408 values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
1409 values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
1410 values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
1411 values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
1413 cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
1415 tup = heap_formtuple(cfgRel->rd_att, values, nulls);
1417 cfgOid = simple_heap_insert(cfgRel, tup);
1419 CatalogUpdateIndexes(cfgRel, tup);
1421 if (OidIsValid(sourceOid))
1424 * Copy token-dicts map from source config
1426 ScanKeyData skey;
1427 SysScanDesc scan;
1428 HeapTuple maptup;
1430 mapRel = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1432 ScanKeyInit(&skey,
1433 Anum_pg_ts_config_map_mapcfg,
1434 BTEqualStrategyNumber, F_OIDEQ,
1435 ObjectIdGetDatum(sourceOid));
1437 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1438 SnapshotNow, 1, &skey);
1440 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1442 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1443 HeapTuple newmaptup;
1444 Datum mapvalues[Natts_pg_ts_config_map];
1445 char mapnulls[Natts_pg_ts_config_map];
1447 memset(mapvalues, 0, sizeof(mapvalues));
1448 memset(mapnulls, ' ', sizeof(mapnulls));
1450 mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
1451 mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
1452 mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
1453 mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
1455 newmaptup = heap_formtuple(mapRel->rd_att, mapvalues, mapnulls);
1457 simple_heap_insert(mapRel, newmaptup);
1459 CatalogUpdateIndexes(mapRel, newmaptup);
1461 heap_freetuple(newmaptup);
1464 systable_endscan(scan);
1467 makeConfigurationDependencies(tup, false, mapRel);
1469 heap_freetuple(tup);
1471 if (mapRel)
1472 heap_close(mapRel, RowExclusiveLock);
1473 heap_close(cfgRel, RowExclusiveLock);
1477 * ALTER TEXT SEARCH CONFIGURATION RENAME
1479 void
1480 RenameTSConfiguration(List *oldname, const char *newname)
1482 HeapTuple tup;
1483 Relation rel;
1484 Oid cfgId;
1485 AclResult aclresult;
1486 Oid namespaceOid;
1488 rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1490 cfgId = TSConfigGetCfgid(oldname, false);
1492 tup = SearchSysCacheCopy(TSCONFIGOID,
1493 ObjectIdGetDatum(cfgId),
1494 0, 0, 0);
1496 if (!HeapTupleIsValid(tup)) /* should not happen */
1497 elog(ERROR, "cache lookup failed for text search configuration %u",
1498 cfgId);
1500 namespaceOid = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
1502 if (SearchSysCacheExists(TSCONFIGNAMENSP,
1503 PointerGetDatum(newname),
1504 ObjectIdGetDatum(namespaceOid),
1505 0, 0))
1506 ereport(ERROR,
1507 (errcode(ERRCODE_DUPLICATE_OBJECT),
1508 errmsg("text search configuration \"%s\" already exists",
1509 newname)));
1511 /* must be owner */
1512 if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1513 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1514 NameListToString(oldname));
1516 /* must have CREATE privilege on namespace */
1517 aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
1518 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1519 get_namespace_name(namespaceOid));
1521 namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname);
1522 simple_heap_update(rel, &tup->t_self, tup);
1523 CatalogUpdateIndexes(rel, tup);
1525 heap_close(rel, NoLock);
1526 heap_freetuple(tup);
1530 * DROP TEXT SEARCH CONFIGURATION
1532 void
1533 RemoveTSConfigurations(DropStmt *drop)
1535 ObjectAddresses *objects;
1536 ListCell *cell;
1539 * First we identify all the objects, then we delete them in a single
1540 * performMultipleDeletions() call. This is to avoid unwanted
1541 * DROP RESTRICT errors if one of the objects depends on another.
1543 objects = new_object_addresses();
1545 foreach(cell, drop->objects)
1547 List *names = (List *) lfirst(cell);
1548 Oid cfgOid;
1549 Oid namespaceId;
1550 ObjectAddress object;
1551 HeapTuple tup;
1553 tup = GetTSConfigTuple(names);
1555 if (!HeapTupleIsValid(tup))
1557 if (!drop->missing_ok)
1559 ereport(ERROR,
1560 (errcode(ERRCODE_UNDEFINED_OBJECT),
1561 errmsg("text search configuration \"%s\" does not exist",
1562 NameListToString(names))));
1564 else
1566 ereport(NOTICE,
1567 (errmsg("text search configuration \"%s\" does not exist, skipping",
1568 NameListToString(names))));
1570 continue;
1573 /* Permission check: must own configuration or its namespace */
1574 cfgOid = HeapTupleGetOid(tup);
1575 namespaceId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
1576 if (!pg_ts_config_ownercheck(cfgOid, GetUserId()) &&
1577 !pg_namespace_ownercheck(namespaceId, GetUserId()))
1578 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1579 NameListToString(names));
1581 object.classId = TSConfigRelationId;
1582 object.objectId = cfgOid;
1583 object.objectSubId = 0;
1585 add_exact_object_address(&object, objects);
1587 ReleaseSysCache(tup);
1590 performMultipleDeletions(objects, drop->behavior);
1592 free_object_addresses(objects);
1596 * Guts of TS configuration deletion.
1598 void
1599 RemoveTSConfigurationById(Oid cfgId)
1601 Relation relCfg,
1602 relMap;
1603 HeapTuple tup;
1604 ScanKeyData skey;
1605 SysScanDesc scan;
1607 /* Remove the pg_ts_config entry */
1608 relCfg = heap_open(TSConfigRelationId, RowExclusiveLock);
1610 tup = SearchSysCache(TSCONFIGOID,
1611 ObjectIdGetDatum(cfgId),
1612 0, 0, 0);
1614 if (!HeapTupleIsValid(tup))
1615 elog(ERROR, "cache lookup failed for text search dictionary %u",
1616 cfgId);
1618 simple_heap_delete(relCfg, &tup->t_self);
1620 ReleaseSysCache(tup);
1622 heap_close(relCfg, RowExclusiveLock);
1624 /* Remove any pg_ts_config_map entries */
1625 relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1627 ScanKeyInit(&skey,
1628 Anum_pg_ts_config_map_mapcfg,
1629 BTEqualStrategyNumber, F_OIDEQ,
1630 ObjectIdGetDatum(cfgId));
1632 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1633 SnapshotNow, 1, &skey);
1635 while (HeapTupleIsValid((tup = systable_getnext(scan))))
1637 simple_heap_delete(relMap, &tup->t_self);
1640 systable_endscan(scan);
1642 heap_close(relMap, RowExclusiveLock);
1646 * ALTER TEXT SEARCH CONFIGURATION OWNER
1648 void
1649 AlterTSConfigurationOwner(List *name, Oid newOwnerId)
1651 HeapTuple tup;
1652 Relation rel;
1653 Oid cfgId;
1654 AclResult aclresult;
1655 Oid namespaceOid;
1656 Form_pg_ts_config form;
1658 rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1660 cfgId = TSConfigGetCfgid(name, false);
1662 tup = SearchSysCacheCopy(TSCONFIGOID,
1663 ObjectIdGetDatum(cfgId),
1664 0, 0, 0);
1666 if (!HeapTupleIsValid(tup)) /* should not happen */
1667 elog(ERROR, "cache lookup failed for text search configuration %u",
1668 cfgId);
1670 form = (Form_pg_ts_config) GETSTRUCT(tup);
1671 namespaceOid = form->cfgnamespace;
1673 if (form->cfgowner != newOwnerId)
1675 /* Superusers can always do it */
1676 if (!superuser())
1678 /* must be owner */
1679 if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1680 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1681 NameListToString(name));
1683 /* Must be able to become new owner */
1684 check_is_member_of_role(GetUserId(), newOwnerId);
1686 /* New owner must have CREATE privilege on namespace */
1687 aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
1688 if (aclresult != ACLCHECK_OK)
1689 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1690 get_namespace_name(namespaceOid));
1693 form->cfgowner = newOwnerId;
1695 simple_heap_update(rel, &tup->t_self, tup);
1696 CatalogUpdateIndexes(rel, tup);
1698 /* Update owner dependency reference */
1699 changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup),
1700 newOwnerId);
1703 heap_close(rel, NoLock);
1704 heap_freetuple(tup);
1708 * ALTER TEXT SEARCH CONFIGURATION - main entry point
1710 void
1711 AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
1713 HeapTuple tup;
1714 Relation relMap;
1716 /* Find the configuration */
1717 tup = GetTSConfigTuple(stmt->cfgname);
1718 if (!HeapTupleIsValid(tup))
1719 ereport(ERROR,
1720 (errcode(ERRCODE_UNDEFINED_OBJECT),
1721 errmsg("text search configuration \"%s\" does not exist",
1722 NameListToString(stmt->cfgname))));
1724 /* must be owner */
1725 if (!pg_ts_config_ownercheck(HeapTupleGetOid(tup), GetUserId()))
1726 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1727 NameListToString(stmt->cfgname));
1729 relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1731 /* Add or drop mappings */
1732 if (stmt->dicts)
1733 MakeConfigurationMapping(stmt, tup, relMap);
1734 else if (stmt->tokentype)
1735 DropConfigurationMapping(stmt, tup, relMap);
1737 /* Update dependencies */
1738 makeConfigurationDependencies(tup, true, relMap);
1740 heap_close(relMap, RowExclusiveLock);
1742 ReleaseSysCache(tup);
1746 * Translate a list of token type names to an array of token type numbers
1748 static int *
1749 getTokenTypes(Oid prsId, List *tokennames)
1751 TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
1752 LexDescr *list;
1753 int *res,
1755 ntoken;
1756 ListCell *tn;
1758 ntoken = list_length(tokennames);
1759 if (ntoken == 0)
1760 return NULL;
1761 res = (int *) palloc(sizeof(int) * ntoken);
1763 if (!OidIsValid(prs->lextypeOid))
1764 elog(ERROR, "method lextype isn't defined for text search parser %u",
1765 prsId);
1767 /* OidFunctionCall0 is absent */
1768 list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
1769 (Datum) 0));
1771 i = 0;
1772 foreach(tn, tokennames)
1774 Value *val = (Value *) lfirst(tn);
1775 bool found = false;
1776 int j;
1778 j = 0;
1779 while (list && list[j].lexid)
1781 /* XXX should we use pg_strcasecmp here? */
1782 if (strcmp(strVal(val), list[j].alias) == 0)
1784 res[i] = list[j].lexid;
1785 found = true;
1786 break;
1788 j++;
1790 if (!found)
1791 ereport(ERROR,
1792 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1793 errmsg("token type \"%s\" does not exist",
1794 strVal(val))));
1795 i++;
1798 return res;
1802 * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
1804 static void
1805 MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
1806 HeapTuple tup, Relation relMap)
1808 Oid cfgId = HeapTupleGetOid(tup);
1809 ScanKeyData skey[2];
1810 SysScanDesc scan;
1811 HeapTuple maptup;
1812 int i;
1813 int j;
1814 Oid prsId;
1815 int *tokens,
1816 ntoken;
1817 Oid *dictIds;
1818 int ndict;
1819 ListCell *c;
1821 prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
1823 tokens = getTokenTypes(prsId, stmt->tokentype);
1824 ntoken = list_length(stmt->tokentype);
1826 if (stmt->override)
1829 * delete maps for tokens if they exist and command was ALTER
1831 for (i = 0; i < ntoken; i++)
1833 ScanKeyInit(&skey[0],
1834 Anum_pg_ts_config_map_mapcfg,
1835 BTEqualStrategyNumber, F_OIDEQ,
1836 ObjectIdGetDatum(cfgId));
1837 ScanKeyInit(&skey[1],
1838 Anum_pg_ts_config_map_maptokentype,
1839 BTEqualStrategyNumber, F_INT4EQ,
1840 Int32GetDatum(tokens[i]));
1842 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1843 SnapshotNow, 2, skey);
1845 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1847 simple_heap_delete(relMap, &maptup->t_self);
1850 systable_endscan(scan);
1855 * Convert list of dictionary names to array of dict OIDs
1857 ndict = list_length(stmt->dicts);
1858 dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
1859 i = 0;
1860 foreach(c, stmt->dicts)
1862 List *names = (List *) lfirst(c);
1864 dictIds[i] = TSDictionaryGetDictid(names, false);
1865 i++;
1868 if (stmt->replace)
1871 * Replace a specific dictionary in existing entries
1873 Oid dictOld = dictIds[0],
1874 dictNew = dictIds[1];
1876 ScanKeyInit(&skey[0],
1877 Anum_pg_ts_config_map_mapcfg,
1878 BTEqualStrategyNumber, F_OIDEQ,
1879 ObjectIdGetDatum(cfgId));
1881 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1882 SnapshotNow, 1, skey);
1884 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1886 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1889 * check if it's one of target token types
1891 if (tokens)
1893 bool tokmatch = false;
1895 for (j = 0; j < ntoken; j++)
1897 if (cfgmap->maptokentype == tokens[j])
1899 tokmatch = true;
1900 break;
1903 if (!tokmatch)
1904 continue;
1908 * replace dictionary if match
1910 if (cfgmap->mapdict == dictOld)
1912 Datum repl_val[Natts_pg_ts_config_map];
1913 char repl_null[Natts_pg_ts_config_map];
1914 char repl_repl[Natts_pg_ts_config_map];
1915 HeapTuple newtup;
1917 memset(repl_val, 0, sizeof(repl_val));
1918 memset(repl_null, ' ', sizeof(repl_null));
1919 memset(repl_repl, ' ', sizeof(repl_repl));
1921 repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
1922 repl_repl[Anum_pg_ts_config_map_mapdict - 1] = 'r';
1924 newtup = heap_modifytuple(maptup,
1925 RelationGetDescr(relMap),
1926 repl_val, repl_null, repl_repl);
1927 simple_heap_update(relMap, &newtup->t_self, newtup);
1929 CatalogUpdateIndexes(relMap, newtup);
1933 systable_endscan(scan);
1935 else
1938 * Insertion of new entries
1940 for (i = 0; i < ntoken; i++)
1942 for (j = 0; j < ndict; j++)
1944 Datum values[Natts_pg_ts_config_map];
1945 char nulls[Natts_pg_ts_config_map];
1947 memset(nulls, ' ', sizeof(nulls));
1948 values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
1949 values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]);
1950 values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
1951 values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
1953 tup = heap_formtuple(relMap->rd_att, values, nulls);
1954 simple_heap_insert(relMap, tup);
1955 CatalogUpdateIndexes(relMap, tup);
1957 heap_freetuple(tup);
1964 * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
1966 static void
1967 DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
1968 HeapTuple tup, Relation relMap)
1970 Oid cfgId = HeapTupleGetOid(tup);
1971 ScanKeyData skey[2];
1972 SysScanDesc scan;
1973 HeapTuple maptup;
1974 int i;
1975 Oid prsId;
1976 int *tokens,
1977 ntoken;
1978 ListCell *c;
1980 prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
1982 tokens = getTokenTypes(prsId, stmt->tokentype);
1983 ntoken = list_length(stmt->tokentype);
1985 i = 0;
1986 foreach(c, stmt->tokentype)
1988 Value *val = (Value *) lfirst(c);
1989 bool found = false;
1991 ScanKeyInit(&skey[0],
1992 Anum_pg_ts_config_map_mapcfg,
1993 BTEqualStrategyNumber, F_OIDEQ,
1994 ObjectIdGetDatum(cfgId));
1995 ScanKeyInit(&skey[1],
1996 Anum_pg_ts_config_map_maptokentype,
1997 BTEqualStrategyNumber, F_INT4EQ,
1998 Int32GetDatum(tokens[i]));
2000 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
2001 SnapshotNow, 2, skey);
2003 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
2005 simple_heap_delete(relMap, &maptup->t_self);
2006 found = true;
2009 systable_endscan(scan);
2011 if (!found)
2013 if (!stmt->missing_ok)
2015 ereport(ERROR,
2016 (errcode(ERRCODE_UNDEFINED_OBJECT),
2017 errmsg("mapping for token type \"%s\" does not exist",
2018 strVal(val))));
2020 else
2022 ereport(NOTICE,
2023 (errmsg("mapping for token type \"%s\" does not exist, skipping",
2024 strVal(val))));
2028 i++;
2034 * Serialize dictionary options, producing a TEXT datum from a List of DefElem
2036 * This is used to form the value stored in pg_ts_dict.dictinitoption.
2037 * For the convenience of pg_dump, the output is formatted exactly as it
2038 * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
2039 * same options.
2041 * Note that we assume that only the textual representation of an option's
2042 * value is interesting --- hence, non-string DefElems get forced to strings.
2044 text *
2045 serialize_deflist(List *deflist)
2047 text *result;
2048 StringInfoData buf;
2049 ListCell *l;
2051 initStringInfo(&buf);
2053 foreach(l, deflist)
2055 DefElem *defel = (DefElem *) lfirst(l);
2056 char *val = defGetString(defel);
2058 appendStringInfo(&buf, "%s = ",
2059 quote_identifier(defel->defname));
2060 /* If backslashes appear, force E syntax to determine their handling */
2061 if (strchr(val, '\\'))
2062 appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
2063 appendStringInfoChar(&buf, '\'');
2064 while (*val)
2066 char ch = *val++;
2068 if (SQL_STR_DOUBLE(ch, true))
2069 appendStringInfoChar(&buf, ch);
2070 appendStringInfoChar(&buf, ch);
2072 appendStringInfoChar(&buf, '\'');
2073 if (lnext(l) != NULL)
2074 appendStringInfo(&buf, ", ");
2077 result = cstring_to_text_with_len(buf.data, buf.len);
2078 pfree(buf.data);
2079 return result;
2083 * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
2085 * This is also used for prsheadline options, so for backward compatibility
2086 * we need to accept a few things serialize_deflist() will never emit:
2087 * in particular, unquoted and double-quoted values.
2089 List *
2090 deserialize_deflist(Datum txt)
2092 text *in = DatumGetTextP(txt); /* in case it's toasted */
2093 List *result = NIL;
2094 int len = VARSIZE(in) - VARHDRSZ;
2095 char *ptr,
2096 *endptr,
2097 *workspace,
2098 *wsptr = NULL,
2099 *startvalue = NULL;
2100 typedef enum
2102 CS_WAITKEY,
2103 CS_INKEY,
2104 CS_INQKEY,
2105 CS_WAITEQ,
2106 CS_WAITVALUE,
2107 CS_INSQVALUE,
2108 CS_INDQVALUE,
2109 CS_INWVALUE
2110 } ds_state;
2111 ds_state state = CS_WAITKEY;
2113 workspace = (char *) palloc(len + 1); /* certainly enough room */
2114 ptr = VARDATA(in);
2115 endptr = ptr + len;
2116 for (; ptr < endptr; ptr++)
2118 switch (state)
2120 case CS_WAITKEY:
2121 if (isspace((unsigned char) *ptr) || *ptr == ',')
2122 continue;
2123 if (*ptr == '"')
2125 wsptr = workspace;
2126 state = CS_INQKEY;
2128 else
2130 wsptr = workspace;
2131 *wsptr++ = *ptr;
2132 state = CS_INKEY;
2134 break;
2135 case CS_INKEY:
2136 if (isspace((unsigned char) *ptr))
2138 *wsptr++ = '\0';
2139 state = CS_WAITEQ;
2141 else if (*ptr == '=')
2143 *wsptr++ = '\0';
2144 state = CS_WAITVALUE;
2146 else
2148 *wsptr++ = *ptr;
2150 break;
2151 case CS_INQKEY:
2152 if (*ptr == '"')
2154 if (ptr + 1 < endptr && ptr[1] == '"')
2156 /* copy only one of the two quotes */
2157 *wsptr++ = *ptr++;
2159 else
2161 *wsptr++ = '\0';
2162 state = CS_WAITEQ;
2165 else
2167 *wsptr++ = *ptr;
2169 break;
2170 case CS_WAITEQ:
2171 if (*ptr == '=')
2172 state = CS_WAITVALUE;
2173 else if (!isspace((unsigned char) *ptr))
2174 ereport(ERROR,
2175 (errcode(ERRCODE_SYNTAX_ERROR),
2176 errmsg("invalid parameter list format: \"%s\"",
2177 text_to_cstring(in))));
2178 break;
2179 case CS_WAITVALUE:
2180 if (*ptr == '\'')
2182 startvalue = wsptr;
2183 state = CS_INSQVALUE;
2185 else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
2187 ptr++;
2188 startvalue = wsptr;
2189 state = CS_INSQVALUE;
2191 else if (*ptr == '"')
2193 startvalue = wsptr;
2194 state = CS_INDQVALUE;
2196 else if (!isspace((unsigned char) *ptr))
2198 startvalue = wsptr;
2199 *wsptr++ = *ptr;
2200 state = CS_INWVALUE;
2202 break;
2203 case CS_INSQVALUE:
2204 if (*ptr == '\'')
2206 if (ptr + 1 < endptr && ptr[1] == '\'')
2208 /* copy only one of the two quotes */
2209 *wsptr++ = *ptr++;
2211 else
2213 *wsptr++ = '\0';
2214 result = lappend(result,
2215 makeDefElem(pstrdup(workspace),
2216 (Node *) makeString(pstrdup(startvalue))));
2217 state = CS_WAITKEY;
2220 else if (*ptr == '\\')
2222 if (ptr + 1 < endptr && ptr[1] == '\\')
2224 /* copy only one of the two backslashes */
2225 *wsptr++ = *ptr++;
2227 else
2228 *wsptr++ = *ptr;
2230 else
2232 *wsptr++ = *ptr;
2234 break;
2235 case CS_INDQVALUE:
2236 if (*ptr == '"')
2238 if (ptr + 1 < endptr && ptr[1] == '"')
2240 /* copy only one of the two quotes */
2241 *wsptr++ = *ptr++;
2243 else
2245 *wsptr++ = '\0';
2246 result = lappend(result,
2247 makeDefElem(pstrdup(workspace),
2248 (Node *) makeString(pstrdup(startvalue))));
2249 state = CS_WAITKEY;
2252 else
2254 *wsptr++ = *ptr;
2256 break;
2257 case CS_INWVALUE:
2258 if (*ptr == ',' || isspace((unsigned char) *ptr))
2260 *wsptr++ = '\0';
2261 result = lappend(result,
2262 makeDefElem(pstrdup(workspace),
2263 (Node *) makeString(pstrdup(startvalue))));
2264 state = CS_WAITKEY;
2266 else
2268 *wsptr++ = *ptr;
2270 break;
2271 default:
2272 elog(ERROR, "unrecognized deserialize_deflist state: %d",
2273 state);
2277 if (state == CS_INWVALUE)
2279 *wsptr++ = '\0';
2280 result = lappend(result,
2281 makeDefElem(pstrdup(workspace),
2282 (Node *) makeString(pstrdup(startvalue))));
2284 else if (state != CS_WAITKEY)
2285 ereport(ERROR,
2286 (errcode(ERRCODE_SYNTAX_ERROR),
2287 errmsg("invalid parameter list format: \"%s\"",
2288 text_to_cstring(in))));
2290 pfree(workspace);
2292 return result;