summaryrefslogtreecommitdiffstats
path: root/src/backend/commands/tsearchcmds.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
commit293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch)
treefc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/backend/commands/tsearchcmds.c
parentInitial commit. (diff)
downloadpostgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz
postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/commands/tsearchcmds.c')
-rw-r--r--src/backend/commands/tsearchcmds.c1870
1 files changed, 1870 insertions, 0 deletions
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
new file mode 100644
index 0000000..31aed5b
--- /dev/null
+++ b/src/backend/commands/tsearchcmds.c
@@ -0,0 +1,1870 @@
+/*-------------------------------------------------------------------------
+ *
+ * tsearchcmds.c
+ *
+ * Routines for tsearch manipulation commands
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/tsearchcmds.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xact.h"
+#include "catalog/catalog.h"
+#include "catalog/dependency.h"
+#include "catalog/indexing.h"
+#include "catalog/objectaccess.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/alter.h"
+#include "commands/defrem.h"
+#include "commands/event_trigger.h"
+#include "common/string.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_func.h"
+#include "tsearch/ts_cache.h"
+#include "tsearch/ts_utils.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/* Single entry of List returned by getTokenTypes() */
+typedef struct
+{
+ int num; /* token type number */
+ char *name; /* token type name */
+} TSTokenTypeItem;
+
+static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup, Relation relMap);
+static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup, Relation relMap);
+static DefElem *buildDefItem(const char *name, const char *val,
+ bool was_quoted);
+
+
+/* --------------------- 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] = INTERNALOID;
+ typeId[2] = TSQUERYOID;
+ break;
+ case Anum_pg_ts_parser_prslextype:
+ nargs = 1;
+
+ /*
+ * Note: because the lextype method returns type internal, it must
+ * have an internal-type argument for security reasons. The
+ * argument is not actually used, but is just passed as a zero.
+ */
+ break;
+ default:
+ /* should not be here */
+ elog(ERROR, "unrecognized 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, NIL, typeId),
+ format_type_be(retTypeId))));
+
+ return ObjectIdGetDatum(procOid);
+}
+
+/*
+ * make pg_depend entries for a new pg_ts_parser entry
+ *
+ * Return value is the address of said new entry.
+ */
+static ObjectAddress
+makeParserDependencies(HeapTuple tuple)
+{
+ Form_pg_ts_parser prs = (Form_pg_ts_parser) GETSTRUCT(tuple);
+ ObjectAddress myself,
+ referenced;
+ ObjectAddresses *addrs;
+
+ ObjectAddressSet(myself, TSParserRelationId, prs->oid);
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ addrs = new_object_addresses();
+
+ /* dependency on namespace */
+ ObjectAddressSet(referenced, NamespaceRelationId, prs->prsnamespace);
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependencies on functions */
+ ObjectAddressSet(referenced, ProcedureRelationId, prs->prsstart);
+ add_exact_object_address(&referenced, addrs);
+
+ referenced.objectId = prs->prstoken;
+ add_exact_object_address(&referenced, addrs);
+
+ referenced.objectId = prs->prsend;
+ add_exact_object_address(&referenced, addrs);
+
+ referenced.objectId = prs->prslextype;
+ add_exact_object_address(&referenced, addrs);
+
+ if (OidIsValid(prs->prsheadline))
+ {
+ referenced.objectId = prs->prsheadline;
+ add_exact_object_address(&referenced, addrs);
+ }
+
+ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+ free_object_addresses(addrs);
+
+ return myself;
+}
+
+/*
+ * CREATE TEXT SEARCH PARSER
+ */
+ObjectAddress
+DefineTSParser(List *names, List *parameters)
+{
+ char *prsname;
+ ListCell *pl;
+ Relation prsRel;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_parser];
+ bool nulls[Natts_pg_ts_parser];
+ NameData pname;
+ Oid prsOid;
+ Oid namespaceoid;
+ ObjectAddress address;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to create text search parsers")));
+
+ prsRel = table_open(TSParserRelationId, RowExclusiveLock);
+
+ /* 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, false, sizeof(nulls));
+
+ prsOid = GetNewOidWithIndex(prsRel, TSParserOidIndexId,
+ Anum_pg_ts_parser_oid);
+ values[Anum_pg_ts_parser_oid - 1] = ObjectIdGetDatum(prsOid);
+ 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 (strcmp(defel->defname, "start") == 0)
+ {
+ values[Anum_pg_ts_parser_prsstart - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
+ }
+ else if (strcmp(defel->defname, "gettoken") == 0)
+ {
+ values[Anum_pg_ts_parser_prstoken - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
+ }
+ else if (strcmp(defel->defname, "end") == 0)
+ {
+ values[Anum_pg_ts_parser_prsend - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
+ }
+ else if (strcmp(defel->defname, "headline") == 0)
+ {
+ values[Anum_pg_ts_parser_prsheadline - 1] =
+ get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
+ }
+ else if (strcmp(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
+ */
+ tup = heap_form_tuple(prsRel->rd_att, values, nulls);
+
+ CatalogTupleInsert(prsRel, tup);
+
+ address = makeParserDependencies(tup);
+
+ /* Post creation hook for new text search parser */
+ InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
+
+ heap_freetuple(tup);
+
+ table_close(prsRel, RowExclusiveLock);
+
+ return address;
+}
+
+/* ---------------------- TS Dictionary commands -----------------------*/
+
+/*
+ * make pg_depend entries for a new pg_ts_dict entry
+ *
+ * Return value is address of the new entry
+ */
+static ObjectAddress
+makeDictionaryDependencies(HeapTuple tuple)
+{
+ Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tuple);
+ ObjectAddress myself,
+ referenced;
+ ObjectAddresses *addrs;
+
+ ObjectAddressSet(myself, TSDictionaryRelationId, dict->oid);
+
+ /* dependency on owner */
+ recordDependencyOnOwner(myself.classId, myself.objectId, dict->dictowner);
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ addrs = new_object_addresses();
+
+ /* dependency on namespace */
+ ObjectAddressSet(referenced, NamespaceRelationId, dict->dictnamespace);
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependency on template */
+ ObjectAddressSet(referenced, TSTemplateRelationId, dict->dicttemplate);
+ add_exact_object_address(&referenced, addrs);
+
+ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+ free_object_addresses(addrs);
+
+ return myself;
+}
+
+/*
+ * verify that a template's init method accepts a proposed option list
+ */
+static void
+verify_dictoptions(Oid tmplId, List *dictoptions)
+{
+ HeapTuple tup;
+ Form_pg_ts_template tform;
+ Oid initmethod;
+
+ /*
+ * Suppress this test when running in a standalone backend. This is a
+ * hack to allow initdb to create prefab dictionaries that might not
+ * actually be usable in template1's encoding (due to using external files
+ * that can't be translated into template1's encoding). We want to create
+ * them anyway, since they might be usable later in other databases.
+ */
+ if (!IsUnderPostmaster)
+ return;
+
+ tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search template %u",
+ tmplId);
+ tform = (Form_pg_ts_template) GETSTRUCT(tup);
+
+ initmethod = tform->tmplinit;
+
+ if (!OidIsValid(initmethod))
+ {
+ /* If there is no init method, disallow any options */
+ if (dictoptions)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search template \"%s\" does not accept options",
+ NameStr(tform->tmplname))));
+ }
+ else
+ {
+ /*
+ * Copy the options just in case init method thinks it can scribble on
+ * them ...
+ */
+ dictoptions = copyObject(dictoptions);
+
+ /*
+ * Call the init method and see if it complains. We don't worry about
+ * it leaking memory, since our command will soon be over anyway.
+ */
+ (void) OidFunctionCall1(initmethod, PointerGetDatum(dictoptions));
+ }
+
+ ReleaseSysCache(tup);
+}
+
+/*
+ * CREATE TEXT SEARCH DICTIONARY
+ */
+ObjectAddress
+DefineTSDictionary(List *names, List *parameters)
+{
+ ListCell *pl;
+ Relation dictRel;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_dict];
+ bool nulls[Natts_pg_ts_dict];
+ NameData dname;
+ Oid templId = InvalidOid;
+ List *dictoptions = NIL;
+ Oid dictOid;
+ Oid namespaceoid;
+ AclResult aclresult;
+ char *dictname;
+ ObjectAddress address;
+
+ /* Convert list of names to a name and namespace */
+ namespaceoid = QualifiedNameGetCreationNamespace(names, &dictname);
+
+ /* Check we have creation rights in target namespace */
+ aclresult = object_aclcheck(NamespaceRelationId, namespaceoid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_SCHEMA,
+ get_namespace_name(namespaceoid));
+
+ /*
+ * loop over the definition list and extract the information we need.
+ */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (strcmp(defel->defname, "template") == 0)
+ {
+ templId = get_ts_template_oid(defGetQualifiedName(defel), false);
+ }
+ else
+ {
+ /* Assume it's an option for the dictionary itself */
+ dictoptions = lappend(dictoptions, defel);
+ }
+ }
+
+ /*
+ * Validation
+ */
+ if (!OidIsValid(templId))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search template is required")));
+
+ verify_dictoptions(templId, dictoptions);
+
+
+ dictRel = table_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ /*
+ * Looks good, insert
+ */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ dictOid = GetNewOidWithIndex(dictRel, TSDictionaryOidIndexId,
+ Anum_pg_ts_dict_oid);
+ values[Anum_pg_ts_dict_oid - 1] = ObjectIdGetDatum(dictOid);
+ 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());
+ values[Anum_pg_ts_dict_dicttemplate - 1] = ObjectIdGetDatum(templId);
+ if (dictoptions)
+ values[Anum_pg_ts_dict_dictinitoption - 1] =
+ PointerGetDatum(serialize_deflist(dictoptions));
+ else
+ nulls[Anum_pg_ts_dict_dictinitoption - 1] = true;
+
+ tup = heap_form_tuple(dictRel->rd_att, values, nulls);
+
+ CatalogTupleInsert(dictRel, tup);
+
+ address = makeDictionaryDependencies(tup);
+
+ /* Post creation hook for new text search dictionary */
+ InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
+
+ heap_freetuple(tup);
+
+ table_close(dictRel, RowExclusiveLock);
+
+ return address;
+}
+
+/*
+ * ALTER TEXT SEARCH DICTIONARY
+ */
+ObjectAddress
+AlterTSDictionary(AlterTSDictionaryStmt *stmt)
+{
+ HeapTuple tup,
+ newtup;
+ Relation rel;
+ Oid dictId;
+ ListCell *pl;
+ List *dictoptions;
+ Datum opt;
+ bool isnull;
+ Datum repl_val[Natts_pg_ts_dict];
+ bool repl_null[Natts_pg_ts_dict];
+ bool repl_repl[Natts_pg_ts_dict];
+ ObjectAddress address;
+
+ dictId = get_ts_dict_oid(stmt->dictname, false);
+
+ rel = table_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+
+ /* must be owner */
+ if (!object_ownercheck(TSDictionaryRelationId, dictId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSDICTIONARY,
+ NameListToString(stmt->dictname));
+
+ /* deserialize the existing set of options */
+ opt = SysCacheGetAttr(TSDICTOID, tup,
+ Anum_pg_ts_dict_dictinitoption,
+ &isnull);
+ if (isnull)
+ dictoptions = NIL;
+ else
+ dictoptions = deserialize_deflist(opt);
+
+ /*
+ * Modify the options list as per specified changes
+ */
+ foreach(pl, stmt->options)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+ ListCell *cell;
+
+ /*
+ * Remove any matches ...
+ */
+ foreach(cell, dictoptions)
+ {
+ DefElem *oldel = (DefElem *) lfirst(cell);
+
+ if (strcmp(oldel->defname, defel->defname) == 0)
+ dictoptions = foreach_delete_current(dictoptions, cell);
+ }
+
+ /*
+ * and add new value if it's got one
+ */
+ if (defel->arg)
+ dictoptions = lappend(dictoptions, defel);
+ }
+
+ /*
+ * Validate
+ */
+ verify_dictoptions(((Form_pg_ts_dict) GETSTRUCT(tup))->dicttemplate,
+ dictoptions);
+
+ /*
+ * Looks good, update
+ */
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ if (dictoptions)
+ repl_val[Anum_pg_ts_dict_dictinitoption - 1] =
+ PointerGetDatum(serialize_deflist(dictoptions));
+ else
+ repl_null[Anum_pg_ts_dict_dictinitoption - 1] = true;
+ repl_repl[Anum_pg_ts_dict_dictinitoption - 1] = true;
+
+ newtup = heap_modify_tuple(tup, RelationGetDescr(rel),
+ repl_val, repl_null, repl_repl);
+
+ CatalogTupleUpdate(rel, &newtup->t_self, newtup);
+
+ InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
+
+ ObjectAddressSet(address, TSDictionaryRelationId, dictId);
+
+ /*
+ * NOTE: because we only support altering the options, not the template,
+ * there is no need to update dependencies. This might have to change if
+ * the options ever reference inside-the-database objects.
+ */
+
+ heap_freetuple(newtup);
+ ReleaseSysCache(tup);
+
+ table_close(rel, RowExclusiveLock);
+
+ return address;
+}
+
+/* ---------------------- 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, "unrecognized 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, NIL, typeId),
+ format_type_be(retTypeId))));
+
+ return ObjectIdGetDatum(procOid);
+}
+
+/*
+ * make pg_depend entries for a new pg_ts_template entry
+ */
+static ObjectAddress
+makeTSTemplateDependencies(HeapTuple tuple)
+{
+ Form_pg_ts_template tmpl = (Form_pg_ts_template) GETSTRUCT(tuple);
+ ObjectAddress myself,
+ referenced;
+ ObjectAddresses *addrs;
+
+ ObjectAddressSet(myself, TSTemplateRelationId, tmpl->oid);
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ addrs = new_object_addresses();
+
+ /* dependency on namespace */
+ ObjectAddressSet(referenced, NamespaceRelationId, tmpl->tmplnamespace);
+ add_exact_object_address(&referenced, addrs);
+
+ /* dependencies on functions */
+ ObjectAddressSet(referenced, ProcedureRelationId, tmpl->tmpllexize);
+ add_exact_object_address(&referenced, addrs);
+
+ if (OidIsValid(tmpl->tmplinit))
+ {
+ referenced.objectId = tmpl->tmplinit;
+ add_exact_object_address(&referenced, addrs);
+ }
+
+ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
+ free_object_addresses(addrs);
+
+ return myself;
+}
+
+/*
+ * CREATE TEXT SEARCH TEMPLATE
+ */
+ObjectAddress
+DefineTSTemplate(List *names, List *parameters)
+{
+ ListCell *pl;
+ Relation tmplRel;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_template];
+ bool nulls[Natts_pg_ts_template];
+ NameData dname;
+ int i;
+ Oid tmplOid;
+ Oid namespaceoid;
+ char *tmplname;
+ ObjectAddress address;
+
+ 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);
+
+ tmplRel = table_open(TSTemplateRelationId, RowExclusiveLock);
+
+ for (i = 0; i < Natts_pg_ts_template; i++)
+ {
+ nulls[i] = false;
+ values[i] = ObjectIdGetDatum(InvalidOid);
+ }
+
+ tmplOid = GetNewOidWithIndex(tmplRel, TSTemplateOidIndexId,
+ Anum_pg_ts_dict_oid);
+ values[Anum_pg_ts_template_oid - 1] = ObjectIdGetDatum(tmplOid);
+ 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 (strcmp(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] = false;
+ }
+ else if (strcmp(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] = false;
+ }
+ 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
+ */
+ tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
+
+ CatalogTupleInsert(tmplRel, tup);
+
+ address = makeTSTemplateDependencies(tup);
+
+ /* Post creation hook for new text search template */
+ InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
+
+ heap_freetuple(tup);
+
+ table_close(tmplRel, RowExclusiveLock);
+
+ return address;
+}
+
+/* ---------------------- TS Configuration commands -----------------------*/
+
+/*
+ * Finds syscache tuple of configuration.
+ * Returns NULL if no such cfg.
+ */
+static HeapTuple
+GetTSConfigTuple(List *names)
+{
+ HeapTuple tup;
+ Oid cfgId;
+
+ cfgId = get_ts_config_oid(names, true);
+ if (!OidIsValid(cfgId))
+ return NULL;
+
+ tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
+
+ 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 ObjectAddress
+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 = cfg->oid;
+ myself.objectSubId = 0;
+
+ /* for ALTER case, first flush old dependencies, except extension deps */
+ if (removeOld)
+ {
+ deleteDependencyRecordsFor(myself.classId, myself.objectId, true);
+ deleteSharedDependencyRecordsFor(myself.classId, myself.objectId, 0);
+ }
+
+ /*
+ * 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 extension */
+ recordDependencyOnCurrentExtension(&myself, removeOld);
+
+ /* 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,
+ NULL, 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);
+
+ return myself;
+}
+
+/*
+ * CREATE TEXT SEARCH CONFIGURATION
+ */
+ObjectAddress
+DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
+{
+ Relation cfgRel;
+ Relation mapRel = NULL;
+ HeapTuple tup;
+ Datum values[Natts_pg_ts_config];
+ bool nulls[Natts_pg_ts_config];
+ AclResult aclresult;
+ Oid namespaceoid;
+ char *cfgname;
+ NameData cname;
+ Oid sourceOid = InvalidOid;
+ Oid prsOid = InvalidOid;
+ Oid cfgOid;
+ ListCell *pl;
+ ObjectAddress address;
+
+ /* Convert list of names to a name and namespace */
+ namespaceoid = QualifiedNameGetCreationNamespace(names, &cfgname);
+
+ /* Check we have creation rights in target namespace */
+ aclresult = object_aclcheck(NamespaceRelationId, namespaceoid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_SCHEMA,
+ get_namespace_name(namespaceoid));
+
+ /*
+ * loop over the definition list and extract the information we need.
+ */
+ foreach(pl, parameters)
+ {
+ DefElem *defel = (DefElem *) lfirst(pl);
+
+ if (strcmp(defel->defname, "parser") == 0)
+ prsOid = get_ts_parser_oid(defGetQualifiedName(defel), false);
+ else if (strcmp(defel->defname, "copy") == 0)
+ sourceOid = get_ts_config_oid(defGetQualifiedName(defel), false);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("text search configuration parameter \"%s\" not recognized",
+ defel->defname)));
+ }
+
+ if (OidIsValid(sourceOid) && OidIsValid(prsOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify both PARSER and COPY options")));
+
+ /* make copied tsconfig available to callers */
+ if (copied && OidIsValid(sourceOid))
+ {
+ ObjectAddressSet(*copied,
+ TSConfigRelationId,
+ sourceOid);
+ }
+
+ /*
+ * Look up source config if given.
+ */
+ if (OidIsValid(sourceOid))
+ {
+ Form_pg_ts_config cfg;
+
+ tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(sourceOid));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ sourceOid);
+
+ cfg = (Form_pg_ts_config) GETSTRUCT(tup);
+
+ /* use source's parser */
+ prsOid = cfg->cfgparser;
+
+ ReleaseSysCache(tup);
+ }
+
+ /*
+ * Validation
+ */
+ if (!OidIsValid(prsOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("text search parser is required")));
+
+ cfgRel = table_open(TSConfigRelationId, RowExclusiveLock);
+
+ /*
+ * Looks good, build tuple and insert
+ */
+ memset(values, 0, sizeof(values));
+ memset(nulls, false, sizeof(nulls));
+
+ cfgOid = GetNewOidWithIndex(cfgRel, TSConfigOidIndexId,
+ Anum_pg_ts_config_oid);
+ values[Anum_pg_ts_config_oid - 1] = ObjectIdGetDatum(cfgOid);
+ 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);
+
+ tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
+
+ CatalogTupleInsert(cfgRel, tup);
+
+ if (OidIsValid(sourceOid))
+ {
+ /*
+ * Copy token-dicts map from source config
+ */
+ ScanKeyData skey;
+ SysScanDesc scan;
+ HeapTuple maptup;
+ TupleDesc mapDesc;
+ TupleTableSlot **slot;
+ CatalogIndexState indstate;
+ int max_slots,
+ slot_init_count,
+ slot_stored_count;
+
+ mapRel = table_open(TSConfigMapRelationId, RowExclusiveLock);
+ mapDesc = RelationGetDescr(mapRel);
+
+ indstate = CatalogOpenIndexes(mapRel);
+
+ /*
+ * Allocate the slots to use, but delay costly initialization until we
+ * know that they will be used.
+ */
+ max_slots = MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_ts_config_map);
+ slot = palloc(sizeof(TupleTableSlot *) * max_slots);
+
+ ScanKeyInit(&skey,
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(sourceOid));
+
+ scan = systable_beginscan(mapRel, TSConfigMapIndexId, true,
+ NULL, 1, &skey);
+
+ /* number of slots currently storing tuples */
+ slot_stored_count = 0;
+ /* number of slots currently initialized */
+ slot_init_count = 0;
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
+
+ if (slot_init_count < max_slots)
+ {
+ slot[slot_stored_count] = MakeSingleTupleTableSlot(mapDesc,
+ &TTSOpsHeapTuple);
+ slot_init_count++;
+ }
+
+ ExecClearTuple(slot[slot_stored_count]);
+
+ memset(slot[slot_stored_count]->tts_isnull, false,
+ slot[slot_stored_count]->tts_tupleDescriptor->natts * sizeof(bool));
+
+ slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapcfg - 1] = cfgOid;
+ slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_maptokentype - 1] = cfgmap->maptokentype;
+ slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = cfgmap->mapseqno;
+ slot[slot_stored_count]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = cfgmap->mapdict;
+
+ ExecStoreVirtualTuple(slot[slot_stored_count]);
+ slot_stored_count++;
+
+ /* If slots are full, insert a batch of tuples */
+ if (slot_stored_count == max_slots)
+ {
+ CatalogTuplesMultiInsertWithInfo(mapRel, slot, slot_stored_count,
+ indstate);
+ slot_stored_count = 0;
+ }
+ }
+
+ /* Insert any tuples left in the buffer */
+ if (slot_stored_count > 0)
+ CatalogTuplesMultiInsertWithInfo(mapRel, slot, slot_stored_count,
+ indstate);
+
+ for (int i = 0; i < slot_init_count; i++)
+ ExecDropSingleTupleTableSlot(slot[i]);
+
+ systable_endscan(scan);
+ CatalogCloseIndexes(indstate);
+ }
+
+ address = makeConfigurationDependencies(tup, false, mapRel);
+
+ /* Post creation hook for new text search configuration */
+ InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
+
+ heap_freetuple(tup);
+
+ if (mapRel)
+ table_close(mapRel, RowExclusiveLock);
+ table_close(cfgRel, RowExclusiveLock);
+
+ return address;
+}
+
+/*
+ * 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 = table_open(TSConfigRelationId, RowExclusiveLock);
+
+ tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ cfgId);
+
+ CatalogTupleDelete(relCfg, &tup->t_self);
+
+ ReleaseSysCache(tup);
+
+ table_close(relCfg, RowExclusiveLock);
+
+ /* Remove any pg_ts_config_map entries */
+ relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&skey,
+ Anum_pg_ts_config_map_mapcfg,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(cfgId));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ NULL, 1, &skey);
+
+ while (HeapTupleIsValid((tup = systable_getnext(scan))))
+ {
+ CatalogTupleDelete(relMap, &tup->t_self);
+ }
+
+ systable_endscan(scan);
+
+ table_close(relMap, RowExclusiveLock);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION - main entry point
+ */
+ObjectAddress
+AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
+{
+ HeapTuple tup;
+ Oid cfgId;
+ Relation relMap;
+ ObjectAddress address;
+
+ /* 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))));
+
+ cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid;
+
+ /* must be owner */
+ if (!object_ownercheck(TSConfigRelationId, cfgId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_TSCONFIGURATION,
+ NameListToString(stmt->cfgname));
+
+ relMap = table_open(TSConfigMapRelationId, RowExclusiveLock);
+
+ /* Add or drop mappings */
+ if (stmt->dicts)
+ MakeConfigurationMapping(stmt, tup, relMap);
+ else if (stmt->tokentype)
+ DropConfigurationMapping(stmt, tup, relMap);
+
+ /* Update dependencies */
+ makeConfigurationDependencies(tup, true, relMap);
+
+ InvokeObjectPostAlterHook(TSConfigRelationId, cfgId, 0);
+
+ ObjectAddressSet(address, TSConfigRelationId, cfgId);
+
+ table_close(relMap, RowExclusiveLock);
+
+ ReleaseSysCache(tup);
+
+ return address;
+}
+
+/*
+ * Check whether a token type name is a member of a TSTokenTypeItem list.
+ */
+static bool
+tstoken_list_member(char *token_name, List *tokens)
+{
+ ListCell *c;
+ bool found = false;
+
+ foreach(c, tokens)
+ {
+ TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
+
+ if (strcmp(token_name, ts->name) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+}
+
+/*
+ * Translate a list of token type names to a list of unique TSTokenTypeItem.
+ *
+ * Duplicated entries list are removed from tokennames.
+ */
+static List *
+getTokenTypes(Oid prsId, List *tokennames)
+{
+ TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
+ LexDescr *list;
+ List *result = NIL;
+ int ntoken;
+ ListCell *tn;
+
+ ntoken = list_length(tokennames);
+ if (ntoken == 0)
+ return NIL;
+
+ if (!OidIsValid(prs->lextypeOid))
+ elog(ERROR, "method lextype isn't defined for text search parser %u",
+ prsId);
+
+ /* lextype takes one dummy argument */
+ list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
+ (Datum) 0));
+
+ foreach(tn, tokennames)
+ {
+ String *val = lfirst_node(String, tn);
+ bool found = false;
+ int j;
+
+ /* Skip if this token is already in the result */
+ if (tstoken_list_member(strVal(val), result))
+ continue;
+
+ j = 0;
+ while (list && list[j].lexid)
+ {
+ if (strcmp(strVal(val), list[j].alias) == 0)
+ {
+ TSTokenTypeItem *ts = (TSTokenTypeItem *) palloc0(sizeof(TSTokenTypeItem));
+
+ ts->num = list[j].lexid;
+ ts->name = pstrdup(strVal(val));
+ result = lappend(result, ts);
+ found = true;
+ break;
+ }
+ j++;
+ }
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("token type \"%s\" does not exist",
+ strVal(val))));
+ }
+
+ return result;
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION ADD/ALTER MAPPING
+ */
+static void
+MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup, Relation relMap)
+{
+ Form_pg_ts_config tsform;
+ Oid cfgId;
+ ScanKeyData skey[2];
+ SysScanDesc scan;
+ HeapTuple maptup;
+ int i;
+ int j;
+ Oid prsId;
+ List *tokens = NIL;
+ int ntoken;
+ Oid *dictIds;
+ int ndict;
+ ListCell *c;
+ CatalogIndexState indstate;
+
+ tsform = (Form_pg_ts_config) GETSTRUCT(tup);
+ cfgId = tsform->oid;
+ prsId = tsform->cfgparser;
+
+ tokens = getTokenTypes(prsId, stmt->tokentype);
+ ntoken = list_length(tokens);
+
+ if (stmt->override)
+ {
+ /*
+ * delete maps for tokens if they exist and command was ALTER
+ */
+ foreach(c, tokens)
+ {
+ TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
+
+ 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(ts->num));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ NULL, 2, skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ CatalogTupleDelete(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] = get_ts_dict_oid(names, false);
+ i++;
+ }
+
+ indstate = CatalogOpenIndexes(relMap);
+
+ 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,
+ NULL, 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;
+
+ foreach(c, tokens)
+ {
+ TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
+
+ if (cfgmap->maptokentype == ts->num)
+ {
+ tokmatch = true;
+ break;
+ }
+ }
+ if (!tokmatch)
+ continue;
+ }
+
+ /*
+ * replace dictionary if match
+ */
+ if (cfgmap->mapdict == dictOld)
+ {
+ Datum repl_val[Natts_pg_ts_config_map];
+ bool repl_null[Natts_pg_ts_config_map];
+ bool repl_repl[Natts_pg_ts_config_map];
+ HeapTuple newtup;
+
+ memset(repl_val, 0, sizeof(repl_val));
+ memset(repl_null, false, sizeof(repl_null));
+ memset(repl_repl, false, sizeof(repl_repl));
+
+ repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
+ repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
+
+ newtup = heap_modify_tuple(maptup,
+ RelationGetDescr(relMap),
+ repl_val, repl_null, repl_repl);
+ CatalogTupleUpdateWithInfo(relMap, &newtup->t_self, newtup, indstate);
+ }
+ }
+
+ systable_endscan(scan);
+ }
+ else
+ {
+ TupleTableSlot **slot;
+ int slotCount = 0;
+ int nslots;
+
+ /* Allocate the slots to use and initialize them */
+ nslots = Min(ntoken * ndict,
+ MAX_CATALOG_MULTI_INSERT_BYTES / sizeof(FormData_pg_ts_config_map));
+ slot = palloc(sizeof(TupleTableSlot *) * nslots);
+ for (i = 0; i < nslots; i++)
+ slot[i] = MakeSingleTupleTableSlot(RelationGetDescr(relMap),
+ &TTSOpsHeapTuple);
+
+ /*
+ * Insertion of new entries
+ */
+ foreach(c, tokens)
+ {
+ TSTokenTypeItem *ts = (TSTokenTypeItem *) lfirst(c);
+
+ for (j = 0; j < ndict; j++)
+ {
+ ExecClearTuple(slot[slotCount]);
+
+ memset(slot[slotCount]->tts_isnull, false,
+ slot[slotCount]->tts_tupleDescriptor->natts * sizeof(bool));
+
+ slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapcfg - 1] = ObjectIdGetDatum(cfgId);
+ slot[slotCount]->tts_values[Anum_pg_ts_config_map_maptokentype - 1] = Int32GetDatum(ts->num);
+ slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
+ slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
+
+ ExecStoreVirtualTuple(slot[slotCount]);
+ slotCount++;
+
+ /* If slots are full, insert a batch of tuples */
+ if (slotCount == nslots)
+ {
+ CatalogTuplesMultiInsertWithInfo(relMap, slot, slotCount,
+ indstate);
+ slotCount = 0;
+ }
+ }
+ }
+
+ /* Insert any tuples left in the buffer */
+ if (slotCount > 0)
+ CatalogTuplesMultiInsertWithInfo(relMap, slot, slotCount,
+ indstate);
+
+ for (i = 0; i < nslots; i++)
+ ExecDropSingleTupleTableSlot(slot[i]);
+ }
+
+ /* clean up */
+ CatalogCloseIndexes(indstate);
+
+ EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict);
+}
+
+/*
+ * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
+ */
+static void
+DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
+ HeapTuple tup, Relation relMap)
+{
+ Form_pg_ts_config tsform;
+ Oid cfgId;
+ ScanKeyData skey[2];
+ SysScanDesc scan;
+ HeapTuple maptup;
+ Oid prsId;
+ List *tokens = NIL;
+ ListCell *c;
+
+ tsform = (Form_pg_ts_config) GETSTRUCT(tup);
+ cfgId = tsform->oid;
+ prsId = tsform->cfgparser;
+
+ tokens = getTokenTypes(prsId, stmt->tokentype);
+
+ foreach(c, tokens)
+ {
+ TSTokenTypeItem *ts = (TSTokenTypeItem *) 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(ts->num));
+
+ scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
+ NULL, 2, skey);
+
+ while (HeapTupleIsValid((maptup = systable_getnext(scan))))
+ {
+ CatalogTupleDelete(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",
+ ts->name)));
+ }
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("mapping for token type \"%s\" does not exist, skipping",
+ ts->name)));
+ }
+ }
+ }
+
+ EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
+}
+
+
+/*
+ * Serialize dictionary options, producing a TEXT datum from a List of DefElem
+ *
+ * This is used to form the value stored in pg_ts_dict.dictinitoption.
+ * For the convenience of pg_dump, the output is formatted exactly as it
+ * would need to appear in CREATE TEXT SEARCH DICTIONARY to reproduce the
+ * same options.
+ */
+text *
+serialize_deflist(List *deflist)
+{
+ text *result;
+ StringInfoData buf;
+ ListCell *l;
+
+ initStringInfo(&buf);
+
+ foreach(l, deflist)
+ {
+ DefElem *defel = (DefElem *) lfirst(l);
+ char *val = defGetString(defel);
+
+ appendStringInfo(&buf, "%s = ",
+ quote_identifier(defel->defname));
+
+ /*
+ * If the value is a T_Integer or T_Float, emit it without quotes,
+ * otherwise with quotes. This is essential to allow correct
+ * reconstruction of the node type as well as the value.
+ */
+ if (IsA(defel->arg, Integer) || IsA(defel->arg, Float))
+ appendStringInfoString(&buf, val);
+ else
+ {
+ /* If backslashes appear, force E syntax to quote them safely */
+ if (strchr(val, '\\'))
+ appendStringInfoChar(&buf, ESCAPE_STRING_SYNTAX);
+ appendStringInfoChar(&buf, '\'');
+ while (*val)
+ {
+ char ch = *val++;
+
+ if (SQL_STR_DOUBLE(ch, true))
+ appendStringInfoChar(&buf, ch);
+ appendStringInfoChar(&buf, ch);
+ }
+ appendStringInfoChar(&buf, '\'');
+ }
+ if (lnext(deflist, l) != NULL)
+ appendStringInfoString(&buf, ", ");
+ }
+
+ result = cstring_to_text_with_len(buf.data, buf.len);
+ pfree(buf.data);
+ return result;
+}
+
+/*
+ * Deserialize dictionary options, reconstructing a List of DefElem from TEXT
+ *
+ * This is also used for prsheadline options, so for backward compatibility
+ * we need to accept a few things serialize_deflist() will never emit:
+ * in particular, unquoted and double-quoted strings.
+ */
+List *
+deserialize_deflist(Datum txt)
+{
+ text *in = DatumGetTextPP(txt); /* in case it's toasted */
+ List *result = NIL;
+ int len = VARSIZE_ANY_EXHDR(in);
+ char *ptr,
+ *endptr,
+ *workspace,
+ *wsptr = NULL,
+ *startvalue = NULL;
+ typedef enum
+ {
+ CS_WAITKEY,
+ CS_INKEY,
+ CS_INQKEY,
+ CS_WAITEQ,
+ CS_WAITVALUE,
+ CS_INSQVALUE,
+ CS_INDQVALUE,
+ CS_INWVALUE
+ } ds_state;
+ ds_state state = CS_WAITKEY;
+
+ workspace = (char *) palloc(len + 1); /* certainly enough room */
+ ptr = VARDATA_ANY(in);
+ endptr = ptr + len;
+ for (; ptr < endptr; ptr++)
+ {
+ switch (state)
+ {
+ case CS_WAITKEY:
+ if (isspace((unsigned char) *ptr) || *ptr == ',')
+ continue;
+ if (*ptr == '"')
+ {
+ wsptr = workspace;
+ state = CS_INQKEY;
+ }
+ else
+ {
+ wsptr = workspace;
+ *wsptr++ = *ptr;
+ state = CS_INKEY;
+ }
+ break;
+ case CS_INKEY:
+ if (isspace((unsigned char) *ptr))
+ {
+ *wsptr++ = '\0';
+ state = CS_WAITEQ;
+ }
+ else if (*ptr == '=')
+ {
+ *wsptr++ = '\0';
+ state = CS_WAITVALUE;
+ }
+ else
+ {
+ *wsptr++ = *ptr;
+ }
+ break;
+ case CS_INQKEY:
+ if (*ptr == '"')
+ {
+ if (ptr + 1 < endptr && ptr[1] == '"')
+ {
+ /* copy only one of the two quotes */
+ *wsptr++ = *ptr++;
+ }
+ else
+ {
+ *wsptr++ = '\0';
+ state = CS_WAITEQ;
+ }
+ }
+ else
+ {
+ *wsptr++ = *ptr;
+ }
+ break;
+ case CS_WAITEQ:
+ if (*ptr == '=')
+ state = CS_WAITVALUE;
+ else if (!isspace((unsigned char) *ptr))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid parameter list format: \"%s\"",
+ text_to_cstring(in))));
+ break;
+ case CS_WAITVALUE:
+ if (*ptr == '\'')
+ {
+ startvalue = wsptr;
+ state = CS_INSQVALUE;
+ }
+ else if (*ptr == 'E' && ptr + 1 < endptr && ptr[1] == '\'')
+ {
+ ptr++;
+ startvalue = wsptr;
+ state = CS_INSQVALUE;
+ }
+ else if (*ptr == '"')
+ {
+ startvalue = wsptr;
+ state = CS_INDQVALUE;
+ }
+ else if (!isspace((unsigned char) *ptr))
+ {
+ startvalue = wsptr;
+ *wsptr++ = *ptr;
+ state = CS_INWVALUE;
+ }
+ break;
+ case CS_INSQVALUE:
+ if (*ptr == '\'')
+ {
+ if (ptr + 1 < endptr && ptr[1] == '\'')
+ {
+ /* copy only one of the two quotes */
+ *wsptr++ = *ptr++;
+ }
+ else
+ {
+ *wsptr++ = '\0';
+ result = lappend(result,
+ buildDefItem(workspace,
+ startvalue,
+ true));
+ state = CS_WAITKEY;
+ }
+ }
+ else if (*ptr == '\\')
+ {
+ if (ptr + 1 < endptr && ptr[1] == '\\')
+ {
+ /* copy only one of the two backslashes */
+ *wsptr++ = *ptr++;
+ }
+ else
+ *wsptr++ = *ptr;
+ }
+ else
+ {
+ *wsptr++ = *ptr;
+ }
+ break;
+ case CS_INDQVALUE:
+ if (*ptr == '"')
+ {
+ if (ptr + 1 < endptr && ptr[1] == '"')
+ {
+ /* copy only one of the two quotes */
+ *wsptr++ = *ptr++;
+ }
+ else
+ {
+ *wsptr++ = '\0';
+ result = lappend(result,
+ buildDefItem(workspace,
+ startvalue,
+ true));
+ state = CS_WAITKEY;
+ }
+ }
+ else
+ {
+ *wsptr++ = *ptr;
+ }
+ break;
+ case CS_INWVALUE:
+ if (*ptr == ',' || isspace((unsigned char) *ptr))
+ {
+ *wsptr++ = '\0';
+ result = lappend(result,
+ buildDefItem(workspace,
+ startvalue,
+ false));
+ state = CS_WAITKEY;
+ }
+ else
+ {
+ *wsptr++ = *ptr;
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized deserialize_deflist state: %d",
+ state);
+ }
+ }
+
+ if (state == CS_INWVALUE)
+ {
+ *wsptr++ = '\0';
+ result = lappend(result,
+ buildDefItem(workspace,
+ startvalue,
+ false));
+ }
+ else if (state != CS_WAITKEY)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("invalid parameter list format: \"%s\"",
+ text_to_cstring(in))));
+
+ pfree(workspace);
+
+ return result;
+}
+
+/*
+ * Build one DefElem for deserialize_deflist
+ */
+static DefElem *
+buildDefItem(const char *name, const char *val, bool was_quoted)
+{
+ /* If input was quoted, always emit as string */
+ if (!was_quoted && val[0] != '\0')
+ {
+ int v;
+ char *endptr;
+
+ /* Try to parse as an integer */
+ errno = 0;
+ v = strtoint(val, &endptr, 10);
+ if (errno == 0 && *endptr == '\0')
+ return makeDefElem(pstrdup(name),
+ (Node *) makeInteger(v),
+ -1);
+ /* Nope, how about as a float? */
+ errno = 0;
+ (void) strtod(val, &endptr);
+ if (errno == 0 && *endptr == '\0')
+ return makeDefElem(pstrdup(name),
+ (Node *) makeFloat(pstrdup(val)),
+ -1);
+
+ if (strcmp(val, "true") == 0)
+ return makeDefElem(pstrdup(name),
+ (Node *) makeBoolean(true),
+ -1);
+ if (strcmp(val, "false") == 0)
+ return makeDefElem(pstrdup(name),
+ (Node *) makeBoolean(false),
+ -1);
+ }
+ /* Just make it a string */
+ return makeDefElem(pstrdup(name),
+ (Node *) makeString(pstrdup(val)),
+ -1);
+}