// 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 "zvec/db/doc.h" #include #include #include #include #include "utils/utils.h" #include "zvec/db/index_params.h" #include "zvec/db/status.h" #include "zvec/db/type.h" using namespace zvec; class DocDetailedTest : public ::testing::Test { protected: void SetUp() override { test_doc_ = std::make_shared(); test_doc_->set_pk("test_pk"); test_doc_->set_doc_id(12345); test_doc_->set_score(0.95f); test_doc_->set_operator(Operator::INSERT); } Doc::Ptr test_doc_; }; // Test serialization and deserialization of basic data types TEST_F(DocDetailedTest, BasicTypeSerializationDeserialization) { // Test boundary values test_doc_->set("bool_true", true); test_doc_->set("bool_false", false); test_doc_->set("int32_min", std::numeric_limits::min()); test_doc_->set("int32_max", std::numeric_limits::max()); test_doc_->set("uint32_min", std::numeric_limits::min()); test_doc_->set("uint32_max", std::numeric_limits::max()); test_doc_->set("int64_min", std::numeric_limits::min()); test_doc_->set("int64_max", std::numeric_limits::max()); test_doc_->set("uint64_min", std::numeric_limits::min()); test_doc_->set("uint64_max", std::numeric_limits::max()); test_doc_->set("float_min", std::numeric_limits::min()); test_doc_->set("float_max", std::numeric_limits::max()); test_doc_->set("float_lowest", std::numeric_limits::lowest()); test_doc_->set("double_min", std::numeric_limits::min()); test_doc_->set("double_max", std::numeric_limits::max()); test_doc_->set("double_lowest", std::numeric_limits::lowest()); auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); EXPECT_EQ(deserialized_doc->get("bool_true").value(), true); EXPECT_EQ(deserialized_doc->get("bool_false").value(), false); EXPECT_EQ(deserialized_doc->get("int32_min").value(), std::numeric_limits::min()); EXPECT_EQ(deserialized_doc->get("int32_max").value(), std::numeric_limits::max()); EXPECT_EQ(deserialized_doc->get("uint32_min").value(), std::numeric_limits::min()); EXPECT_EQ(deserialized_doc->get("uint32_max").value(), std::numeric_limits::max()); EXPECT_EQ(deserialized_doc->get("int64_min").value(), std::numeric_limits::min()); EXPECT_EQ(deserialized_doc->get("int64_max").value(), std::numeric_limits::max()); EXPECT_EQ(deserialized_doc->get("uint64_min").value(), std::numeric_limits::min()); EXPECT_EQ(deserialized_doc->get("uint64_max").value(), std::numeric_limits::max()); // For floating point numbers, use approximate comparison EXPECT_FLOAT_EQ(deserialized_doc->get("float_min").value(), std::numeric_limits::min()); EXPECT_FLOAT_EQ(deserialized_doc->get("float_max").value(), std::numeric_limits::max()); EXPECT_FLOAT_EQ(deserialized_doc->get("float_lowest").value(), std::numeric_limits::lowest()); EXPECT_DOUBLE_EQ(deserialized_doc->get("double_min").value(), std::numeric_limits::min()); EXPECT_DOUBLE_EQ(deserialized_doc->get("double_max").value(), std::numeric_limits::max()); EXPECT_DOUBLE_EQ(deserialized_doc->get("double_lowest").value(), std::numeric_limits::lowest()); } // Test various cases of string types TEST_F(DocDetailedTest, StringTypeSerializationDeserialization) { // Test empty string test_doc_->set("empty_string", std::string("")); // Test long string std::string long_string(10000, 'a'); test_doc_->set("long_string", long_string); // Test string with special characters test_doc_->set("special_chars", std::string("Special characters\t\n\r\0included", 15)); // Test string with binary data std::string binary_string; for (int i = 0; i < 256; ++i) { binary_string.push_back(static_cast(i)); } test_doc_->set("binary_string", binary_string); auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); EXPECT_EQ(deserialized_doc->get("empty_string").value(), ""); EXPECT_EQ(deserialized_doc->get("long_string").value(), long_string); EXPECT_EQ(deserialized_doc->get("special_chars").value(), std::string("Special characters\t\n\r\0included", 15)); EXPECT_EQ(deserialized_doc->get("binary_string").value(), binary_string); } // Test vector type TEST_F(DocDetailedTest, VectorBoolSerializationDeserialization) { std::vector bool_vec; // Create a vector with a large number of elements for (int i = 0; i < 1000; ++i) { bool_vec.push_back(i % 2 == 0); } test_doc_->set("bool_vec", bool_vec); auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); auto deserialized_vec = deserialized_doc->get>("bool_vec").value(); ASSERT_EQ(deserialized_vec.size(), bool_vec.size()); for (size_t i = 0; i < bool_vec.size(); ++i) { EXPECT_EQ(deserialized_vec[i], bool_vec[i]) << "Mismatch at index " << i; } } // Test numeric vector types TEST_F(DocDetailedTest, NumericVectorSerializationDeserialization) { // Test int8_t vector std::vector int8_vec = {std::numeric_limits::min(), -1, 0, 1, std::numeric_limits::max()}; test_doc_->set("int8_vec", int8_vec); // Test int16_t vector std::vector int16_vec = {std::numeric_limits::min(), -1, 0, 1, std::numeric_limits::max()}; test_doc_->set("int16_vec", int16_vec); // Test int32_t vector std::vector int32_vec = {std::numeric_limits::min(), -1, 0, 1, std::numeric_limits::max()}; test_doc_->set("int32_vec", int32_vec); // Test int64_t vector std::vector int64_vec = {std::numeric_limits::min(), -1, 0, 1, std::numeric_limits::max()}; test_doc_->set("int64_vec", int64_vec); // Test uint32_t vector std::vector uint32_vec = {std::numeric_limits::min(), 1, 100, std::numeric_limits::max()}; test_doc_->set("uint32_vec", uint32_vec); // Test uint64_t vector std::vector uint64_vec = {std::numeric_limits::min(), 1, 100, std::numeric_limits::max()}; test_doc_->set("uint64_vec", uint64_vec); // Test float vector std::vector float_vec = {std::numeric_limits::min(), -1.0f, 0.0f, 1.0f, std::numeric_limits::max()}; test_doc_->set("float_vec", float_vec); // Test double vector std::vector double_vec = {std::numeric_limits::min(), -1.0, 0.0, 1.0, std::numeric_limits::max()}; test_doc_->set("double_vec", double_vec); auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); EXPECT_EQ(deserialized_doc->get>("int8_vec").value(), int8_vec); EXPECT_EQ(deserialized_doc->get>("int16_vec").value(), int16_vec); EXPECT_EQ(deserialized_doc->get>("int32_vec").value(), int32_vec); EXPECT_EQ(deserialized_doc->get>("int64_vec").value(), int64_vec); EXPECT_EQ(deserialized_doc->get>("uint32_vec").value(), uint32_vec); EXPECT_EQ(deserialized_doc->get>("uint64_vec").value(), uint64_vec); // Floating point numbers use approximate comparison auto deserialized_float_vec = deserialized_doc->get>("float_vec").value(); ASSERT_EQ(deserialized_float_vec.size(), float_vec.size()); for (size_t i = 0; i < float_vec.size(); ++i) { EXPECT_FLOAT_EQ(deserialized_float_vec[i], float_vec[i]) << "Mismatch at index " << i; } auto deserialized_double_vec = deserialized_doc->get>("double_vec").value(); ASSERT_EQ(deserialized_double_vec.size(), double_vec.size()); for (size_t i = 0; i < double_vec.size(); ++i) { EXPECT_DOUBLE_EQ(deserialized_double_vec[i], double_vec[i]) << "Mismatch at index " << i; } } // Test string vector types TEST_F(DocDetailedTest, StringVectorSerializationDeserialization) { std::vector string_vec; string_vec.push_back(""); // Empty string string_vec.push_back("normal string"); string_vec.push_back(std::string(1000, 'x')); // Long string string_vec.push_back("Special character test"); string_vec.push_back( std::string("binary\0data", 11)); // Contains binary data test_doc_->set("string_vec", string_vec); auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); auto deserialized_vec = deserialized_doc->get>("string_vec").value(); ASSERT_EQ(deserialized_vec.size(), string_vec.size()); for (size_t i = 0; i < string_vec.size(); ++i) { EXPECT_EQ(deserialized_vec[i], string_vec[i]) << "Mismatch at index " << i; } } // Test sparse vector types TEST_F(DocDetailedTest, SparseVectorSerializationDeserialization) { // Test float type sparse vector std::pair, std::vector> sparse_float_vec; sparse_float_vec.first = {0, 100, 1000, 10000}; sparse_float_vec.second = {0.1f, 100.5f, -200.7f, std::numeric_limits::max()}; test_doc_->set("sparse_float_vec", sparse_float_vec); // Test ailego::Float16 type sparse vector std::pair, std::vector> sparse_float16_vec; sparse_float16_vec.first = {1, 50, 500}; sparse_float16_vec.second = {ailego::Float16(0.5f), ailego::Float16(-10.25f), ailego::Float16(1000.0f)}; test_doc_->set("sparse_float16_vec", sparse_float16_vec); auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); // Verify float sparse vector auto deserialized_float_vec = deserialized_doc ->get, std::vector>>( "sparse_float_vec") .value(); EXPECT_EQ(deserialized_float_vec.first, sparse_float_vec.first); ASSERT_EQ(deserialized_float_vec.second.size(), sparse_float_vec.second.size()); for (size_t i = 0; i < sparse_float_vec.second.size(); ++i) { EXPECT_FLOAT_EQ(deserialized_float_vec.second[i], sparse_float_vec.second[i]) << "Mismatch at index " << i; } // Verify float16 sparse vector auto deserialized_float16_vec = deserialized_doc ->get, std::vector>>( "sparse_float16_vec") .value(); EXPECT_EQ(deserialized_float16_vec.first, sparse_float16_vec.first); EXPECT_EQ(deserialized_float16_vec.second, sparse_float16_vec.second); } // Test case with many fields TEST_F(DocDetailedTest, ManyFieldsSerializationDeserialization) { const int field_count = 1000; for (int i = 0; i < field_count; ++i) { test_doc_->set("field_" + std::to_string(i), i); } auto serialized = test_doc_->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); for (int i = 0; i < field_count; ++i) { std::string field_name = "field_" + std::to_string(i); EXPECT_EQ(deserialized_doc->get(field_name).value(), i); } } // Test empty document TEST_F(DocDetailedTest, EmptyDocSerializationDeserialization) { Doc::Ptr empty_doc = std::make_shared(); empty_doc->set_pk(""); // Empty primary key auto serialized = empty_doc->serialize(); ASSERT_FALSE(serialized.empty()); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); EXPECT_EQ(deserialized_doc->pk(), ""); } // Test large document TEST_F(DocDetailedTest, LargeDocSerializationDeserialization) { // Create a document with a large amount of data std::string large_string(100000, 'A'); test_doc_->set("large_string", large_string); std::vector large_vector(50000); std::iota(large_vector.begin(), large_vector.end(), 0); test_doc_->set("large_vector", large_vector); auto serialized = test_doc_->serialize(); EXPECT_GT(serialized.size(), 100000); // Should be a large document auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); ASSERT_NE(deserialized_doc, nullptr); EXPECT_EQ(deserialized_doc->get("large_string").value(), large_string); EXPECT_EQ(deserialized_doc->get>("large_vector").value(), large_vector); } // Test memory usage calculation TEST_F(DocDetailedTest, MemoryUsageCalculation) { size_t initial_usage = test_doc_->memory_usage(); // Add some fields test_doc_->set("small_string", std::string("small")); test_doc_->set("int_field", int32_t(42)); test_doc_->set("float_field", 3.14f); size_t usage_with_fields = test_doc_->memory_usage(); EXPECT_GT(usage_with_fields, initial_usage); // Add a large field std::string large_string(10000, 'B'); test_doc_->set("large_string", large_string); size_t usage_with_large_field = test_doc_->memory_usage(); EXPECT_GT(usage_with_large_field, usage_with_fields); } // Test detailed string representation TEST_F(DocDetailedTest, DetailStringRepresentation) { test_doc_->set("test_bool", true); test_doc_->set("test_int", int32_t(-42)); test_doc_->set("test_string", std::string("hello")); std::vector float_vec = {1.1f, 2.2f, 3.3f}; test_doc_->set("test_float_vec", float_vec); std::string detail_str = test_doc_->to_detail_string(); EXPECT_FALSE(detail_str.empty()); EXPECT_NE(detail_str.find("test_pk"), std::string::npos); EXPECT_NE(detail_str.find("test_bool"), std::string::npos); EXPECT_NE(detail_str.find("test_int"), std::string::npos); EXPECT_NE(detail_str.find("test_string"), std::string::npos); EXPECT_NE(detail_str.find("test_float_vec"), std::string::npos); } // Test operator types TEST_F(DocDetailedTest, OperatorTypes) { test_doc_->set_operator(Operator::INSERT); EXPECT_EQ(test_doc_->get_operator(), Operator::INSERT); test_doc_->set_operator(Operator::DELETE); EXPECT_EQ(test_doc_->get_operator(), Operator::DELETE); test_doc_->set_operator(Operator::UPDATE); EXPECT_EQ(test_doc_->get_operator(), Operator::UPDATE); } // Test document ID and score TEST_F(DocDetailedTest, DocIdAndScore) { test_doc_->set_doc_id(0); EXPECT_EQ(test_doc_->doc_id(), 0); test_doc_->set_doc_id(std::numeric_limits::max()); EXPECT_EQ(test_doc_->doc_id(), std::numeric_limits::max()); test_doc_->set_score(0.0f); EXPECT_FLOAT_EQ(test_doc_->score(), 0.0f); test_doc_->set_score(1.0f); EXPECT_FLOAT_EQ(test_doc_->score(), 1.0f); test_doc_->set_score(-1.0f); EXPECT_FLOAT_EQ(test_doc_->score(), -1.0f); test_doc_->set_score(std::numeric_limits::max()); EXPECT_FLOAT_EQ(test_doc_->score(), std::numeric_limits::max()); } // Test primary key TEST_F(DocDetailedTest, PrimaryKey) { test_doc_->set_pk(""); EXPECT_EQ(test_doc_->pk(), ""); std::string long_pk(10000, 'X'); test_doc_->set_pk(long_pk); EXPECT_EQ(test_doc_->pk(), long_pk); test_doc_->set_pk("normal_pk"); EXPECT_EQ(test_doc_->pk(), "normal_pk"); } // Test duplicate field names (should overwrite old values) TEST_F(DocDetailedTest, DuplicateFieldNames) { test_doc_->set("duplicate_field", int32_t(1)); test_doc_->set("duplicate_field", int32_t(2)); // Overwrite old value auto serialized = test_doc_->serialize(); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); EXPECT_EQ(deserialized_doc->get("duplicate_field").value(), 2); } // Test combination of various data types TEST_F(DocDetailedTest, MixedDataTypes) { test_doc_->set("bool_field", true); test_doc_->set("int_field", int32_t(-1000)); test_doc_->set("uint_field", uint32_t(2000)); test_doc_->set("float_field", 3.14159f); test_doc_->set("double_field", 2.718281828459045); test_doc_->set("string_field", std::string("Hello, World!")); std::vector int_vec = {1, 2, 3, 4, 5}; test_doc_->set("int_vec", int_vec); std::vector float_vec = {1.1f, 2.2f, 3.3f}; test_doc_->set("float_vec", float_vec); std::vector string_vec = {"apple", "banana", "cherry"}; test_doc_->set("string_vec", string_vec); std::pair, std::vector> sparse_vec; sparse_vec.first = {1, 10, 100}; sparse_vec.second = {0.1f, 1.0f, 10.0f}; test_doc_->set("sparse_vec", sparse_vec); auto serialized = test_doc_->serialize(); auto deserialized_doc = Doc::deserialize(serialized.data(), serialized.size()); EXPECT_EQ(deserialized_doc->get("bool_field").value(), true); EXPECT_EQ(deserialized_doc->get("int_field").value(), -1000); EXPECT_EQ(deserialized_doc->get("uint_field").value(), 2000); EXPECT_FLOAT_EQ(deserialized_doc->get("float_field").value(), 3.14159f); EXPECT_DOUBLE_EQ(deserialized_doc->get("double_field").value(), 2.718281828459045); EXPECT_EQ(deserialized_doc->get("string_field").value(), "Hello, World!"); EXPECT_EQ(deserialized_doc->get>("int_vec").value(), int_vec); EXPECT_EQ(deserialized_doc->get>("float_vec").value(), float_vec); EXPECT_EQ( deserialized_doc->get>("string_vec").value(), string_vec); auto deserialized_sparse = deserialized_doc ->get, std::vector>>( "sparse_vec") .value(); EXPECT_EQ(deserialized_sparse.first, sparse_vec.first); EXPECT_EQ(deserialized_sparse.second, sparse_vec.second); } // Test doc validation and sanitization TEST_F(DocDetailedTest, ValidateAndSanitization) { // nullable=false: a doc with a null field is rejected { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); doc = test::TestHelper::CreateDocNull(1, *schema); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // nullable=true: a doc with a null field is accepted { auto schema = test::TestHelper::CreateNormalSchema(true); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); doc = test::TestHelper::CreateDocNull(1, *schema); s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); } // doc has a field that is not declared in the schema { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); doc.set("another_field", 1); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // scalar field value type does not match the schema { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); doc.set("int32", std::string("1")); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // dense vector field element type does not match the schema { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); std::string field = "dense_fp32"; auto field_schema = schema->get_field(field); ASSERT_NE(field_schema, nullptr); doc.set(field, std::vector(field_schema->dimension(), 1)); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // dense vector dimension does not match the schema { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); std::string field = "dense_fp32"; auto field_schema = schema->get_field(field); ASSERT_NE(field_schema, nullptr); doc.set(field, std::vector(field_schema->dimension() - 1, 1.0)); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); doc.set(field, std::vector()); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // sparse vector field value type does not match the schema { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); std::string field = "sparse_fp32"; auto field_schema = schema->get_field(field); ASSERT_NE(field_schema, nullptr); doc.set(field, std::vector(field_schema->dimension(), 1)); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // sparse vector indices and values have different lengths { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); std::string field = "sparse_fp32"; auto field_schema = schema->get_field(field); ASSERT_NE(field_schema, nullptr); std::vector indices; std::vector values; for (uint32_t i = 0; i < 100; i++) { indices.push_back(i); values.push_back(float(0.1)); } values.push_back(float(0.1)); std::pair, std::vector> sparse_float_vec{ indices, values}; doc.set, std::vector>>( field, sparse_float_vec); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // sparse vector indices are sorted in place; duplicates are rejected { auto schema = test::TestHelper::CreateNormalSchema(false); auto doc = test::TestHelper::CreateDoc(1, *schema); // unsorted indices are accepted and sorted in place std::pair, std::vector> unsorted{ {42u, 7u, 1000u, 3u, 128u, 17u, 99u}, {0.7f, 0.1f, 0.9f, 0.2f, 0.5f, 0.3f, 0.6f}}; doc.set, std::vector>>("sparse_fp32", unsorted); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()) << s.message(); const auto sorted_opt = doc.get, std::vector>>( "sparse_fp32"); ASSERT_TRUE(sorted_opt.has_value()); const std::vector expected_sorted_indices{3u, 7u, 17u, 42u, 99u, 128u, 1000u}; ASSERT_EQ(sorted_opt->first, expected_sorted_indices); ASSERT_EQ(sorted_opt->second.size(), expected_sorted_indices.size()); // sorted indices with a duplicate are rejected std::pair, std::vector> dup{ {3u, 7u, 17u, 42u, 42u, 99u, 128u}, {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f}}; doc.set, std::vector>>("sparse_fp32", dup); s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // validate rejects: null schema, missing pk, undefined field type { Doc doc; // null schema auto s = doc.validate_and_sanitize(nullptr); ASSERT_EQ(s.code(), StatusCode::INTERNAL_ERROR); // doc has no pk field auto schema = test::TestHelper::CreateNormalSchema(false); s = doc.validate_and_sanitize(schema); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); // schema contains a field with an undefined data type schema->add_field( std::make_shared("undefined", DataType::UNDEFINED, true)); s = doc.validate_and_sanitize(schema); ASSERT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // validate accepts every supported field data type { auto schema = test::TestHelper::CreateNormalSchema(false); schema->add_field( std::make_shared("binary", DataType::BINARY, false)); schema->add_field(std::make_shared( "array_binary", DataType::ARRAY_BINARY, false)); schema->add_field(std::make_shared( "vector_binary32", DataType::VECTOR_BINARY32, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "vector_binary64", DataType::VECTOR_BINARY64, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "vector_int8", DataType::VECTOR_INT8, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "vector_int8", DataType::VECTOR_INT8, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "vector_int16", DataType::VECTOR_INT16, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "dense_fp16", DataType::VECTOR_FP16, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "dense_fp64", DataType::VECTOR_FP64, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "sparse_fp16", DataType::SPARSE_VECTOR_FP16, 128, false, std::make_shared(MetricType::IP))); schema->add_field(std::make_shared( "sparse_fp32", DataType::SPARSE_VECTOR_FP32, 128, false, std::make_shared(MetricType::IP))); auto doc = test::TestHelper::CreateDoc(1, *schema); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()); } // pk with characters inside the allowed set is accepted { auto schema = test::TestHelper::CreateNormalSchema(false); std::vector valid_names = { // Min length = 1 "a", "Z", "0", "_", "-", "!", "@", "#", "$", "%", "+", "=", ".", // Mixed "a1_", "user.name", "test@example", "log_2025!@#", "metric+=value", "score%change", "user.name", // '.' allowed "test@example", // '@' allowed "log_2025!@#", // !@# allowed "metric+=value", // + = allowed "score%change", // % allowed "file-name_v1.2", // -, _, . allowed "a-b_c.d!@#$%+=.", // all specials in one // Max length = 64 std::string(64, 'a'), std::string(63, 'a') + "_", "_" + std::string(62, 'x') + ".", "!" + std::string(62, '0') + "@", }; for (auto pk : valid_names) { auto doc = test::TestHelper::CreateDoc(1, *schema, pk); auto s = doc.validate_and_sanitize(schema); ASSERT_TRUE(s.ok()) << "expected valid pk: " << pk << ", got: " << s.message(); } } // pk that is too long or uses disallowed characters is rejected { auto schema = test::TestHelper::CreateNormalSchema(false); std::vector invalid_names = { // Too long (>64) std::string(65, 'a'), std::string(64, 'a') + "_", // Illegal characters "a b", // space "a&b", // & not in set "a*b", // * "a(b)", // ( ) "a:b", // : "a;b", // ; "a/b", // / "a\\b", // backslash "a\"b", // " "a'b", // ' "ab", // < > "a?b", // ? "a~b", // ~ "a`b", // ` "a[b", "a]b", // [ ] "a{b", "a}b", // { } "a|b", // | "a^b", // ^ "a,b", // , "用户", // non-ASCII (Chinese) "αβγ", // Greek "résumé", // accented chars (é not in [a-zA-Z]) }; for (auto pk : invalid_names) { auto doc = test::TestHelper::CreateDoc(1, *schema, pk); auto s = doc.validate_and_sanitize(schema); ASSERT_FALSE(s.ok()) << "expected invalid pk: " << pk; } } } TEST_F(DocDetailedTest, GetValueTypeNameCoverage) { Doc::Value bool_val = true; EXPECT_EQ(get_value_type_name(bool_val, false), "BOOL"); Doc::Value int32_val = int32_t(42); EXPECT_EQ(get_value_type_name(int32_val, false), "INT32"); Doc::Value uint32_val = uint32_t(42); EXPECT_EQ(get_value_type_name(uint32_val, false), "UINT32"); Doc::Value int64_val = int64_t(42); EXPECT_EQ(get_value_type_name(int64_val, false), "INT64"); Doc::Value uint64_val = uint64_t(42); EXPECT_EQ(get_value_type_name(uint64_val, false), "UINT64"); Doc::Value float_val = 3.14f; EXPECT_EQ(get_value_type_name(float_val, false), "FLOAT"); Doc::Value double_val = 3.14; EXPECT_EQ(get_value_type_name(double_val, false), "DOUBLE"); Doc::Value string_val = std::string("test"); EXPECT_EQ(get_value_type_name(string_val, false), "STRING"); Doc::Value vector_bool_val = std::vector{true, false}; EXPECT_EQ(get_value_type_name(vector_bool_val, false), "ARRAY_BOOL"); Doc::Value vector_int8_val = std::vector{1, 2, 3}; EXPECT_EQ(get_value_type_name(vector_int8_val, true), "VECTOR_INT8"); Doc::Value vector_int16_val = std::vector{10, 20, 30}; EXPECT_EQ(get_value_type_name(vector_int16_val, true), "VECTOR_INT16"); Doc::Value vector_int32_val = std::vector{100, 200, 300}; EXPECT_EQ(get_value_type_name(vector_int32_val, true), "VECTOR_INT32"); Doc::Value vector_int64_val = std::vector{1000, 2000, 3000}; EXPECT_EQ(get_value_type_name(vector_int64_val, true), "VECTOR_INT64"); Doc::Value vector_uint32_val = std::vector{10, 20, 30}; EXPECT_EQ(get_value_type_name(vector_uint32_val, true), "VECTOR_UINT32"); Doc::Value vector_uint64_val = std::vector{100, 200, 300}; EXPECT_EQ(get_value_type_name(vector_uint64_val, true), "VECTOR_UINT64"); Doc::Value vector_float_val = std::vector{1.1f, 2.2f, 3.3f}; EXPECT_EQ(get_value_type_name(vector_float_val, true), "VECTOR_FP32"); Doc::Value vector_double_val = std::vector{1.1, 2.2, 3.3}; EXPECT_EQ(get_value_type_name(vector_double_val, true), "VECTOR_FP64"); Doc::Value vector_float16_val = std::vector{ ailego::Float16(1.1f), ailego::Float16(2.2f), ailego::Float16(3.3f)}; EXPECT_EQ(get_value_type_name(vector_float16_val, true), "VECTOR_FP16"); Doc::Value vector_string_val = std::vector{"a", "b", "c"}; EXPECT_EQ(get_value_type_name(vector_string_val, false), "ARRAY_STRING"); Doc::Value sparse_fp32_val = std::pair, std::vector>( std::vector{1, 2, 3}, std::vector{1.1f, 2.2f, 3.3f}); EXPECT_EQ(get_value_type_name(sparse_fp32_val, true), "SPARSE_VECTOR_FP32"); Doc::Value sparse_fp16_val = std::pair, std::vector>( std::vector{1, 2, 3}, std::vector{ailego::Float16(1.1f), ailego::Float16(2.2f), ailego::Float16(3.3f)}); EXPECT_EQ(get_value_type_name(sparse_fp16_val, true), "SPARSE_VECTOR_FP16"); // Test monostate (null) value Doc::Value null_val = std::monostate{}; EXPECT_EQ(get_value_type_name(null_val, false), "EMPTY"); } TEST_F(DocDetailedTest, SerializeValueCoverage) { Doc doc; doc.set("bool_field", true); doc.set("int32_field", 42); doc.set("uint32_field", 42); doc.set("int64_field", 42); doc.set("uint64_field", 42); doc.set("float_field", 3.14f); doc.set("double_field", 3.14); doc.set("string_field", "test"); std::vector bool_vec = {true, false}; doc.set>("vector_bool_field", bool_vec); std::vector int8_vec = {1, 2, 3}; doc.set>("vector_int8_field", int8_vec); std::vector int16_vec = {10, 20, 30}; doc.set>("vector_int16_field", int16_vec); std::vector int32_vec = {100, 200, 300}; doc.set>("vector_int32_field", int32_vec); std::vector int64_vec = {1000, 2000, 3000}; doc.set>("vector_int64_field", int64_vec); std::vector uint32_vec = {10, 20, 30}; doc.set>("vector_uint32_field", uint32_vec); std::vector uint64_vec = {100, 200, 300}; doc.set>("vector_uint64_field", uint64_vec); std::vector float_vec = {1.1f, 2.2f, 3.3f}; doc.set>("vector_float_field", float_vec); std::vector double_vec = {1.1, 2.2, 3.3}; doc.set>("vector_double_field", double_vec); std::vector float16_vec = { ailego::Float16(1.1f), ailego::Float16(2.2f), ailego::Float16(3.3f)}; doc.set>("vector_float16_field", float16_vec); std::vector string_vec = {"a", "b", "c"}; doc.set>("vector_string_field", string_vec); std::pair, std::vector> sparse_fp32( std::vector{1, 2, 3}, std::vector{1.1f, 2.2f, 3.3f}); doc.set, std::vector>>( "sparse_fp32_field", sparse_fp32); std::pair, std::vector> sparse_fp16( std::vector{1, 2, 3}, std::vector{ailego::Float16(1.1f), ailego::Float16(2.2f), ailego::Float16(3.3f)}); doc.set, std::vector>>( "sparse_fp16_field", sparse_fp16); // Test null value doc.set_null("null_field"); // for code coverage EXPECT_GT(doc.to_detail_string().size(), doc.to_string().size()); auto buffer = doc.serialize(); EXPECT_FALSE(buffer.empty()); auto deserialized_doc = Doc::deserialize(buffer.data(), buffer.size()); EXPECT_NE(deserialized_doc, nullptr); EXPECT_EQ(deserialized_doc->get("bool_field"), true); EXPECT_EQ(deserialized_doc->get("int32_field"), 42); EXPECT_EQ(deserialized_doc->get("uint32_field"), 42u); EXPECT_EQ(deserialized_doc->get("int64_field"), 42); EXPECT_EQ(deserialized_doc->get("uint64_field"), 42u); EXPECT_FLOAT_EQ(deserialized_doc->get("float_field").value(), 3.14f); EXPECT_DOUBLE_EQ(deserialized_doc->get("double_field").value(), 3.14); EXPECT_EQ(deserialized_doc->get("string_field"), "test"); // Test null value deserialization EXPECT_TRUE(deserialized_doc->is_null("null_field")); EXPECT_FALSE(deserialized_doc->has_value("null_field")); EXPECT_TRUE(deserialized_doc->has("null_field")); } TEST_F(DocDetailedTest, ToDetailStringCoverage) { Doc doc; doc.set_pk("test_pk"); doc.set_doc_id(1); doc.set_score(0.95f); doc.set("bool_field", true); doc.set("int32_field", 42); doc.set("uint32_field", 42); doc.set("int64_field", 42); doc.set("uint64_field", 42); doc.set("float_field", 3.14f); doc.set("double_field", 3.14); doc.set("string_field", "test"); std::vector bool_vec = {true, false}; doc.set>("vector_bool_field", bool_vec); std::vector int8_vec = {1, 2}; doc.set>("vector_int8_field", int8_vec); std::vector int16_vec = {10, 20}; doc.set>("vector_int16_field", int16_vec); std::vector int32_vec = {100, 200}; doc.set>("vector_int32_field", int32_vec); std::vector int64_vec = {1000, 2000}; doc.set>("vector_int64_field", int64_vec); std::vector uint32_vec = {10, 20}; doc.set>("vector_uint32_field", uint32_vec); std::vector uint64_vec = {100, 200}; doc.set>("vector_uint64_field", uint64_vec); std::vector float_vec = {1.1f, 2.2f}; doc.set>("vector_float_field", float_vec); std::vector double_vec = {1.1, 2.2}; doc.set>("vector_double_field", double_vec); std::vector float16_vec = {ailego::Float16(1.1f), ailego::Float16(2.2f)}; doc.set>("vector_float16_field", float16_vec); std::vector string_vec = {"a", "b"}; doc.set>("vector_string_field", string_vec); std::pair, std::vector> sparse_fp32( std::vector{1, 2}, std::vector{1.1f, 2.2f}); doc.set, std::vector>>( "sparse_fp32_field", sparse_fp32); std::pair, std::vector> sparse_fp16( std::vector{1, 2}, std::vector{ailego::Float16(1.1f), ailego::Float16(2.2f)}); doc.set, std::vector>>( "sparse_fp16_field", sparse_fp16); // Test null value in detail string doc.set_null("null_field"); std::string detail_str = doc.to_detail_string(); EXPECT_FALSE(detail_str.empty()); EXPECT_NE(detail_str.find("bool_field"), std::string::npos); EXPECT_NE(detail_str.find("int32_field"), std::string::npos); EXPECT_NE(detail_str.find("vector_float_field"), std::string::npos); EXPECT_NE(detail_str.find("null"), std::string::npos); // Should contain "null" for null field } TEST_F(DocDetailedTest, EqualityOperatorCoverage) { Doc doc1, doc2; doc1.set_pk("test_pk"); doc2.set_pk("test_pk"); doc1.set_doc_id(1); doc2.set_doc_id(1); doc1.set("bool_field", true); doc2.set("bool_field", true); doc1.set("int32_field", 42); doc2.set("int32_field", 42); doc1.set("uint32_field", 42); doc2.set("uint32_field", 42); doc1.set("int64_field", 42); doc2.set("int64_field", 42); doc1.set("uint64_field", 42); doc2.set("uint64_field", 42); doc1.set("float_field", 3.14f); doc2.set("float_field", 3.14f); doc1.set("double_field", 3.14); doc2.set("double_field", 3.14); doc1.set("string_field", "test"); doc2.set("string_field", "test"); std::vector bool_vec = {true, false}; doc1.set>("vector_bool_field", bool_vec); doc2.set>("vector_bool_field", bool_vec); std::vector int8_vec = {1, 2}; doc1.set>("vector_int8_field", int8_vec); doc2.set>("vector_int8_field", int8_vec); std::vector int16_vec = {10, 20}; doc1.set>("vector_int16_field", int16_vec); doc2.set>("vector_int16_field", int16_vec); std::vector int32_vec = {100, 200}; doc1.set>("vector_int32_field", int32_vec); doc2.set>("vector_int32_field", int32_vec); std::vector int64_vec = {1000, 2000}; doc1.set>("vector_int64_field", int64_vec); doc2.set>("vector_int64_field", int64_vec); std::vector uint32_vec = {10, 20}; doc1.set>("vector_uint32_field", uint32_vec); doc2.set>("vector_uint32_field", uint32_vec); std::vector uint64_vec = {100, 200}; doc1.set>("vector_uint64_field", uint64_vec); doc2.set>("vector_uint64_field", uint64_vec); std::vector float_vec = {1.1f, 2.2f}; doc1.set>("vector_float_field", float_vec); doc2.set>("vector_float_field", float_vec); std::vector double_vec = {1.1, 2.2}; doc1.set>("vector_double_field", double_vec); doc2.set>("vector_double_field", double_vec); std::vector float16_vec = {ailego::Float16(1.1f), ailego::Float16(2.2f)}; doc1.set>("vector_float16_field", float16_vec); doc2.set>("vector_float16_field", float16_vec); std::vector string_vec = {"a", "b"}; doc1.set>("vector_string_field", string_vec); doc2.set>("vector_string_field", string_vec); std::pair, std::vector> sparse_fp32( std::vector{1, 2}, std::vector{1.1f, 2.2f}); doc1.set, std::vector>>( "sparse_fp32_field", sparse_fp32); doc2.set, std::vector>>( "sparse_fp32_field", sparse_fp32); std::pair, std::vector> sparse_fp16( std::vector{1, 2}, std::vector{ailego::Float16(1.1f), ailego::Float16(2.2f)}); doc1.set, std::vector>>( "sparse_fp16_field", sparse_fp16); doc2.set, std::vector>>( "sparse_fp16_field", sparse_fp16); // Test equality with null values doc1.set_null("null_field"); doc2.set_null("null_field"); EXPECT_TRUE(doc1 == doc2); doc2.set("int32_field", 43); EXPECT_FALSE(doc1 == doc2); doc1.set_pk("test_pk1"); EXPECT_FALSE(doc1 == doc2); doc1.set_pk("test_pk"); doc1.set("int32_field", 42); EXPECT_FALSE(doc1 == doc2); doc1.set("int32_field", 42); doc1.set("rename_int32_field", 42); EXPECT_FALSE(doc1 == doc2); // Test inequality with different null values Doc doc3, doc4; doc3.set_pk("test"); doc4.set_pk("test"); doc3.set_null("null_field"); doc4.set("null_field", 42); EXPECT_FALSE(doc3 == doc4); } TEST(SearchQuery, ValidateAndSanitize) { // scalar-only query (no query vector): field schema is null { SearchQuery query; query.topk_ = 10; query.target_.field_name_ = "field_name"; auto s = query.validate(nullptr, nullptr); EXPECT_TRUE(s.ok()); } // vector query requires a non-null field schema { SearchQuery query; query.topk_ = 10; query.target_.field_name_ = "field_name"; std::vector query_vector = {1.0f, 2.0f, 3.0f, 4.0f}; std::string query_vector_str = std::string(reinterpret_cast(query_vector.data()), query_vector.size() * sizeof(float)); query.target_.set_vector(query_vector_str); auto s = query.validate(nullptr, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // output_fields count exceeds the allowed maximum { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 10; query.output_fields_ = std::vector(1025); FieldSchema schema = FieldSchema("field_name", DataType::INT32); auto s = query.validate(&schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // dense vector query dimension must match the field schema { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; std::vector query_vector = {1.0f, 2.0f, 3.0f, 4.0f}; std::string query_vector_str = std::string(reinterpret_cast(query_vector.data()), query_vector.size() * sizeof(float)); query.target_.set_vector(query_vector_str); FieldSchema schema = FieldSchema("field_name", DataType::VECTOR_FP32, 4, true); auto s = query.validate(&schema, nullptr); EXPECT_TRUE(s.ok()); query.target_.set_vector(query_vector_str.substr(0, 3)); s = query.validate(&schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // sparse query indices count must not exceed the allowed maximum { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; std::vector query_indices(16385); std::vector query_values(16385); query.target_.set_sparse_vector( std::string(reinterpret_cast(query_indices.data()), query_indices.size() * sizeof(uint32_t)), std::string(reinterpret_cast(query_values.data()), query_values.size() * sizeof(float))); FieldSchema schema = FieldSchema("field_name", DataType::SPARSE_VECTOR_FP32); auto s = query.validate(&schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); // one valid index and matching value: accepted uint32_t one_index = 0; float one_value = 0.0f; query.target_.set_sparse_vector( std::string(reinterpret_cast(&one_index), sizeof(uint32_t)), std::string(reinterpret_cast(&one_value), sizeof(float))); s = query.validate(&schema, nullptr); EXPECT_TRUE(s.ok()); } // sparse: validate sets need_sanitize for unsorted, sanitize sorts and // detects duplicates { auto pack_idx = [](const std::vector &v) { return std::string(reinterpret_cast(v.data()), v.size() * sizeof(uint32_t)); }; auto pack_val = [](const std::vector &v) { return std::string(reinterpret_cast(v.data()), v.size() * sizeof(float)); }; auto decode_idx = [](const std::string &buf) { const auto *p = reinterpret_cast(buf.data()); return std::vector(p, p + buf.size() / sizeof(uint32_t)); }; auto decode_val = [](const std::string &buf) { const auto *p = reinterpret_cast(buf.data()); return std::vector(p, p + buf.size() / sizeof(float)); }; FieldSchema schema = FieldSchema("field_name", DataType::SPARSE_VECTOR_FP32); // unsorted indices: validate sets need_sanitize, sanitize sorts in place { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; query.target_.set_sparse_vector(pack_idx({42u, 7u, 128u, 3u, 99u}), pack_val({0.1f, 0.2f, 0.3f, 0.4f, 0.5f})); bool need_sanitize = false; auto s = query.validate(&schema, &need_sanitize); EXPECT_TRUE(s.ok()) << s.message(); EXPECT_TRUE(need_sanitize); VectorClause vc = *query.target_.get_vector_clause(); s = sanitize_sparse_vector(vc, &schema); EXPECT_TRUE(s.ok()) << s.message(); EXPECT_EQ(decode_idx(vc.sparse_indices_), (std::vector{3u, 7u, 42u, 99u, 128u})); EXPECT_EQ(decode_val(vc.sparse_values_), (std::vector{0.4f, 0.2f, 0.1f, 0.5f, 0.3f})); } // duplicates (sorted): validate detects duplicates directly { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; query.target_.set_sparse_vector(pack_idx({3u, 7u, 42u, 42u, 99u}), pack_val({0.1f, 0.2f, 0.3f, 0.4f, 0.5f})); bool need_sanitize = false; auto s = query.validate(&schema, &need_sanitize); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); EXPECT_FALSE(need_sanitize); } // duplicates (unsorted): sanitize sorts then reports duplicates { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; query.target_.set_sparse_vector(pack_idx({42u, 3u, 7u, 42u, 99u}), pack_val({0.1f, 0.2f, 0.3f, 0.4f, 0.5f})); bool need_sanitize = false; auto s = query.validate(&schema, &need_sanitize); EXPECT_TRUE(s.ok()); EXPECT_TRUE(need_sanitize); VectorClause vc = *query.target_.get_vector_clause(); s = sanitize_sparse_vector(vc, &schema); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // sorted without duplicates: need_sanitize is false { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; query.target_.set_sparse_vector(pack_idx({1u, 2u, 3u, 4u}), pack_val({0.1f, 0.2f, 0.3f, 0.4f})); bool need_sanitize = false; auto s = query.validate(&schema, &need_sanitize); EXPECT_TRUE(s.ok()) << s.message(); EXPECT_FALSE(need_sanitize); } // mismatched counts are rejected by validate { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 100; query.target_.set_sparse_vector(pack_idx({1u, 2u, 3u, 4u}), pack_val({0.1f, 0.2f, 0.3f})); auto s = query.validate(&schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } } // query_params type must match the field's index type { SearchQuery query; query.target_.field_name_ = "embedding"; query.topk_ = 10; std::vector query_vector(128, 1.0f); query.target_.set_vector( std::string(reinterpret_cast(query_vector.data()), query_vector.size() * sizeof(float))); FieldSchema schema = FieldSchema("embedding", DataType::VECTOR_FP32, 128, false, std::make_shared(MetricType::L2)); query.target_.query_params_ = std::make_shared(150); auto s = query.validate(&schema, nullptr); EXPECT_TRUE(s.ok()); query.target_.query_params_ = std::make_shared(50); s = query.validate(&schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); query.target_.query_params_ = nullptr; s = query.validate(&schema, nullptr); EXPECT_TRUE(s.ok()); } // FTS clause validation { auto fts_params = std::make_shared(); FieldSchema fts_schema("content", DataType::STRING, false, fts_params); // FTS query with proper FTS field schema -> OK SearchQuery fts_only; fts_only.target_.field_name_ = "content"; fts_only.topk_ = 10; FtsClause fts_test; fts_test.query_string_ = "test"; fts_only.target_.clause_ = fts_test; auto s = fts_only.validate(&fts_schema, nullptr); EXPECT_TRUE(s.ok()); // FTS query with nullptr schema -> fail (field not found) s = fts_only.validate(nullptr, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); // FTS query with vector field schema -> fail (type mismatch) FieldSchema vec_schema("embedding", DataType::VECTOR_FP32, 128, false, std::make_shared(MetricType::L2)); s = fts_only.validate(&vec_schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // VectorViewClause: validate handles VectorViewClause the same as // VectorClause { FieldSchema schema = FieldSchema("field_name", DataType::VECTOR_FP32, 4, true); std::vector query_vector = {1.0f, 2.0f, 3.0f, 4.0f}; std::string vec_data(reinterpret_cast(query_vector.data()), query_vector.size() * sizeof(float)); // Dense VectorViewClause: valid dimension { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 10; query.target_.clause_ = VectorViewClause{vec_data, std::string_view{}, std::string_view{}}; auto s = query.validate(&schema, nullptr); EXPECT_TRUE(s.ok()) << s.message(); } // Dense VectorViewClause: wrong dimension { SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 10; std::string short_vec = vec_data.substr(0, sizeof(float) * 2); query.target_.clause_ = VectorViewClause{short_vec, std::string_view{}, std::string_view{}}; auto s = query.validate(&schema, nullptr); EXPECT_FALSE(s.ok()); EXPECT_EQ(s.code(), StatusCode::INVALID_ARGUMENT); } // Sparse VectorViewClause: unsorted triggers need_sanitize { FieldSchema sparse_schema( "field_name", DataType::SPARSE_VECTOR_FP32, false, std::make_shared(MetricType::IP)); std::vector idx_vec = {3u, 1u, 2u}; std::vector val_vec = {0.3f, 0.1f, 0.2f}; std::string idx_data(reinterpret_cast(idx_vec.data()), idx_vec.size() * sizeof(uint32_t)); std::string val_data(reinterpret_cast(val_vec.data()), val_vec.size() * sizeof(float)); SearchQuery query; query.target_.field_name_ = "field_name"; query.topk_ = 10; query.target_.clause_ = VectorViewClause{std::string_view{}, idx_data, val_data}; bool need_sanitize = false; auto s = query.validate(&sparse_schema, &need_sanitize); EXPECT_TRUE(s.ok()) << s.message(); EXPECT_TRUE(need_sanitize); } } } // Test null value TEST_F(DocDetailedTest, NullValue) { Doc doc; // Test setting null value doc.set_null("null_field"); EXPECT_TRUE(doc.is_null("null_field")); EXPECT_FALSE(doc.has_value("null_field")); EXPECT_TRUE(doc.has("null_field")); // Test get_field with null field auto result = doc.get_field("null_field"); EXPECT_EQ(result.status(), Doc::FieldGetStatus::IS_NULL); EXPECT_FALSE(result.ok()); // Test get with null field auto opt_result = doc.get("null_field"); EXPECT_FALSE(opt_result.has_value()); // Test overwriting null with actual value doc.set("null_field", 42); EXPECT_FALSE(doc.is_null("null_field")); EXPECT_TRUE(doc.has_value("null_field")); EXPECT_TRUE(doc.has("null_field")); EXPECT_EQ(doc.get("null_field").value(), 42); // Test overwriting value with null doc.set_null("null_field"); EXPECT_TRUE(doc.is_null("null_field")); EXPECT_FALSE(doc.has_value("null_field")); EXPECT_TRUE(doc.has("null_field")); // Test serialization/deserialization of null values auto buffer = doc.serialize(); auto deserialized_doc = Doc::deserialize(buffer.data(), buffer.size()); EXPECT_NE(deserialized_doc, nullptr); EXPECT_TRUE(deserialized_doc->is_null("null_field")); EXPECT_FALSE(deserialized_doc->has_value("null_field")); EXPECT_TRUE(deserialized_doc->has("null_field")); } // Test field existence checks TEST_F(DocDetailedTest, FieldExistenceChecks) { Doc doc; // Test non-existent field EXPECT_FALSE(doc.has("nonexistent")); EXPECT_FALSE(doc.has_value("nonexistent")); EXPECT_FALSE(doc.is_null("nonexistent")); // Test get_field with non-existent field auto result = doc.get_field("nonexistent"); EXPECT_EQ(result.status(), Doc::FieldGetStatus::NOT_FOUND); EXPECT_FALSE(result.ok()); // Test get with non-existent field auto opt_result = doc.get("nonexistent"); EXPECT_FALSE(opt_result.has_value()); // Add a field and test again doc.set("existent", 123); EXPECT_TRUE(doc.has("existent")); EXPECT_TRUE(doc.has_value("existent")); EXPECT_FALSE(doc.is_null("existent")); // Test type mismatch auto type_mismatch_result = doc.get_field("existent"); EXPECT_EQ(type_mismatch_result.status(), Doc::FieldGetStatus::TYPE_MISMATCH); EXPECT_FALSE(type_mismatch_result.ok()); auto type_mismatch_opt = doc.get("existent"); EXPECT_FALSE(type_mismatch_opt.has_value()); }