Files
2026-07-13 12:47:42 +08:00

698 lines
26 KiB
C++

// Copyright 2025-present the zvec project
//
// 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 <memory>
#include <gmock/gmock-matchers.h>
#include <gtest/gtest.h>
#include "db/sqlengine/sqlengine_impl.h"
#include "zvec/db/doc.h"
#include "zvec/db/schema.h"
#include "profiler.h"
namespace zvec::sqlengine {
class QueryInfoTest : public testing::Test {
public:
// Sets up the test fixture.
static void SetUpTestSuite() {
schema = std::make_shared<CollectionSchema>();
auto &param = *schema;
param.set_name("1collection");
auto column1 = std::make_shared<FieldSchema>();
auto vector_params = std::make_shared<FlatIndexParams>(MetricType::IP);
column1->set_name("face_feature");
column1->set_index_params(vector_params);
column1->set_dimension(4);
column1->set_data_type(DataType::VECTOR_FP32);
param.add_field(column1);
auto column2 = std::make_shared<FieldSchema>();
column2->set_name("name");
column2->set_data_type(DataType::UINT32);
param.add_field(column2);
auto column3 = std::make_shared<FieldSchema>();
column3->set_name("category");
column3->set_data_type(DataType::STRING);
param.add_field(column3);
auto column4 = std::make_shared<FieldSchema>();
column4->set_name("face_feature");
column4->set_dimension(4);
column4->set_data_type(DataType::VECTOR_FP32);
param.add_field(column4);
auto column5 = std::make_shared<FieldSchema>();
column5->set_name("1-dash_score_field");
column5->set_dimension(5);
column5->set_data_type(DataType::STRING);
param.add_field(column5);
{
auto column = std::make_shared<FieldSchema>();
column->set_name("name_array");
column->set_data_type(DataType::ARRAY_UINT32);
param.add_field(column);
}
{
auto column = std::make_shared<FieldSchema>();
column->set_name("category_array");
column->set_data_type(DataType::ARRAY_STRING);
param.add_field(column);
}
}
// Tears down the test fixture.
static void TearDownTestSuite() {}
protected:
Profiler::Ptr profiler_{new Profiler};
inline static CollectionSchema::Ptr schema;
};
TEST_F(QueryInfoTest, BasicQueryRequest) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 11;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value()) << ret.error().c_str();
QueryInfo::Ptr new_query_info = ret.value();
auto &query_fields = new_query_info->query_fields();
EXPECT_EQ(query_fields.size(), 5);
EXPECT_EQ(query_fields[0]->field_name(), "name");
EXPECT_EQ(query_fields[1]->field_name(), "category");
EXPECT_EQ(query_fields[2]->field_name(), "1-dash_score_field");
EXPECT_EQ(query_fields[3]->field_name(), "name_array");
EXPECT_EQ(query_fields[4]->field_name(), "category_array");
EXPECT_EQ(new_query_info->query_topn(), 11);
EXPECT_FALSE(new_query_info->filter_cond());
EXPECT_FALSE(new_query_info->invert_cond());
EXPECT_FALSE(new_query_info->post_filter_cond());
EXPECT_FALSE(new_query_info->post_invert_cond());
ASSERT_TRUE(new_query_info->vector_cond_info());
auto vector_cond = new_query_info->vector_cond_info();
EXPECT_EQ(1, vector_cond->batch());
EXPECT_EQ("face_feature", vector_cond->vector_field_name());
EXPECT_EQ(query.target_.get_vector_clause()->query_vector_,
vector_cond->vector_term());
EXPECT_EQ(query.target_.get_vector_clause()->sparse_indices_,
vector_cond->vector_sparse_indices());
EXPECT_EQ(query.target_.get_vector_clause()->sparse_values_,
vector_cond->vector_sparse_values());
EXPECT_EQ(query.target_.query_params_, vector_cond->query_params());
}
TEST_F(QueryInfoTest, QueryRequestWithFilter) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 11;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
query.filter_ = "name<3 or name=4 or 1-dash_score_field='test'";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
QueryInfo::Ptr new_query_info = ret.value();
auto &query_fields = new_query_info->query_fields();
EXPECT_EQ(query_fields.size(), 5);
EXPECT_EQ(query_fields[0]->field_name(), "name");
EXPECT_EQ(query_fields[1]->field_name(), "category");
EXPECT_EQ(query_fields[2]->field_name(), "1-dash_score_field");
EXPECT_EQ(query_fields[3]->field_name(), "name_array");
EXPECT_EQ(query_fields[4]->field_name(), "category_array");
EXPECT_EQ(new_query_info->query_topn(), 11);
EXPECT_TRUE(new_query_info->filter_cond());
EXPECT_FALSE(new_query_info->invert_cond());
EXPECT_FALSE(new_query_info->post_filter_cond());
EXPECT_FALSE(new_query_info->post_invert_cond());
ASSERT_TRUE(new_query_info->vector_cond_info());
auto vector_cond = new_query_info->vector_cond_info();
EXPECT_EQ(1, vector_cond->batch());
EXPECT_EQ("face_feature", vector_cond->vector_field_name());
EXPECT_EQ(query.target_.get_vector_clause()->query_vector_,
vector_cond->vector_term());
EXPECT_EQ(query.target_.get_vector_clause()->sparse_indices_,
vector_cond->vector_sparse_indices());
EXPECT_EQ(query.target_.get_vector_clause()->sparse_values_,
vector_cond->vector_sparse_values());
EXPECT_EQ(query.target_.query_params_, vector_cond->query_params());
EXPECT_TRUE(new_query_info->filter_cond());
// (nullptr) and (xxx)
auto filter_cond = new_query_info->filter_cond();
EXPECT_EQ(filter_cond->op_name(), "and");
EXPECT_FALSE(filter_cond->left());
// ((name<3) or (name=4)) or (1-dash_score_field=test)
auto right = std::dynamic_pointer_cast<QueryNode>(filter_cond->right());
EXPECT_TRUE(right);
EXPECT_EQ(right->op_name(), "or");
// 1-dash_score_field=test
auto number_field_filter =
std::dynamic_pointer_cast<QueryNode>(right->right());
ASSERT_TRUE(number_field_filter);
EXPECT_EQ(number_field_filter->op_name(), "=");
auto left_key =
std::dynamic_pointer_cast<QueryIDNode>(number_field_filter->left());
EXPECT_EQ(left_key->op_name(), "ID");
EXPECT_EQ(left_key->value(), "1-dash_score_field");
auto right_const = std::dynamic_pointer_cast<QueryConstantNode>(
number_field_filter->right());
ASSERT_TRUE(right_const);
EXPECT_EQ(right_const->op_name(), "STRING_VALUE");
EXPECT_EQ(right_const->value(), "test");
// (name<3) or (name=4)
auto left = std::dynamic_pointer_cast<QueryNode>(right->left());
ASSERT_TRUE(left);
EXPECT_EQ(left->op_name(), "or");
auto or1 = std::dynamic_pointer_cast<QueryNode>(left->left());
EXPECT_EQ(or1->op_name(), "<");
auto id1 = std::dynamic_pointer_cast<QueryIDNode>(or1->left());
ASSERT_TRUE(id1);
EXPECT_EQ(id1->op_name(), "ID");
EXPECT_EQ(id1->value(), "name");
auto const1 = std::dynamic_pointer_cast<QueryConstantNode>(or1->right());
ASSERT_TRUE(const1);
EXPECT_EQ(const1->op_name(), "INT_VALUE");
EXPECT_EQ(const1->value(), "3");
auto or2 = std::dynamic_pointer_cast<QueryNode>(left->right());
EXPECT_EQ(or2->op_name(), "=");
}
TEST_F(QueryInfoTest, QueryRequestWithIncludeVector) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 11;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
query.include_vector_ = true;
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
QueryInfo::Ptr new_query_info = ret.value();
auto &query_fields = new_query_info->query_fields();
EXPECT_EQ(query_fields.size(), 6);
EXPECT_EQ(query_fields[0]->field_name(), "name");
EXPECT_EQ(query_fields[1]->field_name(), "category");
EXPECT_EQ(query_fields[2]->field_name(), "1-dash_score_field");
EXPECT_EQ(query_fields[3]->field_name(), "name_array");
EXPECT_EQ(query_fields[4]->field_name(), "category_array");
EXPECT_EQ(query_fields[5]->field_name(), "face_feature");
EXPECT_EQ(new_query_info->query_topn(), 11);
EXPECT_FALSE(new_query_info->filter_cond());
EXPECT_FALSE(new_query_info->invert_cond());
EXPECT_FALSE(new_query_info->post_filter_cond());
EXPECT_FALSE(new_query_info->post_invert_cond());
ASSERT_TRUE(new_query_info->vector_cond_info());
auto vector_cond = new_query_info->vector_cond_info();
EXPECT_EQ(1, vector_cond->batch());
EXPECT_EQ("face_feature", vector_cond->vector_field_name());
EXPECT_EQ(query.target_.get_vector_clause()->query_vector_,
vector_cond->vector_term());
EXPECT_EQ(query.target_.get_vector_clause()->sparse_indices_,
vector_cond->vector_sparse_indices());
EXPECT_EQ(query.target_.get_vector_clause()->sparse_values_,
vector_cond->vector_sparse_values());
EXPECT_EQ(query.target_.query_params_, vector_cond->query_params());
}
TEST_F(QueryInfoTest, OR_ANCESTOR) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 11;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
query.filter_ = "name=1 and (name=2 or name=3)";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
QueryInfo::Ptr new_query_info = ret.value();
}
TEST_F(QueryInfoTest, QueryRequestWithInFilter) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 10;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
query.filter_ =
"name=3 or name in (1, 2, 3) or category not in (\"a\", \"b\", \"c\")";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
QueryInfo::Ptr new_query_info = ret.value();
auto &query_fields = new_query_info->query_fields();
EXPECT_EQ(query_fields.size(), 5);
EXPECT_EQ(query_fields[0]->field_name(), "name");
EXPECT_EQ(query_fields[1]->field_name(), "category");
EXPECT_EQ(query_fields[2]->field_name(), "1-dash_score_field");
EXPECT_EQ(query_fields[3]->field_name(), "name_array");
EXPECT_EQ(query_fields[4]->field_name(), "category_array");
EXPECT_EQ(new_query_info->query_topn(), 10);
EXPECT_FALSE(new_query_info->invert_cond());
EXPECT_FALSE(new_query_info->post_filter_cond());
EXPECT_FALSE(new_query_info->post_invert_cond());
ASSERT_TRUE(new_query_info->vector_cond_info());
auto vector_cond = new_query_info->vector_cond_info();
EXPECT_EQ(1, vector_cond->batch());
EXPECT_EQ("face_feature", vector_cond->vector_field_name());
std::vector<float> data{1.1, 2.2, 3.3, 4.4};
EXPECT_EQ(query.target_.get_vector_clause()->query_vector_,
vector_cond->vector_term());
EXPECT_TRUE(new_query_info->filter_cond());
// (nullptr) and (xxx)
auto filter_cond = new_query_info->filter_cond();
EXPECT_EQ(filter_cond->op_name(), "and");
EXPECT_FALSE(filter_cond->left());
// ((name=3) or (name in (1, 2, 3))) or (category not in ("a", "b", "c"))
auto right = std::dynamic_pointer_cast<QueryNode>(filter_cond->right());
EXPECT_TRUE(right);
EXPECT_EQ(right->op_name(), "or");
// category in ("a", "b", "c")
auto category_filter = std::dynamic_pointer_cast<QueryNode>(right->right());
ASSERT_TRUE(category_filter);
EXPECT_EQ(category_filter->op_name(), " in ");
auto left_key =
std::dynamic_pointer_cast<QueryIDNode>(category_filter->left());
EXPECT_EQ(left_key->op_name(), "ID");
EXPECT_EQ(left_key->value(), "category");
auto right_const =
std::dynamic_pointer_cast<QueryListNode>(category_filter->right());
ASSERT_TRUE(right_const);
EXPECT_EQ(right_const->op_name(), "LIST_VALUE");
EXPECT_EQ(right_const->text(), "NOT (a, b, c)");
// (name=3) or (name in (1, 2, 3))
auto left = std::dynamic_pointer_cast<QueryNode>(right->left());
ASSERT_TRUE(left);
EXPECT_EQ(left->op_name(), "or");
auto or1 = std::dynamic_pointer_cast<QueryNode>(left->left());
EXPECT_EQ(or1->op_name(), "=");
auto id1 = std::dynamic_pointer_cast<QueryIDNode>(or1->left());
ASSERT_TRUE(id1);
EXPECT_EQ(id1->op_name(), "ID");
EXPECT_EQ(id1->value(), "name");
auto const1 = std::dynamic_pointer_cast<QueryConstantNode>(or1->right());
ASSERT_TRUE(const1);
EXPECT_EQ(const1->op_name(), "INT_VALUE");
EXPECT_EQ(const1->value(), "3");
auto or2 = std::dynamic_pointer_cast<QueryNode>(left->right());
EXPECT_EQ(or2->op_name(), " in ");
auto id2 = std::dynamic_pointer_cast<QueryIDNode>(or2->left());
ASSERT_TRUE(id2);
EXPECT_EQ(id2->op_name(), "ID");
EXPECT_EQ(id2->value(), "name");
auto const2 = std::dynamic_pointer_cast<QueryListNode>(or2->right());
ASSERT_TRUE(const2);
EXPECT_EQ(const2->op_name(), "LIST_VALUE");
EXPECT_EQ(const2->text(), "(1, 2, 3)");
}
TEST_F(QueryInfoTest, QueryRequestWithInFilterWrong) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 11;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
query.filter_ = ("name in ()");
ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
query.filter_ = ("name in (\"a\", 2, 3)");
ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
query.filter_ = ("name in (1.1, 2, 3)");
ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
query.filter_ = ("category in (1.1, \"b\")");
ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
}
TEST_F(QueryInfoTest, QueryRequestWithInFilterNum1024) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 10;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
std::string filter_str;
for (int i = 0; i < 1024; i++) {
if (i != 0) {
filter_str += " or ";
}
filter_str += "name=" + std::to_string(i);
}
query.filter_ = filter_str;
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
QueryInfo::Ptr new_query_info = ret.value();
auto &query_fields = new_query_info->query_fields();
EXPECT_EQ(query_fields.size(), 5);
EXPECT_EQ(query_fields[0]->field_name(), "name");
EXPECT_EQ(query_fields[1]->field_name(), "category");
EXPECT_EQ(query_fields[2]->field_name(), "1-dash_score_field");
EXPECT_EQ(query_fields[3]->field_name(), "name_array");
EXPECT_EQ(query_fields[4]->field_name(), "category_array");
EXPECT_EQ(new_query_info->query_topn(), 10);
EXPECT_FALSE(new_query_info->invert_cond());
EXPECT_FALSE(new_query_info->post_filter_cond());
EXPECT_FALSE(new_query_info->post_invert_cond());
ASSERT_TRUE(new_query_info->vector_cond_info());
auto vector_cond = new_query_info->vector_cond_info();
EXPECT_EQ(1, vector_cond->batch());
EXPECT_EQ("face_feature", vector_cond->vector_field_name());
}
TEST_F(QueryInfoTest, QueryRequestWithFilter_contain) {
SearchQuery query;
query.output_fields_ = {"*"};
query.topk_ = 10;
query.target_.set_vector("[0.1, 0.2, 0.3, 0.4]");
query.target_.field_name_ = "face_feature";
query.include_vector_ = false;
query.target_.query_params_ = std::make_shared<QueryParams>(IndexType::FLAT);
query.target_.query_params_->set_radius(0.8F);
query.filter_ =
R"( name_array contain_all (1, 2, 3) and )"
R"( (name_array not contain_all (4, 5) or category_array contain_any
("a", "b")) )"
R"( or category_array not contain_any ("c", "d", "e")
)";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_TRUE(ret.has_value());
QueryInfo::Ptr new_query_info = ret.value();
auto &query_fields = new_query_info->query_fields();
// pre-defined schema field
EXPECT_EQ(query_fields.size(), 5);
EXPECT_EQ(query_fields[0]->field_name(), "name");
EXPECT_EQ(query_fields[1]->field_name(), "category");
EXPECT_EQ(query_fields[2]->field_name(), "1-dash_score_field");
EXPECT_EQ(query_fields[3]->field_name(), "name_array");
EXPECT_EQ(query_fields[4]->field_name(), "category_array");
EXPECT_EQ(new_query_info->query_topn(), 10);
EXPECT_FALSE(new_query_info->invert_cond());
EXPECT_FALSE(new_query_info->post_filter_cond());
EXPECT_FALSE(new_query_info->post_invert_cond());
ASSERT_TRUE(new_query_info->vector_cond_info());
auto vector_cond = new_query_info->vector_cond_info();
EXPECT_EQ(1, vector_cond->batch());
EXPECT_EQ("face_feature", vector_cond->vector_field_name());
EXPECT_TRUE(new_query_info->filter_cond());
/*
_________________[and]__________________
/ \
[nullptr(vector_cond)] [filter condition]
*/
// (nullptr) and (xxx)
auto filter_cond = new_query_info->filter_cond();
EXPECT_EQ(filter_cond->op_name(), "and");
EXPECT_FALSE(filter_cond->left());
/*
_______________[or]_______________
/ \
_____________[and]_____________ [category_array not
contain_any
("c", "d", "e")]
/ \
[name_array contain_all (1, 2, 3)] ___________[or]______________
/ \
[name_array not contain_all (4, 5)] [category_array
contain_any ("a", "b")]
*/
// name_array contain_all (1, 2, 3) and
// (name_array not contain_all (4, 5) or category_array contain_any ("a",
// "b")) or category_array not contain_any ("c", "d", "e")
auto parent_node = std::dynamic_pointer_cast<QueryNode>(filter_cond);
auto cur_node = std::dynamic_pointer_cast<QueryNode>(filter_cond->right());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), "or");
// category_array not contain_any ("c", "d", "e")
parent_node = std::dynamic_pointer_cast<QueryNode>(cur_node);
cur_node = std::dynamic_pointer_cast<QueryNode>(cur_node->right());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), " contain_any ");
{
auto left_key = std::dynamic_pointer_cast<QueryIDNode>(cur_node->left());
EXPECT_EQ(left_key->op_name(), "ID");
EXPECT_EQ(left_key->value(), "category_array");
auto right_const =
std::dynamic_pointer_cast<QueryListNode>(cur_node->right());
ASSERT_TRUE(right_const);
EXPECT_EQ(right_const->op_name(), "LIST_VALUE");
EXPECT_EQ(right_const->text(), "NOT (c, d, e)");
}
cur_node = parent_node;
// name_array contain_all (1, 2, 3) and
// (name_array not contain_all (4, 5) or category_array contain_any ("a",
// "b"))
parent_node = std::dynamic_pointer_cast<QueryNode>(cur_node);
cur_node = std::dynamic_pointer_cast<QueryNode>(cur_node->left());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), "and");
// the left side of 'and'
// name_array contain_all (1, 2, 3)
parent_node = std::dynamic_pointer_cast<QueryNode>(cur_node);
cur_node = std::dynamic_pointer_cast<QueryNode>(cur_node->left());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), " contain_all ");
{
auto left_key = std::dynamic_pointer_cast<QueryIDNode>(cur_node->left());
EXPECT_EQ(left_key->op_name(), "ID");
EXPECT_EQ(left_key->value(), "name_array");
auto right_const =
std::dynamic_pointer_cast<QueryListNode>(cur_node->right());
ASSERT_TRUE(right_const);
EXPECT_EQ(right_const->op_name(), "LIST_VALUE");
EXPECT_EQ(right_const->text(), "(1, 2, 3)");
}
cur_node = parent_node;
// the right side of 'and'
// (name_array not contain_all (4, 5) or category_array contain_any ("a",
// "b"))
parent_node = std::dynamic_pointer_cast<QueryNode>(cur_node);
cur_node = std::dynamic_pointer_cast<QueryNode>(cur_node->right());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), "or");
// name_array not contain_all (4, 5)
parent_node = std::dynamic_pointer_cast<QueryNode>(cur_node);
cur_node = std::dynamic_pointer_cast<QueryNode>(cur_node->left());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), " contain_all ");
{
auto left_key = std::dynamic_pointer_cast<QueryIDNode>(cur_node->left());
EXPECT_EQ(left_key->op_name(), "ID");
EXPECT_EQ(left_key->value(), "name_array");
auto right_const =
std::dynamic_pointer_cast<QueryListNode>(cur_node->right());
ASSERT_TRUE(right_const);
EXPECT_EQ(right_const->op_name(), "LIST_VALUE");
EXPECT_EQ(right_const->text(), "NOT (4, 5)");
}
cur_node = parent_node;
// category_array contain_any ("a", "b"))
parent_node = std::dynamic_pointer_cast<QueryNode>(cur_node);
cur_node = std::dynamic_pointer_cast<QueryNode>(cur_node->right());
EXPECT_TRUE(cur_node);
EXPECT_EQ(cur_node->op_name(), " contain_any ");
{
auto left_key = std::dynamic_pointer_cast<QueryIDNode>(cur_node->left());
EXPECT_EQ(left_key->op_name(), "ID");
EXPECT_EQ(left_key->value(), "category_array");
auto right_const =
std::dynamic_pointer_cast<QueryListNode>(cur_node->right());
ASSERT_TRUE(right_const);
EXPECT_EQ(right_const->op_name(), "LIST_VALUE");
EXPECT_EQ(right_const->text(), "(a, b)");
}
cur_node = parent_node;
}
TEST_F(QueryInfoTest, SelectNonExistField) {
SearchQuery query;
query.output_fields_ = {"category_array", "not_exist_field"};
query.topk_ = 11;
query.include_vector_ = false;
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(ret.error().message(),
testing::HasSubstr("not defined in schema"));
}
TEST_F(QueryInfoTest, ContainAllExceedLimit) {
SearchQuery query;
query.topk_ = 200;
query.filter_ = "name_array not contain_all (";
for (int i = 0; i <= 32; i++) {
query.filter_ += std::to_string(i);
if (i < 32) {
query.filter_ += ", ";
}
}
query.filter_ += ")";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(ret.error().message(),
testing::HasSubstr(
"Contain_* rel expr only support list size no more than 32"));
}
TEST_F(QueryInfoTest, ContainAnyExceedLimit) {
SearchQuery query;
query.topk_ = 200;
query.filter_ = "name_array not contain_any (";
for (int i = 0; i <= 32; i++) {
query.filter_ += std::to_string(i);
if (i < 32) {
query.filter_ += ", ";
}
}
query.filter_ += ")";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(ret.error().message(),
testing::HasSubstr(
"Contain_* rel expr only support list size no more than 32"));
}
TEST_F(QueryInfoTest, ArrayLengthNonExistField) {
SearchQuery query;
query.topk_ = 200;
query.filter_ = "array_length(not_exist_field) > 1";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(ret.error().message(),
testing::HasSubstr("array_length argument not found in schema"));
}
TEST_F(QueryInfoTest, ArrayLengthOnNonArrayField) {
SearchQuery query;
query.topk_ = 200;
query.filter_ = "array_length(name) > 1";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(ret.error().message(),
testing::HasSubstr("array_length only support array"));
}
TEST_F(QueryInfoTest, ArrayLengthInvalidArgument) {
SearchQuery query;
query.topk_ = 200;
query.filter_ = "array_length(name_array) > '1'";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(
ret.error().message(),
testing::HasSubstr("array_length right side only support integer"));
}
TEST_F(QueryInfoTest, ArrayLengthInvalidOp) {
SearchQuery query;
query.topk_ = 200;
query.filter_ = "array_length(name_array) like '%'";
auto engine = std::make_shared<SQLEngineImpl>(std::make_shared<Profiler>());
auto ret = engine->build_query_info(schema, query, nullptr);
ASSERT_FALSE(ret.has_value());
EXPECT_THAT(ret.error().message(), testing::HasSubstr("syntax error"));
}
} // namespace zvec::sqlengine