// 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 #include #include #include #include #include #include #include #include "db/common/file_helper.h" #include "db/index/common/version_manager.h" #include "db/index/segment/segment.h" #include "db/sqlengine/sqlengine.h" #include "zvec/db/index_params.h" #include "zvec/db/schema.h" #include "zvec/db/type.h" #include "test_helper.h" namespace zvec::sqlengine { static Doc create_doc(const uint64_t doc_id) { Doc new_doc; new_doc.set_pk("pk_" + std::to_string(doc_id)); new_doc.set_doc_id(doc_id); auto name = std::string("user-"); if (doc_id >= 5000 && doc_id < 8000) { name += "%"; } else if (doc_id >= 8000) { name += '_'; } name += std::to_string(doc_id % 100); new_doc.set("name", name); new_doc.set("invert_name", name); new_doc.set("extended_invert_name", name); return new_doc; } class LikeTest : public testing::Test { protected: static void SetUpTestSuite() { FileHelper::RemoveDirectory(seg_path_); FileHelper::CreateDirectory(seg_path_); auto invert_params = std::make_shared(true); collection_schema_ = std::make_shared( "test_collection", std::vector{ std::make_shared("name", DataType::STRING, false, nullptr), std::make_shared( "invert_name", DataType::STRING, false, std::make_shared(false, false)), std::make_shared( "extended_invert_name", DataType::STRING, false, std::make_shared(false, true)), }); auto segment = create_segment(seg_path_, *collection_schema_); if (segment == nullptr) { LOG_ERROR("create segment failed"); EXPECT_TRUE(segment != nullptr); std::exit(EXIT_FAILURE); } auto status = InsertDoc(segment, 0, 10000, &create_doc); if (!status.ok()) { LOG_ERROR("insert doc failed: %s", status.c_str()); EXPECT_TRUE(status.ok()); std::exit(EXIT_FAILURE); } segments_.push_back(segment); } static void TearDownTestSuite() { segments_.clear(); FileHelper::RemoveDirectory(seg_path_); } protected: static inline std::string seg_path_ = "./test_collection"; static inline CollectionSchema::Ptr collection_schema_; static inline std::vector segments_; }; TEST_F(LikeTest, ForwardLikeAll) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "name like '%'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(i)); } } TEST_F(LikeTest, InvertLikeAll) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "invert_name like '%'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(i)); } } TEST_F(LikeTest, ForwardPrefixLike) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "name like 'user-22%'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; int doc_id = i * 100 + 22; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, InvertPrefixLike) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "invert_name like 'user-22%'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; int doc_id = i * 100 + 22; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, ForwardSuffixLike) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "name like '%ser-22'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; int doc_id = i * 100 + 22; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, NotExtendedInvertSuffixLikeRunAsForward) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "invert_name like '%ser-22'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; int doc_id = i * 100 + 22; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, ExtendedInvertSuffixLike) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "extended_invert_name like '%ser-22'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; int doc_id = i * 100 + 22; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, ForwardMiddleLike) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "name like 'user%2'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 0; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; while (doc_id % 100 % 10 != 2) { doc_id++; } EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, ExtendedInvertMiddleLike) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "extended_invert_name like 'user%2'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 0; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; while (doc_id % 100 % 10 != 2) { doc_id++; } EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, UnderScore) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "name like 'user-_2'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 0; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; while (doc_id % 100 % 10 != 2 || doc_id % 100 < 10) { doc_id++; } EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, InvertUnderScoreRunAsForward) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = "invert_name like 'user-_2'"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 0; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; while (doc_id % 100 % 10 != 2 || doc_id % 100 < 10) { doc_id++; } EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, ForwardEscapePercent) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = R"(name like 'user-\%%')"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 5000; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, InvertEscapePercent) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = R"(invert_name like 'user-\%%')"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 5000; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, ForwardEscapeUnderscore) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = R"(name like 'user-\_%')"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 8000; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, InvertEscapeUnderscore) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = R"(invert_name like 'user-\_%')"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); ASSERT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0, doc_id = 8000; i < docs.size(); i++, doc_id++) { auto doc = docs[i]; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } TEST_F(LikeTest, NoPercentRunAsEqual) { SearchQuery query; query.output_fields_ = {"name"}; query.topk_ = 200; query.filter_ = R"(invert_name like 'user-22')"; auto engine = SQLEngine::create(std::make_shared()); auto ret = engine->execute(collection_schema_, query, segments_); EXPECT_TRUE(ret.has_value()) << ret.error(); auto docs = std::move(ret.value()); for (size_t i = 0; i < docs.size(); i++) { auto doc = docs[i]; int doc_id = i * 100 + 22; EXPECT_EQ(doc->pk(), "pk_" + std::to_string(doc_id)); } } } // namespace zvec::sqlengine