milvus/internal/core/unittest/test_expr.cpp
smellthemoon 3389a6b500
enhance: support null in text match index (#37517)
#37508

Signed-off-by: lixinguo <xinguo.li@zilliz.com>
Co-authored-by: lixinguo <xinguo.li@zilliz.com>
2024-11-13 11:08:29 +08:00

13971 lines
520 KiB
C++

// Copyright (C) 2019-2020 Zilliz. All rights reserved.
//
// Licensed 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 <boost/format.hpp>
#include <fstream>
#include <gtest/gtest.h>
#include <cstdint>
#include <memory>
#include <regex>
#include <string>
#include <vector>
#include <chrono>
#include <roaring/roaring.hh>
#include "common/Json.h"
#include "common/Types.h"
#include "pb/plan.pb.h"
#include "query/Plan.h"
#include "query/PlanNode.h"
#include "query/PlanProto.h"
#include "query/ExecPlanNodeVisitor.h"
#include "segcore/SegmentGrowingImpl.h"
#include "simdjson/padded_string.h"
#include "segcore/segment_c.h"
#include "test_utils/DataGen.h"
#include "index/IndexFactory.h"
#include "exec/expression/Expr.h"
#include "exec/Task.h"
#include "exec/expression/function/FunctionFactory.h"
#include "expr/ITypeExpr.h"
#include "index/BitmapIndex.h"
#include "index/InvertedIndexTantivy.h"
using namespace milvus;
using namespace milvus::query;
using namespace milvus::segcore;
class ExprTest : public ::testing::TestWithParam<
std::pair<milvus::DataType, knowhere::MetricType>> {
public:
void
SetUp() override {
auto param = GetParam();
data_type = param.first;
metric_type = param.second;
}
// replace the metric type in the plan string with the proper type
std::vector<char>
translate_text_plan_with_metric_type(std::string plan) {
return milvus::segcore::
replace_metric_and_translate_text_plan_to_binary_plan(
std::move(plan), metric_type);
}
milvus::DataType data_type;
knowhere::MetricType metric_type;
};
INSTANTIATE_TEST_SUITE_P(
ExprTestSuite,
ExprTest,
::testing::Values(
std::pair(milvus::DataType::VECTOR_FLOAT, knowhere::metric::L2),
std::pair(milvus::DataType::VECTOR_SPARSE_FLOAT, knowhere::metric::IP),
std::pair(milvus::DataType::VECTOR_BINARY, knowhere::metric::JACCARD)));
TEST_P(ExprTest, Range) {
SUCCEED();
using namespace milvus;
using namespace milvus::query;
using namespace milvus::segcore;
std::string raw_plan = R"(vector_anns: <
field_id: 100
predicates: <
binary_expr: <
op: LogicalAnd
left: <
unary_range_expr: <
column_info: <
field_id: 101
data_type: Int32
>
op: GreaterThan
value: <
int64_val: 1
>
>
>
right: <
unary_range_expr: <
column_info: <
field_id: 101
data_type: Int32
>
op: LessThan
value: <
int64_val: 100
>
>
>
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto schema = std::make_shared<Schema>();
schema->AddDebugField("fakevec", data_type, 16, metric_type);
schema->AddDebugField("age", DataType::INT32);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
Assert(plan->tag2field_.at("$0") ==
schema->get_field_id(FieldName("fakevec")));
}
TEST_P(ExprTest, InvalidRange) {
SUCCEED();
std::string raw_plan = R"(vector_anns: <
field_id: 100
predicates: <
binary_expr: <
op: LogicalAnd
left: <
unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: GreaterThan
value: <
int64_val: 1
>
>
>
right: <
unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: LessThan
value: <
int64_val: 100
>
>
>
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto schema = std::make_shared<Schema>();
schema->AddDebugField("fakevec", data_type, 16, metric_type);
schema->AddDebugField("age", DataType::INT32);
ASSERT_ANY_THROW(
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size()));
}
TEST_P(ExprTest, ShowExecutor) {
auto node = std::make_unique<FloatVectorANNS>();
auto schema = std::make_shared<Schema>();
auto field_id =
schema->AddDebugField("fakevec", data_type, 16, metric_type);
int64_t num_queries = 100L;
auto raw_data = DataGen(schema, num_queries);
auto& info = node->search_info_;
info.metric_type_ = metric_type;
info.topk_ = 20;
info.field_id_ = field_id;
}
TEST_P(ExprTest, TestRange) {
std::vector<std::tuple<std::string, std::function<bool(int)>>> testcases = {
{R"(binary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
lower_inclusive: false,
upper_inclusive: false,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v) { return 2000 < v && v < 3000; }},
{R"(binary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
lower_inclusive: true,
upper_inclusive: false,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v) { return 2000 <= v && v < 3000; }},
{R"(binary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
lower_inclusive: false,
upper_inclusive: true,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v) { return 2000 < v && v <= 3000; }},
{R"(binary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
lower_inclusive: true,
upper_inclusive: true,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v) { return 2000 <= v && v <= 3000; }},
{R"(unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: GreaterEqual,
value: <
int64_val: 2000
>
>)",
[](int v) { return v >= 2000; }},
{R"(unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: GreaterThan,
value: <
int64_val: 2000
>
>)",
[](int v) { return v > 2000; }},
{R"(unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: LessEqual,
value: <
int64_val: 2000
>
>)",
[](int v) { return v <= 2000; }},
{R"(unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: LessThan,
value: <
int64_val: 2000
>
>)",
[](int v) { return v < 2000; }},
{R"(unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: Equal,
value: <
int64_val: 2000
>
>)",
[](int v) { return v == 2000; }},
{R"(unary_range_expr: <
column_info: <
field_id: 101
data_type: Int64
>
op: NotEqual,
value: <
int64_val: 2000
>
>)",
[](int v) { return v != 2000; }},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
@@@@
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> age_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age_col = raw_data.get_col<int>(i64_fid);
age_col.insert(age_col.end(), new_age_col.begin(), new_age_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = age_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
}
}
}
TEST_P(ExprTest, TestRangeNullable) {
std::vector<std::tuple<std::string, std::function<bool(int, bool)>>>
testcases = {
{R"(binary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
lower_inclusive: false,
upper_inclusive: false,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return 2000 < v && v < 3000;
}},
{R"(binary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
lower_inclusive: true,
upper_inclusive: false,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return 2000 <= v && v < 3000;
}},
{R"(binary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
lower_inclusive: false,
upper_inclusive: true,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return 2000 < v && v <= 3000;
}},
{R"(binary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
lower_inclusive: true,
upper_inclusive: true,
lower_value: <
int64_val: 2000
>
upper_value: <
int64_val: 3000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return 2000 <= v && v <= 3000;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: GreaterEqual,
value: <
int64_val: 2000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v >= 2000;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: GreaterThan,
value: <
int64_val: 2000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v > 2000;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: LessEqual,
value: <
int64_val: 2000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v <= 2000;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: LessThan,
value: <
int64_val: 2000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v < 2000;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: Equal,
value: <
int64_val: 2000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v == 2000;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 102
data_type: Int64
>
op: NotEqual,
value: <
int64_val: 2000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v != 2000;
}},
{R"(binary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
lower_inclusive: false,
upper_inclusive: false,
lower_value: <
int64_val: 1000000
>
upper_value: <
int64_val: 1000001
>
>)",
[](int v, bool valid) { return false; }},
{R"(binary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
lower_inclusive: false,
upper_inclusive: false,
lower_value: <
int64_val: -1000001
>
upper_value: <
int64_val: -1000000
>
>)",
[](int v, bool valid) { return false; }},
{R"(unary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
op: GreaterEqual,
value: <
int64_val: 1000000
>
>)",
[](int v, bool valid) { return false; }},
{R"(unary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
op: GreaterEqual,
value: <
int64_val: -1000000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return true;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
op: LessEqual,
value: <
int64_val: 1000000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return true;
}},
{R"(unary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
op: LessThan,
value: <
int64_val: -1000000
>
>)",
[](int v, bool valid) { return false; }},
{R"(unary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
op: Equal,
value: <
int64_val: 1000000
>
>)",
[](int v, bool valid) { return false; }},
{R"(unary_range_expr: <
column_info: <
field_id: 103
data_type: Int8
>
op: NotEqual,
value: <
int64_val: 1000000
>
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return true;
}},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
@@@@
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto nullable_fid =
schema->AddDebugField("nullable", DataType::INT64, true);
auto nullable_fid_pre_check =
schema->AddDebugField("pre_check", DataType::INT8, true);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> data_col;
FixedVector<bool> valid_data_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_data_col = raw_data.get_col<int>(i64_fid);
valid_data_col = raw_data.get_col_valid(nullable_fid);
data_col.insert(
data_col.end(), new_data_col.begin(), new_data_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = data_col[i];
auto valid_data = valid_data_col[i];
auto ref = ref_func(val, valid_data);
ASSERT_EQ(ans, ref)
<< clause << "@" << i << "!!" << val << "!!" << valid_data;
}
}
}
TEST_P(ExprTest, TestBinaryRangeJSON) {
struct Testcase {
bool lower_inclusive;
bool upper_inclusive;
int64_t lower;
int64_t upper;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{true, false, 10, 20, {"int"}},
{true, true, 20, 30, {"int"}},
{false, true, 30, 40, {"int"}},
{false, false, 40, 50, {"int"}},
{true, false, 10, 20, {"double"}},
{true, true, 20, 30, {"double"}},
{false, true, 30, 40, {"double"}},
{false, false, 40, 50, {"double"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
for (auto testcase : testcases) {
auto check = [&](int64_t value) {
int64_t lower = testcase.lower, upper = testcase.upper;
if (!testcase.lower_inclusive) {
lower++;
}
if (!testcase.upper_inclusive) {
upper--;
}
return lower <= value && value <= upper;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
milvus::proto::plan::GenericValue lower_val;
lower_val.set_int64_val(testcase.lower);
milvus::proto::plan::GenericValue upper_val;
upper_val.set_int64_val(testcase.upper);
auto expr = std::make_shared<milvus::expr::BinaryRangeFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
lower_val,
upper_val,
testcase.lower_inclusive,
testcase.upper_inclusive);
auto plannode =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final = ExecuteQueryExpr(
plannode, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (testcase.nested_path[0] == "int") {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>(pointer)
.value();
auto ref = check(val);
ASSERT_EQ(ans, ref)
<< val << testcase.lower_inclusive << testcase.lower
<< testcase.upper_inclusive << testcase.upper;
} else {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>(pointer)
.value();
auto ref = check(val);
ASSERT_EQ(ans, ref)
<< val << testcase.lower_inclusive << testcase.lower
<< testcase.upper_inclusive << testcase.upper;
}
}
}
}
TEST_P(ExprTest, TestBinaryRangeJSONNullable) {
struct Testcase {
bool lower_inclusive;
bool upper_inclusive;
int64_t lower;
int64_t upper;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{true, false, 10, 20, {"int"}},
{true, true, 20, 30, {"int"}},
{false, true, 30, 40, {"int"}},
{false, false, 40, 50, {"int"}},
{true, false, 10, 20, {"double"}},
{true, true, 20, 30, {"double"}},
{false, true, 30, 40, {"double"}},
{false, false, 40, 50, {"double"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
valid_data_col = raw_data.get_col_valid(json_fid);
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
for (auto testcase : testcases) {
auto check = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
int64_t lower = testcase.lower, upper = testcase.upper;
if (!testcase.lower_inclusive) {
lower++;
}
if (!testcase.upper_inclusive) {
upper--;
}
return lower <= value && value <= upper;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
RetrievePlanNode plan;
milvus::proto::plan::GenericValue lower_val;
lower_val.set_int64_val(testcase.lower);
milvus::proto::plan::GenericValue upper_val;
upper_val.set_int64_val(testcase.upper);
auto expr = std::make_shared<milvus::expr::BinaryRangeFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
lower_val,
upper_val,
testcase.lower_inclusive,
testcase.upper_inclusive);
BitsetType final;
auto plannode =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(
plannode, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (testcase.nested_path[0] == "int") {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>(pointer)
.value();
auto ref = check(val, valid_data_col[i]);
ASSERT_EQ(ans, ref)
<< val << testcase.lower_inclusive << testcase.lower
<< testcase.upper_inclusive << testcase.upper;
} else {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>(pointer)
.value();
auto ref = check(val, valid_data_col[i]);
ASSERT_EQ(ans, ref)
<< val << testcase.lower_inclusive << testcase.lower
<< testcase.upper_inclusive << testcase.upper;
}
}
}
}
TEST_P(ExprTest, TestExistsJson) {
struct Testcase {
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{{"A"}},
{{"int"}},
{{"double"}},
{{"B"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto testcase : testcases) {
auto check = [&](bool value) { return value; };
RetrievePlanNode plan;
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr =
std::make_shared<milvus::expr::ExistsExpr>(milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path));
auto plannode =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final = ExecuteQueryExpr(
plannode, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist(pointer);
auto ref = check(val);
ASSERT_EQ(ans, ref);
}
}
}
TEST_P(ExprTest, TestExistsJsonNullable) {
struct Testcase {
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{{"A"}},
{{"int"}},
{{"double"}},
{{"B"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
valid_data_col = raw_data.get_col_valid(json_fid);
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto testcase : testcases) {
auto check = [&](bool value, bool valid) {
if (!valid) {
return false;
}
return value;
};
RetrievePlanNode plan;
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr =
std::make_shared<milvus::expr::ExistsExpr>(milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path));
auto plannode =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final = ExecuteQueryExpr(
plannode, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist(pointer);
auto ref = check(val, valid_data_col[i]);
ASSERT_EQ(ans, ref);
}
}
}
template <typename T>
T
GetValueFromProto(const milvus::proto::plan::GenericValue& value_proto) {
if constexpr (std::is_same_v<T, bool>) {
Assert(value_proto.val_case() ==
milvus::proto::plan::GenericValue::kBoolVal);
return static_cast<T>(value_proto.bool_val());
} else if constexpr (std::is_integral_v<T>) {
Assert(value_proto.val_case() ==
milvus::proto::plan::GenericValue::kInt64Val);
return static_cast<T>(value_proto.int64_val());
} else if constexpr (std::is_floating_point_v<T>) {
Assert(value_proto.val_case() ==
milvus::proto::plan::GenericValue::kFloatVal);
return static_cast<T>(value_proto.float_val());
} else if constexpr (std::is_same_v<T, std::string>) {
Assert(value_proto.val_case() ==
milvus::proto::plan::GenericValue::kStringVal);
return static_cast<T>(value_proto.string_val());
} else if constexpr (std::is_same_v<T, milvus::proto::plan::Array>) {
Assert(value_proto.val_case() ==
milvus::proto::plan::GenericValue::kArrayVal);
return static_cast<T>(value_proto.array_val());
} else if constexpr (std::is_same_v<T, milvus::proto::plan::GenericValue>) {
return static_cast<T>(value_proto);
} else {
PanicInfo(milvus::ErrorCode::UnexpectedError,
"unsupported generic value type");
}
};
TEST_P(ExprTest, TestUnaryRangeJson) {
struct Testcase {
int64_t val;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{10, {"int"}},
{20, {"int"}},
{30, {"int"}},
{40, {"int"}},
{10, {"double"}},
{20, {"double"}},
{30, {"double"}},
{40, {"double"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
std::vector<OpType> ops{
OpType::Equal,
OpType::NotEqual,
OpType::GreaterThan,
OpType::GreaterEqual,
OpType::LessThan,
OpType::LessEqual,
};
for (const auto& testcase : testcases) {
auto check = [&](int64_t value) { return value == testcase.val; };
std::function<bool(int64_t)> f = check;
for (auto& op : ops) {
switch (op) {
case OpType::Equal: {
f = [&](int64_t value) { return value == testcase.val; };
break;
}
case OpType::NotEqual: {
f = [&](int64_t value) { return value != testcase.val; };
break;
}
case OpType::GreaterEqual: {
f = [&](int64_t value) { return value >= testcase.val; };
break;
}
case OpType::GreaterThan: {
f = [&](int64_t value) { return value > testcase.val; };
break;
}
case OpType::LessEqual: {
f = [&](int64_t value) { return value <= testcase.val; };
break;
}
case OpType::LessThan: {
f = [&](int64_t value) { return value < testcase.val; };
break;
}
default: {
PanicInfo(Unsupported, "unsupported range node");
}
}
auto pointer = milvus::Json::pointer(testcase.nested_path);
proto::plan::GenericValue value;
value.set_int64_val(testcase.val);
auto expr = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
op,
value);
auto plan = std::make_shared<plan::FilterBitsNode>(
DEFAULT_PLANNODE_ID, expr);
auto final = ExecuteQueryExpr(
plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (testcase.nested_path[0] == "int") {
auto val =
milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>(pointer)
.value();
auto ref = f(val);
ASSERT_EQ(ans, ref);
} else {
auto val =
milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>(pointer)
.value();
auto ref = f(val);
ASSERT_EQ(ans, ref);
}
}
}
}
struct TestArrayCase {
proto::plan::GenericValue val;
std::vector<std::string> nested_path;
};
proto::plan::GenericValue value;
auto* arr = value.mutable_array_val();
arr->set_same_type(true);
proto::plan::GenericValue int_val1;
int_val1.set_int64_val(int64_t(1));
arr->add_array()->CopyFrom(int_val1);
proto::plan::GenericValue int_val2;
int_val2.set_int64_val(int64_t(2));
arr->add_array()->CopyFrom(int_val2);
proto::plan::GenericValue int_val3;
int_val3.set_int64_val(int64_t(3));
arr->add_array()->CopyFrom(int_val3);
std::vector<TestArrayCase> array_cases = {{value, {"array"}}};
for (const auto& testcase : array_cases) {
auto check = [&](OpType op) {
if (testcase.nested_path[0] == "array" && op == OpType::Equal) {
return true;
}
return false;
};
for (auto& op : ops) {
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
op,
testcase.val);
BitsetType final;
auto plan = std::make_shared<plan::FilterBitsNode>(
DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(
plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto ref = check(op);
ASSERT_EQ(ans, ref) << "@" << i << "op" << op;
}
}
}
}
TEST_P(ExprTest, TestUnaryRangeJsonNullable) {
struct Testcase {
int64_t val;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{10, {"int"}},
{20, {"int"}},
{30, {"int"}},
{40, {"int"}},
{10, {"double"}},
{20, {"double"}},
{30, {"double"}},
{40, {"double"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data_col = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
std::vector<OpType> ops{
OpType::Equal,
OpType::NotEqual,
OpType::GreaterThan,
OpType::GreaterEqual,
OpType::LessThan,
OpType::LessEqual,
};
for (const auto& testcase : testcases) {
auto check = [&](int64_t value, bool valid) {
return value == testcase.val;
};
std::function<bool(int64_t, bool)> f = check;
for (auto& op : ops) {
switch (op) {
case OpType::Equal: {
f = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
return value == testcase.val;
};
break;
}
case OpType::NotEqual: {
f = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
return value != testcase.val;
};
break;
}
case OpType::GreaterEqual: {
f = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
return value >= testcase.val;
};
break;
}
case OpType::GreaterThan: {
f = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
return value > testcase.val;
};
break;
}
case OpType::LessEqual: {
f = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
return value <= testcase.val;
};
break;
}
case OpType::LessThan: {
f = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
return value < testcase.val;
};
break;
}
default: {
PanicInfo(Unsupported, "unsupported range node");
}
}
auto pointer = milvus::Json::pointer(testcase.nested_path);
proto::plan::GenericValue value;
value.set_int64_val(testcase.val);
auto expr = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
op,
value);
BitsetType final;
auto plan = std::make_shared<plan::FilterBitsNode>(
DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(
plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (testcase.nested_path[0] == "int") {
auto val =
milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>(pointer)
.value();
auto ref = f(val, valid_data_col[i]);
ASSERT_EQ(ans, ref);
} else {
auto val =
milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>(pointer)
.value();
auto ref = f(val, valid_data_col[i]);
ASSERT_EQ(ans, ref);
}
}
}
}
struct TestArrayCase {
proto::plan::GenericValue val;
std::vector<std::string> nested_path;
};
proto::plan::GenericValue value;
auto* arr = value.mutable_array_val();
arr->set_same_type(true);
proto::plan::GenericValue int_val1;
int_val1.set_int64_val(int64_t(1));
arr->add_array()->CopyFrom(int_val1);
proto::plan::GenericValue int_val2;
int_val2.set_int64_val(int64_t(2));
arr->add_array()->CopyFrom(int_val2);
proto::plan::GenericValue int_val3;
int_val3.set_int64_val(int64_t(3));
arr->add_array()->CopyFrom(int_val3);
std::vector<TestArrayCase> array_cases = {{value, {"array"}}};
for (const auto& testcase : array_cases) {
auto check = [&](OpType op, bool valid) {
if (!valid) {
return false;
}
if (testcase.nested_path[0] == "array" && op == OpType::Equal) {
return true;
}
return false;
};
for (auto& op : ops) {
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
op,
testcase.val);
BitsetType final;
auto plan = std::make_shared<plan::FilterBitsNode>(
DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(
plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto ref = check(op, valid_data_col[i]);
ASSERT_EQ(ans, ref) << "@" << i << "op" << op;
}
}
}
}
TEST_P(ExprTest, TestTermJson) {
struct Testcase {
std::vector<int64_t> term;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{{1, 2, 3, 4}, {"int"}},
{{10, 100, 1000, 10000}, {"int"}},
{{100, 10000, 9999, 444}, {"int"}},
{{23, 42, 66, 17, 25}, {"int"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 100;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto testcase : testcases) {
auto check = [&](int64_t value) {
std::unordered_set<int64_t> term_set(testcase.term.begin(),
testcase.term.end());
return term_set.find(value) != term_set.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (const auto& val : testcase.term) {
proto::plan::GenericValue value;
value.set_int64_val(val);
values.push_back(value);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>(pointer)
.value();
auto ref = check(val);
ASSERT_EQ(ans, ref);
}
}
}
TEST_P(ExprTest, TestTermJsonNullable) {
struct Testcase {
std::vector<int64_t> term;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{{1, 2, 3, 4}, {"int"}},
{{10, 100, 1000, 10000}, {"int"}},
{{100, 10000, 9999, 444}, {"int"}},
{{23, 42, 66, 17, 25}, {"int"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data_col;
int num_iters = 100;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
auto new_valid_data_col = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
valid_data_col.insert(valid_data_col.end(),
new_valid_data_col.begin(),
new_valid_data_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto testcase : testcases) {
auto check = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
std::unordered_set<int64_t> term_set(testcase.term.begin(),
testcase.term.end());
return term_set.find(value) != term_set.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (const auto& val : testcase.term) {
proto::plan::GenericValue value;
value.set_int64_val(val);
values.push_back(value);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>(pointer)
.value();
auto ref = check(val, valid_data_col[i]);
ASSERT_EQ(ans, ref);
}
}
}
TEST_P(ExprTest, TestTerm) {
auto vec_2k_3k = [] {
std::string buf;
for (int i = 2000; i < 3000; ++i) {
buf += "values: < int64_val: " + std::to_string(i) + " >\n";
}
return buf;
}();
std::vector<std::tuple<std::string, std::function<bool(int)>>> testcases = {
{R"(values: <
int64_val: 2000
>
values: <
int64_val: 3000
>
)",
[](int v) { return v == 2000 || v == 3000; }},
{R"(values: <
int64_val: 2000
>)",
[](int v) { return v == 2000; }},
{R"(values: <
int64_val: 3000
>)",
[](int v) { return v == 3000; }},
{R"()", [](int v) { return false; }},
{vec_2k_3k, [](int v) { return 2000 <= v && v < 3000; }},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
term_expr: <
column_info: <
field_id: 101
data_type: Int64
>
@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> age_col;
int num_iters = 100;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age_col = raw_data.get_col<int>(i64_fid);
age_col.insert(age_col.end(), new_age_col.begin(), new_age_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = age_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
}
}
}
TEST_P(ExprTest, TestTermNullable) {
auto vec_2k_3k = [] {
std::string buf;
for (int i = 2000; i < 3000; ++i) {
buf += "values: < int64_val: " + std::to_string(i) + " >\n";
}
return buf;
}();
std::vector<std::tuple<std::string, std::function<bool(int, bool)>>>
testcases = {
{R"(values: <
int64_val: 2000
>
values: <
int64_val: 3000
>
)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v == 2000 || v == 3000;
}},
{R"(values: <
int64_val: 2000
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v == 2000;
}},
{R"(values: <
int64_val: 3000
>)",
[](int v, bool valid) {
if (!valid) {
return false;
}
return v == 3000;
}},
{R"()",
[](int v, bool valid) {
if (!valid) {
return false;
}
return false;
}},
{vec_2k_3k,
[](int v, bool valid) {
if (!valid) {
return false;
}
return 2000 <= v && v < 3000;
}},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
term_expr: <
column_info: <
field_id: 102
data_type: Int64
>
@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age", DataType::INT64);
auto nullable_fid =
schema->AddDebugField("nullable", DataType::INT64, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> nullable_col;
FixedVector<bool> valid_data_col;
int num_iters = 100;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_nullable_col = raw_data.get_col<int>(nullable_fid);
auto new_valid_data_col = raw_data.get_col_valid(nullable_fid);
valid_data_col.insert(valid_data_col.end(),
new_valid_data_col.begin(),
new_valid_data_col.end());
nullable_col.insert(nullable_col.end(),
new_nullable_col.begin(),
new_nullable_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = nullable_col[i];
auto ref = ref_func(val, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
}
}
}
TEST_P(ExprTest, TestCall) {
milvus::exec::expression::FunctionFactory& factory =
milvus::exec::expression::FunctionFactory::Instance();
factory.Initialize();
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto varchar_fid = schema->AddDebugField("address", DataType::VARCHAR);
schema->set_primary_field_id(varchar_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> address_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_address_col = raw_data.get_col<std::string>(varchar_fid);
address_col.insert(
address_col.end(), new_address_col.begin(), new_address_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
std::tuple<std::string, std::function<bool(std::string&)>> test_cases[] = {
{R"(vector_anns: <
field_id: 100
predicates: <
call_expr: <
function_name: "empty"
function_parameters: <
column_expr: <
info: <
field_id: 101
data_type: VarChar
>
>
>
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)",
[](std::string& v) { return v.empty(); }},
{R"(vector_anns: <
field_id: 100
predicates: <
call_expr: <
function_name: "starts_with"
function_parameters: <
column_expr: <
info: <
field_id: 101
data_type: VarChar
>
>
>
function_parameters: <
column_expr: <
info: <
field_id: 101
data_type: VarChar
>
>
>
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)",
[](std::string&) { return true; }}};
for (auto& [raw_plan, ref_func] : test_cases) {
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, ref_func(address_col[i]))
<< "@" << i << "!!" << address_col[i];
}
}
std::string incorrect_test_cases[] = {
R"(vector_anns: <
field_id: 100
predicates: <
call_expr: <
function_name: "empty"
function_parameters: <
column_expr: <
info: <
field_id: 101
data_type: VarChar
>
>
>
function_parameters: <
column_expr: <
info: <
field_id: 101
data_type: VarChar
>
>
>
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)",
R"(vector_anns: <
field_id: 100
predicates: <
call_expr: <
function_name: "starts_with"
function_parameters: <
column_expr: <
info: <
field_id: 101
data_type: VarChar
>
>
>
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)"};
for (auto& raw_plan : incorrect_test_cases) {
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
EXPECT_ANY_THROW(
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size()));
}
}
TEST_P(ExprTest, TestCompare) {
std::vector<std::tuple<std::string, std::function<bool(int, int64_t)>>>
testcases = {
{R"(LessThan)", [](int a, int64_t b) { return a < b; }},
{R"(LessEqual)", [](int a, int64_t b) { return a <= b; }},
{R"(GreaterThan)", [](int a, int64_t b) { return a > b; }},
{R"(GreaterEqual)", [](int a, int64_t b) { return a >= b; }},
{R"(Equal)", [](int a, int64_t b) { return a == b; }},
{R"(NotEqual)", [](int a, int64_t b) { return a != b; }},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
compare_expr: <
left_column_info: <
field_id: 101
data_type: Int32
>
right_column_info: <
field_id: 102
data_type: Int64
>
op: @@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i32_fid = schema->AddDebugField("age1", DataType::INT32);
auto i64_fid = schema->AddDebugField("age2", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> age1_col;
std::vector<int64_t> age2_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age1_col = raw_data.get_col<int>(i32_fid);
auto new_age2_col = raw_data.get_col<int64_t>(i64_fid);
age1_col.insert(
age1_col.end(), new_age1_col.begin(), new_age1_col.end());
age2_col.insert(
age2_col.end(), new_age2_col.begin(), new_age2_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val1 = age1_col[i];
auto val2 = age2_col[i];
auto ref = ref_func(val1, val2);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareNullable) {
std::vector<
std::tuple<std::string, std::function<bool(int, int64_t, bool)>>>
testcases = {
{R"(LessThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a < b;
}},
{R"(LessEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a <= b;
}},
{R"(GreaterThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a > b;
}},
{R"(GreaterEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a >= b;
}},
{R"(Equal)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a == b;
}},
{R"(NotEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a != b;
}},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
compare_expr: <
left_column_info: <
field_id: 101
data_type: Int32
>
right_column_info: <
field_id: 103
data_type: Int64
>
op: @@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i32_fid = schema->AddDebugField("age1", DataType::INT32);
auto i64_fid = schema->AddDebugField("age2", DataType::INT64);
auto nullable_fid =
schema->AddDebugField("nullable", DataType::INT64, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> age1_col;
std::vector<int64_t> nullable_col;
FixedVector<bool> valid_data_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age1_col = raw_data.get_col<int>(i32_fid);
auto new_nullable_col = raw_data.get_col<int64_t>(nullable_fid);
valid_data_col = raw_data.get_col_valid(nullable_fid);
age1_col.insert(
age1_col.end(), new_age1_col.begin(), new_age1_col.end());
nullable_col.insert(nullable_col.end(),
new_nullable_col.begin(),
new_nullable_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val1 = age1_col[i];
auto val2 = nullable_col[i];
auto ref = ref_func(val1, val2, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareNullable2) {
std::vector<
std::tuple<std::string, std::function<bool(int, int64_t, bool)>>>
testcases = {
{R"(LessThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a < b;
}},
{R"(LessEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a <= b;
}},
{R"(GreaterThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a > b;
}},
{R"(GreaterEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a >= b;
}},
{R"(Equal)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a == b;
}},
{R"(NotEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a != b;
}},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
compare_expr: <
left_column_info: <
field_id: 103
data_type: Int64
>
right_column_info: <
field_id: 101
data_type: Int32
>
op: @@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i32_fid = schema->AddDebugField("age1", DataType::INT32);
auto i64_fid = schema->AddDebugField("age2", DataType::INT64);
auto nullable_fid =
schema->AddDebugField("nullable", DataType::INT64, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int> age1_col;
std::vector<int64_t> nullable_col;
FixedVector<bool> valid_data_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age1_col = raw_data.get_col<int>(i32_fid);
auto new_nullable_col = raw_data.get_col<int64_t>(nullable_fid);
valid_data_col = raw_data.get_col_valid(nullable_fid);
age1_col.insert(
age1_col.end(), new_age1_col.begin(), new_age1_col.end());
nullable_col.insert(nullable_col.end(),
new_nullable_col.begin(),
new_nullable_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val2 = age1_col[i];
auto val1 = nullable_col[i];
auto ref = ref_func(val1, val2, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareWithScalarIndex) {
std::vector<std::tuple<std::string, std::function<bool(int, int64_t)>>>
testcases = {
{R"(LessThan)", [](int a, int64_t b) { return a < b; }},
{R"(LessEqual)", [](int a, int64_t b) { return a <= b; }},
{R"(GreaterThan)", [](int a, int64_t b) { return a > b; }},
{R"(GreaterEqual)", [](int a, int64_t b) { return a >= b; }},
{R"(Equal)", [](int a, int64_t b) { return a == b; }},
{R"(NotEqual)", [](int a, int64_t b) { return a != b; }},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
compare_expr: <
left_column_info: <
field_id: %3%
data_type: %4%
>
right_column_info: <
field_id: %5%
data_type: %6%
>
op: %2%
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i32_fid = schema->AddDebugField("age32", DataType::INT32);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int32 field
auto age32_col = raw_data.get_col<int32_t>(i32_fid);
age32_col[0] = 1000;
auto age32_index = milvus::index::CreateScalarIndexSort<int32_t>();
age32_index->Build(N, age32_col.data());
load_index_info.field_id = i32_fid.get();
load_index_info.field_type = DataType::INT32;
load_index_info.index = std::move(age32_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto age64_col = raw_data.get_col<int64_t>(i64_fid);
age64_col[0] = 2000;
auto age64_index = milvus::index::CreateScalarIndexSort<int64_t>();
age64_index->Build(N, age64_col.data());
load_index_info.field_id = i64_fid.get();
load_index_info.field_type = DataType::INT64;
load_index_info.index = std::move(age64_index);
seg->LoadIndex(load_index_info);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto dsl_string =
boost::format(serialized_expr_plan) % vec_fid.get() % clause %
i32_fid.get() % proto::schema::DataType_Name(int(DataType::INT32)) %
i64_fid.get() % proto::schema::DataType_Name(int(DataType::INT64));
auto binary_plan =
translate_text_plan_with_metric_type(dsl_string.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
// std::cout << ShowPlanNodeVisitor().call_child(*plan->plan_node_) << std::endl;
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg.get(),
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
auto val1 = age32_col[i];
auto val2 = age64_col[i];
auto ref = ref_func(val1, val2);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareWithScalarIndexNullable) {
std::vector<
std::tuple<std::string, std::function<bool(int, int64_t, bool)>>>
testcases = {
{R"(LessThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a < b;
}},
{R"(LessEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a <= b;
}},
{R"(GreaterThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a > b;
}},
{R"(GreaterEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a >= b;
}},
{R"(Equal)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a == b;
}},
{R"(NotEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a != b;
}},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
compare_expr: <
left_column_info: <
field_id: %3%
data_type: %4%
>
right_column_info: <
field_id: %5%
data_type: %6%
>
op: %2%
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto nullable_fid =
schema->AddDebugField("nullable", DataType::INT32, true);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int32 field
auto nullable_col = raw_data.get_col<int32_t>(nullable_fid);
nullable_col[0] = 1000;
auto valid_data_col = raw_data.get_col_valid(nullable_fid);
auto nullable_index = milvus::index::CreateScalarIndexSort<int32_t>();
nullable_index->Build(N, nullable_col.data(), valid_data_col.data());
load_index_info.field_id = nullable_fid.get();
load_index_info.field_type = DataType::INT32;
load_index_info.index = std::move(nullable_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto age64_col = raw_data.get_col<int64_t>(i64_fid);
age64_col[0] = 2000;
auto age64_index = milvus::index::CreateScalarIndexSort<int64_t>();
age64_index->Build(N, age64_col.data());
load_index_info.field_id = i64_fid.get();
load_index_info.field_type = DataType::INT64;
load_index_info.index = std::move(age64_index);
seg->LoadIndex(load_index_info);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto dsl_string = boost::format(serialized_expr_plan) % vec_fid.get() %
clause % nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::INT32)) %
i64_fid.get() %
proto::schema::DataType_Name(int(DataType::INT64));
auto binary_plan =
translate_text_plan_with_metric_type(dsl_string.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
// std::cout << ShowPlanNodeVisitor().call_child(*plan->plan_node_) << std::endl;
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg.get(),
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
auto val1 = nullable_col[i];
auto val2 = age64_col[i];
auto ref = ref_func(val1, val2, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareWithScalarIndexNullable2) {
std::vector<
std::tuple<std::string, std::function<bool(int, int64_t, bool)>>>
testcases = {
{R"(LessThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a < b;
}},
{R"(LessEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a <= b;
}},
{R"(GreaterThan)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a > b;
}},
{R"(GreaterEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a >= b;
}},
{R"(Equal)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a == b;
}},
{R"(NotEqual)",
[](int a, int64_t b, bool valid) {
if (!valid) {
return false;
}
return a != b;
}},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
compare_expr: <
left_column_info: <
field_id: %3%
data_type: %4%
>
right_column_info: <
field_id: %5%
data_type: %6%
>
op: %2%
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto nullable_fid =
schema->AddDebugField("nullable", DataType::INT32, true);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int32 field
auto nullable_col = raw_data.get_col<int32_t>(nullable_fid);
nullable_col[0] = 1000;
auto valid_data_col = raw_data.get_col_valid(nullable_fid);
auto nullable_index = milvus::index::CreateScalarIndexSort<int32_t>();
nullable_index->Build(N, nullable_col.data(), valid_data_col.data());
load_index_info.field_id = nullable_fid.get();
load_index_info.field_type = DataType::INT32;
load_index_info.index = std::move(nullable_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto age64_col = raw_data.get_col<int64_t>(i64_fid);
age64_col[0] = 2000;
auto age64_index = milvus::index::CreateScalarIndexSort<int64_t>();
age64_index->Build(N, age64_col.data());
load_index_info.field_id = i64_fid.get();
load_index_info.field_type = DataType::INT64;
load_index_info.index = std::move(age64_index);
seg->LoadIndex(load_index_info);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto dsl_string = boost::format(serialized_expr_plan) % vec_fid.get() %
clause % i64_fid.get() %
proto::schema::DataType_Name(int(DataType::INT64)) %
nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::INT32));
auto binary_plan =
translate_text_plan_with_metric_type(dsl_string.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
// std::cout << ShowPlanNodeVisitor().call_child(*plan->plan_node_) << std::endl;
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg.get(),
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
auto val2 = nullable_col[i];
auto val1 = age64_col[i];
auto ref = ref_func(val1, val2, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, test_term_pk_with_sorted) {
auto schema = std::make_shared<Schema>();
schema->AddField(
FieldName("Timestamp"), FieldId(1), DataType::INT64, false);
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
schema->set_primary_field_id(int64_fid);
auto seg = CreateSealedSegment(
schema, nullptr, 1, SegcoreConfig::default_config(), false, true);
int N = 100000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
std::vector<proto::plan::GenericValue> retrieve_ints;
for (int i = 0; i < 10; ++i) {
proto::plan::GenericValue val;
val.set_int64_val(i);
retrieve_ints.push_back(val);
}
auto expr = std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64), retrieve_ints);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(final[i], true);
}
for (int i = 10; i < N; ++i) {
EXPECT_EQ(final[i], false);
}
retrieve_ints.clear();
for (int i = 0; i < 10; ++i) {
proto::plan::GenericValue val;
val.set_int64_val(i + N);
retrieve_ints.push_back(val);
}
expr = std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64), retrieve_ints);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
EXPECT_EQ(final[i], false);
}
}
TEST_P(ExprTest, TestSealedSegmentGetBatchSize) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto bool_fid = schema->AddDebugField("bool", DataType::BOOL);
auto bool_1_fid = schema->AddDebugField("bool1", DataType::BOOL);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto float_1_fid = schema->AddDebugField("float1", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
auto double_1_fid = schema->AddDebugField("double1", DataType::DOUBLE);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto str3_fid = schema->AddDebugField("string3", DataType::VARCHAR);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
size_t N = 1000;
auto raw_data = DataGen(schema, N);
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
auto build_expr = [&](enum DataType type) -> expr::TypedExprPtr {
switch (type) {
case DataType::BOOL: {
auto compare_expr = std::make_shared<expr::CompareExpr>(
bool_fid,
bool_1_fid,
DataType::BOOL,
DataType::BOOL,
proto::plan::OpType::LessThan);
return compare_expr;
}
case DataType::INT8: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int8_fid,
int8_1_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
return compare_expr;
}
case DataType::INT16: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int16_fid,
int16_1_fid,
DataType::INT16,
DataType::INT16,
OpType::LessThan);
return compare_expr;
}
case DataType::INT32: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int32_fid,
int32_1_fid,
DataType::INT32,
DataType::INT32,
OpType::LessThan);
return compare_expr;
}
case DataType::INT64: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int64_fid,
int64_1_fid,
DataType::INT64,
DataType::INT64,
OpType::LessThan);
return compare_expr;
}
case DataType::FLOAT: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(float_fid,
float_1_fid,
DataType::FLOAT,
DataType::FLOAT,
OpType::LessThan);
return compare_expr;
}
case DataType::DOUBLE: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(double_fid,
double_1_fid,
DataType::DOUBLE,
DataType::DOUBLE,
OpType::LessThan);
return compare_expr;
}
case DataType::VARCHAR: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(str2_fid,
str3_fid,
DataType::VARCHAR,
DataType::VARCHAR,
OpType::LessThan);
return compare_expr;
}
default:
return std::make_shared<expr::CompareExpr>(int8_fid,
int8_1_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
}
};
std::cout << "start compare test" << std::endl;
auto expr = build_expr(DataType::BOOL);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT8);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT16);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT32);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT64);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::FLOAT);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::DOUBLE);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
std::cout << "end compare test" << std::endl;
}
TEST_P(ExprTest, TestCompareExprNullable) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto bool_fid = schema->AddDebugField("bool", DataType::BOOL);
auto bool_nullable_fid =
schema->AddDebugField("bool1", DataType::BOOL, true);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_nullable_fid =
schema->AddDebugField("int81", DataType::INT8, true);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_nullable_fid =
schema->AddDebugField("int161", DataType::INT16, true);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_nullable_fid =
schema->AddDebugField("int321", DataType::INT32, true);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_nullable_fid =
schema->AddDebugField("int641", DataType::INT64, true);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto float_nullable_fid =
schema->AddDebugField("float1", DataType::FLOAT, true);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
auto double_nullable_fid =
schema->AddDebugField("double1", DataType::DOUBLE, true);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto str_nullable_fid =
schema->AddDebugField("string3", DataType::VARCHAR, true);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
size_t N = 1000;
auto raw_data = DataGen(schema, N);
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
auto build_expr = [&](enum DataType type) -> expr::TypedExprPtr {
switch (type) {
case DataType::BOOL: {
auto compare_expr = std::make_shared<expr::CompareExpr>(
bool_fid,
bool_nullable_fid,
DataType::BOOL,
DataType::BOOL,
proto::plan::OpType::LessThan);
return compare_expr;
}
case DataType::INT8: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int8_fid,
int8_nullable_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
return compare_expr;
}
case DataType::INT16: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int16_fid,
int16_nullable_fid,
DataType::INT16,
DataType::INT16,
OpType::LessThan);
return compare_expr;
}
case DataType::INT32: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int32_fid,
int32_nullable_fid,
DataType::INT32,
DataType::INT32,
OpType::LessThan);
return compare_expr;
}
case DataType::INT64: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int64_fid,
int64_nullable_fid,
DataType::INT64,
DataType::INT64,
OpType::LessThan);
return compare_expr;
}
case DataType::FLOAT: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(float_fid,
float_nullable_fid,
DataType::FLOAT,
DataType::FLOAT,
OpType::LessThan);
return compare_expr;
}
case DataType::DOUBLE: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(double_fid,
double_nullable_fid,
DataType::DOUBLE,
DataType::DOUBLE,
OpType::LessThan);
return compare_expr;
}
case DataType::VARCHAR: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(str2_fid,
str_nullable_fid,
DataType::VARCHAR,
DataType::VARCHAR,
OpType::LessThan);
return compare_expr;
}
default:
return std::make_shared<expr::CompareExpr>(int8_fid,
int8_nullable_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
}
};
std::cout << "start compare test" << std::endl;
auto expr = build_expr(DataType::BOOL);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT8);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT16);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT32);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT64);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::FLOAT);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::DOUBLE);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
std::cout << "end compare test" << std::endl;
}
TEST_P(ExprTest, TestCompareExprNullable2) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto bool_fid = schema->AddDebugField("bool", DataType::BOOL);
auto bool_nullable_fid =
schema->AddDebugField("bool1", DataType::BOOL, true);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_nullable_fid =
schema->AddDebugField("int81", DataType::INT8, true);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_nullable_fid =
schema->AddDebugField("int161", DataType::INT16, true);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_nullable_fid =
schema->AddDebugField("int321", DataType::INT32, true);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_nullable_fid =
schema->AddDebugField("int641", DataType::INT64, true);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto float_nullable_fid =
schema->AddDebugField("float1", DataType::FLOAT, true);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
auto double_nullable_fid =
schema->AddDebugField("double1", DataType::DOUBLE, true);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto str_nullable_fid =
schema->AddDebugField("string3", DataType::VARCHAR, true);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
size_t N = 1000;
auto raw_data = DataGen(schema, N);
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
auto build_expr = [&](enum DataType type) -> expr::TypedExprPtr {
switch (type) {
case DataType::BOOL: {
auto compare_expr = std::make_shared<expr::CompareExpr>(
bool_fid,
bool_nullable_fid,
DataType::BOOL,
DataType::BOOL,
proto::plan::OpType::LessThan);
return compare_expr;
}
case DataType::INT8: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int8_nullable_fid,
int8_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
return compare_expr;
}
case DataType::INT16: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int16_nullable_fid,
int16_fid,
DataType::INT16,
DataType::INT16,
OpType::LessThan);
return compare_expr;
}
case DataType::INT32: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int32_nullable_fid,
int32_fid,
DataType::INT32,
DataType::INT32,
OpType::LessThan);
return compare_expr;
}
case DataType::INT64: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int64_nullable_fid,
int64_fid,
DataType::INT64,
DataType::INT64,
OpType::LessThan);
return compare_expr;
}
case DataType::FLOAT: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(float_nullable_fid,
float_fid,
DataType::FLOAT,
DataType::FLOAT,
OpType::LessThan);
return compare_expr;
}
case DataType::DOUBLE: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(double_nullable_fid,
double_fid,
DataType::DOUBLE,
DataType::DOUBLE,
OpType::LessThan);
return compare_expr;
}
case DataType::VARCHAR: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(str_nullable_fid,
str2_fid,
DataType::VARCHAR,
DataType::VARCHAR,
OpType::LessThan);
return compare_expr;
}
default:
return std::make_shared<expr::CompareExpr>(int8_nullable_fid,
int8_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
}
};
std::cout << "start compare test" << std::endl;
auto expr = build_expr(DataType::BOOL);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT8);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT16);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT32);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::INT64);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::FLOAT);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
expr = build_expr(DataType::DOUBLE);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
std::cout << "end compare test" << std::endl;
}
TEST(Expr, TestExprPerformance) {
GTEST_SKIP() << "Skip performance test, open it when test performance";
auto schema = std::make_shared<Schema>();
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
std::map<DataType, FieldId> fids = {{DataType::INT8, int8_fid},
{DataType::INT16, int16_fid},
{DataType::INT32, int32_fid},
{DataType::INT64, int64_fid},
{DataType::VARCHAR, str2_fid},
{DataType::FLOAT, float_fid},
{DataType::DOUBLE, double_fid}};
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
enum ExprType {
UnaryRangeExpr = 0,
TermExprImpl = 1,
CompareExpr = 2,
LogicalUnaryExpr = 3,
BinaryRangeExpr = 4,
LogicalBinaryExpr = 5,
BinaryArithOpEvalRangeExpr = 6,
};
auto build_unary_range_expr = [&](DataType data_type,
int64_t value) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
proto::plan::GenericValue val;
val.set_int64_val(value);
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::LessThan,
val);
} else if (IsFloatDataType(data_type)) {
proto::plan::GenericValue val;
val.set_float_val(float(value));
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::LessThan,
val);
} else if (IsStringDataType(data_type)) {
proto::plan::GenericValue val;
val.set_string_val(std::to_string(value));
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::LessThan,
val);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_binary_range_expr = [&](DataType data_type,
int64_t low,
int64_t high) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_int64_val(low);
proto::plan::GenericValue val2;
val2.set_int64_val(high);
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
val1,
val2,
true,
true);
} else if (IsFloatDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_float_val(float(low));
proto::plan::GenericValue val2;
val2.set_float_val(float(high));
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
val1,
val2,
true,
true);
} else if (IsStringDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_string_val(std::to_string(low));
proto::plan::GenericValue val2;
val2.set_string_val(std::to_string(low));
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
val1,
val2,
true,
true);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_term_expr =
[&](DataType data_type,
std::vector<int64_t> in_vals) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
std::vector<proto::plan::GenericValue> vals;
for (auto& v : in_vals) {
proto::plan::GenericValue val;
val.set_int64_val(v);
vals.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type), vals, false);
} else if (IsFloatDataType(data_type)) {
std::vector<proto::plan::GenericValue> vals;
for (auto& v : in_vals) {
proto::plan::GenericValue val;
val.set_float_val(float(v));
vals.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type), vals, false);
} else if (IsStringDataType(data_type)) {
std::vector<proto::plan::GenericValue> vals;
for (auto& v : in_vals) {
proto::plan::GenericValue val;
val.set_string_val(std::to_string(v));
vals.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type), vals, false);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_compare_expr = [&](DataType data_type) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type) || IsFloatDataType(data_type) ||
IsStringDataType(data_type)) {
return std::make_shared<expr::CompareExpr>(
fids[data_type],
fids[data_type],
data_type,
data_type,
proto::plan::OpType::LessThan);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_logical_unary_expr =
[&](DataType data_type) -> expr::TypedExprPtr {
auto child_expr = build_unary_range_expr(data_type, 10);
return std::make_shared<expr::LogicalUnaryExpr>(
expr::LogicalUnaryExpr::OpType::LogicalNot, child_expr);
};
auto build_logical_binary_expr =
[&](DataType data_type) -> expr::TypedExprPtr {
auto child1_expr = build_unary_range_expr(data_type, 10);
auto child2_expr = build_unary_range_expr(data_type, 10);
return std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
};
auto build_multi_logical_binary_expr =
[&](DataType data_type) -> expr::TypedExprPtr {
auto child1_expr = build_unary_range_expr(data_type, 100);
auto child2_expr = build_unary_range_expr(data_type, 100);
auto child3_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
auto child4_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
auto child5_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child3_expr, child4_expr);
auto child6_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child3_expr, child4_expr);
return std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child5_expr, child6_expr);
};
auto build_arith_op_expr = [&](DataType data_type,
int64_t right_val,
int64_t val) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_int64_val(right_val);
proto::plan::GenericValue val2;
val2.set_int64_val(val);
return std::make_shared<expr::BinaryArithOpEvalRangeExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::Equal,
proto::plan::ArithOpType::Add,
val1,
val2);
} else if (IsFloatDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_float_val(float(right_val));
proto::plan::GenericValue val2;
val2.set_float_val(float(val));
return std::make_shared<expr::BinaryArithOpEvalRangeExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::Equal,
proto::plan::ArithOpType::Add,
val1,
val2);
} else {
throw std::runtime_error("not supported type");
}
};
auto test_case_base = [=, &seg](expr::TypedExprPtr expr) {
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::cout << expr->ToString() << std::endl;
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 100; i++) {
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
}
std::cout << "cost: "
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count() /
100.0
<< "us" << std::endl;
};
std::cout << "test unary range operator" << std::endl;
auto expr = build_unary_range_expr(DataType::INT8, 10);
test_case_base(expr);
expr = build_unary_range_expr(DataType::INT16, 10);
test_case_base(expr);
expr = build_unary_range_expr(DataType::INT32, 10);
test_case_base(expr);
expr = build_unary_range_expr(DataType::INT64, 10);
test_case_base(expr);
expr = build_unary_range_expr(DataType::FLOAT, 10);
test_case_base(expr);
expr = build_unary_range_expr(DataType::DOUBLE, 10);
test_case_base(expr);
expr = build_unary_range_expr(DataType::VARCHAR, 10);
test_case_base(expr);
std::cout << "test binary range operator" << std::endl;
expr = build_binary_range_expr(DataType::INT8, 10, 100);
test_case_base(expr);
expr = build_binary_range_expr(DataType::INT16, 10, 100);
test_case_base(expr);
expr = build_binary_range_expr(DataType::INT32, 10, 100);
test_case_base(expr);
expr = build_binary_range_expr(DataType::INT64, 10, 100);
test_case_base(expr);
expr = build_binary_range_expr(DataType::FLOAT, 10, 100);
test_case_base(expr);
expr = build_binary_range_expr(DataType::DOUBLE, 10, 100);
test_case_base(expr);
expr = build_binary_range_expr(DataType::VARCHAR, 10, 100);
test_case_base(expr);
std::cout << "test compare expr operator" << std::endl;
expr = build_compare_expr(DataType::INT8);
test_case_base(expr);
expr = build_compare_expr(DataType::INT16);
test_case_base(expr);
expr = build_compare_expr(DataType::INT32);
test_case_base(expr);
expr = build_compare_expr(DataType::INT64);
test_case_base(expr);
expr = build_compare_expr(DataType::FLOAT);
test_case_base(expr);
expr = build_compare_expr(DataType::DOUBLE);
test_case_base(expr);
expr = build_compare_expr(DataType::VARCHAR);
test_case_base(expr);
std::cout << "test artih op val operator" << std::endl;
expr = build_arith_op_expr(DataType::INT8, 10, 100);
test_case_base(expr);
expr = build_arith_op_expr(DataType::INT16, 10, 100);
test_case_base(expr);
expr = build_arith_op_expr(DataType::INT32, 10, 100);
test_case_base(expr);
expr = build_arith_op_expr(DataType::INT64, 10, 100);
test_case_base(expr);
expr = build_arith_op_expr(DataType::FLOAT, 10, 100);
test_case_base(expr);
expr = build_arith_op_expr(DataType::DOUBLE, 10, 100);
test_case_base(expr);
std::cout << "test logical unary expr operator" << std::endl;
expr = build_logical_unary_expr(DataType::INT8);
test_case_base(expr);
expr = build_logical_unary_expr(DataType::INT16);
test_case_base(expr);
expr = build_logical_unary_expr(DataType::INT32);
test_case_base(expr);
expr = build_logical_unary_expr(DataType::INT64);
test_case_base(expr);
expr = build_logical_unary_expr(DataType::FLOAT);
test_case_base(expr);
expr = build_logical_unary_expr(DataType::DOUBLE);
test_case_base(expr);
expr = build_logical_unary_expr(DataType::VARCHAR);
test_case_base(expr);
std::cout << "test logical binary expr operator" << std::endl;
expr = build_logical_binary_expr(DataType::INT8);
test_case_base(expr);
expr = build_logical_binary_expr(DataType::INT16);
test_case_base(expr);
expr = build_logical_binary_expr(DataType::INT32);
test_case_base(expr);
expr = build_logical_binary_expr(DataType::INT64);
test_case_base(expr);
expr = build_logical_binary_expr(DataType::FLOAT);
test_case_base(expr);
expr = build_logical_binary_expr(DataType::DOUBLE);
test_case_base(expr);
expr = build_logical_binary_expr(DataType::VARCHAR);
test_case_base(expr);
std::cout << "test multi logical binary expr operator" << std::endl;
expr = build_multi_logical_binary_expr(DataType::INT8);
test_case_base(expr);
expr = build_multi_logical_binary_expr(DataType::INT16);
test_case_base(expr);
expr = build_multi_logical_binary_expr(DataType::INT32);
test_case_base(expr);
expr = build_multi_logical_binary_expr(DataType::INT64);
test_case_base(expr);
expr = build_multi_logical_binary_expr(DataType::FLOAT);
test_case_base(expr);
expr = build_multi_logical_binary_expr(DataType::DOUBLE);
test_case_base(expr);
expr = build_multi_logical_binary_expr(DataType::VARCHAR);
test_case_base(expr);
}
TEST(Expr, TestExprNOT) {
auto schema = std::make_shared<Schema>();
auto int8_fid = schema->AddDebugField("int8", DataType::INT8, true);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16, true);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32, true);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64, true);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR, true);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT, true);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE, true);
schema->set_primary_field_id(str1_fid);
std::map<DataType, FieldId> fids = {{DataType::INT8, int8_fid},
{DataType::INT16, int16_fid},
{DataType::INT32, int32_fid},
{DataType::INT64, int64_fid},
{DataType::VARCHAR, str2_fid},
{DataType::FLOAT, float_fid},
{DataType::DOUBLE, double_fid}};
auto seg = CreateSealedSegment(schema);
FixedVector<bool> valid_data_i8;
FixedVector<bool> valid_data_i16;
FixedVector<bool> valid_data_i32;
FixedVector<bool> valid_data_i64;
FixedVector<bool> valid_data_str;
FixedVector<bool> valid_data_float;
FixedVector<bool> valid_data_double;
int N = 1000;
auto raw_data = DataGen(schema, N);
valid_data_i8 = raw_data.get_col_valid(int8_fid);
valid_data_i16 = raw_data.get_col_valid(int16_fid);
valid_data_i32 = raw_data.get_col_valid(int32_fid);
valid_data_i64 = raw_data.get_col_valid(int64_fid);
valid_data_str = raw_data.get_col_valid(str2_fid);
valid_data_float = raw_data.get_col_valid(float_fid);
valid_data_double = raw_data.get_col_valid(double_fid);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
enum ExprType {
UnaryRangeExpr = 0,
TermExprImpl = 1,
CompareExpr = 2,
LogicalUnaryExpr = 3,
BinaryRangeExpr = 4,
LogicalBinaryExpr = 5,
BinaryArithOpEvalRangeExpr = 6,
};
auto build_unary_range_expr = [&](DataType data_type,
int64_t value) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
proto::plan::GenericValue val;
val.set_int64_val(value);
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::LessThan,
val);
} else if (IsFloatDataType(data_type)) {
proto::plan::GenericValue val;
val.set_float_val(float(value));
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::LessThan,
val);
} else if (IsStringDataType(data_type)) {
proto::plan::GenericValue val;
val.set_string_val(std::to_string(value));
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::LessThan,
val);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_binary_range_expr = [&](DataType data_type,
int64_t low,
int64_t high) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_int64_val(low);
proto::plan::GenericValue val2;
val2.set_int64_val(high);
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
val1,
val2,
true,
true);
} else if (IsFloatDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_float_val(float(low));
proto::plan::GenericValue val2;
val2.set_float_val(float(high));
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
val1,
val2,
true,
true);
} else if (IsStringDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_string_val(std::to_string(low));
proto::plan::GenericValue val2;
val2.set_string_val(std::to_string(low));
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type),
val1,
val2,
true,
true);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_term_expr =
[&](DataType data_type,
std::vector<int64_t> in_vals) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
std::vector<proto::plan::GenericValue> vals;
for (auto& v : in_vals) {
proto::plan::GenericValue val;
val.set_int64_val(v);
vals.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type), vals, false);
} else if (IsFloatDataType(data_type)) {
std::vector<proto::plan::GenericValue> vals;
for (auto& v : in_vals) {
proto::plan::GenericValue val;
val.set_float_val(float(v));
vals.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type), vals, false);
} else if (IsStringDataType(data_type)) {
std::vector<proto::plan::GenericValue> vals;
for (auto& v : in_vals) {
proto::plan::GenericValue val;
val.set_string_val(std::to_string(v));
vals.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(fids[data_type], data_type), vals, false);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_compare_expr = [&](DataType data_type) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type) || IsFloatDataType(data_type) ||
IsStringDataType(data_type)) {
return std::make_shared<expr::CompareExpr>(
fids[data_type],
fids[data_type],
data_type,
data_type,
proto::plan::OpType::LessThan);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_logical_binary_expr =
[&](DataType data_type) -> expr::TypedExprPtr {
auto child1_expr = build_unary_range_expr(data_type, 10);
auto child2_expr = build_unary_range_expr(data_type, 10);
return std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
};
auto build_multi_logical_binary_expr =
[&](DataType data_type) -> expr::TypedExprPtr {
auto child1_expr = build_unary_range_expr(data_type, 100);
auto child2_expr = build_unary_range_expr(data_type, 100);
auto child3_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
auto child4_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
auto child5_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child3_expr, child4_expr);
auto child6_expr = std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child3_expr, child4_expr);
return std::make_shared<expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child5_expr, child6_expr);
};
auto build_arith_op_expr = [&](DataType data_type,
int64_t right_val,
int64_t val) -> expr::TypedExprPtr {
if (IsIntegerDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_int64_val(right_val);
proto::plan::GenericValue val2;
val2.set_int64_val(val);
return std::make_shared<expr::BinaryArithOpEvalRangeExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::Equal,
proto::plan::ArithOpType::Add,
val1,
val2);
} else if (IsFloatDataType(data_type)) {
proto::plan::GenericValue val1;
val1.set_float_val(float(right_val));
proto::plan::GenericValue val2;
val2.set_float_val(float(val));
return std::make_shared<expr::BinaryArithOpEvalRangeExpr>(
expr::ColumnInfo(fids[data_type], data_type),
proto::plan::OpType::Equal,
proto::plan::ArithOpType::Add,
val1,
val2);
} else {
throw std::runtime_error("not supported type");
}
};
auto build_logical_unary_expr =
[&](DataType data_type) -> expr::TypedExprPtr {
auto child_expr = build_unary_range_expr(data_type, 10);
return std::make_shared<expr::LogicalUnaryExpr>(
expr::LogicalUnaryExpr::OpType::LogicalNot, child_expr);
};
auto test_ans = [=, &seg](expr::TypedExprPtr expr,
FixedVector<bool> valid_data) {
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; i++) {
if (!valid_data[i]) {
EXPECT_EQ(final[i], false);
}
}
};
auto expr = build_unary_range_expr(DataType::INT8, 10);
test_ans(expr, valid_data_i8);
expr = build_unary_range_expr(DataType::INT16, 10);
test_ans(expr, valid_data_i16);
expr = build_unary_range_expr(DataType::INT32, 10);
test_ans(expr, valid_data_i32);
expr = build_unary_range_expr(DataType::INT64, 10);
test_ans(expr, valid_data_i64);
expr = build_unary_range_expr(DataType::FLOAT, 10);
test_ans(expr, valid_data_float);
expr = build_unary_range_expr(DataType::DOUBLE, 10);
test_ans(expr, valid_data_double);
expr = build_unary_range_expr(DataType::VARCHAR, 10);
test_ans(expr, valid_data_str);
expr = build_binary_range_expr(DataType::INT8, 10, 100);
test_ans(expr, valid_data_i8);
expr = build_binary_range_expr(DataType::INT16, 10, 100);
test_ans(expr, valid_data_i16);
expr = build_binary_range_expr(DataType::INT32, 10, 100);
test_ans(expr, valid_data_i32);
expr = build_binary_range_expr(DataType::INT64, 10, 100);
test_ans(expr, valid_data_i64);
expr = build_binary_range_expr(DataType::FLOAT, 10, 100);
test_ans(expr, valid_data_float);
expr = build_binary_range_expr(DataType::DOUBLE, 10, 100);
test_ans(expr, valid_data_double);
expr = build_binary_range_expr(DataType::VARCHAR, 10, 100);
test_ans(expr, valid_data_str);
expr = build_compare_expr(DataType::INT8);
test_ans(expr, valid_data_i8);
expr = build_compare_expr(DataType::INT16);
test_ans(expr, valid_data_i16);
expr = build_compare_expr(DataType::INT32);
test_ans(expr, valid_data_i32);
expr = build_compare_expr(DataType::INT64);
test_ans(expr, valid_data_i64);
expr = build_compare_expr(DataType::FLOAT);
test_ans(expr, valid_data_float);
expr = build_compare_expr(DataType::DOUBLE);
test_ans(expr, valid_data_double);
expr = build_compare_expr(DataType::VARCHAR);
test_ans(expr, valid_data_str);
expr = build_arith_op_expr(DataType::INT8, 10, 100);
test_ans(expr, valid_data_i8);
expr = build_arith_op_expr(DataType::INT16, 10, 100);
test_ans(expr, valid_data_i16);
expr = build_arith_op_expr(DataType::INT32, 10, 100);
test_ans(expr, valid_data_i32);
expr = build_arith_op_expr(DataType::INT64, 10, 100);
test_ans(expr, valid_data_i64);
expr = build_arith_op_expr(DataType::FLOAT, 10, 100);
test_ans(expr, valid_data_float);
expr = build_arith_op_expr(DataType::DOUBLE, 10, 100);
test_ans(expr, valid_data_double);
expr = build_logical_unary_expr(DataType::INT8);
test_ans(expr, valid_data_i8);
expr = build_logical_unary_expr(DataType::INT16);
test_ans(expr, valid_data_i16);
expr = build_logical_unary_expr(DataType::INT32);
test_ans(expr, valid_data_i32);
expr = build_logical_unary_expr(DataType::INT64);
test_ans(expr, valid_data_i64);
expr = build_logical_unary_expr(DataType::FLOAT);
test_ans(expr, valid_data_float);
expr = build_logical_unary_expr(DataType::DOUBLE);
test_ans(expr, valid_data_double);
expr = build_logical_unary_expr(DataType::VARCHAR);
test_ans(expr, valid_data_str);
expr = build_logical_binary_expr(DataType::INT8);
test_ans(expr, valid_data_i8);
expr = build_logical_binary_expr(DataType::INT16);
test_ans(expr, valid_data_i16);
expr = build_logical_binary_expr(DataType::INT32);
test_ans(expr, valid_data_i32);
expr = build_logical_binary_expr(DataType::INT64);
test_ans(expr, valid_data_i64);
expr = build_logical_binary_expr(DataType::FLOAT);
test_ans(expr, valid_data_float);
expr = build_logical_binary_expr(DataType::DOUBLE);
test_ans(expr, valid_data_double);
expr = build_logical_binary_expr(DataType::VARCHAR);
test_ans(expr, valid_data_str);
expr = build_multi_logical_binary_expr(DataType::INT8);
test_ans(expr, valid_data_i8);
expr = build_multi_logical_binary_expr(DataType::INT16);
test_ans(expr, valid_data_i16);
expr = build_multi_logical_binary_expr(DataType::INT32);
test_ans(expr, valid_data_i32);
expr = build_multi_logical_binary_expr(DataType::INT64);
test_ans(expr, valid_data_i64);
expr = build_multi_logical_binary_expr(DataType::FLOAT);
test_ans(expr, valid_data_float);
expr = build_multi_logical_binary_expr(DataType::DOUBLE);
test_ans(expr, valid_data_double);
expr = build_multi_logical_binary_expr(DataType::VARCHAR);
test_ans(expr, valid_data_str);
}
TEST_P(ExprTest, test_term_pk) {
auto schema = std::make_shared<Schema>();
schema->AddField(
FieldName("Timestamp"), FieldId(1), DataType::INT64, false);
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
schema->set_primary_field_id(int64_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
std::vector<proto::plan::GenericValue> retrieve_ints;
for (int i = 0; i < 10; ++i) {
proto::plan::GenericValue val;
val.set_int64_val(i);
retrieve_ints.push_back(val);
}
auto expr = std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64), retrieve_ints);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(final[i], true);
}
for (int i = 10; i < N; ++i) {
EXPECT_EQ(final[i], false);
}
retrieve_ints.clear();
for (int i = 0; i < 10; ++i) {
proto::plan::GenericValue val;
val.set_int64_val(i + N);
retrieve_ints.push_back(val);
}
expr = std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64), retrieve_ints);
plan = std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
EXPECT_EQ(final[i], false);
}
}
TEST_P(ExprTest, TestGrowingSegmentGetBatchSize) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
schema->set_primary_field_id(str1_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
auto raw_data = DataGen(schema, N);
seg->PreInsert(N);
seg->Insert(0,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
proto::plan::GenericValue val;
val.set_int64_val(10);
auto expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int8_fid, DataType::INT8),
proto::plan::OpType::GreaterThan,
val);
auto plan_node =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
std::vector<int64_t> test_batch_size = {
8192, 10240, 20480, 30720, 40960, 102400, 204800, 307200};
for (const auto& batch_size : test_batch_size) {
EXEC_EVAL_EXPR_BATCH_SIZE = batch_size;
auto plan = plan::PlanFragment(plan_node);
auto query_context = std::make_shared<milvus::exec::QueryContext>(
"query id", seg.get(), N, MAX_TIMESTAMP);
auto task =
milvus::exec::Task::Create("task_expr", plan, 0, query_context);
auto last_num = N % batch_size;
auto iter_num = last_num == 0 ? N / batch_size : N / batch_size + 1;
int iter = 0;
for (;;) {
auto result = task->Next();
if (!result) {
break;
}
auto childrens = result->childrens();
if (++iter != iter_num) {
EXPECT_EQ(childrens[0]->size(), batch_size);
} else {
EXPECT_EQ(childrens[0]->size(), last_num);
}
}
}
}
TEST_P(ExprTest, TestConjuctExpr) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
auto build_expr = [&](int l, int r) -> expr::TypedExprPtr {
::milvus::proto::plan::GenericValue value;
value.set_int64_val(l);
auto left = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64),
proto::plan::OpType::GreaterThan,
value);
value.set_int64_val(r);
auto right = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64),
proto::plan::OpType::LessThan,
value);
return std::make_shared<milvus::expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, left, right);
};
std::vector<std::pair<int, int>> test_case = {
{100, 0}, {0, 100}, {8192, 8194}};
for (auto& pair : test_case) {
std::cout << pair.first << "|" << pair.second << std::endl;
auto expr = build_expr(pair.first, pair.second);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
for (int i = 0; i < N; ++i) {
EXPECT_EQ(final[i], pair.first < i && i < pair.second) << i;
}
}
}
TEST_P(ExprTest, TestConjuctExprNullable) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_nullable_fid =
schema->AddDebugField("int8_nullable", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_nullable_fid =
schema->AddDebugField("int16_nullable", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_nullable_fid =
schema->AddDebugField("int32_nullable", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_nullable_fid =
schema->AddDebugField("int64_nullable", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
auto build_expr = [&](int l, int r) -> expr::TypedExprPtr {
::milvus::proto::plan::GenericValue value;
value.set_int64_val(l);
auto left = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64),
proto::plan::OpType::GreaterThan,
value);
value.set_int64_val(r);
auto right = std::make_shared<milvus::expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64),
proto::plan::OpType::LessThan,
value);
return std::make_shared<milvus::expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, left, right);
};
std::vector<std::pair<int, int>> test_case = {
{100, 0}, {0, 100}, {8192, 8194}};
for (auto& pair : test_case) {
std::cout << pair.first << "|" << pair.second << std::endl;
auto expr = build_expr(pair.first, pair.second);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
for (int i = 0; i < N; ++i) {
EXPECT_EQ(final[i], pair.first < i && i < pair.second) << i;
}
}
}
TEST_P(ExprTest, TestUnaryBenchTest) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::vector<std::pair<FieldId, DataType>> test_cases = {
{int8_fid, DataType::INT8},
{int16_fid, DataType::INT16},
{int32_fid, DataType::INT32},
{int64_fid, DataType::INT64},
{float_fid, DataType::FLOAT},
{double_fid, DataType::DOUBLE}};
for (const auto& pair : test_cases) {
std::cout << "start test type:" << int(pair.second) << std::endl;
proto::plan::GenericValue val;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
val.set_float_val(10);
} else {
val.set_int64_val(10);
}
auto expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(pair.first, pair.second),
proto::plan::OpType::GreaterThan,
val);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
int64_t all_cost = 0;
for (int i = 0; i < 10; i++) {
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
all_cost += std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count();
}
std::cout << " cost: " << all_cost / 10.0 << "us" << std::endl;
}
}
TEST_P(ExprTest, TestBinaryRangeBenchTest) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::vector<std::pair<FieldId, DataType>> test_cases = {
{int8_fid, DataType::INT8},
{int16_fid, DataType::INT16},
{int32_fid, DataType::INT32},
{int64_fid, DataType::INT64},
{float_fid, DataType::FLOAT},
{double_fid, DataType::DOUBLE}};
for (const auto& pair : test_cases) {
std::cout << "start test type:" << int(pair.second) << std::endl;
proto::plan::GenericValue lower;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
lower.set_float_val(10);
} else {
lower.set_int64_val(10);
}
proto::plan::GenericValue upper;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
upper.set_float_val(45);
} else {
upper.set_int64_val(45);
}
auto expr = std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(pair.first, pair.second),
lower,
upper,
true,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
int64_t all_cost = 0;
for (int i = 0; i < 10; i++) {
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
all_cost += std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count();
}
std::cout << " cost: " << all_cost / 10.0 << "us" << std::endl;
}
}
TEST_P(ExprTest, TestLogicalUnaryBenchTest) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::vector<std::pair<FieldId, DataType>> test_cases = {
{int8_fid, DataType::INT8},
{int16_fid, DataType::INT16},
{int32_fid, DataType::INT32},
{int64_fid, DataType::INT64},
{float_fid, DataType::FLOAT},
{double_fid, DataType::DOUBLE}};
for (const auto& pair : test_cases) {
std::cout << "start test type:" << int(pair.second) << std::endl;
proto::plan::GenericValue val;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
val.set_float_val(10);
} else {
val.set_int64_val(10);
}
auto child_expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(pair.first, pair.second),
proto::plan::OpType::GreaterThan,
val);
auto expr = std::make_shared<expr::LogicalUnaryExpr>(
expr::LogicalUnaryExpr::OpType::LogicalNot, child_expr);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
int64_t all_cost = 0;
for (int i = 0; i < 50; i++) {
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
all_cost += std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count();
}
std::cout << " cost: " << all_cost / 50.0 << "us" << std::endl;
}
}
TEST_P(ExprTest, TestBinaryLogicalBenchTest) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::vector<std::pair<FieldId, DataType>> test_cases = {
{int8_fid, DataType::INT8},
{int16_fid, DataType::INT16},
{int32_fid, DataType::INT32},
{int64_fid, DataType::INT64},
{float_fid, DataType::FLOAT},
{double_fid, DataType::DOUBLE}};
for (const auto& pair : test_cases) {
std::cout << "start test type:" << int(pair.second) << std::endl;
proto::plan::GenericValue val;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
val.set_float_val(-1000000);
} else {
val.set_int64_val(-1000000);
}
proto::plan::GenericValue val1;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
val1.set_float_val(-100);
} else {
val1.set_int64_val(-100);
}
auto child1_expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(pair.first, pair.second),
proto::plan::OpType::LessThan,
val);
auto child2_expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(pair.first, pair.second),
proto::plan::OpType::NotEqual,
val1);
auto expr = std::make_shared<const expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And, child1_expr, child2_expr);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
int64_t all_cost = 0;
for (int i = 0; i < 50; i++) {
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
all_cost += std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count();
}
std::cout << " cost: " << all_cost / 50.0 << "us" << std::endl;
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeBenchExpr) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::vector<std::pair<FieldId, DataType>> test_cases = {
{int8_fid, DataType::INT8},
{int16_fid, DataType::INT16},
{int32_fid, DataType::INT32},
{int64_fid, DataType::INT64},
{float_fid, DataType::FLOAT},
{double_fid, DataType::DOUBLE}};
for (const auto& pair : test_cases) {
std::cout << "start test type:" << int(pair.second) << std::endl;
proto::plan::GenericValue val;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
val.set_float_val(100);
} else {
val.set_int64_val(100);
}
proto::plan::GenericValue right;
if (pair.second == DataType::FLOAT || pair.second == DataType::DOUBLE) {
right.set_float_val(10);
} else {
right.set_int64_val(10);
}
auto expr = std::make_shared<expr::BinaryArithOpEvalRangeExpr>(
expr::ColumnInfo(pair.first, pair.second),
proto::plan::OpType::Equal,
proto::plan::ArithOpType::Add,
val,
right);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
int64_t all_cost = 0;
for (int i = 0; i < 50; i++) {
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
all_cost += std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count();
}
std::cout << " cost: " << all_cost / 50.0 << "us" << std::endl;
}
}
TEST_P(ExprTest, TestCompareExprBenchTest) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto float_1_fid = schema->AddDebugField("float1", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
auto double_1_fid = schema->AddDebugField("double1", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
std::vector<
std::pair<std::pair<FieldId, DataType>, std::pair<FieldId, DataType>>>
test_cases = {
{{int8_fid, DataType::INT8}, {int8_1_fid, DataType::INT8}},
{{int16_fid, DataType::INT16}, {int16_fid, DataType::INT16}},
{{int32_fid, DataType::INT32}, {int32_1_fid, DataType::INT32}},
{{int64_fid, DataType::INT64}, {int64_1_fid, DataType::INT64}},
{{float_fid, DataType::FLOAT}, {float_1_fid, DataType::FLOAT}},
{{double_fid, DataType::DOUBLE}, {double_1_fid, DataType::DOUBLE}}};
for (const auto& pair : test_cases) {
std::cout << "start test type:" << int(pair.first.second) << std::endl;
proto::plan::GenericValue lower;
auto expr = std::make_shared<expr::CompareExpr>(pair.first.first,
pair.second.first,
pair.first.second,
pair.second.second,
OpType::LessThan);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
int64_t all_cost = 0;
for (int i = 0; i < 10; i++) {
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
all_cost += std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count();
}
std::cout << " cost: " << all_cost / 10 << "us" << std::endl;
}
}
TEST_P(ExprTest, TestRefactorExprs) {
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto int8_fid = schema->AddDebugField("int8", DataType::INT8);
auto int8_1_fid = schema->AddDebugField("int81", DataType::INT8);
auto int16_fid = schema->AddDebugField("int16", DataType::INT16);
auto int16_1_fid = schema->AddDebugField("int161", DataType::INT16);
auto int32_fid = schema->AddDebugField("int32", DataType::INT32);
auto int32_1_fid = schema->AddDebugField("int321", DataType::INT32);
auto int64_fid = schema->AddDebugField("int64", DataType::INT64);
auto int64_1_fid = schema->AddDebugField("int641", DataType::INT64);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
auto float_fid = schema->AddDebugField("float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("double", DataType::DOUBLE);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
// load field data
auto fields = schema->get_fields();
for (auto field_data : raw_data.raw_->fields_data()) {
int64_t field_id = field_data.field_id();
auto info = FieldDataInfo(field_data.field_id(), N, "/tmp/a");
auto field_meta = fields.at(FieldId(field_id));
info.channel->push(
CreateFieldDataFromDataArray(N, &field_data, field_meta));
info.channel->close();
seg->LoadFieldData(FieldId(field_id), info);
}
enum ExprType {
UnaryRangeExpr = 0,
TermExprImpl = 1,
CompareExpr = 2,
LogicalUnaryExpr = 3,
BinaryRangeExpr = 4,
LogicalBinaryExpr = 5,
BinaryArithOpEvalRangeExpr = 6,
};
auto build_expr = [&](enum ExprType test_type,
int n) -> expr::TypedExprPtr {
switch (test_type) {
case UnaryRangeExpr: {
proto::plan::GenericValue val;
val.set_int64_val(10);
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64),
proto::plan::OpType::GreaterThan,
val);
}
case TermExprImpl: {
std::vector<proto::plan::GenericValue> retrieve_ints;
// for (int i = 0; i < n; ++i) {
// retrieve_ints.push_back("xxxxxx" + std::to_string(i % 10));
// }
// return std::make_shared<query::TermExprImpl<std::string>>(
// ColumnInfo(str1_fid, DataType::VARCHAR),
// retrieve_ints,
// proto::plan::GenericValue::ValCase::kStringVal);
for (int i = 0; i < n; ++i) {
proto::plan::GenericValue val;
val.set_float_val(i);
retrieve_ints.push_back(val);
}
return std::make_shared<expr::TermFilterExpr>(
expr::ColumnInfo(double_fid, DataType::DOUBLE),
retrieve_ints);
}
case CompareExpr: {
auto compare_expr =
std::make_shared<expr::CompareExpr>(int8_fid,
int8_1_fid,
DataType::INT8,
DataType::INT8,
OpType::LessThan);
return compare_expr;
}
case BinaryRangeExpr: {
proto::plan::GenericValue lower;
lower.set_int64_val(10);
proto::plan::GenericValue upper;
upper.set_int64_val(45);
return std::make_shared<expr::BinaryRangeFilterExpr>(
expr::ColumnInfo(int64_fid, DataType::INT64),
lower,
upper,
true,
true);
}
case LogicalUnaryExpr: {
proto::plan::GenericValue val;
val.set_int64_val(10);
auto child_expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int8_fid, DataType::INT8),
proto::plan::OpType::GreaterThan,
val);
return std::make_shared<expr::LogicalUnaryExpr>(
expr::LogicalUnaryExpr::OpType::LogicalNot, child_expr);
}
case LogicalBinaryExpr: {
proto::plan::GenericValue val;
val.set_int64_val(10);
auto child1_expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int8_fid, DataType::INT8),
proto::plan::OpType::GreaterThan,
val);
auto child2_expr = std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int8_fid, DataType::INT8),
proto::plan::OpType::NotEqual,
val);
;
return std::make_shared<const expr::LogicalBinaryExpr>(
expr::LogicalBinaryExpr::OpType::And,
child1_expr,
child2_expr);
}
case BinaryArithOpEvalRangeExpr: {
proto::plan::GenericValue val;
val.set_int64_val(100);
proto::plan::GenericValue right;
right.set_int64_val(10);
return std::make_shared<expr::BinaryArithOpEvalRangeExpr>(
expr::ColumnInfo(int8_fid, DataType::INT8),
proto::plan::OpType::Equal,
proto::plan::ArithOpType::Add,
val,
right);
}
default: {
proto::plan::GenericValue val;
val.set_int64_val(10);
return std::make_shared<expr::UnaryRangeFilterExpr>(
expr::ColumnInfo(int8_fid, DataType::INT8),
proto::plan::OpType::GreaterThan,
val);
}
}
};
auto test_case = [&](int n) {
auto expr = build_expr(UnaryRangeExpr, n);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
std::cout << "start test" << std::endl;
auto start = std::chrono::steady_clock::now();
final = ExecuteQueryExpr(plan, seg.get(), N, MAX_TIMESTAMP);
std::cout << n << "cost: "
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< "us" << std::endl;
};
test_case(3);
test_case(10);
test_case(20);
test_case(30);
test_case(50);
test_case(100);
test_case(200);
// test_case(500);
}
TEST_P(ExprTest, TestCompareWithScalarIndexMaris) {
std::vector<
std::tuple<std::string, std::function<bool(std::string, std::string)>>>
testcases = {
{R"(LessThan)",
[](std::string a, std::string b) { return a.compare(b) < 0; }},
{R"(LessEqual)",
[](std::string a, std::string b) { return a.compare(b) <= 0; }},
{R"(GreaterThan)",
[](std::string a, std::string b) { return a.compare(b) > 0; }},
{R"(GreaterEqual)",
[](std::string a, std::string b) { return a.compare(b) >= 0; }},
{R"(Equal)",
[](std::string a, std::string b) { return a.compare(b) == 0; }},
{R"(NotEqual)",
[](std::string a, std::string b) { return a.compare(b) != 0; }},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
compare_expr: <
left_column_info: <
field_id: %3%
data_type: VarChar
>
right_column_info: <
field_id: %4%
data_type: VarChar
>
op: %2%
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto str2_fid = schema->AddDebugField("string2", DataType::VARCHAR);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int32 field
auto str1_col = raw_data.get_col<std::string>(str1_fid);
auto str1_index = milvus::index::CreateScalarIndexSort<std::string>();
str1_index->Build(N, str1_col.data());
load_index_info.field_id = str1_fid.get();
load_index_info.field_type = DataType::VARCHAR;
load_index_info.index = std::move(str1_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto str2_col = raw_data.get_col<std::string>(str2_fid);
auto str2_index = milvus::index::CreateStringIndexMarisa();
str2_index->Build(N, str2_col.data());
load_index_info.field_id = str2_fid.get();
load_index_info.field_type = DataType::VARCHAR;
load_index_info.index = std::move(str2_index);
seg->LoadIndex(load_index_info);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto dsl_string = boost::format(serialized_expr_plan) % vec_fid.get() %
clause % str1_fid.get() % str2_fid.get();
auto binary_plan =
translate_text_plan_with_metric_type(dsl_string.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
// std::cout << ShowPlanNodeVisitor().call_child(*plan->plan_node_) << std::endl;
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg.get(),
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
auto val1 = str1_col[i];
auto val2 = str2_col[i];
auto ref = ref_func(val1, val2);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareWithScalarIndexMarisNullable) {
std::vector<std::tuple<std::string,
std::function<bool(std::string, std::string, bool)>>>
testcases = {
{R"(LessThan)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) < 0;
}},
{R"(LessEqual)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) <= 0;
}},
{R"(GreaterThan)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) > 0;
}},
{R"(GreaterEqual)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) >= 0;
}},
{R"(Equal)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) == 0;
}},
{R"(NotEqual)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) != 0;
}},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
compare_expr: <
left_column_info: <
field_id: %3%
data_type: VarChar
>
right_column_info: <
field_id: %4%
data_type: VarChar
>
op: %2%
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto nullable_fid =
schema->AddDebugField("nullable_fid", DataType::VARCHAR, true);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int32 field
auto str1_col = raw_data.get_col<std::string>(str1_fid);
auto str1_index = milvus::index::CreateScalarIndexSort<std::string>();
str1_index->Build(N, str1_col.data());
load_index_info.field_id = str1_fid.get();
load_index_info.field_type = DataType::VARCHAR;
load_index_info.index = std::move(str1_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto nullable_col = raw_data.get_col<std::string>(nullable_fid);
auto valid_data_col = raw_data.get_col_valid(nullable_fid);
auto str2_index = milvus::index::CreateStringIndexMarisa();
str2_index->Build(N, nullable_col.data(), valid_data_col.data());
load_index_info.field_id = nullable_fid.get();
load_index_info.field_type = DataType::VARCHAR;
load_index_info.index = std::move(str2_index);
seg->LoadIndex(load_index_info);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto dsl_string = boost::format(serialized_expr_plan) % vec_fid.get() %
clause % str1_fid.get() % nullable_fid.get();
auto binary_plan =
translate_text_plan_with_metric_type(dsl_string.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
// std::cout << ShowPlanNodeVisitor().call_child(*plan->plan_node_) << std::endl;
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg.get(),
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
auto val1 = str1_col[i];
auto val2 = nullable_col[i];
auto ref = ref_func(val1, val2, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestCompareWithScalarIndexMarisNullable2) {
std::vector<std::tuple<std::string,
std::function<bool(std::string, std::string, bool)>>>
testcases = {
{R"(LessThan)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) < 0;
}},
{R"(LessEqual)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) <= 0;
}},
{R"(GreaterThan)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) > 0;
}},
{R"(GreaterEqual)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) >= 0;
}},
{R"(Equal)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) == 0;
}},
{R"(NotEqual)",
[](std::string a, std::string b, bool valid) {
if (!valid) {
return false;
}
return a.compare(b) != 0;
}},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
compare_expr: <
left_column_info: <
field_id: %3%
data_type: VarChar
>
right_column_info: <
field_id: %4%
data_type: VarChar
>
op: %2%
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto str1_fid = schema->AddDebugField("string1", DataType::VARCHAR);
auto nullable_fid =
schema->AddDebugField("nullable_fid", DataType::VARCHAR, true);
schema->set_primary_field_id(str1_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int32 field
auto str1_col = raw_data.get_col<std::string>(str1_fid);
auto str1_index = milvus::index::CreateScalarIndexSort<std::string>();
str1_index->Build(N, str1_col.data());
load_index_info.field_id = str1_fid.get();
load_index_info.field_type = DataType::VARCHAR;
load_index_info.index = std::move(str1_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto nullable_col = raw_data.get_col<std::string>(nullable_fid);
auto valid_data_col = raw_data.get_col_valid(nullable_fid);
auto str2_index = milvus::index::CreateStringIndexMarisa();
str2_index->Build(N, nullable_col.data(), valid_data_col.data());
load_index_info.field_id = nullable_fid.get();
load_index_info.field_type = DataType::VARCHAR;
load_index_info.index = std::move(str2_index);
seg->LoadIndex(load_index_info);
query::ExecPlanNodeVisitor visitor(*seg, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto dsl_string = boost::format(serialized_expr_plan) % vec_fid.get() %
clause % nullable_fid.get() % str1_fid.get();
auto binary_plan =
translate_text_plan_with_metric_type(dsl_string.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
// std::cout << ShowPlanNodeVisitor().call_child(*plan->plan_node_) << std::endl;
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg.get(),
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
auto val1 = nullable_col[i];
auto val2 = str1_col[i];
auto ref = ref_func(val1, val2, valid_data_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!"
<< boost::format("[%1%, %2%]") % val1 % val2;
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRange) {
std::vector<std::tuple<std::string, std::function<bool(int)>, DataType>> testcases = {
// Add test cases for BinaryArithOpEvalRangeExpr EQ of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Add
right_operand: <
int64_val: 4
>
op: Equal
value: <
int64_val: 8
>
>)",
[](int8_t v) { return (v + 4) == 8; },
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Sub
right_operand: <
int64_val: 500
>
op: Equal
value: <
int64_val: 1500
>
>)",
[](int16_t v) { return (v - 500) == 1500; },
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4000
>
>)",
[](int32_t v) { return (v * 2) == 4000; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 1000
>
>)",
[](int64_t v) { return (v / 2) == 1000; },
DataType::INT64},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: Equal
value: <
int64_val: 0
>
>)",
[](int32_t v) { return (v % 100) == 0; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>
>)",
[](float v) { return (v + 500) == 2500; },
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>
>)",
[](double v) { return (v + 500) == 2500; },
DataType::DOUBLE},
// Add test cases for BinaryArithOpEvalRangeExpr NE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2500
>
>)",
[](float v) { return (v + 500) != 2500; },
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2500
>
>)",
[](double v) { return (v - 500) != 2500; },
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 2
>
>)",
[](int8_t v) { return (v * 2) != 2; },
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 1000
>
>)",
[](int16_t v) { return (v / 2) != 1000; },
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: NotEqual
value: <
int64_val: 0
>
>)",
[](int32_t v) { return (v % 100) != 0; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: NotEqual
value: <
int64_val: 2500
>
>)",
[](int64_t v) { return (v + 500) != 2500; },
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr GT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: GreaterThan
value: <
float_val: 2500
>
>)",
[](float v) { return (v + 500) > 2500; },
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: GreaterThan
value: <
float_val: 2500
>
>)",
[](double v) { return (v - 500) > 2500; },
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 2
>
>)",
[](int8_t v) { return (v * 2) > 2; },
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 1000
>
>)",
[](int16_t v) { return (v / 2) > 1000; },
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterThan
value: <
int64_val: 0
>
>)",
[](int32_t v) { return (v % 100) > 0; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: GreaterThan
value: <
int64_val: 2500
>
>)",
[](int64_t v) { return (v + 500) > 2500; },
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr GE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: GreaterEqual
value: <
float_val: 2500
>
>)",
[](float v) { return (v + 500) >= 2500; },
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: GreaterEqual
value: <
float_val: 2500
>
>)",
[](double v) { return (v - 500) >= 2500; },
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 2
>
>)",
[](int8_t v) { return (v * 2) >= 2; },
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 1000
>
>)",
[](int16_t v) { return (v / 2) >= 1000; },
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterEqual
value: <
int64_val: 0
>
>)",
[](int32_t v) { return (v % 100) >= 0; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: GreaterEqual
value: <
int64_val: 2500
>
>)",
[](int64_t v) { return (v + 500) >= 2500; },
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr LT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: LessThan
value: <
float_val: 2500
>
>)",
[](float v) { return (v + 500) < 2500; },
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: LessThan
value: <
float_val: 2500
>
>)",
[](double v) { return (v - 500) < 2500; },
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 2
>
>)",
[](int8_t v) { return (v * 2) < 2; },
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 1000
>
>)",
[](int16_t v) { return (v / 2) < 1000; },
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessThan
value: <
int64_val: 0
>
>)",
[](int32_t v) { return (v % 100) < 0; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: LessThan
value: <
int64_val: 2500
>
>)",
[](int64_t v) { return (v + 500) < 2500; },
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr LE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: LessEqual
value: <
float_val: 2500
>
>)",
[](float v) { return (v + 500) <= 2500; },
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: LessEqual
value: <
float_val: 2500
>
>)",
[](double v) { return (v - 500) <= 2500; },
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 2
>
>)",
[](int8_t v) { return (v * 2) <= 2; },
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 1000
>
>)",
[](int16_t v) { return (v / 2) <= 1000; },
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessEqual
value: <
int64_val: 0
>
>)",
[](int32_t v) { return (v % 100) <= 0; },
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: LessEqual
value: <
int64_val: 2500
>
>)",
[](int64_t v) { return (v + 500) <= 2500; },
DataType::INT64},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
@@@@@
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i8_fid = schema->AddDebugField("age8", DataType::INT8);
auto i16_fid = schema->AddDebugField("age16", DataType::INT16);
auto i32_fid = schema->AddDebugField("age32", DataType::INT32);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto float_fid = schema->AddDebugField("age_float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("age_double", DataType::DOUBLE);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int8_t> age8_col;
std::vector<int16_t> age16_col;
std::vector<int32_t> age32_col;
std::vector<int64_t> age64_col;
std::vector<float> age_float_col;
std::vector<double> age_double_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age8_col = raw_data.get_col<int8_t>(i8_fid);
auto new_age16_col = raw_data.get_col<int16_t>(i16_fid);
auto new_age32_col = raw_data.get_col<int32_t>(i32_fid);
auto new_age64_col = raw_data.get_col<int64_t>(i64_fid);
auto new_age_float_col = raw_data.get_col<float>(float_fid);
auto new_age_double_col = raw_data.get_col<double>(double_fid);
age8_col.insert(
age8_col.end(), new_age8_col.begin(), new_age8_col.end());
age16_col.insert(
age16_col.end(), new_age16_col.begin(), new_age16_col.end());
age32_col.insert(
age32_col.end(), new_age32_col.begin(), new_age32_col.end());
age64_col.insert(
age64_col.end(), new_age64_col.begin(), new_age64_col.end());
age_float_col.insert(age_float_col.end(),
new_age_float_col.begin(),
new_age_float_col.end());
age_double_col.insert(age_double_col.end(),
new_age_double_col.begin(),
new_age_double_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = raw_plan_tmp.find("@@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 5, clause);
// if (dtype == DataType::INT8) {
// dsl_string.replace(loc, 5, dsl_string_int8);
// } else if (dtype == DataType::INT16) {
// dsl_string.replace(loc, 5, dsl_string_int16);
// } else if (dtype == DataType::INT32) {
// dsl_string.replace(loc, 5, dsl_string_int32);
// } else if (dtype == DataType::INT64) {
// dsl_string.replace(loc, 5, dsl_string_int64);
// } else if (dtype == DataType::FLOAT) {
// dsl_string.replace(loc, 5, dsl_string_float);
// } else if (dtype == DataType::DOUBLE) {
// dsl_string.replace(loc, 5, dsl_string_double);
// } else {
// ASSERT_TRUE(false) << "No test case defined for this data type";
// }
// loc = dsl_string.find("@@@@");
// dsl_string.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::INT8) {
auto val = age8_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref)
<< clause << "@" << i << "!!" << val << std::endl;
} else if (dtype == DataType::INT16) {
auto val = age16_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT32) {
auto val = age32_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = age64_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::FLOAT) {
auto val = age_float_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = age_double_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeNullable) {
std::vector<
std::tuple<std::string, std::function<bool(int, bool)>, DataType>>
testcases = {
// Add test cases for BinaryArithOpEvalRangeExpr EQ of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Add
right_operand: <
int64_val: 4
>
op: Equal
value: <
int64_val: 8
>
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 4) == 8;
},
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Sub
right_operand: <
int64_val: 500
>
op: Equal
value: <
int64_val: 1500
>
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) == 1500;
},
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4000
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) == 4000;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 1000
>
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) == 1000;
},
DataType::INT64},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: Equal
value: <
int64_val: 0
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) == 0;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) == 2500;
},
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) == 2500;
},
DataType::DOUBLE},
// Add test cases for BinaryArithOpEvalRangeExpr NE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2500
>
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) != 2500;
},
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2500
>
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) != 2500;
},
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 2
>
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) != 2;
},
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 1000
>
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) != 1000;
},
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: NotEqual
value: <
int64_val: 0
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) != 0;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: NotEqual
value: <
int64_val: 2500
>
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) != 2500;
},
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr GT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: GreaterThan
value: <
float_val: 2500
>
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) > 2500;
},
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: GreaterThan
value: <
float_val: 2500
>
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) > 2500;
},
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 2
>
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) > 2;
},
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 1000
>
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) > 1000;
},
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterThan
value: <
int64_val: 0
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) > 0;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: GreaterThan
value: <
int64_val: 2500
>
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) > 2500;
},
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr GE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: GreaterEqual
value: <
float_val: 2500
>
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) >= 2500;
},
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: GreaterEqual
value: <
float_val: 2500
>
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) >= 2500;
},
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 2
>
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) >= 2;
},
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 1000
>
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) >= 1000;
},
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterEqual
value: <
int64_val: 0
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) >= 0;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: GreaterEqual
value: <
int64_val: 2500
>
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) >= 2500;
},
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr LT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: LessThan
value: <
float_val: 2500
>
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) < 2500;
},
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: LessThan
value: <
float_val: 2500
>
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) < 2500;
},
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 2
>
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) < 2;
},
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 1000
>
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) < 1000;
},
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessThan
value: <
int64_val: 0
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) < 0;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: LessThan
value: <
int64_val: 2500
>
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) < 2500;
},
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr LE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 105
data_type: Float
>
arith_op: Add
right_operand: <
float_val: 500
>
op: LessEqual
value: <
float_val: 2500
>
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) <= 2500;
},
DataType::FLOAT},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 106
data_type: Double
>
arith_op: Sub
right_operand: <
float_val: 500
>
op: LessEqual
value: <
float_val: 2500
>
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) <= 2500;
},
DataType::DOUBLE},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 101
data_type: Int8
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 2
>
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) <= 2;
},
DataType::INT8},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 102
data_type: Int16
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 1000
>
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) <= 1000;
},
DataType::INT16},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 103
data_type: Int32
>
arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessEqual
value: <
int64_val: 0
>
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) <= 0;
},
DataType::INT32},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id: 104
data_type: Int64
>
arith_op: Mod
right_operand: <
int64_val: 500
>
op: LessEqual
value: <
int64_val: 2500
>
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) <= 2500;
},
DataType::INT64},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
@@@@@
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i8_nullable_fid = schema->AddDebugField("age8", DataType::INT8, true);
auto i16_nullable_fid =
schema->AddDebugField("age16", DataType::INT16, true);
auto i32_nullable_fid =
schema->AddDebugField("age32", DataType::INT32, true);
auto i64_nullable_fid =
schema->AddDebugField("age64_nullable", DataType::INT64, true);
auto float_nullable_fid =
schema->AddDebugField("age_float", DataType::FLOAT, true);
auto double_nullable_fid =
schema->AddDebugField("age_double", DataType::DOUBLE, true);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<int8_t> age8_col;
std::vector<int16_t> age16_col;
std::vector<int32_t> age32_col;
std::vector<int64_t> age64_col;
std::vector<float> age_float_col;
std::vector<double> age_double_col;
FixedVector<bool> age8_valid_col;
FixedVector<bool> age16_valid_col;
FixedVector<bool> age32_valid_col;
FixedVector<bool> age64_valid_col;
FixedVector<bool> age_float_valid_col;
FixedVector<bool> age_double_valid_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_age8_col = raw_data.get_col<int8_t>(i8_nullable_fid);
auto new_age16_col = raw_data.get_col<int16_t>(i16_nullable_fid);
auto new_age32_col = raw_data.get_col<int32_t>(i32_nullable_fid);
auto new_age64_col = raw_data.get_col<int64_t>(i64_nullable_fid);
auto new_age_float_col = raw_data.get_col<float>(float_nullable_fid);
auto new_age_double_col = raw_data.get_col<double>(double_nullable_fid);
age8_valid_col = raw_data.get_col_valid(i8_nullable_fid);
age16_valid_col = raw_data.get_col_valid(i16_nullable_fid);
age32_valid_col = raw_data.get_col_valid(i32_nullable_fid);
age64_valid_col = raw_data.get_col_valid(i64_nullable_fid);
age_float_valid_col = raw_data.get_col_valid(float_nullable_fid);
age_double_valid_col = raw_data.get_col_valid(double_nullable_fid);
age8_col.insert(
age8_col.end(), new_age8_col.begin(), new_age8_col.end());
age16_col.insert(
age16_col.end(), new_age16_col.begin(), new_age16_col.end());
age32_col.insert(
age32_col.end(), new_age32_col.begin(), new_age32_col.end());
age64_col.insert(
age64_col.end(), new_age64_col.begin(), new_age64_col.end());
age_float_col.insert(age_float_col.end(),
new_age_float_col.begin(),
new_age_float_col.end());
age_double_col.insert(age_double_col.end(),
new_age_double_col.begin(),
new_age_double_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = raw_plan_tmp.find("@@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 5, clause);
// if (dtype == DataType::INT8) {
// dsl_string.replace(loc, 5, dsl_string_int8);
// } else if (dtype == DataType::INT16) {
// dsl_string.replace(loc, 5, dsl_string_int16);
// } else if (dtype == DataType::INT32) {
// dsl_string.replace(loc, 5, dsl_string_int32);
// } else if (dtype == DataType::INT64) {
// dsl_string.replace(loc, 5, dsl_string_int64);
// } else if (dtype == DataType::FLOAT) {
// dsl_string.replace(loc, 5, dsl_string_float);
// } else if (dtype == DataType::DOUBLE) {
// dsl_string.replace(loc, 5, dsl_string_double);
// } else {
// ASSERT_TRUE(false) << "No test case defined for this data type";
// }
// loc = dsl_string.find("@@@@");
// dsl_string.replace(loc, 4, clause);
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::INT8) {
auto val = age8_col[i];
auto ref = ref_func(val, age8_valid_col[i]);
ASSERT_EQ(ans, ref)
<< clause << "@" << i << "!!" << val << std::endl;
} else if (dtype == DataType::INT16) {
auto val = age16_col[i];
auto ref = ref_func(val, age16_valid_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT32) {
auto val = age32_col[i];
auto ref = ref_func(val, age32_valid_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = age64_col[i];
auto ref = ref_func(val, age64_valid_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::FLOAT) {
auto val = age_float_col[i];
auto ref = ref_func(val, age_float_valid_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = age_double_col[i];
auto ref = ref_func(val, age_double_valid_col[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeJSON) {
using namespace milvus;
using namespace milvus::query;
using namespace milvus::segcore;
std::vector<
std::tuple<std::string, std::function<bool(const milvus::Json& json)>>>
testcases = {
// Add test cases for BinaryArithOpEvalRangeExpr EQ of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: Equal
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) == 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: Equal
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) == 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) == 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) == 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: Equal
value:<int64_val:4>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) == 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: Equal
value:<int64_val:4>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length == 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr NQ of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: NotEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) != 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: NotEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) != 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) != 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) != 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) != 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length != 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr GT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: GreaterThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) > 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: GreaterThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) > 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) > 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) > 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) > 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length > 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr GE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: GreaterEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) >= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: GreaterEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) >= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) >= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) >= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) >= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length >= 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr LT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: LessThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) < 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: LessThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) < 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) < 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) < 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) < 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length < 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr LE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: LessEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) <= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: LessEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) <= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) <= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) <= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) <= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json) {
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length <= 4;
}},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
@@@@@
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField(
"fakevec", DataType::VECTOR_FLOAT, 16, knowhere::metric::L2);
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 5, clause);
auto plan_str = translate_text_plan_to_binary_plan(raw_plan.c_str());
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto ref =
ref_func(milvus::Json(simdjson::padded_string(json_col[i])));
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << json_col[i];
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeJSONNullable) {
using namespace milvus;
using namespace milvus::query;
using namespace milvus::segcore;
std::vector<
std::tuple<std::string,
std::function<bool(const milvus::Json& json, bool valid)>>>
testcases = {
// Add test cases for BinaryArithOpEvalRangeExpr EQ of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: Equal
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) == 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: Equal
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) == 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) == 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) == 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: Equal
value:<int64_val:4>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) == 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: Equal
value:<int64_val:4>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length == 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr NQ of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: NotEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) != 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: NotEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) != 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) != 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) != 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) != 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: NotEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length != 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr GT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: GreaterThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) > 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: GreaterThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) > 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) > 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) > 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) > 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: GreaterThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length > 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr GE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: GreaterEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) >= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: GreaterEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) >= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) >= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) >= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) >= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: GreaterEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length >= 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr LT of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: LessThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) < 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: LessThan
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) < 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) < 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) < 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) < 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: LessThan
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length < 4;
}},
// Add test cases for BinaryArithOpEvalRangeExpr LE of various data types
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Add
right_operand: <
int64_val: 1
>
op: LessEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val + 1) <= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Sub
right_operand: <
int64_val: 1
>
op: LessEqual
value: <
int64_val: 2
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val - 1) <= 2;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val * 2) <= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Div
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val / 2) <= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"int"
>
arith_op: Mod
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"int"});
auto val = json.template at<int64_t>(pointer).value();
return (val % 2) <= 4;
}},
{R"(binary_arith_op_eval_range_expr: <
column_info: <
field_id:102
data_type:JSON
nested_path:"array"
>
arith_op: ArrayLength
op: LessEqual
value: <
int64_val: 4
>
>)",
[](const milvus::Json& json, bool valid) {
if (!valid) {
return false;
}
auto pointer = milvus::Json::pointer({"array"});
int array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
return array_length <= 4;
}},
};
std::string raw_plan_tmp = R"(vector_anns: <
field_id: 100
predicates: <
@@@@@
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField(
"fakevec", DataType::VECTOR_FLOAT, 16, knowhere::metric::L2);
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto nullable_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(nullable_fid);
valid_data = raw_data.get_col_valid(nullable_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto [clause, ref_func] : testcases) {
auto loc = raw_plan_tmp.find("@@@@@");
auto raw_plan = raw_plan_tmp;
raw_plan.replace(loc, 5, clause);
auto plan_str = translate_text_plan_to_binary_plan(raw_plan.c_str());
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto ref =
ref_func(milvus::Json(simdjson::padded_string(json_col[i])),
valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << json_col[i];
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeJSONFloat) {
struct Testcase {
double right_operand;
double value;
OpType op;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{10, 20, OpType::Equal, {"double"}},
{20, 30, OpType::Equal, {"double"}},
{30, 40, OpType::NotEqual, {"double"}},
{40, 50, OpType::NotEqual, {"double"}},
{10, 20, OpType::Equal, {"int"}},
{20, 30, OpType::Equal, {"int"}},
{30, 40, OpType::NotEqual, {"int"}},
{40, 50, OpType::NotEqual, {"int"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto testcase : testcases) {
auto check = [&](double value) {
if (testcase.op == OpType::Equal) {
return value + testcase.right_operand == testcase.value;
}
return value + testcase.right_operand != testcase.value;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
proto::plan::GenericValue value;
value.set_float_val(testcase.value);
proto::plan::GenericValue right_operand;
right_operand.set_float_val(testcase.right_operand);
auto expr = std::make_shared<milvus::expr::BinaryArithOpEvalRangeExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
testcase.op,
ArithOpType::Add,
value,
right_operand);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>(pointer)
.value();
auto ref = check(val);
ASSERT_EQ(ans, ref)
<< testcase.value << " " << val << " " << testcase.op;
}
}
std::vector<Testcase> array_testcases{
{0, 3, OpType::Equal, {"array"}},
{0, 5, OpType::NotEqual, {"array"}},
};
for (auto testcase : array_testcases) {
auto check = [&](int64_t value) {
if (testcase.op == OpType::Equal) {
return value == testcase.value;
}
return value != testcase.value;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
proto::plan::GenericValue value;
value.set_int64_val(testcase.value);
proto::plan::GenericValue right_operand;
right_operand.set_int64_val(testcase.right_operand);
auto expr = std::make_shared<milvus::expr::BinaryArithOpEvalRangeExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
testcase.op,
ArithOpType::ArrayLength,
value,
right_operand);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto json = milvus::Json(simdjson::padded_string(json_col[i]));
int64_t array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
auto ref = check(array_length);
ASSERT_EQ(ans, ref) << testcase.value << " " << array_length;
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeJSONFloatNullable) {
struct Testcase {
double right_operand;
double value;
OpType op;
std::vector<std::string> nested_path;
};
std::vector<Testcase> testcases{
{10, 20, OpType::Equal, {"double"}},
{20, 30, OpType::Equal, {"double"}},
{30, 40, OpType::NotEqual, {"double"}},
{40, 50, OpType::NotEqual, {"double"}},
{10, 20, OpType::Equal, {"int"}},
{20, 30, OpType::Equal, {"int"}},
{30, 40, OpType::NotEqual, {"int"}},
{40, 50, OpType::NotEqual, {"int"}},
};
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
for (auto testcase : testcases) {
auto check = [&](double value, bool valid) {
if (!valid) {
return false;
}
if (testcase.op == OpType::Equal) {
return value + testcase.right_operand == testcase.value;
}
return value + testcase.right_operand != testcase.value;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
proto::plan::GenericValue value;
value.set_float_val(testcase.value);
proto::plan::GenericValue right_operand;
right_operand.set_float_val(testcase.right_operand);
auto expr = std::make_shared<milvus::expr::BinaryArithOpEvalRangeExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
testcase.op,
ArithOpType::Add,
value,
right_operand);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>(pointer)
.value();
auto ref = check(val, valid_data[i]);
ASSERT_EQ(ans, ref)
<< testcase.value << " " << val << " " << testcase.op;
}
}
std::vector<Testcase> array_testcases{
{0, 3, OpType::Equal, {"array"}},
{0, 5, OpType::NotEqual, {"array"}},
};
for (auto testcase : array_testcases) {
auto check = [&](int64_t value, bool valid) {
if (!valid) {
return false;
}
if (testcase.op == OpType::Equal) {
return value == testcase.value;
}
return value != testcase.value;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
proto::plan::GenericValue value;
value.set_int64_val(testcase.value);
proto::plan::GenericValue right_operand;
right_operand.set_int64_val(testcase.right_operand);
auto expr = std::make_shared<milvus::expr::BinaryArithOpEvalRangeExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
testcase.op,
ArithOpType::ArrayLength,
value,
right_operand);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto json = milvus::Json(simdjson::padded_string(json_col[i]));
int64_t array_length = 0;
auto doc = json.doc();
auto array = doc.at_pointer(pointer).get_array();
if (!array.error()) {
array_length = array.count_elements();
}
auto ref = check(array_length, valid_data[i]);
ASSERT_EQ(ans, ref) << testcase.value << " " << array_length;
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeWithScalarSortIndex) {
std::vector<std::tuple<std::string, std::function<bool(int)>, DataType>>
testcases = {
// Add test cases for BinaryArithOpEvalRangeExpr EQ of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: Equal
value: <
int64_val: 8
>)",
[](int8_t v) { return (v + 4) == 8; },
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: Equal
value: <
int64_val: 1500
>)",
[](int16_t v) { return (v - 500) == 1500; },
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4000
>)",
[](int32_t v) { return (v * 2) == 4000; },
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 1000
>)",
[](int64_t v) { return (v / 2) == 1000; },
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: Equal
value: <
int64_val: 0
>)",
[](int32_t v) { return (v % 100) == 0; },
DataType::INT32},
{R"(arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>)",
[](float v) { return (v + 500) == 2500; },
DataType::FLOAT},
{R"(arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>)",
[](double v) { return (v + 500) == 2500; },
DataType::DOUBLE},
{R"(arith_op: Add
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2000
>)",
[](float v) { return (v + 500) != 2000; },
DataType::FLOAT},
{R"(arith_op: Sub
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2500
>)",
[](double v) { return (v - 500) != 2000; },
DataType::DOUBLE},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 2
>)",
[](int8_t v) { return (v * 2) != 2; },
DataType::INT8},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 2000
>)",
[](int16_t v) { return (v / 2) != 2000; },
DataType::INT16},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: NotEqual
value: <
int64_val: 1
>)",
[](int32_t v) { return (v % 100) != 1; },
DataType::INT32},
{R"(arith_op: Add
right_operand: <
int64_val: 500
>
op: NotEqual
value: <
int64_val: 2000
>)",
[](int64_t v) { return (v + 500) != 2000; },
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr GT of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: GreaterThan
value: <
int64_val: 8
>)",
[](int8_t v) { return (v + 4) > 8; },
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: GreaterThan
value: <
int64_val: 1500
>)",
[](int16_t v) { return (v - 500) > 1500; },
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4000
>)",
[](int32_t v) { return (v * 2) > 4000; },
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 1000
>)",
[](int64_t v) { return (v / 2) > 1000; },
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterThan
value: <
int64_val: 0
>)",
[](int32_t v) { return (v % 100) > 0; },
DataType::INT32},
// Add test cases for BinaryArithOpEvalRangeExpr GE of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: GreaterEqual
value: <
int64_val: 8
>)",
[](int8_t v) { return (v + 4) >= 8; },
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: GreaterEqual
value: <
int64_val: 1500
>)",
[](int16_t v) { return (v - 500) >= 1500; },
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4000
>)",
[](int32_t v) { return (v * 2) >= 4000; },
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 1000
>)",
[](int64_t v) { return (v / 2) >= 1000; },
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterEqual
value: <
int64_val: 0
>)",
[](int32_t v) { return (v % 100) >= 0; },
DataType::INT32},
// Add test cases for BinaryArithOpEvalRangeExpr LT of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: LessThan
value: <
int64_val: 8
>)",
[](int8_t v) { return (v + 4) < 8; },
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: LessThan
value: <
int64_val: 1500
>)",
[](int16_t v) { return (v - 500) < 1500; },
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4000
>)",
[](int32_t v) { return (v * 2) < 4000; },
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 1000
>)",
[](int64_t v) { return (v / 2) < 1000; },
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessThan
value: <
int64_val: 0
>)",
[](int32_t v) { return (v % 100) < 0; },
DataType::INT32},
// Add test cases for BinaryArithOpEvalRangeExpr LE of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: LessEqual
value: <
int64_val: 8
>)",
[](int8_t v) { return (v + 4) <= 8; },
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: LessEqual
value: <
int64_val: 1500
>)",
[](int16_t v) { return (v - 500) <= 1500; },
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4000
>)",
[](int32_t v) { return (v * 2) <= 4000; },
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 1000
>)",
[](int64_t v) { return (v / 2) <= 1000; },
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessEqual
value: <
int64_val: 0
>)",
[](int32_t v) { return (v % 100) <= 0; },
DataType::INT32},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
binary_arith_op_eval_range_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
column_info: <
field_id: %2%
data_type: %3%
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i8_fid = schema->AddDebugField("age8", DataType::INT8);
auto i16_fid = schema->AddDebugField("age16", DataType::INT16);
auto i32_fid = schema->AddDebugField("age32", DataType::INT32);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto float_fid = schema->AddDebugField("age_float", DataType::FLOAT);
auto double_fid = schema->AddDebugField("age_double", DataType::DOUBLE);
schema->set_primary_field_id(i64_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
// load index for int8 field
auto age8_col = raw_data.get_col<int8_t>(i8_fid);
age8_col[0] = 4;
auto age8_index = milvus::index::CreateScalarIndexSort<int8_t>();
age8_index->Build(N, age8_col.data(), nullptr);
load_index_info.field_id = i8_fid.get();
load_index_info.field_type = DataType::INT8;
load_index_info.index = std::move(age8_index);
seg->LoadIndex(load_index_info);
// load index for 16 field
auto age16_col = raw_data.get_col<int16_t>(i16_fid);
age16_col[0] = 2000;
auto age16_index = milvus::index::CreateScalarIndexSort<int16_t>();
age16_index->Build(N, age16_col.data(), nullptr);
load_index_info.field_id = i16_fid.get();
load_index_info.field_type = DataType::INT16;
load_index_info.index = std::move(age16_index);
seg->LoadIndex(load_index_info);
// load index for int32 field
auto age32_col = raw_data.get_col<int32_t>(i32_fid);
age32_col[0] = 2000;
auto age32_index = milvus::index::CreateScalarIndexSort<int32_t>();
age32_index->Build(N, age32_col.data(), nullptr);
load_index_info.field_id = i32_fid.get();
load_index_info.field_type = DataType::INT32;
load_index_info.index = std::move(age32_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto age64_col = raw_data.get_col<int64_t>(i64_fid);
age64_col[0] = 2000;
auto age64_index = milvus::index::CreateScalarIndexSort<int64_t>();
age64_index->Build(N, age64_col.data(), nullptr);
load_index_info.field_id = i64_fid.get();
load_index_info.field_type = DataType::INT64;
load_index_info.index = std::move(age64_index);
seg->LoadIndex(load_index_info);
// load index for float field
auto age_float_col = raw_data.get_col<float>(float_fid);
age_float_col[0] = 2000;
auto age_float_index = milvus::index::CreateScalarIndexSort<float>();
age_float_index->Build(N, age_float_col.data(), nullptr);
load_index_info.field_id = float_fid.get();
load_index_info.field_type = DataType::FLOAT;
load_index_info.index = std::move(age_float_index);
seg->LoadIndex(load_index_info);
// load index for double field
auto age_double_col = raw_data.get_col<double>(double_fid);
age_double_col[0] = 2000;
auto age_double_index = milvus::index::CreateScalarIndexSort<double>();
age_double_index->Build(N, age_double_col.data(), nullptr);
load_index_info.field_id = double_fid.get();
load_index_info.field_type = DataType::FLOAT;
load_index_info.index = std::move(age_double_index);
seg->LoadIndex(load_index_info);
auto seg_promote = dynamic_cast<SegmentSealedImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
if (dtype == DataType::INT8) {
expr = boost::format(expr_plan) % vec_fid.get() % i8_fid.get() %
proto::schema::DataType_Name(int(DataType::INT8));
} else if (dtype == DataType::INT16) {
expr = boost::format(expr_plan) % vec_fid.get() % i16_fid.get() %
proto::schema::DataType_Name(int(DataType::INT16));
} else if (dtype == DataType::INT32) {
expr = boost::format(expr_plan) % vec_fid.get() % i32_fid.get() %
proto::schema::DataType_Name(int(DataType::INT32));
} else if (dtype == DataType::INT64) {
expr = boost::format(expr_plan) % vec_fid.get() % i64_fid.get() %
proto::schema::DataType_Name(int(DataType::INT64));
} else if (dtype == DataType::FLOAT) {
expr = boost::format(expr_plan) % vec_fid.get() % float_fid.get() %
proto::schema::DataType_Name(int(DataType::FLOAT));
} else if (dtype == DataType::DOUBLE) {
expr = boost::format(expr_plan) % vec_fid.get() % double_fid.get() %
proto::schema::DataType_Name(int(DataType::DOUBLE));
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
auto binary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
if (dtype == DataType::INT8) {
auto val = age8_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT16) {
auto val = age16_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT32) {
auto val = age32_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = age64_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::FLOAT) {
auto val = age_float_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = age_double_col[i];
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestBinaryArithOpEvalRangeWithScalarSortIndexNullable) {
std::vector<
std::tuple<std::string, std::function<bool(int, bool)>, DataType>>
testcases = {
// Add test cases for BinaryArithOpEvalRangeExpr EQ of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: Equal
value: <
int64_val: 8
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 4) == 8;
},
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: Equal
value: <
int64_val: 1500
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) == 1500;
},
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 4000
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) == 4000;
},
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: Equal
value: <
int64_val: 1000
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) == 1000;
},
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: Equal
value: <
int64_val: 0
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) == 0;
},
DataType::INT32},
{R"(arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) == 2500;
},
DataType::FLOAT},
{R"(arith_op: Add
right_operand: <
float_val: 500
>
op: Equal
value: <
float_val: 2500
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) == 2500;
},
DataType::DOUBLE},
{R"(arith_op: Add
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2000
>)",
[](float v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) != 2000;
},
DataType::FLOAT},
{R"(arith_op: Sub
right_operand: <
float_val: 500
>
op: NotEqual
value: <
float_val: 2500
>)",
[](double v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) != 2000;
},
DataType::DOUBLE},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 2
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) != 2;
},
DataType::INT8},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: NotEqual
value: <
int64_val: 2000
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) != 2000;
},
DataType::INT16},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: NotEqual
value: <
int64_val: 1
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) != 1;
},
DataType::INT32},
{R"(arith_op: Add
right_operand: <
int64_val: 500
>
op: NotEqual
value: <
int64_val: 2000
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 500) != 2000;
},
DataType::INT64},
// Add test cases for BinaryArithOpEvalRangeExpr GT of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: GreaterThan
value: <
int64_val: 8
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 4) > 8;
},
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: GreaterThan
value: <
int64_val: 1500
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) > 1500;
},
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 4000
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) > 4000;
},
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterThan
value: <
int64_val: 1000
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) > 1000;
},
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterThan
value: <
int64_val: 0
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) > 0;
},
DataType::INT32},
// Add test cases for BinaryArithOpEvalRangeExpr GE of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: GreaterEqual
value: <
int64_val: 8
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 4) >= 8;
},
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: GreaterEqual
value: <
int64_val: 1500
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) >= 1500;
},
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 4000
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) >= 4000;
},
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: GreaterEqual
value: <
int64_val: 1000
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) >= 1000;
},
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: GreaterEqual
value: <
int64_val: 0
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) >= 0;
},
DataType::INT32},
// Add test cases for BinaryArithOpEvalRangeExpr LT of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: LessThan
value: <
int64_val: 8
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 4) < 8;
},
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: LessThan
value: <
int64_val: 1500
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) < 1500;
},
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 4000
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) < 4000;
},
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: LessThan
value: <
int64_val: 1000
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) < 1000;
},
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessThan
value: <
int64_val: 0
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) < 0;
},
DataType::INT32},
// Add test cases for BinaryArithOpEvalRangeExpr LE of various data types
{R"(arith_op: Add
right_operand: <
int64_val: 4
>
op: LessEqual
value: <
int64_val: 8
>)",
[](int8_t v, bool valid) {
if (!valid) {
return false;
}
return (v + 4) <= 8;
},
DataType::INT8},
{R"(arith_op: Sub
right_operand: <
int64_val: 500
>
op: LessEqual
value: <
int64_val: 1500
>)",
[](int16_t v, bool valid) {
if (!valid) {
return false;
}
return (v - 500) <= 1500;
},
DataType::INT16},
{R"(arith_op: Mul
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 4000
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v * 2) <= 4000;
},
DataType::INT32},
{R"(arith_op: Div
right_operand: <
int64_val: 2
>
op: LessEqual
value: <
int64_val: 1000
>)",
[](int64_t v, bool valid) {
if (!valid) {
return false;
}
return (v / 2) <= 1000;
},
DataType::INT64},
{R"(arith_op: Mod
right_operand: <
int64_val: 100
>
op: LessEqual
value: <
int64_val: 0
>)",
[](int32_t v, bool valid) {
if (!valid) {
return false;
}
return (v % 100) <= 0;
},
DataType::INT32},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
binary_arith_op_eval_range_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
column_info: <
field_id: %2%
data_type: %3%
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i8_nullable_fid = schema->AddDebugField("age8", DataType::INT8, true);
auto i16_nullable_fid =
schema->AddDebugField("age16", DataType::INT16, true);
auto i32_nullable_fid =
schema->AddDebugField("age32", DataType::INT32, true);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto i64_nullable_fid =
schema->AddDebugField("age641", DataType::INT64, true);
auto float_nullable_fid =
schema->AddDebugField("age_float", DataType::FLOAT, true);
auto double_nullable_fid =
schema->AddDebugField("age_double", DataType::DOUBLE, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateSealedSegment(schema);
int N = 1000;
auto raw_data = DataGen(schema, N);
segcore::LoadIndexInfo load_index_info;
auto i8_valid_data = raw_data.get_col_valid(i8_nullable_fid);
auto i16_valid_data = raw_data.get_col_valid(i16_nullable_fid);
auto i32_valid_data = raw_data.get_col_valid(i32_nullable_fid);
auto i64_valid_data = raw_data.get_col_valid(i64_nullable_fid);
auto float_valid_data = raw_data.get_col_valid(float_nullable_fid);
auto double_valid_data = raw_data.get_col_valid(double_nullable_fid);
// load index for int8 field
auto age8_col = raw_data.get_col<int8_t>(i8_nullable_fid);
age8_col[0] = 4;
auto age8_index = milvus::index::CreateScalarIndexSort<int8_t>();
age8_index->Build(N, age8_col.data(), i8_valid_data.data());
load_index_info.field_id = i8_nullable_fid.get();
load_index_info.field_type = DataType::INT8;
load_index_info.index = std::move(age8_index);
seg->LoadIndex(load_index_info);
// load index for 16 field
auto age16_col = raw_data.get_col<int16_t>(i16_nullable_fid);
age16_col[0] = 2000;
auto age16_index = milvus::index::CreateScalarIndexSort<int16_t>();
age16_index->Build(N, age16_col.data(), i16_valid_data.data());
load_index_info.field_id = i16_nullable_fid.get();
load_index_info.field_type = DataType::INT16;
load_index_info.index = std::move(age16_index);
seg->LoadIndex(load_index_info);
// load index for int32 field
auto age32_col = raw_data.get_col<int32_t>(i32_nullable_fid);
age32_col[0] = 2000;
auto age32_index = milvus::index::CreateScalarIndexSort<int32_t>();
age32_index->Build(N, age32_col.data(), i32_valid_data.data());
load_index_info.field_id = i32_nullable_fid.get();
load_index_info.field_type = DataType::INT32;
load_index_info.index = std::move(age32_index);
seg->LoadIndex(load_index_info);
// load index for int64 field
auto age64_col = raw_data.get_col<int64_t>(i64_nullable_fid);
age64_col[0] = 2000;
auto age64_index = milvus::index::CreateScalarIndexSort<int64_t>();
age64_index->Build(N, age64_col.data(), i64_valid_data.data());
load_index_info.field_id = i64_nullable_fid.get();
load_index_info.field_type = DataType::INT64;
load_index_info.index = std::move(age64_index);
seg->LoadIndex(load_index_info);
// load index for float field
auto age_float_col = raw_data.get_col<float>(float_nullable_fid);
age_float_col[0] = 2000;
auto age_float_index = milvus::index::CreateScalarIndexSort<float>();
age_float_index->Build(N, age_float_col.data(), float_valid_data.data());
load_index_info.field_id = float_nullable_fid.get();
load_index_info.field_type = DataType::FLOAT;
load_index_info.index = std::move(age_float_index);
seg->LoadIndex(load_index_info);
// load index for double field
auto age_double_col = raw_data.get_col<double>(double_nullable_fid);
age_double_col[0] = 2000;
auto age_double_index = milvus::index::CreateScalarIndexSort<double>();
age_double_index->Build(N, age_double_col.data(), double_valid_data.data());
load_index_info.field_id = double_nullable_fid.get();
load_index_info.field_type = DataType::FLOAT;
load_index_info.index = std::move(age_double_index);
seg->LoadIndex(load_index_info);
auto seg_promote = dynamic_cast<SegmentSealedImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
if (dtype == DataType::INT8) {
expr = boost::format(expr_plan) % vec_fid.get() %
i8_nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::INT8));
} else if (dtype == DataType::INT16) {
expr = boost::format(expr_plan) % vec_fid.get() %
i16_nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::INT16));
} else if (dtype == DataType::INT32) {
expr = boost::format(expr_plan) % vec_fid.get() %
i32_nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::INT32));
} else if (dtype == DataType::INT64) {
expr = boost::format(expr_plan) % vec_fid.get() %
i64_nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::INT64));
} else if (dtype == DataType::FLOAT) {
expr = boost::format(expr_plan) % vec_fid.get() %
float_nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::FLOAT));
} else if (dtype == DataType::DOUBLE) {
expr = boost::format(expr_plan) % vec_fid.get() %
double_nullable_fid.get() %
proto::schema::DataType_Name(int(DataType::DOUBLE));
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
auto binary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, binary_plan.data(), binary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N);
for (int i = 0; i < N; ++i) {
auto ans = final[i];
if (dtype == DataType::INT8) {
auto val = age8_col[i];
auto ref = ref_func(val, i8_valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT16) {
auto val = age16_col[i];
auto ref = ref_func(val, i16_valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT32) {
auto val = age32_col[i];
auto ref = ref_func(val, i32_valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = age64_col[i];
auto ref = ref_func(val, i64_valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::FLOAT) {
auto val = age_float_col[i];
auto ref = ref_func(val, float_valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = age_double_col[i];
auto ref = ref_func(val, double_valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestUnaryRangeWithJSON) {
std::vector<
std::tuple<std::string,
std::function<bool(
std::variant<int64_t, bool, double, std::string_view>)>,
DataType>>
testcases = {
{R"(op: Equal
value: <
bool_val: true
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return std::get<bool>(v);
},
DataType::BOOL},
{R"(op: LessEqual
value: <
int64_val: 1500
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return std::get<int64_t>(v) < 1500;
},
DataType::INT64},
{R"(op: LessEqual
value: <
float_val: 4000
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return std::get<double>(v) <= 4000;
},
DataType::DOUBLE},
{R"(op: GreaterThan
value: <
float_val: 1000
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return std::get<double>(v) > 1000;
},
DataType::DOUBLE},
{R"(op: GreaterEqual
value: <
int64_val: 0
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return std::get<int64_t>(v) >= 0;
},
DataType::INT64},
{R"(op: NotEqual
value: <
bool_val: true
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return !std::get<bool>(v);
},
DataType::BOOL},
{R"(op: Equal
value: <
string_val: "test"
>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return std::get<std::string_view>(v) == "test";
},
DataType::STRING},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
unary_range_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
column_info: <
field_id: %2%
data_type: %3%
nested_path:"%4%"
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
switch (dtype) {
case DataType::BOOL: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "bool";
break;
}
case DataType::INT64: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "int";
break;
}
case DataType::DOUBLE: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"double";
break;
}
case DataType::STRING: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"string";
break;
}
default: {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
auto unary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, unary_plan.data(), unary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::BOOL) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<bool>("/bool")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>("/int")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>("/double")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::STRING) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<std::string_view>("/string")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestUnaryRangeWithJSONNullable) {
std::vector<std::tuple<
std::string,
std::function<bool(
std::variant<int64_t, bool, double, std::string_view>, bool)>,
DataType>>
testcases = {
{R"(op: Equal
value: <
bool_val: true
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return std::get<bool>(v);
},
DataType::BOOL},
{R"(op: LessEqual
value: <
int64_val: 1500
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return std::get<int64_t>(v) < 1500;
},
DataType::INT64},
{R"(op: LessEqual
value: <
float_val: 4000
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return std::get<double>(v) <= 4000;
},
DataType::DOUBLE},
{R"(op: GreaterThan
value: <
float_val: 1000
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return std::get<double>(v) > 1000;
},
DataType::DOUBLE},
{R"(op: GreaterEqual
value: <
int64_val: 0
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return std::get<int64_t>(v) >= 0;
},
DataType::INT64},
{R"(op: NotEqual
value: <
bool_val: true
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return !std::get<bool>(v);
},
DataType::BOOL},
{R"(op: Equal
value: <
string_val: "test"
>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return std::get<std::string_view>(v) == "test";
},
DataType::STRING},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
unary_range_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
column_info: <
field_id: %2%
data_type: %3%
nested_path:"%4%"
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
switch (dtype) {
case DataType::BOOL: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "bool";
break;
}
case DataType::INT64: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "int";
break;
}
case DataType::DOUBLE: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"double";
break;
}
case DataType::STRING: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"string";
break;
}
default: {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
auto unary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, unary_plan.data(), unary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::BOOL) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<bool>("/bool")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>("/int")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>("/double")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::STRING) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<std::string_view>("/string")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestTermWithJSON) {
std::vector<
std::tuple<std::string,
std::function<bool(
std::variant<int64_t, bool, double, std::string_view>)>,
DataType>>
testcases = {
{R"(values: <bool_val: true>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
std::unordered_set<bool> term_set;
term_set = {true, false};
return term_set.find(std::get<bool>(v)) != term_set.end();
},
DataType::BOOL},
{R"(values: <int64_val: 1500>, values: <int64_val: 2048>, values: <int64_val: 3216>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
std::unordered_set<int64_t> term_set;
term_set = {1500, 2048, 3216};
return term_set.find(std::get<int64_t>(v)) != term_set.end();
},
DataType::INT64},
{R"(values: <float_val: 1500.0>, values: <float_val: 4000>, values: <float_val: 235.14>)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
std::unordered_set<double> term_set;
term_set = {1500.0, 4000, 235.14};
return term_set.find(std::get<double>(v)) != term_set.end();
},
DataType::DOUBLE},
{R"(values: <string_val: "aaa">, values: <string_val: "abc">, values: <string_val: "235.14">)",
[](std::variant<int64_t, bool, double, std::string_view> v) {
std::unordered_set<std::string_view> term_set;
term_set = {"aaa", "abc", "235.14"};
return term_set.find(std::get<std::string_view>(v)) !=
term_set.end();
},
DataType::STRING},
{R"()",
[](std::variant<int64_t, bool, double, std::string_view> v) {
return false;
},
DataType::INT64},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
term_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
column_info: <
field_id: %2%
data_type: %3%
nested_path:"%4%"
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
switch (dtype) {
case DataType::BOOL: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "bool";
break;
}
case DataType::INT64: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "int";
break;
}
case DataType::DOUBLE: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"double";
break;
}
case DataType::STRING: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"string";
break;
}
default: {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
auto unary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, unary_plan.data(), unary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::BOOL) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<bool>("/bool")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>("/int")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>("/double")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::STRING) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<std::string_view>("/string")
.value();
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestTermWithJSONNullable) {
std::vector<std::tuple<
std::string,
std::function<bool(
std::variant<int64_t, bool, double, std::string_view>, bool)>,
DataType>>
testcases = {
{R"(values: <bool_val: true>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
std::unordered_set<bool> term_set;
term_set = {true, false};
return term_set.find(std::get<bool>(v)) != term_set.end();
},
DataType::BOOL},
{R"(values: <int64_val: 1500>, values: <int64_val: 2048>, values: <int64_val: 3216>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
std::unordered_set<int64_t> term_set;
term_set = {1500, 2048, 3216};
return term_set.find(std::get<int64_t>(v)) != term_set.end();
},
DataType::INT64},
{R"(values: <float_val: 1500.0>, values: <float_val: 4000>, values: <float_val: 235.14>)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
std::unordered_set<double> term_set;
term_set = {1500.0, 4000, 235.14};
return term_set.find(std::get<double>(v)) != term_set.end();
},
DataType::DOUBLE},
{R"(values: <string_val: "aaa">, values: <string_val: "abc">, values: <string_val: "235.14">)",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
std::unordered_set<std::string_view> term_set;
term_set = {"aaa", "abc", "235.14"};
return term_set.find(std::get<std::string_view>(v)) !=
term_set.end();
},
DataType::STRING},
{R"()",
[](std::variant<int64_t, bool, double, std::string_view> v,
bool valid) {
if (!valid) {
return false;
}
return false;
},
DataType::INT64},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
term_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
column_info: <
field_id: %2%
data_type: %3%
nested_path:"%4%"
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
switch (dtype) {
case DataType::BOOL: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "bool";
break;
}
case DataType::INT64: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "int";
break;
}
case DataType::DOUBLE: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"double";
break;
}
case DataType::STRING: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"string";
break;
}
default: {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
auto unary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, unary_plan.data(), unary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::BOOL) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<bool>("/bool")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<int64_t>("/int")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<double>("/double")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::STRING) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.template at<std::string_view>("/string")
.value();
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestExistsWithJSON) {
std::vector<std::tuple<std::string, std::function<bool(bool)>, DataType>>
testcases = {
{R"()", [](bool v) { return v; }, DataType::BOOL},
{R"()", [](bool v) { return v; }, DataType::INT64},
{R"()", [](bool v) { return v; }, DataType::STRING},
{R"()", [](bool v) { return v; }, DataType::VARCHAR},
{R"()", [](bool v) { return v; }, DataType::DOUBLE},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
exists_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
info: <
field_id: %2%
data_type: %3%
nested_path:"%4%"
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
switch (dtype) {
case DataType::BOOL: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "bool";
break;
}
case DataType::INT64: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "int";
break;
}
case DataType::DOUBLE: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"double";
break;
}
case DataType::STRING: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"string";
break;
}
case DataType::VARCHAR: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"varchar";
break;
}
default: {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
auto unary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, unary_plan.data(), unary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::BOOL) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/bool");
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/int");
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/double");
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::STRING) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/string");
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::VARCHAR) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/varchar");
auto ref = ref_func(val);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
TEST_P(ExprTest, TestExistsWithJSONNullable) {
std::vector<
std::tuple<std::string, std::function<bool(bool, bool)>, DataType>>
testcases = {
{R"()",
[](bool v, bool valid) {
if (!valid) {
return false;
}
return v;
},
DataType::BOOL},
{R"()",
[](bool v, bool valid) {
if (!valid) {
return false;
}
return v;
},
DataType::INT64},
{R"()",
[](bool v, bool valid) {
if (!valid) {
return false;
}
return v;
},
DataType::STRING},
{R"()",
[](bool v, bool valid) {
if (!valid) {
return false;
}
return v;
},
DataType::VARCHAR},
{R"()",
[](bool v, bool valid) {
if (!valid) {
return false;
}
return v;
},
DataType::DOUBLE},
};
std::string serialized_expr_plan = R"(vector_anns: <
field_id: %1%
predicates: <
exists_expr: <
@@@@@
>
>
query_info: <
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
>
placeholder_tag: "$0"
>)";
std::string arith_expr = R"(
info: <
field_id: %2%
data_type: %3%
nested_path:"%4%"
>
@@@@)";
auto schema = std::make_shared<Schema>();
auto vec_fid = schema->AddDebugField("fakevec", data_type, 16, metric_type);
auto i64_fid = schema->AddDebugField("age64", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGen(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
int offset = 0;
for (auto [clause, ref_func, dtype] : testcases) {
auto loc = serialized_expr_plan.find("@@@@@");
auto expr_plan = serialized_expr_plan;
expr_plan.replace(loc, 5, arith_expr);
loc = expr_plan.find("@@@@");
expr_plan.replace(loc, 4, clause);
boost::format expr;
switch (dtype) {
case DataType::BOOL: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "bool";
break;
}
case DataType::INT64: {
expr =
boost::format(expr_plan) % vec_fid.get() % json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) % "int";
break;
}
case DataType::DOUBLE: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"double";
break;
}
case DataType::STRING: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"string";
break;
}
case DataType::VARCHAR: {
expr = boost::format(expr_plan) % vec_fid.get() %
json_fid.get() %
proto::schema::DataType_Name(int(DataType::JSON)) %
"varchar";
break;
}
default: {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
auto unary_plan = translate_text_plan_with_metric_type(expr.str());
auto plan = CreateSearchPlanByExpr(
*schema, unary_plan.data(), unary_plan.size());
BitsetType final;
final = ExecuteQueryExpr(
plan->plan_node_->plannodes_->sources()[0]->sources()[0],
seg_promote,
N * num_iters,
MAX_TIMESTAMP);
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (dtype == DataType::BOOL) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/bool");
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::INT64) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/int");
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::DOUBLE) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/double");
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::STRING) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/string");
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else if (dtype == DataType::VARCHAR) {
auto val = milvus::Json(simdjson::padded_string(json_col[i]))
.exist("/varchar");
auto ref = ref_func(val, valid_data[i]);
ASSERT_EQ(ans, ref) << clause << "@" << i << "!!" << val;
} else {
ASSERT_TRUE(false) << "No test case defined for this data type";
}
}
}
}
template <typename T>
struct Testcase {
std::vector<T> term;
std::vector<std::string> nested_path;
bool res;
};
TEST_P(ExprTest, TestTermInFieldJson) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
std::vector<Testcase<bool>> bool_testcases{{{true}, {"bool"}},
{{false}, {"bool"}}};
for (auto testcase : bool_testcases) {
auto check = [&](const std::vector<bool>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_bool_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
// std::cout << "cost"
// << std::chrono::duration_cast<std::chrono::microseconds>(
// std::chrono::steady_clock::now() - start)
// .count()
// << std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<bool> res;
for (const auto& element : array) {
res.push_back(element.template get<bool>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<double>> double_testcases{
{{1.123}, {"double"}},
{{10.34}, {"double"}},
{{100.234}, {"double"}},
{{1000.4546}, {"double"}},
};
for (auto testcase : double_testcases) {
auto check = [&](const std::vector<double>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_float_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<double> res;
for (const auto& element : array) {
res.push_back(element.template get<double>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<int64_t>> testcases{
{{1}, {"int"}},
{{10}, {"int"}},
{{100}, {"int"}},
{{1000}, {"int"}},
};
for (auto testcase : testcases) {
auto check = [&](const std::vector<int64_t>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_int64_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<int64_t> res;
for (const auto& element : array) {
res.push_back(element.template get<int64_t>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<std::string>> testcases_string = {
{{"1sads"}, {"string"}},
{{"10dsf"}, {"string"}},
{{"100"}, {"string"}},
{{"100ddfdsssdfdsfsd0"}, {"string"}},
};
for (auto testcase : testcases_string) {
auto check = [&](const std::vector<std::string_view>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_string_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<std::string_view> res;
for (const auto& element : array) {
res.push_back(element.template get<std::string_view>());
}
ASSERT_EQ(ans, check(res));
}
}
}
TEST_P(ExprTest, TestTermInFieldJsonNullable) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
std::vector<Testcase<bool>> bool_testcases{{{true}, {"bool"}},
{{false}, {"bool"}}};
for (auto testcase : bool_testcases) {
auto check = [&](const std::vector<bool>& values, bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_bool_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
// std::cout << "cost"
// << std::chrono::duration_cast<std::chrono::microseconds>(
// std::chrono::steady_clock::now() - start)
// .count()
// << std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<bool> res;
for (const auto& element : array) {
res.push_back(element.template get<bool>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<double>> double_testcases{
{{1.123}, {"double"}},
{{10.34}, {"double"}},
{{100.234}, {"double"}},
{{1000.4546}, {"double"}},
};
for (auto testcase : double_testcases) {
auto check = [&](const std::vector<double>& values, bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_float_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<double> res;
for (const auto& element : array) {
res.push_back(element.template get<double>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<int64_t>> testcases{
{{1}, {"int"}},
{{10}, {"int"}},
{{100}, {"int"}},
{{1000}, {"int"}},
};
for (auto testcase : testcases) {
auto check = [&](const std::vector<int64_t>& values, bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_int64_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<int64_t> res;
for (const auto& element : array) {
res.push_back(element.template get<int64_t>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<std::string>> testcases_string = {
{{"1sads"}, {"string"}},
{{"10dsf"}, {"string"}},
{{"100"}, {"string"}},
{{"100ddfdsssdfdsfsd0"}, {"string"}},
};
for (auto testcase : testcases_string) {
auto check = [&](const std::vector<std::string_view>& values,
bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_string_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::TermFilterExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
values,
true);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<std::string_view> res;
for (const auto& element : array) {
res.push_back(element.template get<std::string_view>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
}
TEST_P(ExprTest, PraseJsonContainsExpr) {
std::vector<std::string> raw_plans{
R"(vector_anns:<
field_id:100
predicates:<
json_contains_expr:<
column_info:<
field_id:101
data_type:JSON
nested_path:"A"
>
elements:<int64_val:1 > elements:<int64_val:2 > elements:<int64_val:3 >
op:ContainsAny
elements_same_type:true
>
>
query_info:<
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
> placeholder_tag:"$0"
>)",
R"(vector_anns:<
field_id:100
predicates:<
json_contains_expr:<
column_info:<
field_id:101
data_type:JSON
nested_path:"A"
>
elements:<int64_val:1 > elements:<int64_val:2 > elements:<int64_val:3 >
op:ContainsAll
elements_same_type:true
>
>
query_info:<
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
> placeholder_tag:"$0"
>)",
R"(vector_anns:<
field_id:100
predicates:<
json_contains_expr:<
column_info:<
field_id:101
data_type:JSON
nested_path:"A"
>
elements:<bool_val:true > elements:<bool_val:false > elements:<bool_val:true >
op:ContainsAll
elements_same_type:true
>
>
query_info:<
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
> placeholder_tag:"$0"
>)",
R"(vector_anns:<
field_id:100
predicates:<
json_contains_expr:<
column_info:<
field_id:101
data_type:JSON
nested_path:"A"
>
elements:<float_val:1.1 > elements:<float_val:2.2 > elements:<float_val:3.3 >
op:ContainsAll
elements_same_type:true
>
>
query_info:<
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
> placeholder_tag:"$0"
>)",
R"(vector_anns:<
field_id:100
predicates:<
json_contains_expr:<
column_info:<
field_id:101
data_type:JSON
nested_path:"A"
>
elements:<string_val:"1" > elements:<string_val:"2" > elements:<string_val:"3" >
op:ContainsAll
elements_same_type:true
>
>
query_info:<
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
> placeholder_tag:"$0"
>)",
R"(vector_anns:<
field_id:100
predicates:<
json_contains_expr:<
column_info:<
field_id:101
data_type:JSON
nested_path:"A"
>
elements:<string_val:"1" >
elements:<int64_val:2 >
elements:<float_val:3.3 >
elements:<bool_val:true>
op:ContainsAll
>
>
query_info:<
topk: 10
round_decimal: 3
metric_type: "L2"
search_params: "{\"nprobe\": 10}"
> placeholder_tag:"$0"
>)",
};
for (auto& raw_plan : raw_plans) {
auto plan_str = translate_text_plan_with_metric_type(raw_plan);
auto schema = std::make_shared<Schema>();
schema->AddDebugField("fakevec", data_type, 16, metric_type);
schema->AddDebugField("json", DataType::JSON);
auto plan =
CreateSearchPlanByExpr(*schema, plan_str.data(), plan_str.size());
}
}
TEST_P(ExprTest, TestJsonContainsAny) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
std::vector<Testcase<bool>> bool_testcases{{{true}, {"bool"}},
{{false}, {"bool"}}};
for (auto testcase : bool_testcases) {
auto check = [&](const std::vector<bool>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_bool_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<bool> res;
for (const auto& element : array) {
res.push_back(element.template get<bool>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<double>> double_testcases{
{{1.123}, {"double"}},
{{10.34}, {"double"}},
{{100.234}, {"double"}},
{{1000.4546}, {"double"}},
};
for (auto testcase : double_testcases) {
auto check = [&](const std::vector<double>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_float_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<double> res;
for (const auto& element : array) {
res.push_back(element.template get<double>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<int64_t>> testcases{
{{1}, {"int"}},
{{10}, {"int"}},
{{100}, {"int"}},
{{1000}, {"int"}},
};
for (auto testcase : testcases) {
auto check = [&](const std::vector<int64_t>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_int64_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<int64_t> res;
for (const auto& element : array) {
res.push_back(element.template get<int64_t>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<std::string>> testcases_string = {
{{"1sads"}, {"string"}},
{{"10dsf"}, {"string"}},
{{"100"}, {"string"}},
{{"100ddfdsssdfdsfsd0"}, {"string"}},
};
for (auto testcase : testcases_string) {
auto check = [&](const std::vector<std::string_view>& values) {
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_string_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<std::string_view> res;
for (const auto& element : array) {
res.push_back(element.template get<std::string_view>());
}
ASSERT_EQ(ans, check(res));
}
}
}
TEST_P(ExprTest, TestJsonContainsAnyNullable) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
std::vector<Testcase<bool>> bool_testcases{{{true}, {"bool"}},
{{false}, {"bool"}}};
for (auto testcase : bool_testcases) {
auto check = [&](const std::vector<bool>& values, bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_bool_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<bool> res;
for (const auto& element : array) {
res.push_back(element.template get<bool>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<double>> double_testcases{
{{1.123}, {"double"}},
{{10.34}, {"double"}},
{{100.234}, {"double"}},
{{1000.4546}, {"double"}},
};
for (auto testcase : double_testcases) {
auto check = [&](const std::vector<double>& values, bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_float_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<double> res;
for (const auto& element : array) {
res.push_back(element.template get<double>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<int64_t>> testcases{
{{1}, {"int"}},
{{10}, {"int"}},
{{100}, {"int"}},
{{1000}, {"int"}},
};
for (auto testcase : testcases) {
auto check = [&](const std::vector<int64_t>& values, bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_int64_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<int64_t> res;
for (const auto& element : array) {
res.push_back(element.template get<int64_t>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<std::string>> testcases_string = {
{{"1sads"}, {"string"}},
{{"10dsf"}, {"string"}},
{{"100"}, {"string"}},
{{"100ddfdsssdfdsfsd0"}, {"string"}},
};
for (auto testcase : testcases_string) {
auto check = [&](const std::vector<std::string_view>& values,
bool valid) {
if (!valid) {
return false;
}
return std::find(values.begin(), values.end(), testcase.term[0]) !=
values.end();
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_string_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<std::string_view> res;
for (const auto& element : array) {
res.push_back(element.template get<std::string_view>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
}
TEST_P(ExprTest, TestJsonContainsAll) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
std::vector<Testcase<bool>> bool_testcases{{{true, true}, {"bool"}},
{{false, false}, {"bool"}}};
for (auto testcase : bool_testcases) {
auto check = [&](const std::vector<bool>& values) {
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_bool_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<bool> res;
for (const auto& element : array) {
res.push_back(element.template get<bool>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<double>> double_testcases{
{{1.123, 10.34}, {"double"}},
{{10.34, 100.234}, {"double"}},
{{100.234, 1000.4546}, {"double"}},
{{1000.4546, 1.123}, {"double"}},
{{1000.4546, 10.34}, {"double"}},
{{1.123, 100.234}, {"double"}},
};
for (auto testcase : double_testcases) {
auto check = [&](const std::vector<double>& values) {
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_float_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<double> res;
for (const auto& element : array) {
res.push_back(element.template get<double>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<int64_t>> testcases{
{{1, 10}, {"int"}},
{{10, 100}, {"int"}},
{{100, 1000}, {"int"}},
{{1000, 10}, {"int"}},
{{2, 4, 6, 8, 10}, {"int"}},
{{1, 2, 3, 4, 5}, {"int"}},
};
for (auto testcase : testcases) {
auto check = [&](const std::vector<int64_t>& values) {
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_int64_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<int64_t> res;
for (const auto& element : array) {
res.push_back(element.template get<int64_t>());
}
ASSERT_EQ(ans, check(res));
}
}
std::vector<Testcase<std::string>> testcases_string = {
{{"1sads", "10dsf"}, {"string"}},
{{"10dsf", "100"}, {"string"}},
{{"100", "10dsf", "1sads"}, {"string"}},
{{"100ddfdsssdfdsfsd0", "100"}, {"string"}},
};
for (auto testcase : testcases_string) {
auto check = [&](const std::vector<std::string_view>& values) {
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_string_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<std::string_view> res;
for (const auto& element : array) {
res.push_back(element.template get<std::string_view>());
}
ASSERT_EQ(ans, check(res));
}
}
}
TEST_P(ExprTest, TestJsonContainsAllNullable) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
std::vector<Testcase<bool>> bool_testcases{{{true, true}, {"bool"}},
{{false, false}, {"bool"}}};
for (auto testcase : bool_testcases) {
auto check = [&](const std::vector<bool>& values, bool valid) {
if (!valid) {
return false;
}
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto v : testcase.term) {
proto::plan::GenericValue val;
val.set_bool_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<bool> res;
for (const auto& element : array) {
res.push_back(element.template get<bool>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<double>> double_testcases{
{{1.123, 10.34}, {"double"}},
{{10.34, 100.234}, {"double"}},
{{100.234, 1000.4546}, {"double"}},
{{1000.4546, 1.123}, {"double"}},
{{1000.4546, 10.34}, {"double"}},
{{1.123, 100.234}, {"double"}},
};
for (auto testcase : double_testcases) {
auto check = [&](const std::vector<double>& values, bool valid) {
if (!valid) {
return false;
}
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_float_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<double> res;
for (const auto& element : array) {
res.push_back(element.template get<double>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<int64_t>> testcases{
{{1, 10}, {"int"}},
{{10, 100}, {"int"}},
{{100, 1000}, {"int"}},
{{1000, 10}, {"int"}},
{{2, 4, 6, 8, 10}, {"int"}},
{{1, 2, 3, 4, 5}, {"int"}},
};
for (auto testcase : testcases) {
auto check = [&](const std::vector<int64_t>& values, bool valid) {
if (!valid) {
return false;
}
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_int64_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<int64_t> res;
for (const auto& element : array) {
res.push_back(element.template get<int64_t>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
std::vector<Testcase<std::string>> testcases_string = {
{{"1sads", "10dsf"}, {"string"}},
{{"10dsf", "100"}, {"string"}},
{{"100", "10dsf", "1sads"}, {"string"}},
{{"100ddfdsssdfdsfsd0", "100"}, {"string"}},
};
for (auto testcase : testcases_string) {
auto check = [&](const std::vector<std::string_view>& values,
bool valid) {
if (!valid) {
return false;
}
for (auto const& e : testcase.term) {
if (std::find(values.begin(), values.end(), e) ==
values.end()) {
return false;
}
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
std::vector<proto::plan::GenericValue> values;
for (auto& v : testcase.term) {
proto::plan::GenericValue val;
val.set_string_val(v);
values.push_back(val);
}
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
values);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
auto array = milvus::Json(simdjson::padded_string(json_col[i]))
.array_at(pointer);
std::vector<std::string_view> res;
for (const auto& element : array) {
res.push_back(element.template get<std::string_view>());
}
ASSERT_EQ(ans, check(res, valid_data[i]));
}
}
}
TEST_P(ExprTest, TestJsonContainsArray) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
proto::plan::GenericValue generic_a;
auto* a = generic_a.mutable_array_val();
a->set_same_type(false);
for (int i = 0; i < 4; ++i) {
if (i % 4 == 0) {
proto::plan::GenericValue int_val;
int_val.set_int64_val(int64_t(i));
a->add_array()->CopyFrom(int_val);
} else if ((i - 1) % 4 == 0) {
proto::plan::GenericValue bool_val;
bool_val.set_bool_val(bool(i));
a->add_array()->CopyFrom(bool_val);
} else if ((i - 2) % 4 == 0) {
proto::plan::GenericValue float_val;
float_val.set_float_val(double(i));
a->add_array()->CopyFrom(float_val);
} else if ((i - 3) % 4 == 0) {
proto::plan::GenericValue string_val;
string_val.set_string_val(std::to_string(i));
a->add_array()->CopyFrom(string_val);
}
}
proto::plan::GenericValue generic_b;
auto* b = generic_b.mutable_array_val();
b->set_same_type(true);
proto::plan::GenericValue int_val1;
int_val1.set_int64_val(int64_t(1));
b->add_array()->CopyFrom(int_val1);
proto::plan::GenericValue int_val2;
int_val2.set_int64_val(int64_t(2));
b->add_array()->CopyFrom(int_val2);
proto::plan::GenericValue int_val3;
int_val3.set_int64_val(int64_t(3));
b->add_array()->CopyFrom(int_val3);
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases{
{{generic_a}, {"string"}}, {{generic_b}, {"array"}}};
for (auto& testcase : diff_testcases) {
auto check = [&](const std::vector<bool>& values, int i) {
if (testcase.nested_path[0] == "array" && (i == 1 || i == N + 1)) {
return true;
}
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
for (auto& testcase : diff_testcases) {
auto check = [&](const std::vector<bool>& values, int i) {
if (testcase.nested_path[0] == "array" && (i == 1 || i == N + 1)) {
return true;
}
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
proto::plan::GenericValue g_sub_arr1;
auto* sub_arr1 = g_sub_arr1.mutable_array_val();
sub_arr1->set_same_type(true);
proto::plan::GenericValue int_val11;
int_val11.set_int64_val(int64_t(1));
sub_arr1->add_array()->CopyFrom(int_val11);
proto::plan::GenericValue int_val12;
int_val12.set_int64_val(int64_t(2));
sub_arr1->add_array()->CopyFrom(int_val12);
proto::plan::GenericValue g_sub_arr2;
auto* sub_arr2 = g_sub_arr2.mutable_array_val();
sub_arr2->set_same_type(true);
proto::plan::GenericValue int_val21;
int_val21.set_int64_val(int64_t(3));
sub_arr2->add_array()->CopyFrom(int_val21);
proto::plan::GenericValue int_val22;
int_val22.set_int64_val(int64_t(4));
sub_arr2->add_array()->CopyFrom(int_val22);
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases2{
{{g_sub_arr1, g_sub_arr2}, {"array2"}}};
for (auto& testcase : diff_testcases2) {
auto check = [&]() { return true; };
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, check());
}
}
for (auto& testcase : diff_testcases2) {
auto check = [&](const std::vector<bool>& values, int i) {
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
proto::plan::GenericValue g_sub_arr3;
auto* sub_arr3 = g_sub_arr3.mutable_array_val();
sub_arr3->set_same_type(true);
proto::plan::GenericValue int_val31;
int_val31.set_int64_val(int64_t(5));
sub_arr3->add_array()->CopyFrom(int_val31);
proto::plan::GenericValue int_val32;
int_val32.set_int64_val(int64_t(6));
sub_arr3->add_array()->CopyFrom(int_val32);
proto::plan::GenericValue g_sub_arr4;
auto* sub_arr4 = g_sub_arr4.mutable_array_val();
sub_arr4->set_same_type(true);
proto::plan::GenericValue int_val41;
int_val41.set_int64_val(int64_t(7));
sub_arr4->add_array()->CopyFrom(int_val41);
proto::plan::GenericValue int_val42;
int_val42.set_int64_val(int64_t(8));
sub_arr4->add_array()->CopyFrom(int_val42);
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases3{
{{g_sub_arr3, g_sub_arr4}, {"array2"}}};
for (auto& testcase : diff_testcases3) {
auto check = [&](const std::vector<bool>& values, int i) {
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
for (auto& testcase : diff_testcases3) {
auto check = [&](const std::vector<bool>& values, int i) {
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
}
TEST_P(ExprTest, TestJsonContainsArrayNullable) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
proto::plan::GenericValue generic_a;
auto* a = generic_a.mutable_array_val();
a->set_same_type(false);
for (int i = 0; i < 4; ++i) {
if (i % 4 == 0) {
proto::plan::GenericValue int_val;
int_val.set_int64_val(int64_t(i));
a->add_array()->CopyFrom(int_val);
} else if ((i - 1) % 4 == 0) {
proto::plan::GenericValue bool_val;
bool_val.set_bool_val(bool(i));
a->add_array()->CopyFrom(bool_val);
} else if ((i - 2) % 4 == 0) {
proto::plan::GenericValue float_val;
float_val.set_float_val(double(i));
a->add_array()->CopyFrom(float_val);
} else if ((i - 3) % 4 == 0) {
proto::plan::GenericValue string_val;
string_val.set_string_val(std::to_string(i));
a->add_array()->CopyFrom(string_val);
}
}
proto::plan::GenericValue generic_b;
auto* b = generic_b.mutable_array_val();
b->set_same_type(true);
proto::plan::GenericValue int_val1;
int_val1.set_int64_val(int64_t(1));
b->add_array()->CopyFrom(int_val1);
proto::plan::GenericValue int_val2;
int_val2.set_int64_val(int64_t(2));
b->add_array()->CopyFrom(int_val2);
proto::plan::GenericValue int_val3;
int_val3.set_int64_val(int64_t(3));
b->add_array()->CopyFrom(int_val3);
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases{
{{generic_a}, {"string"}}, {{generic_b}, {"array"}}};
for (auto& testcase : diff_testcases) {
auto check = [&](const std::vector<bool>& values, int i, bool valid) {
if (!valid) {
return false;
}
if (testcase.nested_path[0] == "array" && (i == 1 || i == N + 1)) {
return true;
}
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i, valid_data[i]));
}
}
for (auto& testcase : diff_testcases) {
auto check = [&](const std::vector<bool>& values, int i, bool valid) {
if (!valid) {
return false;
}
if (testcase.nested_path[0] == "array" && (i == 1 || i == N + 1)) {
return true;
}
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i, valid_data[i]));
}
}
proto::plan::GenericValue g_sub_arr1;
auto* sub_arr1 = g_sub_arr1.mutable_array_val();
sub_arr1->set_same_type(true);
proto::plan::GenericValue int_val11;
int_val11.set_int64_val(int64_t(1));
sub_arr1->add_array()->CopyFrom(int_val11);
proto::plan::GenericValue int_val12;
int_val12.set_int64_val(int64_t(2));
sub_arr1->add_array()->CopyFrom(int_val12);
proto::plan::GenericValue g_sub_arr2;
auto* sub_arr2 = g_sub_arr2.mutable_array_val();
sub_arr2->set_same_type(true);
proto::plan::GenericValue int_val21;
int_val21.set_int64_val(int64_t(3));
sub_arr2->add_array()->CopyFrom(int_val21);
proto::plan::GenericValue int_val22;
int_val22.set_int64_val(int64_t(4));
sub_arr2->add_array()->CopyFrom(int_val22);
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases2{
{{g_sub_arr1, g_sub_arr2}, {"array2"}}};
for (auto& testcase : diff_testcases2) {
auto check = [&](bool valid) {
if (!valid) {
return false;
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, check(valid_data[i]));
}
}
for (auto& testcase : diff_testcases2) {
auto check = [&](const std::vector<bool>& values, int i, bool valid) {
if (!valid) {
return false;
}
return true;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i, valid_data[i]));
}
}
proto::plan::GenericValue g_sub_arr3;
auto* sub_arr3 = g_sub_arr3.mutable_array_val();
sub_arr3->set_same_type(true);
proto::plan::GenericValue int_val31;
int_val31.set_int64_val(int64_t(5));
sub_arr3->add_array()->CopyFrom(int_val31);
proto::plan::GenericValue int_val32;
int_val32.set_int64_val(int64_t(6));
sub_arr3->add_array()->CopyFrom(int_val32);
proto::plan::GenericValue g_sub_arr4;
auto* sub_arr4 = g_sub_arr4.mutable_array_val();
sub_arr4->set_same_type(true);
proto::plan::GenericValue int_val41;
int_val41.set_int64_val(int64_t(7));
sub_arr4->add_array()->CopyFrom(int_val41);
proto::plan::GenericValue int_val42;
int_val42.set_int64_val(int64_t(8));
sub_arr4->add_array()->CopyFrom(int_val42);
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases3{
{{g_sub_arr3, g_sub_arr4}, {"array2"}}};
for (auto& testcase : diff_testcases3) {
auto check = [&](const std::vector<bool>& values, int i) {
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
for (auto& testcase : diff_testcases3) {
auto check = [&](const std::vector<bool>& values, int i) {
return false;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
true,
testcase.term);
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
BitsetType final;
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
std::vector<bool> res;
ASSERT_EQ(ans, check(res, i));
}
}
}
milvus::proto::plan::GenericValue
generatedArrayWithFourDiffType(int64_t int_val,
double float_val,
bool bool_val,
std::string string_val) {
proto::plan::GenericValue value;
proto::plan::Array diff_type_array;
diff_type_array.set_same_type(false);
proto::plan::GenericValue int_value;
int_value.set_int64_val(int_val);
diff_type_array.add_array()->CopyFrom(int_value);
proto::plan::GenericValue float_value;
float_value.set_float_val(float_val);
diff_type_array.add_array()->CopyFrom(float_value);
proto::plan::GenericValue bool_value;
bool_value.set_bool_val(bool_val);
diff_type_array.add_array()->CopyFrom(bool_value);
proto::plan::GenericValue string_value;
string_value.set_string_val(string_val);
diff_type_array.add_array()->CopyFrom(string_value);
value.mutable_array_val()->CopyFrom(diff_type_array);
return value;
}
TEST_P(ExprTest, TestJsonContainsDiffTypeArray) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
proto::plan::GenericValue int_value;
int_value.set_int64_val(1);
auto diff_type_array1 =
generatedArrayWithFourDiffType(1, 2.2, false, "abc");
auto diff_type_array2 =
generatedArrayWithFourDiffType(1, 2.2, false, "def");
auto diff_type_array3 = generatedArrayWithFourDiffType(1, 2.2, true, "abc");
auto diff_type_array4 =
generatedArrayWithFourDiffType(1, 3.3, false, "abc");
auto diff_type_array5 =
generatedArrayWithFourDiffType(2, 2.2, false, "abc");
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases{
{{diff_type_array1, int_value}, {"array3"}, true},
{{diff_type_array2, int_value}, {"array3"}, false},
{{diff_type_array3, int_value}, {"array3"}, false},
{{diff_type_array4, int_value}, {"array3"}, false},
{{diff_type_array5, int_value}, {"array3"}, false},
};
for (auto& testcase : diff_testcases) {
auto check = [&]() { return testcase.res; };
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, check());
}
}
for (auto& testcase : diff_testcases) {
auto check = [&]() { return false; };
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, check());
}
}
}
TEST_P(ExprTest, TestJsonContainsDiffTypeArrayNullable) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
proto::plan::GenericValue int_value;
int_value.set_int64_val(1);
auto diff_type_array1 =
generatedArrayWithFourDiffType(1, 2.2, false, "abc");
auto diff_type_array2 =
generatedArrayWithFourDiffType(1, 2.2, false, "def");
auto diff_type_array3 = generatedArrayWithFourDiffType(1, 2.2, true, "abc");
auto diff_type_array4 =
generatedArrayWithFourDiffType(1, 3.3, false, "abc");
auto diff_type_array5 =
generatedArrayWithFourDiffType(2, 2.2, false, "abc");
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases{
{{diff_type_array1, int_value}, {"array3"}, true},
{{diff_type_array2, int_value}, {"array3"}, false},
{{diff_type_array3, int_value}, {"array3"}, false},
{{diff_type_array4, int_value}, {"array3"}, false},
{{diff_type_array5, int_value}, {"array3"}, false},
};
for (auto& testcase : diff_testcases) {
auto check = [&](bool valid) {
if (!valid) {
return false;
}
return testcase.res;
};
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, check(valid_data[i]));
}
}
for (auto& testcase : diff_testcases) {
auto check = [&]() { return false; };
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, check());
}
}
}
TEST_P(ExprTest, TestJsonContainsDiffType) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
proto::plan::GenericValue int_val;
int_val.set_int64_val(int64_t(3));
proto::plan::GenericValue bool_val;
bool_val.set_bool_val(bool(false));
proto::plan::GenericValue float_val;
float_val.set_float_val(double(100.34));
proto::plan::GenericValue string_val;
string_val.set_string_val("10dsf");
proto::plan::GenericValue string_val2;
string_val2.set_string_val("abc");
proto::plan::GenericValue bool_val2;
bool_val2.set_bool_val(bool(true));
proto::plan::GenericValue float_val2;
float_val2.set_float_val(double(2.2));
proto::plan::GenericValue int_val2;
int_val2.set_int64_val(int64_t(1));
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases{
{{int_val, bool_val, float_val, string_val},
{"diff_type_array"},
false},
{{string_val2, bool_val2, float_val2, int_val2},
{"diff_type_array"},
true},
};
for (auto& testcase : diff_testcases) {
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, testcase.res);
}
}
for (auto& testcase : diff_testcases) {
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
ASSERT_EQ(ans, testcase.res);
}
}
}
TEST_P(ExprTest, TestJsonContainsDiffTypeNullable) {
auto schema = std::make_shared<Schema>();
auto i64_fid = schema->AddDebugField("id", DataType::INT64);
auto json_fid = schema->AddDebugField("json", DataType::JSON, true);
schema->set_primary_field_id(i64_fid);
auto seg = CreateGrowingSegment(schema, empty_index_meta);
int N = 1000;
std::vector<std::string> json_col;
FixedVector<bool> valid_data;
int num_iters = 1;
for (int iter = 0; iter < num_iters; ++iter) {
auto raw_data = DataGenForJsonArray(schema, N, iter);
auto new_json_col = raw_data.get_col<std::string>(json_fid);
valid_data = raw_data.get_col_valid(json_fid);
json_col.insert(
json_col.end(), new_json_col.begin(), new_json_col.end());
seg->PreInsert(N);
seg->Insert(iter * N,
N,
raw_data.row_ids_.data(),
raw_data.timestamps_.data(),
raw_data.raw_);
}
auto seg_promote = dynamic_cast<SegmentGrowingImpl*>(seg.get());
query::ExecPlanNodeVisitor visitor(*seg_promote, MAX_TIMESTAMP);
proto::plan::GenericValue int_val;
int_val.set_int64_val(int64_t(3));
proto::plan::GenericValue bool_val;
bool_val.set_bool_val(bool(false));
proto::plan::GenericValue float_val;
float_val.set_float_val(double(100.34));
proto::plan::GenericValue string_val;
string_val.set_string_val("10dsf");
proto::plan::GenericValue string_val2;
string_val2.set_string_val("abc");
proto::plan::GenericValue bool_val2;
bool_val2.set_bool_val(bool(true));
proto::plan::GenericValue float_val2;
float_val2.set_float_val(double(2.2));
proto::plan::GenericValue int_val2;
int_val2.set_int64_val(int64_t(1));
std::vector<Testcase<proto::plan::GenericValue>> diff_testcases{
{{int_val, bool_val, float_val, string_val},
{"diff_type_array"},
false},
{{string_val2, bool_val2, float_val2, int_val2},
{"diff_type_array"},
true},
};
for (auto& testcase : diff_testcases) {
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAny,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (!valid_data[i]) {
ASSERT_EQ(ans, false);
} else {
ASSERT_EQ(ans, testcase.res);
}
}
}
for (auto& testcase : diff_testcases) {
auto pointer = milvus::Json::pointer(testcase.nested_path);
auto expr = std::make_shared<milvus::expr::JsonContainsExpr>(
milvus::expr::ColumnInfo(
json_fid, DataType::JSON, testcase.nested_path),
proto::plan::JSONContainsExpr_JSONOp_ContainsAll,
false,
testcase.term);
BitsetType final;
auto plan =
std::make_shared<plan::FilterBitsNode>(DEFAULT_PLANNODE_ID, expr);
auto start = std::chrono::steady_clock::now();
final =
ExecuteQueryExpr(plan, seg_promote, N * num_iters, MAX_TIMESTAMP);
std::cout << "cost"
<< std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - start)
.count()
<< std::endl;
EXPECT_EQ(final.size(), N * num_iters);
for (int i = 0; i < N * num_iters; ++i) {
auto ans = final[i];
if (!valid_data[i]) {
ASSERT_EQ(ans, false);
} else {
ASSERT_EQ(ans, testcase.res);
}
}
}
}