diff --git a/src/server/generic_family.cc b/src/server/generic_family.cc index 24af2a959099..bb7e744d7dd6 100644 --- a/src/server/generic_family.cc +++ b/src/server/generic_family.cc @@ -1477,6 +1477,13 @@ OpResult OpStore(const OpArgs& op_args, std::string_view key, Iterator IteratorEnd&& end_it) { uint32_t len = 0; + // If we are about to overwrite an existing indexed document (HASH/JSON), + // remove it from search indices first to avoid duplicate entries. + auto existing = op_args.GetDbSlice().FindReadOnly(op_args.db_cntx, key).it; + if (IsValid(existing)) { + RemoveKeyFromIndexesIfNeeded(key, op_args.db_cntx, existing->second, op_args.shard); + } + QList* ql_v2 = CompactObj::AllocateMR(); QList::Where where = QList::TAIL; for (auto it = start_it; it != end_it; ++it) { diff --git a/src/server/search/search_family_test.cc b/src/server/search/search_family_test.cc index d9d4fcf638bd..0986041e48ae 100644 --- a/src/server/search/search_family_test.cc +++ b/src/server/search/search_family_test.cc @@ -2728,6 +2728,30 @@ TEST_F(SearchFamilyTest, SetDoesNotUpdateIndexesBug) { EXPECT_THAT(resp, AreDocIds("k1")); } +TEST_F(SearchFamilyTest, SortStoreDoesNotUpdateIndexesBug) { + // Create an index over HASH + auto resp = Run({"FT.CREATE", "index", "ON", "HASH", "SCHEMA", "field", "TEXT"}); + EXPECT_THAT(resp, "OK"); + + // Index a HASH document under k1 + resp = Run({"HSET", "k1", "field", "value"}); + EXPECT_THAT(resp, IntArg(1)); + + // Prepare a source list to sort and store into k1 (overwriting k1 to LIST) + EXPECT_THAT(Run({"RPUSH", "lst", "b", "a"}), IntArg(2)); + // SORT lst STORE k1 -> changes type of k1 from HASH to LIST + Run({"SORT", "lst", "ALPHA", "STORE", "k1"}); + + // Rename away and recreate k1 as HASH again + EXPECT_EQ(Run({"RENAME", "k1", "anotherkey"}), "OK"); + EXPECT_THAT(Run({"HSET", "k1", "field", "value"}), IntArg(1)); + + // If SORT/STORE failed to remove k1 from indexes, the re-index here should crash. + // Successful run should contain only the new k1 document in the index. + resp = Run({"FT.SEARCH", "index", "*"}); + EXPECT_THAT(resp, AreDocIds("k1")); +} + TEST_F(SearchFamilyTest, BlockSizeOptionFtCreate) { // Create an index with a block size option auto resp = Run({"FT.CREATE", "index", "ON", "HASH", "SCHEMA", "number1", "NUMERIC", "BLOCKSIZE",