aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands/tsearchcmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/commands/tsearchcmds.c')
-rw-r--r--src/backend/commands/tsearchcmds.c1948
1 files changed, 1948 insertions, 0 deletions
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
new file mode 100644
index 00000000000..7092da132d8
--- /dev/null
+++ b/src/backend/commands/tsearchcmds.c
@@ -0,0 +1,1948 @@
+/*-------------------------------------------------------------------------
+ *
+ * tsearchcmds.c
+ *
+ * Routines for tsearch manipulation commands
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/backend/commands/tsearchcmds.c,v 1.1 2007/08/21 01:11:15 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "miscadmin.h"
+
+#include "access/heapam.h"
+#include "access/genam.h"
+#include "access/xact.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_ts_config.h"
+#include "catalog/pg_ts_config_map.h"
+#include "catalog/pg_ts_dict.h"
+#include "catalog/pg_ts_parser.h"
+#include "catalog/pg_ts_template.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "parser/parse_func.h"
+#include "tsearch/ts_cache.h"
+#include "tsearch/ts_public.h"
+#include "tsearch/ts_utils.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/catcache.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+
+static HeapTuple UpdateTSConfiguration(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup);
+static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup);
+static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup);
+
+
+/* --------------------- TS Parser commands ------------------------ */
+
+/*
+ * lookup a parser support function and return its OID (as a Datum)
+ *
+ * attnum is the pg_ts_parser column the function will go into
+ */
+static Datum
+get_ts_parser_func(DefElem *defel, int attnum)
+{
+ List *funcName = defGetQualifiedName(defel);
+ Oid typeId[3];
+ Oid retTypeId;
+ int nargs;
+ Oid procOid;
+
+ retTypeId = INTERNALOID; /* correct for most */
+ typeId[0] = INTERNALOID;
+ switch (attnum)
+ {
+ case Anum_pg_ts_parser_prsstart:
+ nargs = 2;
+ typeId[1] = INT4OID;
+ break;
+ case Anum_pg_ts_parser_prstoken:
+ nargs = 3;
+ typeId[1] = INTERNALOID;
+ typeId[2] = INTERNALOID;
+ break;
+ case Anum_pg_ts_parser_prsend:
+ nargs = 1;
+ retTypeId = VOIDOID;
+ break;
+ case Anum_pg_ts_parser_prsheadline:
+ nargs = 3;
+ typeId[1] = TEXTOID;
+ typeId[2] = TSQUERYOID;
+ break;
+ case Anum_pg_ts_parser_prslextype:
+ nargs = 1;
+ break;
+ default:
+ /* should not be here */
+ elog(ERROR, "unknown attribute for text search parser: %d", attnum);
+ nargs = 0; /* keep compiler quiet */
+ }
+
+ procOid = LookupFuncName(funcName, nargs, typeId, false);
+ if (get_func_rettype(procOid) != retTypeId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("function %s should return type %s",
+ func_signature_string(funcName, nargs, typeId),
+ format_type_be(retTypeId))));
+
+ return ObjectIdGetDatum(procOid);
+}
+
+/*
+ * make pg_depend entries for a new pg_ts_parser entry
+ */
+static void
+makeParserDependencies(HeapTuple tuple)
+{
+ Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
+ ObjectAddress myself,
+ referenced;
+
+ myself.classId = TSParserRelationId;
+ myself.objectId = HeapTupleGetOid(tuple);
+ myself.objectSubId = 0;
+
+ /* dependency on namespace */
+ referenced.classId = NamespaceRelationId;
+ referenced.objectId = prs->prsnamespace;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependencies on functions */
+ referenced.classId = ProcedureRelationId;
+ referenced.objectSubId = 0;
+
+ referenced.objectId = prs->prsstart;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.objectId = prs->prstoken;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.objectId = prs->prsend;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ referenced.objectId = prs->prslextype;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ if (OidIsValid(prs->prsheadline))
+ {
+ referenced.objectId = prs->prsheadline;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+}
+
+/*
+ * CREATE TEXT SEARCH PARSER
+ */
+void
+DefineTSParser(List *names, List *parameters)
+{
+ char *prsname;
+ ListCell *pl;
+ Relation prsRel;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_parser];
+ char nulls[Natts_pg_ts_parser];
+ NameData pname;
+ Oid prsOid;
+ Oid namespaceoid;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create text search parsers")));
+
+ /* Convert list of names to a name and namespace */
+ namespaceoid = QualifiedNameGetCreationNamespace(names, &prsname);
+
+ /* initialize tuple fields with name/namespace */
+ memset(values, 0, sizeof(values));
+ memset(nulls, ' ', sizeof(nulls));
+
+ namestrcpy(&pname, prsname);
+ values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
+ values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+
+ /*
+ * loop over the definition list and extract the information we need.
+ */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (pg_strcasecmp(defel->defname, "start") == 0)
+ {
+ values[Anum_pg_ts_parser_prsstart - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
+ }
+ else if (pg_strcasecmp(defel->defname, "gettoken") == 0)
+ {
+ values[Anum_pg_ts_parser_prstoken - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
+ }
+ else if (pg_strcasecmp(defel->defname, "end") == 0)
+ {
+ values[Anum_pg_ts_parser_prsend - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
+ }
+ else if (pg_strcasecmp(defel->defname, "headline") == 0)
+ {
+ values[Anum_pg_ts_parser_prsheadline - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
+ }
+ else if (pg_strcasecmp(defel->defname, "lextypes") == 0)
+ {
+ values[Anum_pg_ts_parser_prslextype - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search parser parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ /*
+ * Validation
+ */
+ if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsstart - 1])))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search parser start method is required")));
+
+ if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prstoken - 1])))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search parser gettoken method is required")));
+
+ if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prsend - 1])))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search parser end method is required")));
+
+ if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_parser_prslextype - 1])))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search parser lextypes method is required")));
+
+ /*
+ * Looks good, insert
+ */
+ prsRel = heap_open(TSParserRelationId, RowExclusiveLock);
+
+ tup = heap_formtuple(prsRel->rd_att, values, nulls);
+
+ prsOid = simple_heap_insert(prsRel, tup);
+
+ CatalogUpdateIndexes(prsRel, tup);
+
+ makeParserDependencies(tup);
+
+ heap_freetuple(tup);
+
+ heap_close(prsRel, RowExclusiveLock);
+}
+
+/*
+ * DROP TEXT SEARCH PARSER
+ */
+void
+RemoveTSParser(List *names, DropBehavior behavior, bool missing_ok)
+{
+ Oid prsOid;
+ ObjectAddress object;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to drop text search parsers")));
+
+ prsOid = TSParserGetPrsid(names, true);
+ if (!OidIsValid(prsOid))
+ {
+ if (!missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("text search parser \"%s\" does not exist",
+ NameListToString(names))));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("text search parser \"%s\" does not exist, skipping",
+ NameListToString(names))));
+ }
+ return;
+ }
+
+ object.classId = TSParserRelationId;
+ object.objectId = prsOid;
+ object.objectSubId = 0;
+
+ performDeletion(&object, behavior);
+}
+
+/*
+ * Guts of TS parser deletion.
+ */
+void
+RemoveTSParserById(Oid prsId)
+{
+ Relation relation;
+ HeapTuple tup;
+
+ relation = heap_open(TSParserRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache(TSPARSEROID,
+ ObjectIdGetDatum(prsId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search parser %u", prsId);
+
+ simple_heap_delete(relation, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ heap_close(relation, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH PARSER RENAME
+ */
+void
+RenameTSParser(List *oldname, const char *newname)
+{
+ HeapTuple tup;
+ Relation rel;
+ Oid prsId;
+ Oid namespaceOid;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to rename text search parsers")));
+
+ rel = heap_open(TSParserRelationId, RowExclusiveLock);
+
+ prsId = TSParserGetPrsid(oldname, false);
+
+ tup = SearchSysCacheCopy(TSPARSEROID,
+ ObjectIdGetDatum(prsId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search parser %u", prsId);
+
+ namespaceOid = ((Form_pg_ts_parser) GETSTRUCT(tup))->prsnamespace;
+
+ if (SearchSysCacheExists(TSPARSERNAMENSP,
+ PointerGetDatum(newname),
+ ObjectIdGetDatum(namespaceOid),
+ 0, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("text search parser \"%s\" already exists",
+ newname)));
+
+ namestrcpy(&(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname), newname);
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ heap_close(rel, NoLock);
+ heap_freetuple(tup);
+}
+
+/* ---------------------- TS Dictionary commands -----------------------*/
+
+/*
+ * make pg_depend entries for a new pg_ts_dict entry
+ */
+static void
+makeDictionaryDependencies(HeapTuple tuple)
+{
+ Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
+ ObjectAddress myself,
+ referenced;
+
+ myself.classId = TSDictionaryRelationId;
+ myself.objectId = HeapTupleGetOid(tuple);
+ myself.objectSubId = 0;
+
+ /* dependency on namespace */
+ referenced.classId = NamespaceRelationId;
+ referenced.objectId = dict->dictnamespace;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependency on owner */
+ recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
+
+ /* dependency on template */
+ referenced.classId = TSTemplateRelationId;
+ referenced.objectId = dict->dicttemplate;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+}
+
+/*
+ * CREATE TEXT SEARCH DICTIONARY
+ */
+void
+DefineTSDictionary(List *names, List *parameters)
+{
+ ListCell *pl;
+ Relation dictRel;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_dict];
+ char nulls[Natts_pg_ts_dict];
+ NameData dname;
+ int i;
+ Oid dictOid;
+ Oid namespaceoid;
+ AclResult aclresult;
+ char *dictname;
+
+ /* Convert list of names to a name and namespace */
+ namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
+
+ /* Check we have creation rights in target namespace */
+ aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(namespaceoid));
+
+ for (i = 0; i < Natts_pg_ts_dict; i++)
+ {
+ nulls[i] = ' ';
+ values[i] = ObjectIdGetDatum(InvalidOid);
+ }
+
+ namestrcpy(&dname, dictname);
+ values[Anum_pg_ts_dict_dictname - 1] = NameGetDatum(&dname);
+ values[Anum_pg_ts_dict_dictnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+ values[Anum_pg_ts_dict_dictowner - 1] = ObjectIdGetDatum(GetUserId());
+ nulls[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
+
+ /*
+ * loop over the definition list and extract the information we need.
+ */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (pg_strcasecmp(defel->defname, "template") == 0)
+ {
+ Oid templId;
+
+ templId = TSTemplateGetTmplid(defGetQualifiedName(defel), false);
+
+ values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
+ nulls[Anum_pg_ts_dict_dicttemplate - 1] = ' ';
+ }
+ else if (pg_strcasecmp(defel->defname, "option") == 0)
+ {
+ char *opt = defGetString(defel);
+
+ if (pg_strcasecmp(opt, "null") != 0)
+ {
+ values[Anum_pg_ts_dict_dictinitoption - 1] =
+ DirectFunctionCall1(textin, CStringGetDatum(opt));
+ nulls[Anum_pg_ts_dict_dictinitoption - 1] = ' ';
+ }
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search dictionary parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ /*
+ * Validation
+ */
+ if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_dict_dicttemplate - 1])))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search template is required")));
+
+ /*
+ * Looks good, insert
+ */
+
+ dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ tup = heap_formtuple(dictRel->rd_att, values, nulls);
+
+ dictOid = simple_heap_insert(dictRel, tup);
+
+ CatalogUpdateIndexes(dictRel, tup);
+
+ makeDictionaryDependencies(tup);
+
+ heap_freetuple(tup);
+
+ heap_close(dictRel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH DICTIONARY RENAME
+ */
+void
+RenameTSDictionary(List *oldname, const char *newname)
+{
+ HeapTuple tup;
+ Relation rel;
+ Oid dictId;
+ Oid namespaceOid;
+ AclResult aclresult;
+
+ rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ dictId = TSDictionaryGetDictid(oldname, false);
+
+ tup = SearchSysCacheCopy(TSDICTOID,
+ ObjectIdGetDatum(dictId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+
+ namespaceOid = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
+
+ if (SearchSysCacheExists(TSDICTNAMENSP,
+ PointerGetDatum(newname),
+ ObjectIdGetDatum(namespaceOid),
+ 0, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("text search dictionary \"%s\" already exists",
+ newname)));
+
+ /* must be owner */
+ if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
+ NameListToString(oldname));
+
+ /* must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(namespaceOid));
+
+ namestrcpy(&(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname), newname);
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ heap_close(rel, NoLock);
+ heap_freetuple(tup);
+}
+
+/*
+ * DROP TEXT SEARCH DICTIONARY
+ */
+void
+RemoveTSDictionary(List *names, DropBehavior behavior, bool missing_ok)
+{
+ Oid dictOid;
+ ObjectAddress object;
+ HeapTuple tup;
+ Oid namespaceId;
+
+ dictOid = TSDictionaryGetDictid(names, true);
+ if (!OidIsValid(dictOid))
+ {
+ if (!missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("text search dictionary \"%s\" does not exist",
+ NameListToString(names))));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("text search dictionary \"%s\" does not exist, skipping",
+ NameListToString(names))));
+ }
+ return;
+ }
+
+ tup = SearchSysCache(TSDICTOID,
+ ObjectIdGetDatum(dictOid),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictOid);
+
+ /* Permission check: must own dictionary or its namespace */
+ namespaceId = ((Form_pg_ts_dict) GETSTRUCT(tup))->dictnamespace;
+ if (!pg_ts_dict_ownercheck(dictOid, GetUserId()) &&
+ !pg_namespace_ownercheck(namespaceId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
+ NameListToString(names));
+
+ ReleaseSysCache(tup);
+
+ object.classId = TSDictionaryRelationId;
+ object.objectId = dictOid;
+ object.objectSubId = 0;
+
+ performDeletion(&object, behavior);
+}
+
+/*
+ * Guts of TS dictionary deletion.
+ */
+void
+RemoveTSDictionaryById(Oid dictId)
+{
+ Relation relation;
+ HeapTuple tup;
+
+ relation = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache(TSDICTOID,
+ ObjectIdGetDatum(dictId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+
+ simple_heap_delete(relation, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ heap_close(relation, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH DICTIONARY
+ */
+void
+AlterTSDictionary(AlterTSDictionaryStmt * stmt)
+{
+ HeapTuple tup,
+ newtup;
+ Relation rel;
+ Oid dictId;
+ ListCell *pl;
+ Datum repl_val[Natts_pg_ts_dict];
+ char repl_null[Natts_pg_ts_dict];
+ char repl_repl[Natts_pg_ts_dict];
+
+ dictId = TSDictionaryGetDictid(stmt->dictname, false);
+
+ rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache(TSDICTOID,
+ ObjectIdGetDatum(dictId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+
+ /* must be owner */
+ if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
+ NameListToString(stmt->dictname));
+
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, ' ', sizeof(repl_null));
+ memset(repl_repl, ' ', sizeof(repl_repl));
+
+ /*
+ * NOTE: because we only support altering the option, not the template,
+ * there is no need to update dependencies.
+ */
+ foreach(pl, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (pg_strcasecmp(defel->defname, "option") == 0)
+ {
+ char *opt = defGetString(defel);
+
+ if (pg_strcasecmp(opt, "null") == 0)
+ {
+ repl_null[Anum_pg_ts_dict_dictinitoption - 1] = 'n';
+ }
+ else
+ {
+ repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
+ DirectFunctionCall1(textin, CStringGetDatum(opt));
+ repl_null[Anum_pg_ts_dict_dictinitoption - 1] = ' ';
+ }
+ repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = 'r';
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search dictionary parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ newtup = heap_modifytuple(tup, RelationGetDescr(rel),
+ repl_val, repl_null, repl_repl);
+
+ simple_heap_update(rel, &newtup->t_self, newtup);
+
+ CatalogUpdateIndexes(rel, newtup);
+
+ heap_freetuple(newtup);
+ ReleaseSysCache(tup);
+
+ heap_close(rel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH DICTIONARY OWNER
+ */
+void
+AlterTSDictionaryOwner(List *name, Oid newOwnerId)
+{
+ HeapTuple tup;
+ Relation rel;
+ Oid dictId;
+ Oid namespaceOid;
+ AclResult aclresult;
+ Form_pg_ts_dict form;
+
+ rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ dictId = TSDictionaryGetDictid(name, false);
+
+ tup = SearchSysCacheCopy(TSDICTOID,
+ ObjectIdGetDatum(dictId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+
+ form = (Form_pg_ts_dict) GETSTRUCT(tup);
+ namespaceOid = form->dictnamespace;
+
+ if (form->dictowner != newOwnerId)
+ {
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ /* must be owner */
+ if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
+ NameListToString(name));
+
+ /* Must be able to become new owner */
+ check_is_member_of_role(GetUserId(), newOwnerId);
+
+ /* New owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(namespaceOid));
+ }
+
+ form->dictowner = newOwnerId;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* Update owner dependency reference */
+ changeDependencyOnOwner(TSDictionaryRelationId, HeapTupleGetOid(tup),
+ newOwnerId);
+ }
+
+ heap_close(rel, NoLock);
+ heap_freetuple(tup);
+}
+
+/* ---------------------- TS Template commands -----------------------*/
+
+/*
+ * lookup a template support function and return its OID (as a Datum)
+ *
+ * attnum is the pg_ts_template column the function will go into
+ */
+static Datum
+get_ts_template_func(DefElem *defel, int attnum)
+{
+ List *funcName = defGetQualifiedName(defel);
+ Oid typeId[4];
+ Oid retTypeId;
+ int nargs;
+ Oid procOid;
+
+ retTypeId = INTERNALOID;
+ typeId[0] = INTERNALOID;
+ typeId[1] = INTERNALOID;
+ typeId[2] = INTERNALOID;
+ typeId[3] = INTERNALOID;
+ switch (attnum)
+ {
+ case Anum_pg_ts_template_tmplinit:
+ nargs = 1;
+ break;
+ case Anum_pg_ts_template_tmpllexize:
+ nargs = 4;
+ break;
+ default:
+ /* should not be here */
+ elog(ERROR, "unknown attribute for text search template: %d",
+ attnum);
+ nargs = 0; /* keep compiler quiet */
+ }
+
+ procOid = LookupFuncName(funcName, nargs, typeId, false);
+ if (get_func_rettype(procOid) != retTypeId)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("function %s should return type %s",
+ func_signature_string(funcName, nargs, typeId),
+ format_type_be(retTypeId))));
+
+ return ObjectIdGetDatum(procOid);
+}
+
+/*
+ * make pg_depend entries for a new pg_ts_template entry
+ */
+static void
+makeTSTemplateDependencies(HeapTuple tuple)
+{
+ Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
+ ObjectAddress myself,
+ referenced;
+
+ myself.classId = TSTemplateRelationId;
+ myself.objectId = HeapTupleGetOid(tuple);
+ myself.objectSubId = 0;
+
+ /* dependency on namespace */
+ referenced.classId = NamespaceRelationId;
+ referenced.objectId = tmpl->tmplnamespace;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependencies on functions */
+ referenced.classId = ProcedureRelationId;
+ referenced.objectSubId = 0;
+
+ referenced.objectId = tmpl->tmpllexize;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ if (OidIsValid(tmpl->tmplinit))
+ {
+ referenced.objectId = tmpl->tmplinit;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+}
+
+/*
+ * CREATE TEXT SEARCH TEMPLATE
+ */
+void
+DefineTSTemplate(List *names, List *parameters)
+{
+ ListCell *pl;
+ Relation tmplRel;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_template];
+ char nulls[Natts_pg_ts_template];
+ NameData dname;
+ int i;
+ Oid dictOid;
+ Oid namespaceoid;
+ char *tmplname;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create text search templates")));
+
+ /* Convert list of names to a name and namespace */
+ namespaceoid = QualifiedNameGetCreationNamespace(names, &tmplname);
+
+ for (i = 0; i < Natts_pg_ts_template; i++)
+ {
+ nulls[i] = ' ';
+ values[i] = ObjectIdGetDatum(InvalidOid);
+ }
+
+ namestrcpy(&dname, tmplname);
+ values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
+ values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+
+ /*
+ * loop over the definition list and extract the information we need.
+ */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (pg_strcasecmp(defel->defname, "init") == 0)
+ {
+ values[Anum_pg_ts_template_tmplinit - 1] =
+ get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
+ nulls[Anum_pg_ts_template_tmplinit - 1] = ' ';
+ }
+ else if (pg_strcasecmp(defel->defname, "lexize") == 0)
+ {
+ values[Anum_pg_ts_template_tmpllexize - 1] =
+ get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
+ nulls[Anum_pg_ts_template_tmpllexize - 1] = ' ';
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search template parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ /*
+ * Validation
+ */
+ if (!OidIsValid(DatumGetObjectId(values[Anum_pg_ts_template_tmpllexize - 1])))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search template lexize method is required")));
+
+ /*
+ * Looks good, insert
+ */
+
+ tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
+
+ tup = heap_formtuple(tmplRel->rd_att, values, nulls);
+
+ dictOid = simple_heap_insert(tmplRel, tup);
+
+ CatalogUpdateIndexes(tmplRel, tup);
+
+ makeTSTemplateDependencies(tup);
+
+ heap_freetuple(tup);
+
+ heap_close(tmplRel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH TEMPLATE RENAME
+ */
+void
+RenameTSTemplate(List *oldname, const char *newname)
+{
+ HeapTuple tup;
+ Relation rel;
+ Oid tmplId;
+ Oid namespaceOid;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to rename text search templates")));
+
+ rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
+
+ tmplId = TSTemplateGetTmplid(oldname, false);
+
+ tup = SearchSysCacheCopy(TSTEMPLATEOID,
+ ObjectIdGetDatum(tmplId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search template %u",
+ tmplId);
+
+ namespaceOid = ((Form_pg_ts_template) GETSTRUCT(tup))->tmplnamespace;
+
+ if (SearchSysCacheExists(TSTEMPLATENAMENSP,
+ PointerGetDatum(newname),
+ ObjectIdGetDatum(namespaceOid),
+ 0, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("text search template \"%s\" already exists",
+ newname)));
+
+ namestrcpy(&(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname), newname);
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ heap_close(rel, NoLock);
+ heap_freetuple(tup);
+}
+
+/*
+ * DROP TEXT SEARCH TEMPLATE
+ */
+void
+RemoveTSTemplate(List *names, DropBehavior behavior, bool missing_ok)
+{
+ Oid tmplOid;
+ ObjectAddress object;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to drop text search templates")));
+
+ tmplOid = TSTemplateGetTmplid(names, true);
+ if (!OidIsValid(tmplOid))
+ {
+ if (!missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("text search template \"%s\" does not exist",
+ NameListToString(names))));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("text search template \"%s\" does not exist, skipping",
+ NameListToString(names))));
+ }
+ return;
+ }
+
+ object.classId = TSTemplateRelationId;
+ object.objectId = tmplOid;
+ object.objectSubId = 0;
+
+ performDeletion(&object, behavior);
+}
+
+/*
+ * Guts of TS template deletion.
+ */
+void
+RemoveTSTemplateById(Oid tmplId)
+{
+ Relation relation;
+ HeapTuple tup;
+
+ relation = heap_open(TSTemplateRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache(TSTEMPLATEOID,
+ ObjectIdGetDatum(tmplId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search template %u",
+ tmplId);
+
+ simple_heap_delete(relation, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ heap_close(relation, RowExclusiveLock);
+}
+
+/* ---------------------- TS Configuration commands -----------------------*/
+
+/*
+ * Finds syscache tuple of configuration.
+ * Returns NULL if no such cfg.
+ */
+static HeapTuple
+GetTSConfigTuple(List *names)
+{
+ HeapTuple tup;
+ Oid cfgId;
+
+ cfgId = TSConfigGetCfgid(names, true);
+ if (!OidIsValid(cfgId))
+ return NULL;
+
+ tup = SearchSysCache(TSCONFIGOID,
+ ObjectIdGetDatum(cfgId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ cfgId);
+
+ return tup;
+}
+
+/*
+ * make pg_depend entries for a new or updated pg_ts_config entry
+ *
+ * Pass opened pg_ts_config_map relation if there might be any config map
+ * entries for the config.
+ */
+static void
+makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
+ Relation mapRel)
+{
+ Form_pg_ts_config cfg = (Form_pg_ts_config) GETSTRUCT(tuple);
+ ObjectAddresses *addrs;
+ ObjectAddress myself,
+ referenced;
+
+ myself.classId = TSConfigRelationId;
+ myself.objectId = HeapTupleGetOid(tuple);
+ myself.objectSubId = 0;
+
+ /* for ALTER case, first flush old dependencies */
+ if (removeOld)
+ {
+ deleteDependencyRecordsFor(myself.classId, myself.objectId);
+ deleteSharedDependencyRecordsFor(myself.classId, myself.objectId);
+ }
+
+ /*
+ * We use an ObjectAddresses list to remove possible duplicate
+ * dependencies from the config map info. The pg_ts_config items
+ * shouldn't be duplicates, but might as well fold them all into one call.
+ */
+ addrs = new_object_addresses();
+
+ /* dependency on namespace */
+ referenced.classId = NamespaceRelationId;
+ referenced.objectId = cfg->cfgnamespace;
+ referenced.objectSubId = 0;
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependency on owner */
+ recordDependencyOnOwner(myself.classId, myself.objectId, cfg->cfgowner);
+
+ /* dependency on parser */
+ referenced.classId = TSParserRelationId;
+ referenced.objectId = cfg->cfgparser;
+ referenced.objectSubId = 0;
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependencies on dictionaries listed in config map */
+ if (mapRel)
+ {
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple maptup;
+
+ /* CCI to ensure we can see effects of caller's changes */
+ CommandCounterIncrement();
+
+ ScanKeyInit(&skey,
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(myself.objectId));
+
+ scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
+ SnapshotNow, 1, &skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
+
+ referenced.classId = TSDictionaryRelationId;
+ referenced.objectId = cfgmap->mapdict;
+ referenced.objectSubId = 0;
+ add_exact_object_address(&referenced, addrs);
+ }
+
+ systable_endscan(scan);
+ }
+
+ /* Record 'em (this includes duplicate elimination) */
+ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+
+ free_object_addresses(addrs);
+}
+
+/*
+ * CREATE TEXT SEARCH CONFIGURATION
+ */
+void
+DefineTSConfiguration(List *names, List *parameters)
+{
+ Relation cfgRel;
+ Relation mapRel = NULL;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_config];
+ char nulls[Natts_pg_ts_config];
+ AclResult aclresult;
+ Oid namespaceoid;
+ char *cfgname;
+ NameData cname;
+ List *templateName = NIL;
+ Oid templateOid = InvalidOid;
+ Oid prsOid = InvalidOid;
+ bool with_map = false;
+ Oid cfgOid;
+ ListCell *pl;
+
+ /* Convert list of names to a name and namespace */
+ namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
+
+ /* Check we have creation rights in target namespace */
+ aclresult = pg_namespace_aclcheck(namespaceoid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(namespaceoid));
+
+ /*
+ * loop over the definition list and extract the information we need.
+ */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (pg_strcasecmp(defel->defname, "parser") == 0)
+ prsOid = TSParserGetPrsid(defGetQualifiedName(defel), false);
+ else if (pg_strcasecmp(defel->defname, "template") == 0)
+ templateName = defGetQualifiedName(defel);
+ else if (pg_strcasecmp(defel->defname, "map") == 0)
+ with_map = defGetBoolean(defel);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search configuration parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ /*
+ * Look up template if given. XXX the "template" is an existing config
+ * that we copy, not a pg_ts_template entry. This seems confusing.
+ * Maybe should use "source" or some other word?
+ */
+ if (templateName)
+ {
+ Form_pg_ts_config cfg;
+
+ templateOid = TSConfigGetCfgid(templateName, false);
+
+ tup = SearchSysCache(TSCONFIGOID,
+ ObjectIdGetDatum(templateOid),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ templateOid);
+
+ cfg = (Form_pg_ts_config) GETSTRUCT(tup);
+
+ /* Use template's parser if no other was specified */
+ if (!OidIsValid(prsOid))
+ prsOid = cfg->cfgparser;
+
+ ReleaseSysCache(tup);
+ }
+
+ /*
+ * Validation
+ */
+ if (!OidIsValid(prsOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search parser is required")));
+
+ /*
+ * Looks good, build tuple and insert
+ */
+ memset(values, 0, sizeof(values));
+ memset(nulls, ' ', sizeof(nulls));
+
+ namestrcpy(&cname, cfgname);
+ values[Anum_pg_ts_config_cfgname - 1] = NameGetDatum(&cname);
+ values[Anum_pg_ts_config_cfgnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+ values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
+ values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
+
+ cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ tup = heap_formtuple(cfgRel->rd_att, values, nulls);
+
+ cfgOid = simple_heap_insert(cfgRel, tup);
+
+ CatalogUpdateIndexes(cfgRel, tup);
+
+ if (OidIsValid(templateOid) && with_map)
+ {
+ /*
+ * Copy token-dicts map from template
+ */
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple maptup;
+
+ mapRel = heap_open(TSConfigMapRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&skey,
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(templateOid));
+
+ scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
+ SnapshotNow, 1, &skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
+ HeapTuple newmaptup;
+ Datum mapvalues[Natts_pg_ts_config_map];
+ char mapnulls[Natts_pg_ts_config_map];
+
+ memset(mapvalues, 0, sizeof(mapvalues));
+ memset(mapnulls, ' ', sizeof(mapnulls));
+
+ mapvalues[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
+ mapvalues[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
+ mapvalues[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
+ mapvalues[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
+
+ newmaptup = heap_formtuple(mapRel->rd_att, mapvalues, mapnulls);
+
+ simple_heap_insert(mapRel, newmaptup);
+
+ CatalogUpdateIndexes(mapRel, newmaptup);
+
+ heap_freetuple(newmaptup);
+ }
+
+ systable_endscan(scan);
+ }
+
+ makeConfigurationDependencies(tup, false, mapRel);
+
+ heap_freetuple(tup);
+
+ if (mapRel)
+ heap_close(mapRel, RowExclusiveLock);
+ heap_close(cfgRel, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION RENAME
+ */
+void
+RenameTSConfiguration(List *oldname, const char *newname)
+{
+ HeapTuple tup;
+ Relation rel;
+ Oid cfgId;
+ AclResult aclresult;
+ Oid namespaceOid;
+
+ rel = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ cfgId = TSConfigGetCfgid(oldname, false);
+
+ tup = SearchSysCacheCopy(TSCONFIGOID,
+ ObjectIdGetDatum(cfgId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ cfgId);
+
+ namespaceOid = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
+
+ if (SearchSysCacheExists(TSCONFIGNAMENSP,
+ PointerGetDatum(newname),
+ ObjectIdGetDatum(namespaceOid),
+ 0, 0))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("text search configuration \"%s\" already exists",
+ newname)));
+
+ /* must be owner */
+ if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
+ NameListToString(oldname));
+
+ /* must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE);
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(namespaceOid));
+
+ namestrcpy(&(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname), newname);
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ heap_close(rel, NoLock);
+ heap_freetuple(tup);
+}
+
+/*
+ * DROP TEXT SEARCH CONFIGURATION
+ */
+void
+RemoveTSConfiguration(List *names, DropBehavior behavior, bool missing_ok)
+{
+ Oid cfgOid;
+ Oid namespaceId;
+ ObjectAddress object;
+ HeapTuple tup;
+
+ tup = GetTSConfigTuple(names);
+
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("text search configuration \"%s\" does not exist",
+ NameListToString(names))));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("text search configuration \"%s\" does not exist, skipping",
+ NameListToString(names))));
+ }
+ return;
+ }
+
+ /* Permission check: must own configuration or its namespace */
+ cfgOid = HeapTupleGetOid(tup);
+ namespaceId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgnamespace;
+ if (!pg_ts_config_ownercheck(cfgOid, GetUserId()) &&
+ !pg_namespace_ownercheck(namespaceId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
+ NameListToString(names));
+
+ ReleaseSysCache(tup);
+
+ object.classId = TSConfigRelationId;
+ object.objectId = cfgOid;
+ object.objectSubId = 0;
+
+ performDeletion(&object, behavior);
+}
+
+/*
+ * Guts of TS configuration deletion.
+ */
+void
+RemoveTSConfigurationById(Oid cfgId)
+{
+ Relation relCfg,
+ relMap;
+ HeapTuple tup;
+ ScanKeyData skey;
+ SysScanDesc scan;
+
+ /* Remove the pg_ts_config entry */
+ relCfg = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache(TSCONFIGOID,
+ ObjectIdGetDatum(cfgId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ cfgId);
+
+ simple_heap_delete(relCfg, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ heap_close(relCfg, RowExclusiveLock);
+
+ /* Remove any pg_ts_config_map entries */
+ relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&skey,
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(cfgId));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ SnapshotNow, 1, &skey);
+
+ while (HeapTupleIsValid((tup = systable_getnext(scan))))
+ {
+ simple_heap_delete(relMap, &tup->t_self);
+ }
+
+ systable_endscan(scan);
+
+ heap_close(relMap, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION OWNER
+ */
+void
+AlterTSConfigurationOwner(List *name, Oid newOwnerId)
+{
+ HeapTuple tup;
+ Relation rel;
+ Oid cfgId;
+ AclResult aclresult;
+ Oid namespaceOid;
+ Form_pg_ts_config form;
+
+ rel = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ cfgId = TSConfigGetCfgid(name, false);
+
+ tup = SearchSysCacheCopy(TSCONFIGOID,
+ ObjectIdGetDatum(cfgId),
+ 0, 0, 0);
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ cfgId);
+
+ form = (Form_pg_ts_config) GETSTRUCT(tup);
+ namespaceOid = form->cfgnamespace;
+
+ if (form->cfgowner != newOwnerId)
+ {
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ /* must be owner */
+ if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
+ NameListToString(name));
+
+ /* Must be able to become new owner */
+ check_is_member_of_role(GetUserId(), newOwnerId);
+
+ /* New owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(namespaceOid, newOwnerId, ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(namespaceOid));
+ }
+
+ form->cfgowner = newOwnerId;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* Update owner dependency reference */
+ changeDependencyOnOwner(TSConfigRelationId, HeapTupleGetOid(tup),
+ newOwnerId);
+ }
+
+ heap_close(rel, NoLock);
+ heap_freetuple(tup);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION - main entry point
+ */
+void
+AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
+{
+ HeapTuple tup;
+ HeapTuple newtup;
+ Relation mapRel;
+
+ /* Find the configuration */
+ tup = GetTSConfigTuple(stmt->cfgname);
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("text search configuration \"%s\" does not exist",
+ NameListToString(stmt->cfgname))));
+
+ /* must be owner */
+ if (!pg_ts_config_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
+ NameListToString(stmt->cfgname));
+
+ /* Update fields of config tuple? */
+ if (stmt->options)
+ newtup = UpdateTSConfiguration(stmt, tup);
+ else
+ newtup = tup;
+
+ /* Add or drop mappings? */
+ if (stmt->dicts)
+ MakeConfigurationMapping(stmt, newtup);
+ else if (stmt->tokentype)
+ DropConfigurationMapping(stmt, newtup);
+
+ /*
+ * Even if we aren't changing mappings, there could already be some,
+ * so makeConfigurationDependencies always has to look.
+ */
+ mapRel = heap_open(TSConfigMapRelationId, AccessShareLock);
+
+ /* Update dependencies */
+ makeConfigurationDependencies(newtup, true, mapRel);
+
+ heap_close(mapRel, AccessShareLock);
+
+ ReleaseSysCache(tup);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION - update fields of pg_ts_config tuple
+ */
+static HeapTuple
+UpdateTSConfiguration(AlterTSConfigurationStmt *stmt, HeapTuple tup)
+{
+ Relation cfgRel;
+ ListCell *pl;
+ Datum repl_val[Natts_pg_ts_config];
+ char repl_null[Natts_pg_ts_config];
+ char repl_repl[Natts_pg_ts_config];
+ HeapTuple newtup;
+
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, ' ', sizeof(repl_null));
+ memset(repl_repl, ' ', sizeof(repl_repl));
+
+ cfgRel = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ foreach(pl, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (pg_strcasecmp(defel->defname, "parser") == 0)
+ {
+ Oid newPrs;
+
+ newPrs = TSParserGetPrsid(defGetQualifiedName(defel), false);
+ repl_val[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(newPrs);
+ repl_repl[Anum_pg_ts_config_cfgparser - 1] = 'r';
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search configuration parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ newtup = heap_modifytuple(tup, RelationGetDescr(cfgRel),
+ repl_val, repl_null, repl_repl);
+
+ simple_heap_update(cfgRel, &newtup->t_self, newtup);
+
+ CatalogUpdateIndexes(cfgRel, newtup);
+
+ heap_close(cfgRel, RowExclusiveLock);
+
+ return newtup;
+}
+
+/*------------------- TS Configuration mapping stuff ----------------*/
+
+/*
+ * Translate a list of token type names to an array of token type numbers
+ */
+static int *
+getTokenTypes(Oid prsId, List *tokennames)
+{
+ TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
+ LexDescr *list;
+ int *res,
+ i,
+ ntoken;
+ ListCell *tn;
+
+ ntoken = list_length(tokennames);
+ if (ntoken == 0)
+ return NULL;
+ res = (int *) palloc(sizeof(int) * ntoken);
+
+ if (!OidIsValid(prs->lextypeOid))
+ elog(ERROR, "method lextype isn't defined for text search parser %u",
+ prsId);
+
+ /* OidFunctionCall0 is absent */
+ list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
+ (Datum) 0));
+
+ i = 0;
+ foreach(tn, tokennames)
+ {
+ Value *val = (Value *) lfirst(tn);
+ bool found = false;
+ int j;
+
+ j = 0;
+ while (list && list[j].lexid)
+ {
+ /* XXX should we use pg_strcasecmp here? */
+ if (strcmp(strVal(val), list[j].alias) == 0)
+ {
+ res[i] = list[j].lexid;
+ found = true;
+ break;
+ }
+ j++;
+ }
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("token type \"%s\" does not exist",
+ strVal(val))));
+ i++;
+ }
+
+ return res;
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
+ */
+static void
+MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, HeapTuple tup)
+{
+ Oid cfgId = HeapTupleGetOid(tup);
+ Relation relMap;
+ ScanKeyData skey[2];
+ SysScanDesc scan;
+ HeapTuple maptup;
+ int i;
+ int j;
+ Oid prsId;
+ int *tokens,
+ ntoken;
+ Oid *dictIds;
+ int ndict;
+ ListCell *c;
+
+ prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
+
+ tokens = getTokenTypes(prsId, stmt->tokentype);
+ ntoken = list_length(stmt->tokentype);
+
+ relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
+
+ if (stmt->override)
+ {
+ /*
+ * delete maps for tokens if they exist and command was ALTER
+ */
+ for (i = 0; i < ntoken; i++)
+ {
+ ScanKeyInit(&skey[0],
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(cfgId));
+ ScanKeyInit(&skey[1],
+ Anum_pg_ts_config_map_maptokentype,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(tokens[i]));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ SnapshotNow, 2, skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ simple_heap_delete(relMap, &maptup->t_self);
+ }
+
+ systable_endscan(scan);
+ }
+ }
+
+ /*
+ * Convert list of dictionary names to array of dict OIDs
+ */
+ ndict = list_length(stmt->dicts);
+ dictIds = (Oid *) palloc(sizeof(Oid) * ndict);
+ i = 0;
+ foreach(c, stmt->dicts)
+ {
+ List *names = (List *) lfirst(c);
+
+ dictIds[i] = TSDictionaryGetDictid(names, false);
+ i++;
+ }
+
+ if (stmt->replace)
+ {
+ /*
+ * Replace a specific dictionary in existing entries
+ */
+ Oid dictOld = dictIds[0],
+ dictNew = dictIds[1];
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(cfgId));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ SnapshotNow, 1, skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
+
+ /*
+ * check if it's one of target token types
+ */
+ if (tokens)
+ {
+ bool tokmatch = false;
+
+ for (j = 0; j < ntoken; j++)
+ {
+ if (cfgmap->maptokentype == tokens[j])
+ {
+ tokmatch = true;
+ break;
+ }
+ }
+ if (!tokmatch)
+ continue;
+ }
+
+ /*
+ * replace dictionary if match
+ */
+ if (cfgmap->mapdict == dictOld)
+ {
+ Datum repl_val[Natts_pg_ts_config_map];
+ char repl_null[Natts_pg_ts_config_map];
+ char repl_repl[Natts_pg_ts_config_map];
+ HeapTuple newtup;
+
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, ' ', sizeof(repl_null));
+ memset(repl_repl, ' ', sizeof(repl_repl));
+
+ repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
+ repl_repl[Anum_pg_ts_config_map_mapdict - 1] = 'r';
+
+ newtup = heap_modifytuple(maptup,
+ RelationGetDescr(relMap),
+ repl_val, repl_null, repl_repl);
+ simple_heap_update(relMap, &newtup->t_self, newtup);
+
+ CatalogUpdateIndexes(relMap, newtup);
+ }
+ }
+
+ systable_endscan(scan);
+ }
+ else
+ {
+ /*
+ * Insertion of new entries
+ */
+ for (i = 0; i < ntoken; i++)
+ {
+ for (j = 0; j < ndict; j++)
+ {
+ Datum values[Natts_pg_ts_config_map];
+ char nulls[Natts_pg_ts_config_map];
+
+ memset(nulls, ' ', sizeof(nulls));
+ values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
+ values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(tokens[i]);
+ values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
+ values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
+
+ tup = heap_formtuple(relMap->rd_att, values, nulls);
+ simple_heap_insert(relMap, tup);
+ CatalogUpdateIndexes(relMap, tup);
+
+ heap_freetuple(tup);
+ }
+ }
+ }
+
+ heap_close(relMap, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
+ */
+static void
+DropConfigurationMapping(AlterTSConfigurationStmt *stmt, HeapTuple tup)
+{
+ Oid cfgId = HeapTupleGetOid(tup);
+ Relation relMap;
+ ScanKeyData skey[2];
+ SysScanDesc scan;
+ HeapTuple maptup;
+ int i;
+ Oid prsId;
+ int *tokens,
+ ntoken;
+ ListCell *c;
+
+ prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
+
+ tokens = getTokenTypes(prsId, stmt->tokentype);
+ ntoken = list_length(stmt->tokentype);
+
+ relMap = heap_open(TSConfigMapRelationId, RowExclusiveLock);
+
+ i = 0;
+ foreach(c, stmt->tokentype)
+ {
+ Value *val = (Value *) lfirst(c);
+ bool found = false;
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(cfgId));
+ ScanKeyInit(&skey[1],
+ Anum_pg_ts_config_map_maptokentype,
+ BTEqualStrategyNumber, F_INT4EQ,
+ Int32GetDatum(tokens[i]));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ SnapshotNow, 2, skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ simple_heap_delete(relMap, &maptup->t_self);
+ found = true;
+ }
+
+ systable_endscan(scan);
+
+ if (!found)
+ {
+ if (!stmt->missing_ok)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("mapping for token type \"%s\" does not exist",
+ strVal(val))));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("mapping for token type \"%s\" does not exist, skipping",
+ strVal(val))));
+ }
+ }
+
+ i++;
+ }
+
+ heap_close(relMap, RowExclusiveLock);
+}