/*-------------------------------------------------------------------------
 *
 * tsearchcmds.c
 *
 *	  Routines for tsearch manipulation commands
 *
 * Portions Copyright (c) 1996-2025, 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/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_public.h"
#include "utils/acl.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);
}
