From 4a0e73a36073d5d6d3381a257387441195516e49 Mon Sep 17 00:00:00 2001
From: "tender.wang" <tender.wang@openpie.com>
Date: Tue, 30 Jan 2024 19:20:00 +0800
Subject: [PATCH] Fix "tuple already updated by self" error when duplicate
 token type name in ALTER TEXT SEARCH CONFIGURATION xxx ALTER MAPPING FOR.

Currently, we don't remove duplicated token type name in getTokenTypes().
Above error will report in MakeConfigurationMapping() when meets duplicated
token type name.

We remove duplicated token type name in getTokenTypes() and return List * not
int *. DropConfigurationMapping() need token type name when reports error,
so the returned List point to a structue, which not only has token type
numbuer, but has the token type name.
---
 src/backend/commands/tsearchcmds.c    | 98 +++++++++++++++++++--------
 src/test/regress/expected/tsdicts.out |  3 +
 src/test/regress/sql/tsdicts.sql      |  5 ++
 3 files changed, 76 insertions(+), 30 deletions(-)

diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index 07671720ea..3905dd2897 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -48,6 +48,12 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
+/* result structure for getTokenTypes() */
+typedef struct
+{
+	int		num;	/* token type number */
+	char	*name;	/* token type name */
+} TSTokenTypes;
 
 static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 									 HeapTuple tup, Relation relMap);
@@ -1192,22 +1198,47 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
 }
 
 /*
- * Translate a list of token type names to an array of token type numbers
+ * Test whether a token type name is present in a TSTokenTypes list.
+ *
+ * We return "true" if a name is already in the list.
+ */
+static bool
+token_type_name_present(char *token_name, List *tokens)
+{
+	ListCell *c;
+	bool found = false;
+
+	foreach(c, tokens)
+	{
+		TSTokenTypes *ts = (TSTokenTypes *) lfirst(c);
+
+		if (strcmp(token_name, ts->name) == 0)
+		{
+			found = true;
+			break;
+		}
+	}
+
+	return found;
+}
+
+/*
+ * Translate a list of token type names to an array of token type numbers.
+ *
+ * We remove duplicated entry in tokennames list, and return a list of structure.
  */
-static int *
+static List *
 getTokenTypes(Oid prsId, List *tokennames)
 {
 	TSParserCacheEntry *prs = lookup_ts_parser_cache(prsId);
 	LexDescr   *list;
-	int		   *res,
-				i,
-				ntoken;
+	List	   *result = NIL;
+	int		   ntoken;
 	ListCell   *tn;
 
 	ntoken = list_length(tokennames);
 	if (ntoken == 0)
-		return NULL;
-	res = (int *) palloc(sizeof(int) * ntoken);
+		return NIL;
 
 	if (!OidIsValid(prs->lextypeOid))
 		elog(ERROR, "method lextype isn't defined for text search parser %u",
@@ -1217,19 +1248,25 @@ getTokenTypes(Oid prsId, List *tokennames)
 	list = (LexDescr *) DatumGetPointer(OidFunctionCall1(prs->lextypeOid,
 														 (Datum) 0));
 
-	i = 0;
 	foreach(tn, tokennames)
 	{
 		String	   *val = lfirst_node(String, tn);
 		bool		found = false;
 		int			j;
 
+		/* Skip if the token type name is present in the list */
+		if (token_type_name_present(strVal(val), result))
+			continue;
+
 		j = 0;
 		while (list && list[j].lexid)
 		{
 			if (strcmp(strVal(val), list[j].alias) == 0)
 			{
-				res[i] = list[j].lexid;
+				TSTokenTypes *ts = (TSTokenTypes *) palloc(sizeof(TSTokenTypes));
+				ts->num = list[j].lexid;
+				ts->name = pstrdup(strVal(val));
+				result = lappend(result, ts);
 				found = true;
 				break;
 			}
@@ -1240,10 +1277,9 @@ getTokenTypes(Oid prsId, List *tokennames)
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("token type \"%s\" does not exist",
 							strVal(val))));
-		i++;
 	}
 
-	return res;
+	return result;
 }
 
 /*
@@ -1261,8 +1297,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 	int			i;
 	int			j;
 	Oid			prsId;
-	int		   *tokens,
-				ntoken;
+	List		*tokens = NIL;
+	int			ntoken;
 	Oid		   *dictIds;
 	int			ndict;
 	ListCell   *c;
@@ -1273,15 +1309,17 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 	prsId = tsform->cfgparser;
 
 	tokens = getTokenTypes(prsId, stmt->tokentype);
-	ntoken = list_length(stmt->tokentype);
+	ntoken = list_length(tokens);
 
 	if (stmt->override)
 	{
 		/*
 		 * delete maps for tokens if they exist and command was ALTER
 		 */
-		for (i = 0; i < ntoken; i++)
+		foreach(c, tokens)
 		{
+			TSTokenTypes	*ts = (TSTokenTypes *) lfirst(c);
+
 			ScanKeyInit(&skey[0],
 						Anum_pg_ts_config_map_mapcfg,
 						BTEqualStrategyNumber, F_OIDEQ,
@@ -1289,7 +1327,7 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			ScanKeyInit(&skey[1],
 						Anum_pg_ts_config_map_maptokentype,
 						BTEqualStrategyNumber, F_INT4EQ,
-						Int32GetDatum(tokens[i]));
+						Int32GetDatum(ts->num));
 
 			scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
 									  NULL, 2, skey);
@@ -1346,9 +1384,11 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 			{
 				bool		tokmatch = false;
 
-				for (j = 0; j < ntoken; j++)
+				foreach(c, tokens)
 				{
-					if (cfgmap->maptokentype == tokens[j])
+					TSTokenTypes	*ts = (TSTokenTypes *) lfirst(c);
+
+					if (cfgmap->maptokentype == ts->num)
 					{
 						tokmatch = true;
 						break;
@@ -1401,8 +1441,10 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 		/*
 		 * Insertion of new entries
 		 */
-		for (i = 0; i < ntoken; i++)
+		foreach(c, tokens)
 		{
+			TSTokenTypes	*ts = (TSTokenTypes *) lfirst(c);
+
 			for (j = 0; j < ndict; j++)
 			{
 				ExecClearTuple(slot[slotCount]);
@@ -1411,7 +1453,7 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 					   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(tokens[i]);
+				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]);
 
@@ -1455,9 +1497,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 	ScanKeyData skey[2];
 	SysScanDesc scan;
 	HeapTuple	maptup;
-	int			i;
 	Oid			prsId;
-	int		   *tokens;
+	List		*tokens = NIL;
 	ListCell   *c;
 
 	tsform = (Form_pg_ts_config) GETSTRUCT(tup);
@@ -1466,10 +1507,9 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 
 	tokens = getTokenTypes(prsId, stmt->tokentype);
 
-	i = 0;
-	foreach(c, stmt->tokentype)
+	foreach(c, tokens)
 	{
-		String	   *val = lfirst_node(String, c);
+		TSTokenTypes	*ts = (TSTokenTypes *) lfirst(c);
 		bool		found = false;
 
 		ScanKeyInit(&skey[0],
@@ -1479,7 +1519,7 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 		ScanKeyInit(&skey[1],
 					Anum_pg_ts_config_map_maptokentype,
 					BTEqualStrategyNumber, F_INT4EQ,
-					Int32GetDatum(tokens[i]));
+					Int32GetDatum(ts->num));
 
 		scan = systable_beginscan(relMap, TSConfigMapIndexId, true,
 								  NULL, 2, skey);
@@ -1499,17 +1539,15 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt,
 				ereport(ERROR,
 						(errcode(ERRCODE_UNDEFINED_OBJECT),
 						 errmsg("mapping for token type \"%s\" does not exist",
-								strVal(val))));
+								ts->name)));
 			}
 			else
 			{
 				ereport(NOTICE,
 						(errmsg("mapping for token type \"%s\" does not exist, skipping",
-								strVal(val))));
+								ts->name)));
 			}
 		}
-
-		i++;
 	}
 
 	EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
diff --git a/src/test/regress/expected/tsdicts.out b/src/test/regress/expected/tsdicts.out
index c804293142..94aed1164b 100644
--- a/src/test/regress/expected/tsdicts.out
+++ b/src/test/regress/expected/tsdicts.out
@@ -687,3 +687,6 @@ CREATE TEXT SEARCH DICTIONARY tsdict_case
 	"AffFile" = ispell_sample
 );
 ERROR:  unrecognized Ispell parameter: "DictFile"
+-- test duplicate token type name
+CREATE TEXT SEARCH CONFIGURATION dup_ispell_tst (COPY=english);
+ALTER TEXT SEARCH CONFIGURATION dup_ispell_tst ALTER MAPPING FOR word, word WITH ispell;
diff --git a/src/test/regress/sql/tsdicts.sql b/src/test/regress/sql/tsdicts.sql
index ddc6c7f445..88db4f4516 100644
--- a/src/test/regress/sql/tsdicts.sql
+++ b/src/test/regress/sql/tsdicts.sql
@@ -251,3 +251,8 @@ CREATE TEXT SEARCH DICTIONARY tsdict_case
 	"DictFile" = ispell_sample,
 	"AffFile" = ispell_sample
 );
+
+-- test duplicate token type name
+CREATE TEXT SEARCH CONFIGURATION dup_ispell_tst (COPY=english);
+
+ALTER TEXT SEARCH CONFIGURATION dup_ispell_tst ALTER MAPPING FOR word, word WITH ispell;
-- 
2.25.1

