Skip to content
Snippets Groups Projects
Select Git revision
  • benchmark-tools
  • postgres-lambda
  • master default
  • REL9_4_25
  • REL9_5_20
  • REL9_6_16
  • REL_10_11
  • REL_11_6
  • REL_12_1
  • REL_12_0
  • REL_12_RC1
  • REL_12_BETA4
  • REL9_4_24
  • REL9_5_19
  • REL9_6_15
  • REL_10_10
  • REL_11_5
  • REL_12_BETA3
  • REL9_4_23
  • REL9_5_18
  • REL9_6_14
  • REL_10_9
  • REL_11_4
23 results

tsearchcmds.c

Blame
  • tsearchcmds.c 42.01 KiB
    /*-------------------------------------------------------------------------
     *
     * tsearchcmds.c
     *
     *	  Routines for tsearch manipulation commands
     *
     * Portions Copyright (c) 1996-2014, 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/heapam.h"
    #include "access/htup_details.h"
    #include "access/xact.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 "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"
    #include "utils/tqual.h"
    
    
    static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
    						 HeapTuple tup, Relation relMap);
    static void DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
    						 HeapTuple tup, Relation relMap);
    
    
    /* --------------------- 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
     */
    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);
    
    	/* dependency on extension */
    	recordDependencyOnCurrentExtension(&myself, false);
    
    	/* 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
     */
    Oid
    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;
    
    	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, false, 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_form_tuple(prsRel->rd_att, values, nulls);
    
    	prsOid = simple_heap_insert(prsRel, tup);
    
    	CatalogUpdateIndexes(prsRel, tup);
    
    	makeParserDependencies(tup);
    
    	/* Post creation hook for new text search parser */
    	InvokeObjectPostCreateHook(TSParserRelationId, prsOid, 0);
    
    	heap_freetuple(tup);
    
    	heap_close(prsRel, RowExclusiveLock);
    
    	return prsOid;
    }
    
    /*
     * Guts of TS parser deletion.
     */
    void
    RemoveTSParserById(Oid prsId)
    {
    	Relation	relation;
    	HeapTuple	tup;
    
    	relation = heap_open(TSParserRelationId, RowExclusiveLock);
    
    	tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(prsId));
    
    	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);
    }
    
    /* ---------------------- 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 extension */
    	recordDependencyOnCurrentExtension(&myself, false);
    
    	/* dependency on template */
    	referenced.classId = TSTemplateRelationId;
    	referenced.objectId = dict->dicttemplate;
    	referenced.objectSubId = 0;
    	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
    }
    
    /*
     * 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
     */
    Oid
    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;
    
    	/* 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));
    
    	/*
    	 * 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)
    		{
    			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);
    
    	/*
    	 * Looks good, insert
    	 */
    	memset(values, 0, sizeof(values));
    	memset(nulls, false, sizeof(nulls));
    
    	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;
    
    	dictRel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
    
    	tup = heap_form_tuple(dictRel->rd_att, values, nulls);
    
    	dictOid = simple_heap_insert(dictRel, tup);
    
    	CatalogUpdateIndexes(dictRel, tup);
    
    	makeDictionaryDependencies(tup);
    
    	/* Post creation hook for new text search dictionary */
    	InvokeObjectPostCreateHook(TSDictionaryRelationId, dictOid, 0);
    
    	heap_freetuple(tup);
    
    	heap_close(dictRel, RowExclusiveLock);
    
    	return dictOid;
    }
    
    /*
     * Guts of TS dictionary deletion.
     */
    void
    RemoveTSDictionaryById(Oid dictId)
    {
    	Relation	relation;
    	HeapTuple	tup;
    
    	relation = heap_open(TSDictionaryRelationId, RowExclusiveLock);
    
    	tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(dictId));
    
    	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
     */
    Oid
    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];
    
    	dictId = get_ts_dict_oid(stmt->dictname, false);
    
    	rel = heap_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 (!pg_ts_dict_ownercheck(dictId, GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_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;
    		ListCell   *prev;
    		ListCell   *next;
    
    		/*
    		 * Remove any matches ...
    		 */
    		prev = NULL;
    		for (cell = list_head(dictoptions); cell; cell = next)
    		{
    			DefElem    *oldel = (DefElem *) lfirst(cell);
    
    			next = lnext(cell);
    			if (pg_strcasecmp(oldel->defname, defel->defname) == 0)
    				dictoptions = list_delete_cell(dictoptions, cell, prev);
    			else
    				prev = 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);
    
    	simple_heap_update(rel, &newtup->t_self, newtup);
    
    	CatalogUpdateIndexes(rel, newtup);
    
    	InvokeObjectPostAlterHook(TSDictionaryRelationId, dictId, 0);
    
    	/*
    	 * 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);
    
    	heap_close(rel, RowExclusiveLock);
    
    	return dictId;
    }
    
    /* ---------------------- 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 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);
    
    	/* dependency on extension */
    	recordDependencyOnCurrentExtension(&myself, false);
    
    	/* 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
     */
    Oid
    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;
    
    	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] = false;
    		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] = false;
    		}
    		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] = 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
    	 */
    
    	tmplRel = heap_open(TSTemplateRelationId, RowExclusiveLock);
    
    	tup = heap_form_tuple(tmplRel->rd_att, values, nulls);
    
    	tmplOid = simple_heap_insert(tmplRel, tup);
    
    	CatalogUpdateIndexes(tmplRel, tup);
    
    	makeTSTemplateDependencies(tup);
    
    	/* Post creation hook for new text search template */
    	InvokeObjectPostCreateHook(TSTemplateRelationId, tmplOid, 0);
    
    	heap_freetuple(tup);
    
    	heap_close(tmplRel, RowExclusiveLock);
    
    	return tmplOid;
    }
    
    /*
     * Guts of TS template deletion.
     */
    void
    RemoveTSTemplateById(Oid tmplId)
    {
    	Relation	relation;
    	HeapTuple	tup;
    
    	relation = heap_open(TSTemplateRelationId, RowExclusiveLock);
    
    	tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
    
    	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 = 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 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, 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);
    }
    
    /*
     * CREATE TEXT SEARCH CONFIGURATION
     */
    Oid
    DefineTSConfiguration(List *names, List *parameters)
    {
    	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;
    
    	/* 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 = get_ts_parser_oid(defGetQualifiedName(defel), false);
    		else if (pg_strcasecmp(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")));
    
    	/*
    	 * 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")));
    
    	/*
    	 * Looks good, build tuple and insert
    	 */
    	memset(values, 0, sizeof(values));
    	memset(nulls, false, 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_form_tuple(cfgRel->rd_att, values, nulls);
    
    	cfgOid = simple_heap_insert(cfgRel, tup);
    
    	CatalogUpdateIndexes(cfgRel, tup);
    
    	if (OidIsValid(sourceOid))
    	{
    		/*
    		 * Copy token-dicts map from source config
    		 */
    		ScanKeyData skey;
    		SysScanDesc scan;
    		HeapTuple	maptup;
    
    		mapRel = heap_open(TSConfigMapRelationId, RowExclusiveLock);
    
    		ScanKeyInit(&skey,
    					Anum_pg_ts_config_map_mapcfg,
    					BTEqualStrategyNumber, F_OIDEQ,
    					ObjectIdGetDatum(sourceOid));
    
    		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);
    			HeapTuple	newmaptup;
    			Datum		mapvalues[Natts_pg_ts_config_map];
    			bool		mapnulls[Natts_pg_ts_config_map];
    
    			memset(mapvalues, 0, sizeof(mapvalues));
    			memset(mapnulls, false, 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_form_tuple(mapRel->rd_att, mapvalues, mapnulls);
    
    			simple_heap_insert(mapRel, newmaptup);
    
    			CatalogUpdateIndexes(mapRel, newmaptup);
    
    			heap_freetuple(newmaptup);
    		}
    
    		systable_endscan(scan);
    	}
    
    	makeConfigurationDependencies(tup, false, mapRel);
    
    	/* Post creation hook for new text search configuration */
    	InvokeObjectPostCreateHook(TSConfigRelationId, cfgOid, 0);
    
    	heap_freetuple(tup);
    
    	if (mapRel)
    		heap_close(mapRel, RowExclusiveLock);
    	heap_close(cfgRel, RowExclusiveLock);
    
    	return cfgOid;
    }
    
    /*
     * 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 = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
    
    	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,
    							  NULL, 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 - main entry point
     */
    Oid
    AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
    {
    	HeapTuple	tup;
    	Oid			cfgId;
    	Relation	relMap;
    
    	/* 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 = HeapTupleGetOid(tup);
    
    	/* must be owner */
    	if (!pg_ts_config_ownercheck(HeapTupleGetOid(tup), GetUserId()))
    		aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
    					   NameListToString(stmt->cfgname));
    
    	relMap = heap_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(TSConfigMapRelationId,
    							  HeapTupleGetOid(tup), 0);
    
    	heap_close(relMap, RowExclusiveLock);
    
    	ReleaseSysCache(tup);
    
    	return cfgId;
    }
    
    /*
     * 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);
    
    	/* lextype takes one dummy argument */
    	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, Relation relMap)
    {
    	Oid			cfgId = HeapTupleGetOid(tup);
    	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);
    
    	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,
    									  NULL, 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] = get_ts_dict_oid(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,
    								  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;
    
    				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];
    				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);
    				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];
    				bool		nulls[Natts_pg_ts_config_map];
    
    				memset(nulls, false, 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_form_tuple(relMap->rd_att, values, nulls);
    				simple_heap_insert(relMap, tup);
    				CatalogUpdateIndexes(relMap, tup);
    
    				heap_freetuple(tup);
    			}
    		}
    	}
    }
    
    /*
     * ALTER TEXT SEARCH CONFIGURATION DROP MAPPING
     */
    static void
    DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
    						 HeapTuple tup, Relation relMap)
    {
    	Oid			cfgId = HeapTupleGetOid(tup);
    	ScanKeyData skey[2];
    	SysScanDesc scan;
    	HeapTuple	maptup;
    	int			i;
    	Oid			prsId;
    	int		   *tokens;
    	ListCell   *c;
    
    	prsId = ((Form_pg_ts_config) GETSTRUCT(tup))->cfgparser;
    
    	tokens = getTokenTypes(prsId, stmt->tokentype);
    
    	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,
    								  NULL, 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++;
    	}
    }
    
    
    /*
     * 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.
     *
     * Note that we assume that only the textual representation of an option's
     * value is interesting --- hence, non-string DefElems get forced to strings.
     */
    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 backslashes appear, force E syntax to determine their handling */
    		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(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 values.
     */
    List *
    deserialize_deflist(Datum txt)
    {
    	text	   *in = DatumGetTextP(txt);		/* in case it's toasted */
    	List	   *result = NIL;
    	int			len = VARSIZE(in) - VARHDRSZ;
    	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(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,
    										 makeDefElem(pstrdup(workspace),
    								  (Node *) makeString(pstrdup(startvalue))));
    						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,
    										 makeDefElem(pstrdup(workspace),
    								  (Node *) makeString(pstrdup(startvalue))));
    						state = CS_WAITKEY;
    					}
    				}
    				else
    				{
    					*wsptr++ = *ptr;
    				}
    				break;
    			case CS_INWVALUE:
    				if (*ptr == ',' || isspace((unsigned char) *ptr))
    				{
    					*wsptr++ = '\0';
    					result = lappend(result,
    									 makeDefElem(pstrdup(workspace),
    								  (Node *) makeString(pstrdup(startvalue))));
    					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,
    						 makeDefElem(pstrdup(workspace),
    								  (Node *) makeString(pstrdup(startvalue))));
    	}
    	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;
    }