Force a checkpoint in CREATE DATABASE before starting to copy the files,
[PostgreSQL.git] / src / backend / commands / tsearchcmds.c
blob47e113a74f071776bd68bff03fa4eaece28992c5
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/rel.h"
47 #include "utils/syscache.h"
48 #include "utils/tqual.h"
51 static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
52 HeapTuple tup, Relation relMap);
53 static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
54 HeapTuple tup, Relation relMap);
57 /* --------------------- TS Parser commands ------------------------ */
60 * lookup a parser support function and return its OID (as a Datum)
62 * attnum is the pg_ts_parser column the function will go into
64 static Datum
65 get_ts_parser_func(DefElem *defel, int attnum)
67 List *funcName = defGetQualifiedName(defel);
68 Oid typeId[3];
69 Oid retTypeId;
70 int nargs;
71 Oid procOid;
73 retTypeId = INTERNALOID; /* correct for most */
74 typeId[0] = INTERNALOID;
75 switch (attnum)
77 case Anum_pg_ts_parser_prsstart:
78 nargs = 2;
79 typeId[1] = INT4OID;
80 break;
81 case Anum_pg_ts_parser_prstoken:
82 nargs = 3;
83 typeId[1] = INTERNALOID;
84 typeId[2] = INTERNALOID;
85 break;
86 case Anum_pg_ts_parser_prsend:
87 nargs = 1;
88 retTypeId = VOIDOID;
89 break;
90 case Anum_pg_ts_parser_prsheadline:
91 nargs = 3;
92 typeId[1] = INTERNALOID;
93 typeId[2] = TSQUERYOID;
94 break;
95 case Anum_pg_ts_parser_prslextype:
96 nargs = 1;
97 break;
98 default:
99 /* should not be here */
100 elog(ERROR, "unrecognized attribute for text search parser: %d",
101 attnum);
102 nargs = 0; /* keep compiler quiet */
105 procOid = LookupFuncName(funcName, nargs, typeId, false);
106 if (get_func_rettype(procOid) != retTypeId)
107 ereport(ERROR,
108 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
109 errmsg("function %s should return type %s",
110 func_signature_string(funcName, nargs, typeId),
111 format_type_be(retTypeId))));
113 return ObjectIdGetDatum(procOid);
117 * make pg_depend entries for a new pg_ts_parser entry
119 static void
120 makeParserDependencies(HeapTuple tuple)
122 Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
123 ObjectAddress myself,
124 referenced;
126 myself.classId = TSParserRelationId;
127 myself.objectId = HeapTupleGetOid(tuple);
128 myself.objectSubId = 0;
130 /* dependency on namespace */
131 referenced.classId = NamespaceRelationId;
132 referenced.objectId = prs->prsnamespace;
133 referenced.objectSubId = 0;
134 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
136 /* dependencies on functions */
137 referenced.classId = ProcedureRelationId;
138 referenced.objectSubId = 0;
140 referenced.objectId = prs->prsstart;
141 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
143 referenced.objectId = prs->prstoken;
144 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
146 referenced.objectId = prs->prsend;
147 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
149 referenced.objectId = prs->prslextype;
150 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
152 if (OidIsValid(prs->prsheadline))
154 referenced.objectId = prs->prsheadline;
155 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
160 * CREATE TEXT SEARCH PARSER
162 void
163 DefineTSParser(List *names, List *parameters)
165 char *prsname;
166 ListCell *pl;
167 Relation prsRel;
168 HeapTuple tup;
169 Datum values[Natts_pg_ts_parser];
170 char nulls[Natts_pg_ts_parser];
171 NameData pname;
172 Oid prsOid;
173 Oid namespaceoid;
175 if (!superuser())
176 ereport(ERROR,
177 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
178 errmsg("must be superuser to create text search parsers")));
180 /* Convert list of names to a name and namespace */
181 namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
183 /* initialize tuple fields with name/namespace */
184 memset(values, 0, sizeof(values));
185 memset(nulls, ' ', sizeof(nulls));
187 namestrcpy(&pname, prsname);
188 values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
189 values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
192 * loop over the definition list and extract the information we need.
194 foreach(pl, parameters)
196 DefElem *defel = (DefElem *) lfirst(pl);
198 if (pg_strcasecmp(defel->defname, "start") == 0)
200 values[Anum_pg_ts_parser_prsstart - 1] =
201 get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
203 else if (pg_strcasecmp(defel->defname, "gettoken") == 0)
205 values[Anum_pg_ts_parser_prstoken - 1] =
206 get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
208 else if (pg_strcasecmp(defel->defname, "end") == 0)
210 values[Anum_pg_ts_parser_prsend - 1] =
211 get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
213 else if (pg_strcasecmp(defel->defname, "headline") == 0)
215 values[Anum_pg_ts_parser_prsheadline - 1] =
216 get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
218 else if (pg_strcasecmp(defel->defname, "lextypes") == 0)
220 values[Anum_pg_ts_parser_prslextype - 1] =
221 get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
223 else
224 ereport(ERROR,
225 (errcode(ERRCODE_SYNTAX_ERROR),
226 errmsg("text search parser parameter \"%s\" not recognized",
227 defel->defname)));
231 * Validation
233 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
234 ereport(ERROR,
235 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
236 errmsg("text search parser start method is required")));
238 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
239 ereport(ERROR,
240 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
241 errmsg("text search parser gettoken method is required")));
243 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
244 ereport(ERROR,
245 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
246 errmsg("text search parser end method is required")));
248 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
249 ereport(ERROR,
250 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
251 errmsg("text search parser lextypes method is required")));
254 * Looks good, insert
256 prsRel = heap_open(TSParserRelationId, RowExclusiveLock);
258 tup = heap_formtuple(prsRel->rd_att, values, nulls);
260 prsOid = simple_heap_insert(prsRel, tup);
262 CatalogUpdateIndexes(prsRel, tup);
264 makeParserDependencies(tup);
266 heap_freetuple(tup);
268 heap_close(prsRel, RowExclusiveLock);
272 * DROP TEXT SEARCH PARSER
274 void
275 RemoveTSParsers(DropStmt *drop)
277 ObjectAddresses *objects;
278 ListCell *cell;
280 if (!superuser())
281 ereport(ERROR,
282 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
283 errmsg("must be superuser to drop text search parsers")));
286 * First we identify all the objects, then we delete them in a single
287 * performMultipleDeletions() call. This is to avoid unwanted
288 * DROP RESTRICT errors if one of the objects depends on another.
290 objects = new_object_addresses();
292 foreach(cell, drop->objects)
294 List *names = (List *) lfirst(cell);
295 Oid prsOid;
296 ObjectAddress object;
298 prsOid = TSParserGetPrsid(names, true);
300 if (!OidIsValid(prsOid))
302 if (!drop->missing_ok)
304 ereport(ERROR,
305 (errcode(ERRCODE_UNDEFINED_OBJECT),
306 errmsg("text search parser \"%s\" does not exist",
307 NameListToString(names))));
309 else
311 ereport(NOTICE,
312 (errmsg("text search parser \"%s\" does not exist, skipping",
313 NameListToString(names))));
315 continue;
318 object.classId = TSParserRelationId;
319 object.objectId = prsOid;
320 object.objectSubId = 0;
322 add_exact_object_address(&object, objects);
325 performMultipleDeletions(objects, drop->behavior);
327 free_object_addresses(objects);
331 * Guts of TS parser deletion.
333 void
334 RemoveTSParserById(Oid prsId)
336 Relation relation;
337 HeapTuple tup;
339 relation = heap_open(TSParserRelationId, RowExclusiveLock);
341 tup = SearchSysCache(TSPARSEROID,
342 ObjectIdGetDatum(prsId),
343 0, 0, 0);
345 if (!HeapTupleIsValid(tup))
346 elog(ERROR, "cache lookup failed for text search parser %u", prsId);
348 simple_heap_delete(relation, &tup->t_self);
350 ReleaseSysCache(tup);
352 heap_close(relation, RowExclusiveLock);
356 * ALTER TEXT SEARCH PARSER RENAME
358 void
359 RenameTSParser(List *oldname, const char *newname)
361 HeapTuple tup;
362 Relation rel;
363 Oid prsId;
364 Oid namespaceOid;
366 if (!superuser())
367 ereport(ERROR,
368 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
369 errmsg("must be superuser to rename text search parsers")));
371 rel = heap_open(TSParserRelationId, RowExclusiveLock);
373 prsId = TSParserGetPrsid(oldname, false);
375 tup = SearchSysCacheCopy(TSPARSEROID,
376 ObjectIdGetDatum(prsId),
377 0, 0, 0);
379 if (!HeapTupleIsValid(tup)) /* should not happen */
380 elog(ERROR, "cache lookup failed for text search parser %u", prsId);
382 namespaceOid = ((Form_pg_ts_parser) GETSTRUCT(tup))->prsnamespace;
384 if (SearchSysCacheExists(TSPARSERNAMENSP,
385 PointerGetDatum(newname),
386 ObjectIdGetDatum(namespaceOid),
387 0, 0))
388 ereport(ERROR,
389 (errcode(ERRCODE_DUPLICATE_OBJECT),
390 errmsg("text search parser \"%s\" already exists",
391 newname)));
393 namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname);
394 simple_heap_update(rel, &tup->t_self, tup);
395 CatalogUpdateIndexes(rel, tup);
397 heap_close(rel, NoLock);
398 heap_freetuple(tup);
401 /* ---------------------- TS Dictionary commands -----------------------*/
404 * make pg_depend entries for a new pg_ts_dict entry
406 static void
407 makeDictionaryDependencies(HeapTuple tuple)
409 Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
410 ObjectAddress myself,
411 referenced;
413 myself.classId = TSDictionaryRelationId;
414 myself.objectId = HeapTupleGetOid(tuple);
415 myself.objectSubId = 0;
417 /* dependency on namespace */
418 referenced.classId = NamespaceRelationId;
419 referenced.objectId = dict->dictnamespace;
420 referenced.objectSubId = 0;
421 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
423 /* dependency on owner */
424 recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
426 /* dependency on template */
427 referenced.classId = TSTemplateRelationId;
428 referenced.objectId = dict->dicttemplate;
429 referenced.objectSubId = 0;
430 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
434 * verify that a template's init method accepts a proposed option list
436 static void
437 verify_dictoptions(Oid tmplId, List *dictoptions)
439 HeapTuple tup;
440 Form_pg_ts_template tform;
441 Oid initmethod;
444 * Suppress this test when running in a standalone backend. This is a
445 * hack to allow initdb to create prefab dictionaries that might not
446 * actually be usable in template1's encoding (due to using external files
447 * that can't be translated into template1's encoding). We want to create
448 * them anyway, since they might be usable later in other databases.
450 if (!IsUnderPostmaster)
451 return;
453 tup = SearchSysCache(TSTEMPLATEOID,
454 ObjectIdGetDatum(tmplId),
455 0, 0, 0);
456 if (!HeapTupleIsValid(tup)) /* should not happen */
457 elog(ERROR, "cache lookup failed for text search template %u",
458 tmplId);
459 tform = (Form_pg_ts_template) GETSTRUCT(tup);
461 initmethod = tform->tmplinit;
463 if (!OidIsValid(initmethod))
465 /* If there is no init method, disallow any options */
466 if (dictoptions)
467 ereport(ERROR,
468 (errcode(ERRCODE_SYNTAX_ERROR),
469 errmsg("text search template \"%s\" does not accept options",
470 NameStr(tform->tmplname))));
472 else
475 * Copy the options just in case init method thinks it can scribble on
476 * them ...
478 dictoptions = copyObject(dictoptions);
481 * Call the init method and see if it complains. We don't worry about
482 * it leaking memory, since our command will soon be over anyway.
484 (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
487 ReleaseSysCache(tup);
491 * CREATE TEXT SEARCH DICTIONARY
493 void
494 DefineTSDictionary(List *names, List *parameters)
496 ListCell *pl;
497 Relation dictRel;
498 HeapTuple tup;
499 Datum values[Natts_pg_ts_dict];
500 char nulls[Natts_pg_ts_dict];
501 NameData dname;
502 Oid templId = InvalidOid;
503 List *dictoptions = NIL;
504 Oid dictOid;
505 Oid namespaceoid;
506 AclResult aclresult;
507 char *dictname;
509 /* Convert list of names to a name and namespace */
510 namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
512 /* Check we have creation rights in target namespace */
513 aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
514 if (aclresult != ACLCHECK_OK)
515 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
516 get_namespace_name(namespaceoid));
519 * loop over the definition list and extract the information we need.
521 foreach(pl, parameters)
523 DefElem *defel = (DefElem *) lfirst(pl);
525 if (pg_strcasecmp(defel->defname, "template") == 0)
527 templId = TSTemplateGetTmplid(defGetQualifiedName(defel), false);
529 else
531 /* Assume it's an option for the dictionary itself */
532 dictoptions = lappend(dictoptions, defel);
537 * Validation
539 if (!OidIsValid(templId))
540 ereport(ERROR,
541 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
542 errmsg("text search template is required")));
544 verify_dictoptions(templId, dictoptions);
547 * Looks good, insert
549 memset(values, 0, sizeof(values));
550 memset(nulls, ' ', sizeof(nulls));
552 namestrcpy(&dname, dictname);
553 values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
554 values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
555 values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
556 values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
557 if (dictoptions)
558 values[Anum_pg_ts_dict_dictinitoption - 1] =
559 PointerGetDatum(serialize_deflist(dictoptions));
560 else
561 nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
563 dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
565 tup = heap_formtuple(dictRel->rd_att, values, nulls);
567 dictOid = simple_heap_insert(dictRel, tup);
569 CatalogUpdateIndexes(dictRel, tup);
571 makeDictionaryDependencies(tup);
573 heap_freetuple(tup);
575 heap_close(dictRel, RowExclusiveLock);
579 * ALTER TEXT SEARCH DICTIONARY RENAME
581 void
582 RenameTSDictionary(List *oldname, const char *newname)
584 HeapTuple tup;
585 Relation rel;
586 Oid dictId;
587 Oid namespaceOid;
588 AclResult aclresult;
590 rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
592 dictId = TSDictionaryGetDictid(oldname, false);
594 tup = SearchSysCacheCopy(TSDICTOID,
595 ObjectIdGetDatum(dictId),
596 0, 0, 0);
598 if (!HeapTupleIsValid(tup)) /* should not happen */
599 elog(ERROR, "cache lookup failed for text search dictionary %u",
600 dictId);
602 namespaceOid = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
604 if (SearchSysCacheExists(TSDICTNAMENSP,
605 PointerGetDatum(newname),
606 ObjectIdGetDatum(namespaceOid),
607 0, 0))
608 ereport(ERROR,
609 (errcode(ERRCODE_DUPLICATE_OBJECT),
610 errmsg("text search dictionary \"%s\" already exists",
611 newname)));
613 /* must be owner */
614 if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
615 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
616 NameListToString(oldname));
618 /* must have CREATE privilege on namespace */
619 aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
620 if (aclresult != ACLCHECK_OK)
621 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
622 get_namespace_name(namespaceOid));
624 namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname);
625 simple_heap_update(rel, &tup->t_self, tup);
626 CatalogUpdateIndexes(rel, tup);
628 heap_close(rel, NoLock);
629 heap_freetuple(tup);
633 * DROP TEXT SEARCH DICTIONARY
635 void
636 RemoveTSDictionaries(DropStmt *drop)
638 ObjectAddresses *objects;
639 ListCell *cell;
642 * First we identify all the objects, then we delete them in a single
643 * performMultipleDeletions() call. This is to avoid unwanted
644 * DROP RESTRICT errors if one of the objects depends on another.
646 objects = new_object_addresses();
648 foreach(cell, drop->objects)
650 List *names = (List *) lfirst(cell);
651 Oid dictOid;
652 ObjectAddress object;
653 HeapTuple tup;
654 Oid namespaceId;
656 dictOid = TSDictionaryGetDictid(names, true);
658 if (!OidIsValid(dictOid))
660 if (!drop->missing_ok)
662 ereport(ERROR,
663 (errcode(ERRCODE_UNDEFINED_OBJECT),
664 errmsg("text search dictionary \"%s\" does not exist",
665 NameListToString(names))));
667 else
669 ereport(NOTICE,
670 (errmsg("text search dictionary \"%s\" does not exist, skipping",
671 NameListToString(names))));
673 continue;
676 tup = SearchSysCache(TSDICTOID,
677 ObjectIdGetDatum(dictOid),
678 0, 0, 0);
679 if (!HeapTupleIsValid(tup)) /* should not happen */
680 elog(ERROR, "cache lookup failed for text search dictionary %u",
681 dictOid);
683 /* Permission check: must own dictionary or its namespace */
684 namespaceId = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
685 if (!pg_ts_dict_ownercheck(dictOid, GetUserId()) &&
686 !pg_namespace_ownercheck(namespaceId, GetUserId()))
687 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
688 NameListToString(names));
690 object.classId = TSDictionaryRelationId;
691 object.objectId = dictOid;
692 object.objectSubId = 0;
694 add_exact_object_address(&object, objects);
696 ReleaseSysCache(tup);
699 performMultipleDeletions(objects, drop->behavior);
701 free_object_addresses(objects);
705 * Guts of TS dictionary deletion.
707 void
708 RemoveTSDictionaryById(Oid dictId)
710 Relation relation;
711 HeapTuple tup;
713 relation = heap_open(TSDictionaryRelationId, RowExclusiveLock);
715 tup = SearchSysCache(TSDICTOID,
716 ObjectIdGetDatum(dictId),
717 0, 0, 0);
719 if (!HeapTupleIsValid(tup))
720 elog(ERROR, "cache lookup failed for text search dictionary %u",
721 dictId);
723 simple_heap_delete(relation, &tup->t_self);
725 ReleaseSysCache(tup);
727 heap_close(relation, RowExclusiveLock);
731 * ALTER TEXT SEARCH DICTIONARY
733 void
734 AlterTSDictionary(AlterTSDictionaryStmt *stmt)
736 HeapTuple tup,
737 newtup;
738 Relation rel;
739 Oid dictId;
740 ListCell *pl;
741 List *dictoptions;
742 Datum opt;
743 bool isnull;
744 Datum repl_val[Natts_pg_ts_dict];
745 char repl_null[Natts_pg_ts_dict];
746 char repl_repl[Natts_pg_ts_dict];
748 dictId = TSDictionaryGetDictid(stmt->dictname, false);
750 rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
752 tup = SearchSysCache(TSDICTOID,
753 ObjectIdGetDatum(dictId),
754 0, 0, 0);
756 if (!HeapTupleIsValid(tup))
757 elog(ERROR, "cache lookup failed for text search dictionary %u",
758 dictId);
760 /* must be owner */
761 if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
762 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
763 NameListToString(stmt->dictname));
765 /* deserialize the existing set of options */
766 opt = SysCacheGetAttr(TSDICTOID, tup,
767 Anum_pg_ts_dict_dictinitoption,
768 &isnull);
769 if (isnull)
770 dictoptions = NIL;
771 else
772 dictoptions = deserialize_deflist(opt);
775 * Modify the options list as per specified changes
777 foreach(pl, stmt->options)
779 DefElem *defel = (DefElem *) lfirst(pl);
780 ListCell *cell;
781 ListCell *prev;
782 ListCell *next;
785 * Remove any matches ...
787 prev = NULL;
788 for (cell = list_head(dictoptions); cell; cell = next)
790 DefElem *oldel = (DefElem *) lfirst(cell);
792 next = lnext(cell);
793 if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
794 dictoptions = list_delete_cell(dictoptions, cell, prev);
795 else
796 prev = cell;
800 * and add new value if it's got one
802 if (defel->arg)
803 dictoptions = lappend(dictoptions, defel);
807 * Validate
809 verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
810 dictoptions);
813 * Looks good, update
815 memset(repl_val, 0, sizeof(repl_val));
816 memset(repl_null, ' ', sizeof(repl_null));
817 memset(repl_repl, ' ', sizeof(repl_repl));
819 if (dictoptions)
820 repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
821 PointerGetDatum(serialize_deflist(dictoptions));
822 else
823 repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
824 repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
826 newtup = heap_modifytuple(tup, RelationGetDescr(rel),
827 repl_val, repl_null, repl_repl);
829 simple_heap_update(rel, &newtup->t_self, newtup);
831 CatalogUpdateIndexes(rel, newtup);
834 * NOTE: because we only support altering the options, not the template,
835 * there is no need to update dependencies. This might have to change if
836 * the options ever reference inside-the-database objects.
839 heap_freetuple(newtup);
840 ReleaseSysCache(tup);
842 heap_close(rel, RowExclusiveLock);
846 * ALTER TEXT SEARCH DICTIONARY OWNER
848 void
849 AlterTSDictionaryOwner(List *name, Oid newOwnerId)
851 HeapTuple tup;
852 Relation rel;
853 Oid dictId;
854 Oid namespaceOid;
855 AclResult aclresult;
856 Form_pg_ts_dict form;
858 rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
860 dictId = TSDictionaryGetDictid(name, false);
862 tup = SearchSysCacheCopy(TSDICTOID,
863 ObjectIdGetDatum(dictId),
864 0, 0, 0);
866 if (!HeapTupleIsValid(tup)) /* should not happen */
867 elog(ERROR, "cache lookup failed for text search dictionary %u",
868 dictId);
870 form = (Form_pg_ts_dict) GETSTRUCT(tup);
871 namespaceOid = form->dictnamespace;
873 if (form->dictowner != newOwnerId)
875 /* Superusers can always do it */
876 if (!superuser())
878 /* must be owner */
879 if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
880 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
881 NameListToString(name));
883 /* Must be able to become new owner */
884 check_is_member_of_role(GetUserId(), newOwnerId);
886 /* New owner must have CREATE privilege on namespace */
887 aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
888 if (aclresult != ACLCHECK_OK)
889 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
890 get_namespace_name(namespaceOid));
893 form->dictowner = newOwnerId;
895 simple_heap_update(rel, &tup->t_self, tup);
896 CatalogUpdateIndexes(rel, tup);
898 /* Update owner dependency reference */
899 changeDependencyOnOwner(TSDictionaryRelationId, HeapTupleGetOid(tup),
900 newOwnerId);
903 heap_close(rel, NoLock);
904 heap_freetuple(tup);
907 /* ---------------------- TS Template commands -----------------------*/
910 * lookup a template support function and return its OID (as a Datum)
912 * attnum is the pg_ts_template column the function will go into
914 static Datum
915 get_ts_template_func(DefElem *defel, int attnum)
917 List *funcName = defGetQualifiedName(defel);
918 Oid typeId[4];
919 Oid retTypeId;
920 int nargs;
921 Oid procOid;
923 retTypeId = INTERNALOID;
924 typeId[0] = INTERNALOID;
925 typeId[1] = INTERNALOID;
926 typeId[2] = INTERNALOID;
927 typeId[3] = INTERNALOID;
928 switch (attnum)
930 case Anum_pg_ts_template_tmplinit:
931 nargs = 1;
932 break;
933 case Anum_pg_ts_template_tmpllexize:
934 nargs = 4;
935 break;
936 default:
937 /* should not be here */
938 elog(ERROR, "unrecognized attribute for text search template: %d",
939 attnum);
940 nargs = 0; /* keep compiler quiet */
943 procOid = LookupFuncName(funcName, nargs, typeId, false);
944 if (get_func_rettype(procOid) != retTypeId)
945 ereport(ERROR,
946 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
947 errmsg("function %s should return type %s",
948 func_signature_string(funcName, nargs, typeId),
949 format_type_be(retTypeId))));
951 return ObjectIdGetDatum(procOid);
955 * make pg_depend entries for a new pg_ts_template entry
957 static void
958 makeTSTemplateDependencies(HeapTuple tuple)
960 Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
961 ObjectAddress myself,
962 referenced;
964 myself.classId = TSTemplateRelationId;
965 myself.objectId = HeapTupleGetOid(tuple);
966 myself.objectSubId = 0;
968 /* dependency on namespace */
969 referenced.classId = NamespaceRelationId;
970 referenced.objectId = tmpl->tmplnamespace;
971 referenced.objectSubId = 0;
972 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
974 /* dependencies on functions */
975 referenced.classId = ProcedureRelationId;
976 referenced.objectSubId = 0;
978 referenced.objectId = tmpl->tmpllexize;
979 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
981 if (OidIsValid(tmpl->tmplinit))
983 referenced.objectId = tmpl->tmplinit;
984 recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
989 * CREATE TEXT SEARCH TEMPLATE
991 void
992 DefineTSTemplate(List *names, List *parameters)
994 ListCell *pl;
995 Relation tmplRel;
996 HeapTuple tup;
997 Datum values[Natts_pg_ts_template];
998 char nulls[Natts_pg_ts_template];
999 NameData dname;
1000 int i;
1001 Oid dictOid;
1002 Oid namespaceoid;
1003 char *tmplname;
1005 if (!superuser())
1006 ereport(ERROR,
1007 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1008 errmsg("must be superuser to create text search templates")));
1010 /* Convert list of names to a name and namespace */
1011 namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
1013 for (i = 0; i < Natts_pg_ts_template; i++)
1015 nulls[i] = ' ';
1016 values[i] = ObjectIdGetDatum(InvalidOid);
1019 namestrcpy(&dname, tmplname);
1020 values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
1021 values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
1024 * loop over the definition list and extract the information we need.
1026 foreach(pl, parameters)
1028 DefElem *defel = (DefElem *) lfirst(pl);
1030 if (pg_strcasecmp(defel->defname, "init") == 0)
1032 values[Anum_pg_ts_template_tmplinit - 1] =
1033 get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
1034 nulls[Anum_pg_ts_template_tmplinit - 1] = ' ';
1036 else if (pg_strcasecmp(defel->defname, "lexize") == 0)
1038 values[Anum_pg_ts_template_tmpllexize - 1] =
1039 get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
1040 nulls[Anum_pg_ts_template_tmpllexize - 1] = ' ';
1042 else
1043 ereport(ERROR,
1044 (errcode(ERRCODE_SYNTAX_ERROR),
1045 errmsg("text search template parameter \"%s\" not recognized",
1046 defel->defname)));
1050 * Validation
1052 if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
1053 ereport(ERROR,
1054 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1055 errmsg("text search template lexize method is required")));
1058 * Looks good, insert
1061 tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1063 tup = heap_formtuple(tmplRel->rd_att, values, nulls);
1065 dictOid = simple_heap_insert(tmplRel, tup);
1067 CatalogUpdateIndexes(tmplRel, tup);
1069 makeTSTemplateDependencies(tup);
1071 heap_freetuple(tup);
1073 heap_close(tmplRel, RowExclusiveLock);
1077 * ALTER TEXT SEARCH TEMPLATE RENAME
1079 void
1080 RenameTSTemplate(List *oldname, const char *newname)
1082 HeapTuple tup;
1083 Relation rel;
1084 Oid tmplId;
1085 Oid namespaceOid;
1087 if (!superuser())
1088 ereport(ERROR,
1089 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1090 errmsg("must be superuser to rename text search templates")));
1092 rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
1094 tmplId = TSTemplateGetTmplid(oldname, false);
1096 tup = SearchSysCacheCopy(TSTEMPLATEOID,
1097 ObjectIdGetDatum(tmplId),
1098 0, 0, 0);
1100 if (!HeapTupleIsValid(tup)) /* should not happen */
1101 elog(ERROR, "cache lookup failed for text search template %u",
1102 tmplId);
1104 namespaceOid = ((Form_pg_ts_template) GETSTRUCT(tup))->tmplnamespace;
1106 if (SearchSysCacheExists(TSTEMPLATENAMENSP,
1107 PointerGetDatum(newname),
1108 ObjectIdGetDatum(namespaceOid),
1109 0, 0))
1110 ereport(ERROR,
1111 (errcode(ERRCODE_DUPLICATE_OBJECT),
1112 errmsg("text search template \"%s\" already exists",
1113 newname)));
1115 namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname);
1116 simple_heap_update(rel, &tup->t_self, tup);
1117 CatalogUpdateIndexes(rel, tup);
1119 heap_close(rel, NoLock);
1120 heap_freetuple(tup);
1124 * DROP TEXT SEARCH TEMPLATE
1126 void
1127 RemoveTSTemplates(DropStmt *drop)
1129 ObjectAddresses *objects;
1130 ListCell *cell;
1132 if (!superuser())
1133 ereport(ERROR,
1134 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
1135 errmsg("must be superuser to drop text search templates")));
1138 * First we identify all the objects, then we delete them in a single
1139 * performMultipleDeletions() call. This is to avoid unwanted
1140 * DROP RESTRICT errors if one of the objects depends on another.
1142 objects = new_object_addresses();
1144 foreach(cell, drop->objects)
1146 List *names = (List *) lfirst(cell);
1147 Oid tmplOid;
1148 ObjectAddress object;
1150 tmplOid = TSTemplateGetTmplid(names, true);
1152 if (!OidIsValid(tmplOid))
1154 if (!drop->missing_ok)
1156 ereport(ERROR,
1157 (errcode(ERRCODE_UNDEFINED_OBJECT),
1158 errmsg("text search template \"%s\" does not exist",
1159 NameListToString(names))));
1161 else
1163 ereport(NOTICE,
1164 (errmsg("text search template \"%s\" does not exist, skipping",
1165 NameListToString(names))));
1167 continue;
1170 object.classId = TSTemplateRelationId;
1171 object.objectId = tmplOid;
1172 object.objectSubId = 0;
1174 add_exact_object_address(&object, objects);
1177 performMultipleDeletions(objects, drop->behavior);
1179 free_object_addresses(objects);
1183 * Guts of TS template deletion.
1185 void
1186 RemoveTSTemplateById(Oid tmplId)
1188 Relation relation;
1189 HeapTuple tup;
1191 relation = heap_open(TSTemplateRelationId, RowExclusiveLock);
1193 tup = SearchSysCache(TSTEMPLATEOID,
1194 ObjectIdGetDatum(tmplId),
1195 0, 0, 0);
1197 if (!HeapTupleIsValid(tup))
1198 elog(ERROR, "cache lookup failed for text search template %u",
1199 tmplId);
1201 simple_heap_delete(relation, &tup->t_self);
1203 ReleaseSysCache(tup);
1205 heap_close(relation, RowExclusiveLock);
1208 /* ---------------------- TS Configuration commands -----------------------*/
1211 * Finds syscache tuple of configuration.
1212 * Returns NULL if no such cfg.
1214 static HeapTuple
1215 GetTSConfigTuple(List *names)
1217 HeapTuple tup;
1218 Oid cfgId;
1220 cfgId = TSConfigGetCfgid(names, true);
1221 if (!OidIsValid(cfgId))
1222 return NULL;
1224 tup = SearchSysCache(TSCONFIGOID,
1225 ObjectIdGetDatum(cfgId),
1226 0, 0, 0);
1228 if (!HeapTupleIsValid(tup)) /* should not happen */
1229 elog(ERROR, "cache lookup failed for text search configuration %u",
1230 cfgId);
1232 return tup;
1236 * make pg_depend entries for a new or updated pg_ts_config entry
1238 * Pass opened pg_ts_config_map relation if there might be any config map
1239 * entries for the config.
1241 static void
1242 makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
1243 Relation mapRel)
1245 Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
1246 ObjectAddresses *addrs;
1247 ObjectAddress myself,
1248 referenced;
1250 myself.classId = TSConfigRelationId;
1251 myself.objectId = HeapTupleGetOid(tuple);
1252 myself.objectSubId = 0;
1254 /* for ALTER case, first flush old dependencies */
1255 if (removeOld)
1257 deleteDependencyRecordsFor(myself.classId, myself.objectId);
1258 deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
1262 * We use an ObjectAddresses list to remove possible duplicate
1263 * dependencies from the config map info. The pg_ts_config items
1264 * shouldn't be duplicates, but might as well fold them all into one call.
1266 addrs = new_object_addresses();
1268 /* dependency on namespace */
1269 referenced.classId = NamespaceRelationId;
1270 referenced.objectId = cfg->cfgnamespace;
1271 referenced.objectSubId = 0;
1272 add_exact_object_address(&referenced, addrs);
1274 /* dependency on owner */
1275 recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
1277 /* dependency on parser */
1278 referenced.classId = TSParserRelationId;
1279 referenced.objectId = cfg->cfgparser;
1280 referenced.objectSubId = 0;
1281 add_exact_object_address(&referenced, addrs);
1283 /* dependencies on dictionaries listed in config map */
1284 if (mapRel)
1286 ScanKeyData skey;
1287 SysScanDesc scan;
1288 HeapTuple maptup;
1290 /* CCI to ensure we can see effects of caller's changes */
1291 CommandCounterIncrement();
1293 ScanKeyInit(&skey,
1294 Anum_pg_ts_config_map_mapcfg,
1295 BTEqualStrategyNumber, F_OIDEQ,
1296 ObjectIdGetDatum(myself.objectId));
1298 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1299 SnapshotNow, 1, &skey);
1301 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1303 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1305 referenced.classId = TSDictionaryRelationId;
1306 referenced.objectId = cfgmap->mapdict;
1307 referenced.objectSubId = 0;
1308 add_exact_object_address(&referenced, addrs);
1311 systable_endscan(scan);
1314 /* Record 'em (this includes duplicate elimination) */
1315 record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
1317 free_object_addresses(addrs);
1321 * CREATE TEXT SEARCH CONFIGURATION
1323 void
1324 DefineTSConfiguration(List *names, List *parameters)
1326 Relation cfgRel;
1327 Relation mapRel = NULL;
1328 HeapTuple tup;
1329 Datum values[Natts_pg_ts_config];
1330 char nulls[Natts_pg_ts_config];
1331 AclResult aclresult;
1332 Oid namespaceoid;
1333 char *cfgname;
1334 NameData cname;
1335 Oid sourceOid = InvalidOid;
1336 Oid prsOid = InvalidOid;
1337 Oid cfgOid;
1338 ListCell *pl;
1340 /* Convert list of names to a name and namespace */
1341 namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
1343 /* Check we have creation rights in target namespace */
1344 aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
1345 if (aclresult != ACLCHECK_OK)
1346 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1347 get_namespace_name(namespaceoid));
1350 * loop over the definition list and extract the information we need.
1352 foreach(pl, parameters)
1354 DefElem *defel = (DefElem *) lfirst(pl);
1356 if (pg_strcasecmp(defel->defname, "parser") == 0)
1357 prsOid = TSParserGetPrsid(defGetQualifiedName(defel), false);
1358 else if (pg_strcasecmp(defel->defname, "copy") == 0)
1359 sourceOid = TSConfigGetCfgid(defGetQualifiedName(defel), false);
1360 else
1361 ereport(ERROR,
1362 (errcode(ERRCODE_SYNTAX_ERROR),
1363 errmsg("text search configuration parameter \"%s\" not recognized",
1364 defel->defname)));
1367 if (OidIsValid(sourceOid) && OidIsValid(prsOid))
1368 ereport(ERROR,
1369 (errcode(ERRCODE_SYNTAX_ERROR),
1370 errmsg("cannot specify both PARSER and COPY options")));
1373 * Look up source config if given.
1375 if (OidIsValid(sourceOid))
1377 Form_pg_ts_config cfg;
1379 tup = SearchSysCache(TSCONFIGOID,
1380 ObjectIdGetDatum(sourceOid),
1381 0, 0, 0);
1382 if (!HeapTupleIsValid(tup))
1383 elog(ERROR, "cache lookup failed for text search configuration %u",
1384 sourceOid);
1386 cfg = (Form_pg_ts_config) GETSTRUCT(tup);
1388 /* use source's parser */
1389 prsOid = cfg->cfgparser;
1391 ReleaseSysCache(tup);
1395 * Validation
1397 if (!OidIsValid(prsOid))
1398 ereport(ERROR,
1399 (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
1400 errmsg("text search parser is required")));
1403 * Looks good, build tuple and insert
1405 memset(values, 0, sizeof(values));
1406 memset(nulls, ' ', sizeof(nulls));
1408 namestrcpy(&cname, cfgname);
1409 values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
1410 values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
1411 values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
1412 values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
1414 cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
1416 tup = heap_formtuple(cfgRel->rd_att, values, nulls);
1418 cfgOid = simple_heap_insert(cfgRel, tup);
1420 CatalogUpdateIndexes(cfgRel, tup);
1422 if (OidIsValid(sourceOid))
1425 * Copy token-dicts map from source config
1427 ScanKeyData skey;
1428 SysScanDesc scan;
1429 HeapTuple maptup;
1431 mapRel = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1433 ScanKeyInit(&skey,
1434 Anum_pg_ts_config_map_mapcfg,
1435 BTEqualStrategyNumber, F_OIDEQ,
1436 ObjectIdGetDatum(sourceOid));
1438 scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
1439 SnapshotNow, 1, &skey);
1441 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1443 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1444 HeapTuple newmaptup;
1445 Datum mapvalues[Natts_pg_ts_config_map];
1446 char mapnulls[Natts_pg_ts_config_map];
1448 memset(mapvalues, 0, sizeof(mapvalues));
1449 memset(mapnulls, ' ', sizeof(mapnulls));
1451 mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
1452 mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
1453 mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
1454 mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
1456 newmaptup = heap_formtuple(mapRel->rd_att, mapvalues, mapnulls);
1458 simple_heap_insert(mapRel, newmaptup);
1460 CatalogUpdateIndexes(mapRel, newmaptup);
1462 heap_freetuple(newmaptup);
1465 systable_endscan(scan);
1468 makeConfigurationDependencies(tup, false, mapRel);
1470 heap_freetuple(tup);
1472 if (mapRel)
1473 heap_close(mapRel, RowExclusiveLock);
1474 heap_close(cfgRel, RowExclusiveLock);
1478 * ALTER TEXT SEARCH CONFIGURATION RENAME
1480 void
1481 RenameTSConfiguration(List *oldname, const char *newname)
1483 HeapTuple tup;
1484 Relation rel;
1485 Oid cfgId;
1486 AclResult aclresult;
1487 Oid namespaceOid;
1489 rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1491 cfgId = TSConfigGetCfgid(oldname, false);
1493 tup = SearchSysCacheCopy(TSCONFIGOID,
1494 ObjectIdGetDatum(cfgId),
1495 0, 0, 0);
1497 if (!HeapTupleIsValid(tup)) /* should not happen */
1498 elog(ERROR, "cache lookup failed for text search configuration %u",
1499 cfgId);
1501 namespaceOid = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
1503 if (SearchSysCacheExists(TSCONFIGNAMENSP,
1504 PointerGetDatum(newname),
1505 ObjectIdGetDatum(namespaceOid),
1506 0, 0))
1507 ereport(ERROR,
1508 (errcode(ERRCODE_DUPLICATE_OBJECT),
1509 errmsg("text search configuration \"%s\" already exists",
1510 newname)));
1512 /* must be owner */
1513 if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1514 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1515 NameListToString(oldname));
1517 /* must have CREATE privilege on namespace */
1518 aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
1519 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1520 get_namespace_name(namespaceOid));
1522 namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname);
1523 simple_heap_update(rel, &tup->t_self, tup);
1524 CatalogUpdateIndexes(rel, tup);
1526 heap_close(rel, NoLock);
1527 heap_freetuple(tup);
1531 * DROP TEXT SEARCH CONFIGURATION
1533 void
1534 RemoveTSConfigurations(DropStmt *drop)
1536 ObjectAddresses *objects;
1537 ListCell *cell;
1540 * First we identify all the objects, then we delete them in a single
1541 * performMultipleDeletions() call. This is to avoid unwanted
1542 * DROP RESTRICT errors if one of the objects depends on another.
1544 objects = new_object_addresses();
1546 foreach(cell, drop->objects)
1548 List *names = (List *) lfirst(cell);
1549 Oid cfgOid;
1550 Oid namespaceId;
1551 ObjectAddress object;
1552 HeapTuple tup;
1554 tup = GetTSConfigTuple(names);
1556 if (!HeapTupleIsValid(tup))
1558 if (!drop->missing_ok)
1560 ereport(ERROR,
1561 (errcode(ERRCODE_UNDEFINED_OBJECT),
1562 errmsg("text search configuration \"%s\" does not exist",
1563 NameListToString(names))));
1565 else
1567 ereport(NOTICE,
1568 (errmsg("text search configuration \"%s\" does not exist, skipping",
1569 NameListToString(names))));
1571 continue;
1574 /* Permission check: must own configuration or its namespace */
1575 cfgOid = HeapTupleGetOid(tup);
1576 namespaceId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
1577 if (!pg_ts_config_ownercheck(cfgOid, GetUserId()) &&
1578 !pg_namespace_ownercheck(namespaceId, GetUserId()))
1579 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1580 NameListToString(names));
1582 object.classId = TSConfigRelationId;
1583 object.objectId = cfgOid;
1584 object.objectSubId = 0;
1586 add_exact_object_address(&object, objects);
1588 ReleaseSysCache(tup);
1591 performMultipleDeletions(objects, drop->behavior);
1593 free_object_addresses(objects);
1597 * Guts of TS configuration deletion.
1599 void
1600 RemoveTSConfigurationById(Oid cfgId)
1602 Relation relCfg,
1603 relMap;
1604 HeapTuple tup;
1605 ScanKeyData skey;
1606 SysScanDesc scan;
1608 /* Remove the pg_ts_config entry */
1609 relCfg = heap_open(TSConfigRelationId, RowExclusiveLock);
1611 tup = SearchSysCache(TSCONFIGOID,
1612 ObjectIdGetDatum(cfgId),
1613 0, 0, 0);
1615 if (!HeapTupleIsValid(tup))
1616 elog(ERROR, "cache lookup failed for text search dictionary %u",
1617 cfgId);
1619 simple_heap_delete(relCfg, &tup->t_self);
1621 ReleaseSysCache(tup);
1623 heap_close(relCfg, RowExclusiveLock);
1625 /* Remove any pg_ts_config_map entries */
1626 relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1628 ScanKeyInit(&skey,
1629 Anum_pg_ts_config_map_mapcfg,
1630 BTEqualStrategyNumber, F_OIDEQ,
1631 ObjectIdGetDatum(cfgId));
1633 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1634 SnapshotNow, 1, &skey);
1636 while (HeapTupleIsValid((tup = systable_getnext(scan))))
1638 simple_heap_delete(relMap, &tup->t_self);
1641 systable_endscan(scan);
1643 heap_close(relMap, RowExclusiveLock);
1647 * ALTER TEXT SEARCH CONFIGURATION OWNER
1649 void
1650 AlterTSConfigurationOwner(List *name, Oid newOwnerId)
1652 HeapTuple tup;
1653 Relation rel;
1654 Oid cfgId;
1655 AclResult aclresult;
1656 Oid namespaceOid;
1657 Form_pg_ts_config form;
1659 rel = heap_open(TSConfigRelationId, RowExclusiveLock);
1661 cfgId = TSConfigGetCfgid(name, false);
1663 tup = SearchSysCacheCopy(TSCONFIGOID,
1664 ObjectIdGetDatum(cfgId),
1665 0, 0, 0);
1667 if (!HeapTupleIsValid(tup)) /* should not happen */
1668 elog(ERROR, "cache lookup failed for text search configuration %u",
1669 cfgId);
1671 form = (Form_pg_ts_config) GETSTRUCT(tup);
1672 namespaceOid = form->cfgnamespace;
1674 if (form->cfgowner != newOwnerId)
1676 /* Superusers can always do it */
1677 if (!superuser())
1679 /* must be owner */
1680 if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
1681 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1682 NameListToString(name));
1684 /* Must be able to become new owner */
1685 check_is_member_of_role(GetUserId(), newOwnerId);
1687 /* New owner must have CREATE privilege on namespace */
1688 aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
1689 if (aclresult != ACLCHECK_OK)
1690 aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
1691 get_namespace_name(namespaceOid));
1694 form->cfgowner = newOwnerId;
1696 simple_heap_update(rel, &tup->t_self, tup);
1697 CatalogUpdateIndexes(rel, tup);
1699 /* Update owner dependency reference */
1700 changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup),
1701 newOwnerId);
1704 heap_close(rel, NoLock);
1705 heap_freetuple(tup);
1709 * ALTER TEXT SEARCH CONFIGURATION - main entry point
1711 void
1712 AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
1714 HeapTuple tup;
1715 Relation relMap;
1717 /* Find the configuration */
1718 tup = GetTSConfigTuple(stmt->cfgname);
1719 if (!HeapTupleIsValid(tup))
1720 ereport(ERROR,
1721 (errcode(ERRCODE_UNDEFINED_OBJECT),
1722 errmsg("text search configuration \"%s\" does not exist",
1723 NameListToString(stmt->cfgname))));
1725 /* must be owner */
1726 if (!pg_ts_config_ownercheck(HeapTupleGetOid(tup), GetUserId()))
1727 aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
1728 NameListToString(stmt->cfgname));
1730 relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
1732 /* Add or drop mappings */
1733 if (stmt->dicts)
1734 MakeConfigurationMapping(stmt, tup, relMap);
1735 else if (stmt->tokentype)
1736 DropConfigurationMapping(stmt, tup, relMap);
1738 /* Update dependencies */
1739 makeConfigurationDependencies(tup, true, relMap);
1741 heap_close(relMap, RowExclusiveLock);
1743 ReleaseSysCache(tup);
1747 * Translate a list of token type names to an array of token type numbers
1749 static int *
1750 getTokenTypes(Oid prsId, List *tokennames)
1752 TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
1753 LexDescr *list;
1754 int *res,
1756 ntoken;
1757 ListCell *tn;
1759 ntoken = list_length(tokennames);
1760 if (ntoken == 0)
1761 return NULL;
1762 res = (int *) palloc(sizeof(int) * ntoken);
1764 if (!OidIsValid(prs->lextypeOid))
1765 elog(ERROR, "method lextype isn't defined for text search parser %u",
1766 prsId);
1768 /* OidFunctionCall0 is absent */
1769 list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
1770 (Datum) 0));
1772 i = 0;
1773 foreach(tn, tokennames)
1775 Value *val = (Value *) lfirst(tn);
1776 bool found = false;
1777 int j;
1779 j = 0;
1780 while (list && list[j].lexid)
1782 /* XXX should we use pg_strcasecmp here? */
1783 if (strcmp(strVal(val), list[j].alias) == 0)
1785 res[i] = list[j].lexid;
1786 found = true;
1787 break;
1789 j++;
1791 if (!found)
1792 ereport(ERROR,
1793 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1794 errmsg("token type \"%s\" does not exist",
1795 strVal(val))));
1796 i++;
1799 return res;
1803 * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
1805 static void
1806 MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
1807 HeapTuple tup, Relation relMap)
1809 Oid cfgId = HeapTupleGetOid(tup);
1810 ScanKeyData skey[2];
1811 SysScanDesc scan;
1812 HeapTuple maptup;
1813 int i;
1814 int j;
1815 Oid prsId;
1816 int *tokens,
1817 ntoken;
1818 Oid *dictIds;
1819 int ndict;
1820 ListCell *c;
1822 prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
1824 tokens = getTokenTypes(prsId, stmt->tokentype);
1825 ntoken = list_length(stmt->tokentype);
1827 if (stmt->override)
1830 * delete maps for tokens if they exist and command was ALTER
1832 for (i = 0; i < ntoken; i++)
1834 ScanKeyInit(&skey[0],
1835 Anum_pg_ts_config_map_mapcfg,
1836 BTEqualStrategyNumber, F_OIDEQ,
1837 ObjectIdGetDatum(cfgId));
1838 ScanKeyInit(&skey[1],
1839 Anum_pg_ts_config_map_maptokentype,
1840 BTEqualStrategyNumber, F_INT4EQ,
1841 Int32GetDatum(tokens[i]));
1843 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1844 SnapshotNow, 2, skey);
1846 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1848 simple_heap_delete(relMap, &maptup->t_self);
1851 systable_endscan(scan);
1856 * Convert list of dictionary names to array of dict OIDs
1858 ndict = list_length(stmt->dicts);
1859 dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
1860 i = 0;
1861 foreach(c, stmt->dicts)
1863 List *names = (List *) lfirst(c);
1865 dictIds[i] = TSDictionaryGetDictid(names, false);
1866 i++;
1869 if (stmt->replace)
1872 * Replace a specific dictionary in existing entries
1874 Oid dictOld = dictIds[0],
1875 dictNew = dictIds[1];
1877 ScanKeyInit(&skey[0],
1878 Anum_pg_ts_config_map_mapcfg,
1879 BTEqualStrategyNumber, F_OIDEQ,
1880 ObjectIdGetDatum(cfgId));
1882 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
1883 SnapshotNow, 1, skey);
1885 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
1887 Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
1890 * check if it's one of target token types
1892 if (tokens)
1894 bool tokmatch = false;
1896 for (j = 0; j < ntoken; j++)
1898 if (cfgmap->maptokentype == tokens[j])
1900 tokmatch = true;
1901 break;
1904 if (!tokmatch)
1905 continue;
1909 * replace dictionary if match
1911 if (cfgmap->mapdict == dictOld)
1913 Datum repl_val[Natts_pg_ts_config_map];
1914 char repl_null[Natts_pg_ts_config_map];
1915 char repl_repl[Natts_pg_ts_config_map];
1916 HeapTuple newtup;
1918 memset(repl_val, 0, sizeof(repl_val));
1919 memset(repl_null, ' ', sizeof(repl_null));
1920 memset(repl_repl, ' ', sizeof(repl_repl));
1922 repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
1923 repl_repl[Anum_pg_ts_config_map_mapdict - 1] = 'r';
1925 newtup = heap_modifytuple(maptup,
1926 RelationGetDescr(relMap),
1927 repl_val, repl_null, repl_repl);
1928 simple_heap_update(relMap, &newtup->t_self, newtup);
1930 CatalogUpdateIndexes(relMap, newtup);
1934 systable_endscan(scan);
1936 else
1939 * Insertion of new entries
1941 for (i = 0; i < ntoken; i++)
1943 for (j = 0; j < ndict; j++)
1945 Datum values[Natts_pg_ts_config_map];
1946 char nulls[Natts_pg_ts_config_map];
1948 memset(nulls, ' ', sizeof(nulls));
1949 values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
1950 values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]);
1951 values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
1952 values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
1954 tup = heap_formtuple(relMap->rd_att, values, nulls);
1955 simple_heap_insert(relMap, tup);
1956 CatalogUpdateIndexes(relMap, tup);
1958 heap_freetuple(tup);
1965 * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
1967 static void
1968 DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
1969 HeapTuple tup, Relation relMap)
1971 Oid cfgId = HeapTupleGetOid(tup);
1972 ScanKeyData skey[2];
1973 SysScanDesc scan;
1974 HeapTuple maptup;
1975 int i;
1976 Oid prsId;
1977 int *tokens,
1978 ntoken;
1979 ListCell *c;
1981 prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
1983 tokens = getTokenTypes(prsId, stmt->tokentype);
1984 ntoken = list_length(stmt->tokentype);
1986 i = 0;
1987 foreach(c, stmt->tokentype)
1989 Value *val = (Value *) lfirst(c);
1990 bool found = false;
1992 ScanKeyInit(&skey[0],
1993 Anum_pg_ts_config_map_mapcfg,
1994 BTEqualStrategyNumber, F_OIDEQ,
1995 ObjectIdGetDatum(cfgId));
1996 ScanKeyInit(&skey[1],
1997 Anum_pg_ts_config_map_maptokentype,
1998 BTEqualStrategyNumber, F_INT4EQ,
1999 Int32GetDatum(tokens[i]));
2001 scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
2002 SnapshotNow, 2, skey);
2004 while (HeapTupleIsValid((maptup = systable_getnext(scan))))
2006 simple_heap_delete(relMap, &maptup->t_self);
2007 found = true;
2010 systable_endscan(scan);
2012 if (!found)
2014 if (!stmt->missing_ok)
2016 ereport(ERROR,
2017 (errcode(ERRCODE_UNDEFINED_OBJECT),
2018 errmsg("mapping for token type \"%s\" does not exist",
2019 strVal(val))));
2021 else
2023 ereport(NOTICE,
2024 (errmsg("mapping for token type \"%s\" does not exist, skipping",
2025 strVal(val))));
2029 i++;
2035 * Serialize dictionary options, producing a TEXT datum from a List of DefElem
2037 * This is used to form the value stored in pg_ts_dict.dictinitoption.
2038 * For the convenience of pg_dump, the output is formatted exactly as it
2039 * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
2040 * same options.
2042 * Note that we assume that only the textual representation of an option's
2043 * value is interesting --- hence, non-string DefElems get forced to strings.
2045 text *
2046 serialize_deflist(List *deflist)
2048 text *result;
2049 StringInfoData buf;
2050 ListCell *l;
2052 initStringInfo(&buf);
2054 foreach(l, deflist)
2056 DefElem *defel = (DefElem *) lfirst(l);
2057 char *val = defGetString(defel);
2059 appendStringInfo(&buf, "%s = ",
2060 quote_identifier(defel->defname));
2061 /* If backslashes appear, force E syntax to determine their handling */
2062 if (strchr(val, '\\'))
2063 appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
2064 appendStringInfoChar(&buf, '\'');
2065 while (*val)
2067 char ch = *val++;
2069 if (SQL_STR_DOUBLE(ch, true))
2070 appendStringInfoChar(&buf, ch);
2071 appendStringInfoChar(&buf, ch);
2073 appendStringInfoChar(&buf, '\'');
2074 if (lnext(l) != NULL)
2075 appendStringInfo(&buf, ", ");
2078 result = cstring_to_text_with_len(buf.data, buf.len);
2079 pfree(buf.data);
2080 return result;
2084 * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
2086 * This is also used for prsheadline options, so for backward compatibility
2087 * we need to accept a few things serialize_deflist() will never emit:
2088 * in particular, unquoted and double-quoted values.
2090 List *
2091 deserialize_deflist(Datum txt)
2093 text *in = DatumGetTextP(txt); /* in case it's toasted */
2094 List *result = NIL;
2095 int len = VARSIZE(in) - VARHDRSZ;
2096 char *ptr,
2097 *endptr,
2098 *workspace,
2099 *wsptr = NULL,
2100 *startvalue = NULL;
2101 typedef enum
2103 CS_WAITKEY,
2104 CS_INKEY,
2105 CS_INQKEY,
2106 CS_WAITEQ,
2107 CS_WAITVALUE,
2108 CS_INSQVALUE,
2109 CS_INDQVALUE,
2110 CS_INWVALUE
2111 } ds_state;
2112 ds_state state = CS_WAITKEY;
2114 workspace = (char *) palloc(len + 1); /* certainly enough room */
2115 ptr = VARDATA(in);
2116 endptr = ptr + len;
2117 for (; ptr < endptr; ptr++)
2119 switch (state)
2121 case CS_WAITKEY:
2122 if (isspace((unsigned char) *ptr) || *ptr == ',')
2123 continue;
2124 if (*ptr == '"')
2126 wsptr = workspace;
2127 state = CS_INQKEY;
2129 else
2131 wsptr = workspace;
2132 *wsptr++ = *ptr;
2133 state = CS_INKEY;
2135 break;
2136 case CS_INKEY:
2137 if (isspace((unsigned char) *ptr))
2139 *wsptr++ = '\0';
2140 state = CS_WAITEQ;
2142 else if (*ptr == '=')
2144 *wsptr++ = '\0';
2145 state = CS_WAITVALUE;
2147 else
2149 *wsptr++ = *ptr;
2151 break;
2152 case CS_INQKEY:
2153 if (*ptr == '"')
2155 if (ptr + 1 < endptr && ptr[1] == '"')
2157 /* copy only one of the two quotes */
2158 *wsptr++ = *ptr++;
2160 else
2162 *wsptr++ = '\0';
2163 state = CS_WAITEQ;
2166 else
2168 *wsptr++ = *ptr;
2170 break;
2171 case CS_WAITEQ:
2172 if (*ptr == '=')
2173 state = CS_WAITVALUE;
2174 else if (!isspace((unsigned char) *ptr))
2175 ereport(ERROR,
2176 (errcode(ERRCODE_SYNTAX_ERROR),
2177 errmsg("invalid parameter list format: \"%s\"",
2178 text_to_cstring(in))));
2179 break;
2180 case CS_WAITVALUE:
2181 if (*ptr == '\'')
2183 startvalue = wsptr;
2184 state = CS_INSQVALUE;
2186 else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
2188 ptr++;
2189 startvalue = wsptr;
2190 state = CS_INSQVALUE;
2192 else if (*ptr == '"')
2194 startvalue = wsptr;
2195 state = CS_INDQVALUE;
2197 else if (!isspace((unsigned char) *ptr))
2199 startvalue = wsptr;
2200 *wsptr++ = *ptr;
2201 state = CS_INWVALUE;
2203 break;
2204 case CS_INSQVALUE:
2205 if (*ptr == '\'')
2207 if (ptr + 1 < endptr && ptr[1] == '\'')
2209 /* copy only one of the two quotes */
2210 *wsptr++ = *ptr++;
2212 else
2214 *wsptr++ = '\0';
2215 result = lappend(result,
2216 makeDefElem(pstrdup(workspace),
2217 (Node *) makeString(pstrdup(startvalue))));
2218 state = CS_WAITKEY;
2221 else if (*ptr == '\\')
2223 if (ptr + 1 < endptr && ptr[1] == '\\')
2225 /* copy only one of the two backslashes */
2226 *wsptr++ = *ptr++;
2228 else
2229 *wsptr++ = *ptr;
2231 else
2233 *wsptr++ = *ptr;
2235 break;
2236 case CS_INDQVALUE:
2237 if (*ptr == '"')
2239 if (ptr + 1 < endptr && ptr[1] == '"')
2241 /* copy only one of the two quotes */
2242 *wsptr++ = *ptr++;
2244 else
2246 *wsptr++ = '\0';
2247 result = lappend(result,
2248 makeDefElem(pstrdup(workspace),
2249 (Node *) makeString(pstrdup(startvalue))));
2250 state = CS_WAITKEY;
2253 else
2255 *wsptr++ = *ptr;
2257 break;
2258 case CS_INWVALUE:
2259 if (*ptr == ',' || isspace((unsigned char) *ptr))
2261 *wsptr++ = '\0';
2262 result = lappend(result,
2263 makeDefElem(pstrdup(workspace),
2264 (Node *) makeString(pstrdup(startvalue))));
2265 state = CS_WAITKEY;
2267 else
2269 *wsptr++ = *ptr;
2271 break;
2272 default:
2273 elog(ERROR, "unrecognized deserialize_deflist state: %d",
2274 state);
2278 if (state == CS_INWVALUE)
2280 *wsptr++ = '\0';
2281 result = lappend(result,
2282 makeDefElem(pstrdup(workspace),
2283 (Node *) makeString(pstrdup(startvalue))));
2285 else if (state != CS_WAITKEY)
2286 ereport(ERROR,
2287 (errcode(ERRCODE_SYNTAX_ERROR),
2288 errmsg("invalid parameter list format: \"%s\"",
2289 text_to_cstring(in))));
2291 pfree(workspace);
2293 return result;