Skip to content

Commit 8ce10fe

Browse files
authored
feat(search): Add support for multiple prefixes in search indexes (#5985)
* feat(search): Add support for multiple prefixes in search indexes * fix: failing tests * fix: review comments
1 parent 6818b74 commit 8ce10fe

File tree

5 files changed

+55
-22
lines changed

5 files changed

+55
-22
lines changed

src/server/search/doc_index.cc

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,9 @@ void TraverseAllMatching(const DocIndex& index, const OpArgs& op_args, F&& f) {
3535
string scratch;
3636
auto cb = [&](PrimeTable::iterator it) {
3737
const PrimeValue& pv = it->second;
38-
if (pv.ObjType() != index.GetObjCode())
39-
return;
40-
4138
string_view key = it->first.GetSlice(&scratch);
42-
if (key.rfind(index.prefix, 0) != 0)
39+
40+
if (!index.Matches(key, pv.ObjType()))
4341
return;
4442

4543
auto accessor = GetAccessor(op_args.db_cntx, pv);
@@ -149,9 +147,13 @@ string DocIndexInfo::BuildRestoreCommand() const {
149147
// ON HASH/JSON
150148
absl::StrAppend(&out, "ON", " ", base_index.type == DocIndex::HASH ? "HASH" : "JSON");
151149

152-
// optional PREFIX 1 *prefix*
153-
if (!base_index.prefix.empty())
154-
absl::StrAppend(&out, " PREFIX", " 1 ", base_index.prefix);
150+
// optional PREFIX count *prefix1* *prefix2* ...
151+
if (!base_index.prefixes.empty()) {
152+
absl::StrAppend(&out, " PREFIX", " ", base_index.prefixes.size());
153+
for (const auto& prefix : base_index.prefixes) {
154+
absl::StrAppend(&out, " ", prefix);
155+
}
156+
}
155157

156158
// STOPWORDS
157159
absl::StrAppend(&out, " STOPWORDS ", base_index.options.stopwords.size());
@@ -247,7 +249,18 @@ uint8_t DocIndex::GetObjCode() const {
247249
}
248250

249251
bool DocIndex::Matches(string_view key, unsigned obj_code) const {
250-
return obj_code == GetObjCode() && key.rfind(prefix, 0) == 0;
252+
if (obj_code != GetObjCode())
253+
return false;
254+
255+
// Empty prefixes means match all keys
256+
if (prefixes.empty())
257+
return true;
258+
259+
for (const auto& prefix : prefixes) {
260+
if (key.rfind(prefix, 0) == 0)
261+
return true;
262+
}
263+
return false;
251264
}
252265

253266
ShardDocIndex::ShardDocIndex(shared_ptr<const DocIndex> index)
@@ -269,7 +282,8 @@ void ShardDocIndex::Rebuild(const OpArgs& op_args, PMR_NS::memory_resource* mr)
269282

270283
indices_->FinalizeInitialization();
271284

272-
VLOG(1) << "Indexed " << key_index_.Size() << " docs on " << base_->prefix;
285+
VLOG(1) << "Indexed " << key_index_.Size()
286+
<< " docs on prefixes: " << absl::StrJoin(base_->prefixes, ", ");
273287
}
274288

275289
void ShardDocIndex::RebuildForGroup(const OpArgs& op_args, const std::string_view& group_id,

src/server/search/doc_index.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ struct DocIndex {
194194

195195
search::Schema schema;
196196
search::IndicesOptions options{};
197-
std::string prefix{};
197+
std::vector<std::string> prefixes{};
198198
DataType type{HASH};
199199
};
200200

src/server/search/search_family.cc

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,11 @@ ParseResult<bool> ParseOnOption(CmdArgParser* parser, DocIndex* index) {
211211

212212
// PREFIX count prefix [prefix ...]
213213
ParseResult<bool> ParsePrefix(CmdArgParser* parser, DocIndex* index) {
214-
if (!parser->Check("1")) {
215-
return CreateSyntaxError("Multiple prefixes are not supported"sv);
214+
size_t count = parser->Next<size_t>();
215+
index->prefixes.reserve(count);
216+
for (size_t i = 0; i < count; i++) {
217+
index->prefixes.push_back(parser->Next<std::string>());
216218
}
217-
index->prefix = parser->Next<std::string>();
218219
return true;
219220
}
220221

@@ -1217,8 +1218,11 @@ void SearchFamily::FtInfo(CmdArgList args, const CommandContext& cmd_cntx) {
12171218
rb->StartCollection(3, RedisReplyBuilder::MAP);
12181219
rb->SendSimpleString("key_type");
12191220
rb->SendSimpleString(info.base_index.type == DocIndex::JSON ? "JSON" : "HASH");
1220-
rb->SendSimpleString("prefix");
1221-
rb->SendSimpleString(info.base_index.prefix);
1221+
rb->SendSimpleString("prefixes");
1222+
rb->StartArray(info.base_index.prefixes.size());
1223+
for (const auto& prefix : info.base_index.prefixes) {
1224+
rb->SendBulkString(prefix);
1225+
}
12221226
rb->SendSimpleString("default_score");
12231227
rb->SendLong(1);
12241228
}

src/server/search/search_family_test.cc

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,11 +290,12 @@ TEST_F(SearchFamilyTest, InfoIndex) {
290290
}
291291

292292
auto info = Run({"ft.info", "idx-1"});
293-
EXPECT_THAT(info,
294-
IsArray(_, _, _, IsArray("key_type", "HASH", "prefix", "doc-", "default_score", 1),
295-
"index_options", RespArray(IsEmpty()), "attributes",
296-
IsArray(IsArray("identifier", "name", "attribute", "name", "type", "TEXT")),
297-
"num_docs", IntArg(15)));
293+
EXPECT_THAT(
294+
info,
295+
IsArray(_, _, _, IsArray("key_type", "HASH", "prefixes", IsArray("doc-"), "default_score", 1),
296+
"index_options", RespArray(IsEmpty()), "attributes",
297+
IsArray(IsArray("identifier", "name", "attribute", "name", "type", "TEXT")),
298+
"num_docs", IntArg(15)));
298299
}
299300

300301
TEST_F(SearchFamilyTest, Stats) {

tests/dragonfly/search_test.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,29 @@ async def test_management(async_client: aioredis.Redis):
128128
assert sorted(await async_client.execute_command("FT._LIST")) == ["i1", "i2"]
129129

130130
i1info = await i1.info()
131-
assert i1info["index_definition"] == ["key_type", "HASH", "prefix", "p1", "default_score", 1]
131+
assert i1info["index_definition"] == [
132+
"key_type",
133+
"HASH",
134+
"prefixes",
135+
["p1"],
136+
"default_score",
137+
1,
138+
]
132139
assert i1info["num_docs"] == 10
133140
assert sorted(i1info["attributes"]) == [
134141
["identifier", "f1", "attribute", "f1", "type", "TEXT"],
135142
["identifier", "f2", "attribute", "f2", "type", "NUMERIC", "SORTABLE", "blocksize", "7000"],
136143
]
137144

138145
i2info = await i2.info()
139-
assert i2info["index_definition"] == ["key_type", "HASH", "prefix", "p2", "default_score", 1]
146+
assert i2info["index_definition"] == [
147+
"key_type",
148+
"HASH",
149+
"prefixes",
150+
["p2"],
151+
"default_score",
152+
1,
153+
]
140154
assert i2info["num_docs"] == 15
141155
assert sorted(i2info["attributes"]) == [
142156
[

0 commit comments

Comments
 (0)