| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| #include "search/plan_executor.h" |
| |
| #include <gtest/gtest.h> |
| |
| #include <memory> |
| |
| #include "config/config.h" |
| #include "search/executors/mock_executor.h" |
| #include "search/indexer.h" |
| #include "search/interval.h" |
| #include "search/ir.h" |
| #include "search/ir_plan.h" |
| #include "search/value.h" |
| #include "string_util.h" |
| #include "test_base.h" |
| #include "types/redis_json.h" |
| |
| using namespace kqir; |
| |
| static auto exe_end = ExecutorNode::Result(ExecutorNode::end); |
| |
| static IndexMap MakeIndexMap() { |
| auto f1 = FieldInfo("f1", std::make_unique<redis::TagFieldMetadata>()); |
| auto f2 = FieldInfo("f2", std::make_unique<redis::NumericFieldMetadata>()); |
| auto f3 = FieldInfo("f3", std::make_unique<redis::NumericFieldMetadata>()); |
| |
| auto hnsw_field_meta = std::make_unique<redis::HnswVectorFieldMetadata>(); |
| hnsw_field_meta->vector_type = redis::VectorType::FLOAT64; |
| hnsw_field_meta->dim = 3; |
| hnsw_field_meta->distance_metric = redis::DistanceMetric::L2; |
| auto f4 = FieldInfo("f4", std::move(hnsw_field_meta)); |
| |
| auto ia = std::make_unique<IndexInfo>("ia", redis::IndexMetadata(), "search_ns"); |
| ia->metadata.on_data_type = redis::IndexOnDataType::JSON; |
| ia->prefixes.prefixes.emplace_back("test2:"); |
| ia->prefixes.prefixes.emplace_back("test4:"); |
| ia->Add(std::move(f1)); |
| ia->Add(std::move(f2)); |
| ia->Add(std::move(f3)); |
| ia->Add(std::move(f4)); |
| |
| IndexMap res; |
| res.Insert(std::move(ia)); |
| return res; |
| } |
| |
| static auto index_map = MakeIndexMap(); |
| |
| static auto NextRow(ExecutorContext& ctx) { |
| auto n = ctx.Next(); |
| EXPECT_EQ(n.Msg(), Status::ok_msg); |
| auto v = std::move(n).GetValue(); |
| EXPECT_EQ(v.index(), 1); |
| return std::get<ExecutorNode::RowType>(std::move(v)); |
| } |
| |
| TEST(PlanExecutorTest, Mock) { |
| auto op = std::make_unique<Mock>(std::vector<ExecutorNode::RowType>{}); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| |
| op = std::make_unique<Mock>(std::vector<ExecutorNode::RowType>{{"a"}, {"b"}, {"c"}}); |
| |
| ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| static auto IndexI() -> const IndexInfo* { return index_map.Find("ia", "search_ns")->second.get(); } |
| static auto FieldI(const std::string& f) -> const FieldInfo* { return &IndexI()->fields.at(f); } |
| |
| static auto N(double n) { return MakeValue<Numeric>(n); } |
| static auto T(const std::string& v) { return MakeValue<StringArray>(util::Split(v, ",")); } |
| static auto V(const std::vector<double>& vals) { return MakeValue<NumericArray>(vals); } |
| |
| TEST(PlanExecutorTest, TopN) { |
| std::vector<ExecutorNode::RowType> data{ |
| {"a", {{FieldI("f3"), N(4)}}, IndexI()}, {"b", {{FieldI("f3"), N(2)}}, IndexI()}, |
| {"c", {{FieldI("f3"), N(7)}}, IndexI()}, {"d", {{FieldI("f3"), N(3)}}, IndexI()}, |
| {"e", {{FieldI("f3"), N(1)}}, IndexI()}, {"f", {{FieldI("f3"), N(6)}}, IndexI()}, |
| {"g", {{FieldI("f3"), N(8)}}, IndexI()}, |
| }; |
| { |
| auto op = std::make_unique<TopN>( |
| std::make_unique<Mock>(data), |
| std::make_unique<SortByClause>(SortByClause::ASC, std::make_unique<FieldRef>("f3", FieldI("f3"))), |
| std::make_unique<LimitClause>(0, 4)); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "e"); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "d"); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| { |
| auto op = std::make_unique<TopN>( |
| std::make_unique<Mock>(data), |
| std::make_unique<SortByClause>(SortByClause::ASC, std::make_unique<FieldRef>("f3", FieldI("f3"))), |
| std::make_unique<LimitClause>(1, 4)); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "d"); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "f"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| TEST(PlanExecutorTest, Filter) { |
| std::vector<ExecutorNode::RowType> data{ |
| {"a", {{FieldI("f3"), N(4)}}, IndexI()}, {"b", {{FieldI("f3"), N(2)}}, IndexI()}, |
| {"c", {{FieldI("f3"), N(7)}}, IndexI()}, {"d", {{FieldI("f3"), N(3)}}, IndexI()}, |
| {"e", {{FieldI("f3"), N(1)}}, IndexI()}, {"f", {{FieldI("f3"), N(6)}}, IndexI()}, |
| {"g", {{FieldI("f3"), N(8)}}, IndexI()}, |
| }; |
| { |
| auto field = std::make_unique<FieldRef>("f3", FieldI("f3")); |
| auto op = std::make_unique<Filter>( |
| std::make_unique<Mock>(data), |
| AndExpr::Create(Node::List<QueryExpr>( |
| std::make_unique<NumericCompareExpr>(NumericCompareExpr::GT, field->CloneAs<FieldRef>(), |
| std::make_unique<NumericLiteral>(2)), |
| std::make_unique<NumericCompareExpr>(NumericCompareExpr::LET, field->CloneAs<FieldRef>(), |
| std::make_unique<NumericLiteral>(6))))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "d"); |
| ASSERT_EQ(NextRow(ctx).key, "f"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| { |
| auto field = std::make_unique<FieldRef>("f3", FieldI("f3")); |
| auto op = std::make_unique<Filter>( |
| std::make_unique<Mock>(data), |
| OrExpr::Create(Node::List<QueryExpr>( |
| std::make_unique<NumericCompareExpr>(NumericCompareExpr::GET, field->CloneAs<FieldRef>(), |
| std::make_unique<NumericLiteral>(6)), |
| std::make_unique<NumericCompareExpr>(NumericCompareExpr::LT, field->CloneAs<FieldRef>(), |
| std::make_unique<NumericLiteral>(2))))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(NextRow(ctx).key, "e"); |
| ASSERT_EQ(NextRow(ctx).key, "f"); |
| ASSERT_EQ(NextRow(ctx).key, "g"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| data = {{"a", {{FieldI("f1"), T("cpp,java")}}, IndexI()}, {"b", {{FieldI("f1"), T("python,cpp,c")}}, IndexI()}, |
| {"c", {{FieldI("f1"), T("c,perl")}}, IndexI()}, {"d", {{FieldI("f1"), T("rust,python")}}, IndexI()}, |
| {"e", {{FieldI("f1"), T("java,kotlin")}}, IndexI()}, {"f", {{FieldI("f1"), T("c,rust")}}, IndexI()}, |
| {"g", {{FieldI("f1"), T("c,cpp,java")}}, IndexI()}}; |
| { |
| auto field = std::make_unique<FieldRef>("f1", FieldI("f1")); |
| auto op = std::make_unique<Filter>( |
| std::make_unique<Mock>(data), |
| AndExpr::Create(Node::List<QueryExpr>( |
| std::make_unique<TagContainExpr>(field->CloneAs<FieldRef>(), std::make_unique<StringLiteral>("c")), |
| std::make_unique<TagContainExpr>(field->CloneAs<FieldRef>(), std::make_unique<StringLiteral>("cpp"))))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "g"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| { |
| auto field = std::make_unique<FieldRef>("f1", FieldI("f1")); |
| auto op = std::make_unique<Filter>( |
| std::make_unique<Mock>(data), |
| OrExpr::Create(Node::List<QueryExpr>( |
| std::make_unique<TagContainExpr>(field->CloneAs<FieldRef>(), std::make_unique<StringLiteral>("rust")), |
| std::make_unique<TagContainExpr>(field->CloneAs<FieldRef>(), std::make_unique<StringLiteral>("perl"))))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(NextRow(ctx).key, "d"); |
| ASSERT_EQ(NextRow(ctx).key, "f"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| data = {{"a", {{FieldI("f4"), V({1, 2, 3})}}, IndexI()}, {"b", {{FieldI("f4"), V({9, 10, 11})}}, IndexI()}, |
| {"c", {{FieldI("f4"), V({4, 5, 6})}}, IndexI()}, {"d", {{FieldI("f4"), V({1, 2, 3})}}, IndexI()}, |
| {"e", {{FieldI("f4"), V({2, 3, 4})}}, IndexI()}, {"f", {{FieldI("f4"), V({12, 13, 14})}}, IndexI()}, |
| {"g", {{FieldI("f4"), V({1, 2, 3})}}, IndexI()}}; |
| { |
| auto field = std::make_unique<FieldRef>("f4", FieldI("f4")); |
| std::vector<double> vector = {11, 12, 13}; |
| auto op = std::make_unique<Filter>( |
| std::make_unique<Mock>(data), |
| std::make_unique<VectorRangeExpr>(field->CloneAs<FieldRef>(), std::make_unique<NumericLiteral>(4), |
| std::make_unique<VectorLiteral>(std::move(vector)))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "f"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| auto field = std::make_unique<FieldRef>("f4", FieldI("f4")); |
| std::vector<double> vector = {2, 3, 4}; |
| auto op = std::make_unique<Filter>( |
| std::make_unique<Mock>(data), |
| std::make_unique<VectorRangeExpr>(field->CloneAs<FieldRef>(), std::make_unique<NumericLiteral>(5), |
| std::make_unique<VectorLiteral>(std::move(vector)))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(NextRow(ctx).key, "d"); |
| ASSERT_EQ(NextRow(ctx).key, "e"); |
| ASSERT_EQ(NextRow(ctx).key, "g"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| TEST(PlanExecutorTest, Limit) { |
| std::vector<ExecutorNode::RowType> data{ |
| {"a", {{FieldI("f3"), N(4)}}, IndexI()}, {"b", {{FieldI("f3"), N(2)}}, IndexI()}, |
| {"c", {{FieldI("f3"), N(7)}}, IndexI()}, {"d", {{FieldI("f3"), N(3)}}, IndexI()}, |
| {"e", {{FieldI("f3"), N(1)}}, IndexI()}, {"f", {{FieldI("f3"), N(6)}}, IndexI()}, |
| {"g", {{FieldI("f3"), N(8)}}, IndexI()}, |
| }; |
| { |
| auto op = std::make_unique<Limit>(std::make_unique<Mock>(data), std::make_unique<LimitClause>(1, 2)); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| { |
| auto field = std::make_unique<FieldRef>("f3", FieldI("f3")); |
| auto op = std::make_unique<Limit>(std::make_unique<Mock>(data), std::make_unique<LimitClause>(0, 3)); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| TEST(PlanExecutorTest, Merge) { |
| std::vector<ExecutorNode::RowType> data1{ |
| {"a", {{FieldI("f3"), N(4)}}, IndexI()}, |
| {"b", {{FieldI("f3"), N(2)}}, IndexI()}, |
| }; |
| std::vector<ExecutorNode::RowType> data2{{"c", {{FieldI("f3"), N(7)}}, IndexI()}, |
| {"d", {{FieldI("f3"), N(3)}}, IndexI()}, |
| {"e", {{FieldI("f3"), N(1)}}, IndexI()}}; |
| { |
| auto op = |
| std::make_unique<Merge>(Node::List<PlanOperator>(std::make_unique<Mock>(data1), std::make_unique<Mock>(data2))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(NextRow(ctx).key, "c"); |
| ASSERT_EQ(NextRow(ctx).key, "d"); |
| ASSERT_EQ(NextRow(ctx).key, "e"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| { |
| auto op = std::make_unique<Merge>( |
| Node::List<PlanOperator>(std::make_unique<Mock>(decltype(data1){}), std::make_unique<Mock>(data1))); |
| |
| auto ctx = ExecutorContext(op.get()); |
| ASSERT_EQ(NextRow(ctx).key, "a"); |
| ASSERT_EQ(NextRow(ctx).key, "b"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| class PlanExecutorTestC : public TestBase { |
| protected: |
| explicit PlanExecutorTestC() : json_(std::make_unique<redis::Json>(storage_.get(), "search_ns")) {} |
| ~PlanExecutorTestC() override = default; |
| |
| void SetUp() override {} |
| void TearDown() override {} |
| |
| std::unique_ptr<redis::Json> json_; |
| }; |
| |
| TEST_F(PlanExecutorTestC, FullIndexScan) { |
| json_->Set(*ctx_, "test1:a", "$", "{}"); |
| json_->Set(*ctx_, "test1:b", "$", "{}"); |
| json_->Set(*ctx_, "test2:c", "$", "{\"f3\": 6}"); |
| json_->Set(*ctx_, "test3:d", "$", "{}"); |
| json_->Set(*ctx_, "test4:e", "$", "{\"f3\": 7}"); |
| json_->Set(*ctx_, "test4:f", "$", "{\"f3\": 2}"); |
| json_->Set(*ctx_, "test4:g", "$", "{\"f3\": 8}"); |
| json_->Set(*ctx_, "test5:h", "$", "{}"); |
| json_->Set(*ctx_, "test5:i", "$", "{}"); |
| json_->Set(*ctx_, "test5:g", "$", "{}"); |
| |
| { |
| auto op = std::make_unique<FullIndexScan>(std::make_unique<IndexRef>("ia", IndexI())); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(NextRow(ctx).key, "test4:e"); |
| ASSERT_EQ(NextRow(ctx).key, "test4:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test4:g"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| auto op = std::make_unique<Filter>( |
| std::make_unique<FullIndexScan>(std::make_unique<IndexRef>("ia", IndexI())), |
| std::make_unique<NumericCompareExpr>(NumericCompareExpr::GT, std::make_unique<FieldRef>("f3", FieldI("f3")), |
| std::make_unique<NumericLiteral>(3))); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(NextRow(ctx).key, "test4:e"); |
| ASSERT_EQ(NextRow(ctx).key, "test4:g"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| struct ScopedUpdate { |
| engine::Context* db_ctx; |
| redis::GlobalIndexer::RecordResult rr; |
| std::string_view key; |
| std::string ns; |
| |
| static auto Create(engine::Context& ctx, redis::GlobalIndexer& indexer, std::string_view key, const std::string& ns) { |
| auto s = indexer.Record(ctx, key, ns); |
| EXPECT_EQ(s.Msg(), Status::ok_msg); |
| return *s; |
| } |
| |
| ScopedUpdate(engine::Context* ctx, redis::GlobalIndexer& indexer, std::string_view key, const std::string& ns) |
| : db_ctx(ctx), rr(Create(*ctx, indexer, key, ns)), key(key), ns(ns) {} |
| |
| ScopedUpdate(const ScopedUpdate&) = delete; |
| ScopedUpdate(ScopedUpdate&&) = delete; |
| ScopedUpdate& operator=(const ScopedUpdate&) = delete; |
| ScopedUpdate& operator=(ScopedUpdate&&) = delete; |
| |
| ~ScopedUpdate() { |
| auto s = redis::GlobalIndexer::Update(*db_ctx, rr); |
| EXPECT_EQ(s.Msg(), Status::ok_msg); |
| } |
| }; |
| |
| std::vector<std::unique_ptr<ScopedUpdate>> ScopedUpdates(engine::Context& ctx, redis::GlobalIndexer& indexer, |
| const std::vector<std::string_view>& keys, |
| const std::string& ns) { |
| std::vector<std::unique_ptr<ScopedUpdate>> sus; |
| |
| sus.reserve(keys.size()); |
| for (auto key : keys) { |
| sus.emplace_back(std::make_unique<ScopedUpdate>(&ctx, indexer, key, ns)); |
| } |
| |
| return sus; |
| } |
| |
| TEST_F(PlanExecutorTestC, NumericFieldScan) { |
| redis::GlobalIndexer indexer(storage_.get()); |
| indexer.Add(std::make_unique<redis::IndexUpdater>(IndexI())); |
| |
| { |
| engine::Context ctx(storage_.get()); |
| auto updates = ScopedUpdates( |
| *ctx_, indexer, {"test2:a", "test2:b", "test2:c", "test2:d", "test2:e", "test2:f", "test2:g"}, "search_ns"); |
| json_->Set(*ctx_, "test2:a", "$", "{\"f2\": 6}"); |
| json_->Set(*ctx_, "test2:b", "$", "{\"f2\": 3}"); |
| json_->Set(*ctx_, "test2:c", "$", "{\"f2\": 8}"); |
| json_->Set(*ctx_, "test2:d", "$", "{\"f2\": 14}"); |
| json_->Set(*ctx_, "test2:e", "$", "{\"f2\": 1}"); |
| json_->Set(*ctx_, "test2:f", "$", "{\"f2\": 3}"); |
| json_->Set(*ctx_, "test2:g", "$", "{\"f2\": 9}"); |
| } |
| |
| { |
| auto op = std::make_unique<NumericFieldScan>(std::make_unique<FieldRef>("f2", FieldI("f2")), Interval(3, 9), |
| SortByClause::ASC); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:b"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:a"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| auto op = std::make_unique<NumericFieldScan>(std::make_unique<FieldRef>("f2", FieldI("f2")), Interval(3, 9), |
| SortByClause::DESC); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:a"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:b"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| TEST_F(PlanExecutorTestC, TagFieldScan) { |
| redis::GlobalIndexer indexer(storage_.get()); |
| indexer.Add(std::make_unique<redis::IndexUpdater>(IndexI())); |
| |
| { |
| engine::Context ctx(storage_.get()); |
| auto updates = ScopedUpdates( |
| *ctx_, indexer, {"test2:a", "test2:b", "test2:c", "test2:d", "test2:e", "test2:f", "test2:g"}, "search_ns"); |
| json_->Set(*ctx_, "test2:a", "$", "{\"f1\": \"c,cpp,java\"}"); |
| json_->Set(*ctx_, "test2:b", "$", "{\"f1\": \"python,c\"}"); |
| json_->Set(*ctx_, "test2:c", "$", "{\"f1\": \"java,scala\"}"); |
| json_->Set(*ctx_, "test2:d", "$", "{\"f1\": \"rust,python,perl\"}"); |
| json_->Set(*ctx_, "test2:e", "$", "{\"f1\": \"python,cpp\"}"); |
| json_->Set(*ctx_, "test2:f", "$", "{\"f1\": \"c,cpp\"}"); |
| json_->Set(*ctx_, "test2:g", "$", "{\"f1\": \"cpp,rust\"}"); |
| } |
| |
| { |
| auto op = std::make_unique<TagFieldScan>(std::make_unique<FieldRef>("f1", FieldI("f1")), "cpp"); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:a"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:e"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:g"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| auto op = std::make_unique<TagFieldScan>(std::make_unique<FieldRef>("f1", FieldI("f1")), "python"); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:b"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:d"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:e"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |
| |
| TEST_F(PlanExecutorTestC, HnswVectorFieldScans) { |
| redis::GlobalIndexer indexer(storage_.get()); |
| indexer.Add(std::make_unique<redis::IndexUpdater>(IndexI())); |
| |
| { |
| auto updates = ScopedUpdates(*ctx_, indexer, |
| {"test2:a", "test2:b", "test2:c", "test2:d", "test2:e", "test2:f", "test2:g", |
| "test2:h", "test2:i", "test2:j", "test2:k", "test2:l", "test2:m", "test2:n"}, |
| "search_ns"); |
| json_->Set(*ctx_, "test2:a", "$", "{\"f4\": [1,2,3]}"); |
| json_->Set(*ctx_, "test2:b", "$", "{\"f4\": [4,5,6]}"); |
| json_->Set(*ctx_, "test2:c", "$", "{\"f4\": [7,8,9]}"); |
| json_->Set(*ctx_, "test2:d", "$", "{\"f4\": [10,11,12]}"); |
| json_->Set(*ctx_, "test2:e", "$", "{\"f4\": [13,14,15]}"); |
| json_->Set(*ctx_, "test2:f", "$", "{\"f4\": [23,24,25]}"); |
| json_->Set(*ctx_, "test2:g", "$", "{\"f4\": [26,27,28]}"); |
| json_->Set(*ctx_, "test2:h", "$", "{\"f4\": [77,78,79]}"); |
| json_->Set(*ctx_, "test2:i", "$", "{\"f4\": [80,81,82]}"); |
| json_->Set(*ctx_, "test2:j", "$", "{\"f4\": [83,84,85]}"); |
| json_->Set(*ctx_, "test2:k", "$", "{\"f4\": [86,87,88]}"); |
| json_->Set(*ctx_, "test2:l", "$", "{\"f4\": [89,90,91]}"); |
| json_->Set(*ctx_, "test2:m", "$", "{\"f4\": [1026,1027,1028]}"); |
| json_->Set(*ctx_, "test2:n", "$", "{\"f4\": [2226,2227,2228]}"); |
| } |
| |
| { |
| std::vector<double> target_vector = {14, 15, 16}; |
| auto op = |
| std::make_unique<HnswVectorFieldKnnScan>(std::make_unique<FieldRef>("f4", FieldI("f4")), target_vector, 5); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:e"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:d"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:b"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| std::vector<double> target_vector = {24, 25, 26}; |
| auto op = |
| std::make_unique<HnswVectorFieldKnnScan>(std::make_unique<FieldRef>("f4", FieldI("f4")), target_vector, 3); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:g"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:e"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| std::vector<double> query_vector = {11, 12, 13}; |
| auto op = |
| std::make_unique<HnswVectorFieldRangeScan>(std::make_unique<FieldRef>("f4", FieldI("f4")), query_vector, 25); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:d"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:e"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:b"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:a"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| |
| { |
| std::vector<double> query_vector = {12, 13, 14}; |
| auto op = |
| std::make_unique<HnswVectorFieldRangeScan>(std::make_unique<FieldRef>("f4", FieldI("f4")), query_vector, 5000); |
| |
| auto ctx = ExecutorContext(op.get(), storage_.get()); |
| ASSERT_EQ(NextRow(ctx).key, "test2:e"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:d"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:c"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:b"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:a"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:f"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:g"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:h"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:i"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:j"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:k"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:l"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:m"); |
| ASSERT_EQ(NextRow(ctx).key, "test2:n"); |
| ASSERT_EQ(ctx.Next().GetValue(), exe_end); |
| } |
| } |