1593 lines
58 KiB
C++
1593 lines
58 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 "zvec/db/doc.h"
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <gtest/gtest.h>
|
|
#include <zvec/ailego/utility/float_helper.h>
|
|
#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<Doc>();
|
|
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<int32_t>::min());
|
|
test_doc_->set("int32_max", std::numeric_limits<int32_t>::max());
|
|
test_doc_->set("uint32_min", std::numeric_limits<uint32_t>::min());
|
|
test_doc_->set("uint32_max", std::numeric_limits<uint32_t>::max());
|
|
test_doc_->set("int64_min", std::numeric_limits<int64_t>::min());
|
|
test_doc_->set("int64_max", std::numeric_limits<int64_t>::max());
|
|
test_doc_->set("uint64_min", std::numeric_limits<uint64_t>::min());
|
|
test_doc_->set("uint64_max", std::numeric_limits<uint64_t>::max());
|
|
test_doc_->set("float_min", std::numeric_limits<float>::min());
|
|
test_doc_->set("float_max", std::numeric_limits<float>::max());
|
|
test_doc_->set("float_lowest", std::numeric_limits<float>::lowest());
|
|
test_doc_->set("double_min", std::numeric_limits<double>::min());
|
|
test_doc_->set("double_max", std::numeric_limits<double>::max());
|
|
test_doc_->set("double_lowest", std::numeric_limits<double>::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>("bool_true").value(), true);
|
|
EXPECT_EQ(deserialized_doc->get<bool>("bool_false").value(), false);
|
|
EXPECT_EQ(deserialized_doc->get<int32_t>("int32_min").value(),
|
|
std::numeric_limits<int32_t>::min());
|
|
EXPECT_EQ(deserialized_doc->get<int32_t>("int32_max").value(),
|
|
std::numeric_limits<int32_t>::max());
|
|
EXPECT_EQ(deserialized_doc->get<uint32_t>("uint32_min").value(),
|
|
std::numeric_limits<uint32_t>::min());
|
|
EXPECT_EQ(deserialized_doc->get<uint32_t>("uint32_max").value(),
|
|
std::numeric_limits<uint32_t>::max());
|
|
EXPECT_EQ(deserialized_doc->get<int64_t>("int64_min").value(),
|
|
std::numeric_limits<int64_t>::min());
|
|
EXPECT_EQ(deserialized_doc->get<int64_t>("int64_max").value(),
|
|
std::numeric_limits<int64_t>::max());
|
|
EXPECT_EQ(deserialized_doc->get<uint64_t>("uint64_min").value(),
|
|
std::numeric_limits<uint64_t>::min());
|
|
EXPECT_EQ(deserialized_doc->get<uint64_t>("uint64_max").value(),
|
|
std::numeric_limits<uint64_t>::max());
|
|
|
|
// For floating point numbers, use approximate comparison
|
|
EXPECT_FLOAT_EQ(deserialized_doc->get<float>("float_min").value(),
|
|
std::numeric_limits<float>::min());
|
|
EXPECT_FLOAT_EQ(deserialized_doc->get<float>("float_max").value(),
|
|
std::numeric_limits<float>::max());
|
|
EXPECT_FLOAT_EQ(deserialized_doc->get<float>("float_lowest").value(),
|
|
std::numeric_limits<float>::lowest());
|
|
EXPECT_DOUBLE_EQ(deserialized_doc->get<double>("double_min").value(),
|
|
std::numeric_limits<double>::min());
|
|
EXPECT_DOUBLE_EQ(deserialized_doc->get<double>("double_max").value(),
|
|
std::numeric_limits<double>::max());
|
|
EXPECT_DOUBLE_EQ(deserialized_doc->get<double>("double_lowest").value(),
|
|
std::numeric_limits<double>::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<char>(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<std::string>("empty_string").value(), "");
|
|
EXPECT_EQ(deserialized_doc->get<std::string>("long_string").value(),
|
|
long_string);
|
|
EXPECT_EQ(deserialized_doc->get<std::string>("special_chars").value(),
|
|
std::string("Special characters\t\n\r\0included", 15));
|
|
EXPECT_EQ(deserialized_doc->get<std::string>("binary_string").value(),
|
|
binary_string);
|
|
}
|
|
|
|
|
|
// Test vector<bool> type
|
|
TEST_F(DocDetailedTest, VectorBoolSerializationDeserialization) {
|
|
std::vector<bool> bool_vec;
|
|
// Create a vector<bool> 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<std::vector<bool>>("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_t> int8_vec = {std::numeric_limits<int8_t>::min(), -1, 0, 1,
|
|
std::numeric_limits<int8_t>::max()};
|
|
test_doc_->set("int8_vec", int8_vec);
|
|
|
|
// Test int16_t vector
|
|
std::vector<int16_t> int16_vec = {std::numeric_limits<int16_t>::min(), -1, 0,
|
|
1, std::numeric_limits<int16_t>::max()};
|
|
test_doc_->set("int16_vec", int16_vec);
|
|
|
|
// Test int32_t vector
|
|
std::vector<int32_t> int32_vec = {std::numeric_limits<int32_t>::min(), -1, 0,
|
|
1, std::numeric_limits<int32_t>::max()};
|
|
test_doc_->set("int32_vec", int32_vec);
|
|
|
|
// Test int64_t vector
|
|
std::vector<int64_t> int64_vec = {std::numeric_limits<int64_t>::min(), -1, 0,
|
|
1, std::numeric_limits<int64_t>::max()};
|
|
test_doc_->set("int64_vec", int64_vec);
|
|
|
|
// Test uint32_t vector
|
|
std::vector<uint32_t> uint32_vec = {std::numeric_limits<uint32_t>::min(), 1,
|
|
100,
|
|
std::numeric_limits<uint32_t>::max()};
|
|
test_doc_->set("uint32_vec", uint32_vec);
|
|
|
|
// Test uint64_t vector
|
|
std::vector<uint64_t> uint64_vec = {std::numeric_limits<uint64_t>::min(), 1,
|
|
100,
|
|
std::numeric_limits<uint64_t>::max()};
|
|
test_doc_->set("uint64_vec", uint64_vec);
|
|
|
|
// Test float vector
|
|
std::vector<float> float_vec = {std::numeric_limits<float>::min(), -1.0f,
|
|
0.0f, 1.0f,
|
|
std::numeric_limits<float>::max()};
|
|
test_doc_->set("float_vec", float_vec);
|
|
|
|
// Test double vector
|
|
std::vector<double> double_vec = {std::numeric_limits<double>::min(), -1.0,
|
|
0.0, 1.0,
|
|
std::numeric_limits<double>::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<std::vector<int8_t>>("int8_vec").value(),
|
|
int8_vec);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<int16_t>>("int16_vec").value(),
|
|
int16_vec);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<int32_t>>("int32_vec").value(),
|
|
int32_vec);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<int64_t>>("int64_vec").value(),
|
|
int64_vec);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<uint32_t>>("uint32_vec").value(),
|
|
uint32_vec);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<uint64_t>>("uint64_vec").value(),
|
|
uint64_vec);
|
|
|
|
|
|
// Floating point numbers use approximate comparison
|
|
auto deserialized_float_vec =
|
|
deserialized_doc->get<std::vector<float>>("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<std::vector<double>>("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<std::string> 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<std::vector<std::string>>("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<uint32_t>, std::vector<float>> sparse_float_vec;
|
|
sparse_float_vec.first = {0, 100, 1000, 10000};
|
|
sparse_float_vec.second = {0.1f, 100.5f, -200.7f,
|
|
std::numeric_limits<float>::max()};
|
|
|
|
test_doc_->set("sparse_float_vec", sparse_float_vec);
|
|
|
|
// Test ailego::Float16 type sparse vector
|
|
std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>>
|
|
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::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"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::pair<std::vector<uint32_t>, std::vector<ailego::Float16>>>(
|
|
"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<int32_t>(field_name).value(), i);
|
|
}
|
|
}
|
|
|
|
// Test empty document
|
|
TEST_F(DocDetailedTest, EmptyDocSerializationDeserialization) {
|
|
Doc::Ptr empty_doc = std::make_shared<Doc>();
|
|
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<int32_t> 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<std::string>("large_string").value(),
|
|
large_string);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<int32_t>>("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> 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<uint64_t>::max());
|
|
EXPECT_EQ(test_doc_->doc_id(), std::numeric_limits<uint64_t>::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<float>::max());
|
|
EXPECT_FLOAT_EQ(test_doc_->score(), std::numeric_limits<float>::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<int32_t>("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<int32_t> int_vec = {1, 2, 3, 4, 5};
|
|
test_doc_->set("int_vec", int_vec);
|
|
|
|
std::vector<float> float_vec = {1.1f, 2.2f, 3.3f};
|
|
test_doc_->set("float_vec", float_vec);
|
|
|
|
std::vector<std::string> string_vec = {"apple", "banana", "cherry"};
|
|
test_doc_->set("string_vec", string_vec);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<float>> 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>("bool_field").value(), true);
|
|
EXPECT_EQ(deserialized_doc->get<int32_t>("int_field").value(), -1000);
|
|
EXPECT_EQ(deserialized_doc->get<uint32_t>("uint_field").value(), 2000);
|
|
EXPECT_FLOAT_EQ(deserialized_doc->get<float>("float_field").value(),
|
|
3.14159f);
|
|
EXPECT_DOUBLE_EQ(deserialized_doc->get<double>("double_field").value(),
|
|
2.718281828459045);
|
|
EXPECT_EQ(deserialized_doc->get<std::string>("string_field").value(),
|
|
"Hello, World!");
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<int32_t>>("int_vec").value(),
|
|
int_vec);
|
|
EXPECT_EQ(deserialized_doc->get<std::vector<float>>("float_vec").value(),
|
|
float_vec);
|
|
EXPECT_EQ(
|
|
deserialized_doc->get<std::vector<std::string>>("string_vec").value(),
|
|
string_vec);
|
|
|
|
auto deserialized_sparse =
|
|
deserialized_doc
|
|
->get<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"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<int16_t>(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<float>(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<float>());
|
|
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<int16_t>(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<uint32_t> indices;
|
|
std::vector<float> 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<uint32_t>, std::vector<float>> sparse_float_vec{
|
|
indices, values};
|
|
doc.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
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<uint32_t>, std::vector<float>> 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::pair<std::vector<uint32_t>, std::vector<float>>>("sparse_fp32",
|
|
unsorted);
|
|
auto s = doc.validate_and_sanitize(schema);
|
|
ASSERT_TRUE(s.ok()) << s.message();
|
|
const auto sorted_opt =
|
|
doc.get<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"sparse_fp32");
|
|
ASSERT_TRUE(sorted_opt.has_value());
|
|
const std::vector<uint32_t> 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<uint32_t>, std::vector<float>> 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::pair<std::vector<uint32_t>, std::vector<float>>>("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<FieldSchema>("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<FieldSchema>("binary", DataType::BINARY, false));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"array_binary", DataType::ARRAY_BINARY, false));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"vector_binary32", DataType::VECTOR_BINARY32, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"vector_binary64", DataType::VECTOR_BINARY64, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"vector_int8", DataType::VECTOR_INT8, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"vector_int8", DataType::VECTOR_INT8, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"vector_int16", DataType::VECTOR_INT16, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"dense_fp16", DataType::VECTOR_FP16, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"dense_fp64", DataType::VECTOR_FP64, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"sparse_fp16", DataType::SPARSE_VECTOR_FP16, 128, false,
|
|
std::make_shared<FlatIndexParams>(MetricType::IP)));
|
|
schema->add_field(std::make_shared<FieldSchema>(
|
|
"sparse_fp32", DataType::SPARSE_VECTOR_FP32, 128, false,
|
|
std::make_shared<FlatIndexParams>(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<std::string> 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<std::string> 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", // '
|
|
"a<b",
|
|
"a>b", // < >
|
|
"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<bool>{true, false};
|
|
EXPECT_EQ(get_value_type_name(vector_bool_val, false), "ARRAY_BOOL");
|
|
|
|
Doc::Value vector_int8_val = std::vector<int8_t>{1, 2, 3};
|
|
EXPECT_EQ(get_value_type_name(vector_int8_val, true), "VECTOR_INT8");
|
|
|
|
Doc::Value vector_int16_val = std::vector<int16_t>{10, 20, 30};
|
|
EXPECT_EQ(get_value_type_name(vector_int16_val, true), "VECTOR_INT16");
|
|
|
|
Doc::Value vector_int32_val = std::vector<int32_t>{100, 200, 300};
|
|
EXPECT_EQ(get_value_type_name(vector_int32_val, true), "VECTOR_INT32");
|
|
|
|
Doc::Value vector_int64_val = std::vector<int64_t>{1000, 2000, 3000};
|
|
EXPECT_EQ(get_value_type_name(vector_int64_val, true), "VECTOR_INT64");
|
|
|
|
Doc::Value vector_uint32_val = std::vector<uint32_t>{10, 20, 30};
|
|
EXPECT_EQ(get_value_type_name(vector_uint32_val, true), "VECTOR_UINT32");
|
|
|
|
Doc::Value vector_uint64_val = std::vector<uint64_t>{100, 200, 300};
|
|
EXPECT_EQ(get_value_type_name(vector_uint64_val, true), "VECTOR_UINT64");
|
|
|
|
Doc::Value vector_float_val = std::vector<float>{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<double>{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>{
|
|
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<std::string>{"a", "b", "c"};
|
|
EXPECT_EQ(get_value_type_name(vector_string_val, false), "ARRAY_STRING");
|
|
|
|
Doc::Value sparse_fp32_val =
|
|
std::pair<std::vector<uint32_t>, std::vector<float>>(
|
|
std::vector<uint32_t>{1, 2, 3}, std::vector<float>{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<uint32_t>, std::vector<ailego::Float16>>(
|
|
std::vector<uint32_t>{1, 2, 3},
|
|
std::vector<ailego::Float16>{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>("bool_field", true);
|
|
doc.set<int32_t>("int32_field", 42);
|
|
doc.set<uint32_t>("uint32_field", 42);
|
|
doc.set<int64_t>("int64_field", 42);
|
|
doc.set<uint64_t>("uint64_field", 42);
|
|
doc.set<float>("float_field", 3.14f);
|
|
doc.set<double>("double_field", 3.14);
|
|
doc.set<std::string>("string_field", "test");
|
|
|
|
std::vector<bool> bool_vec = {true, false};
|
|
doc.set<std::vector<bool>>("vector_bool_field", bool_vec);
|
|
|
|
std::vector<int8_t> int8_vec = {1, 2, 3};
|
|
doc.set<std::vector<int8_t>>("vector_int8_field", int8_vec);
|
|
|
|
std::vector<int16_t> int16_vec = {10, 20, 30};
|
|
doc.set<std::vector<int16_t>>("vector_int16_field", int16_vec);
|
|
|
|
std::vector<int32_t> int32_vec = {100, 200, 300};
|
|
doc.set<std::vector<int32_t>>("vector_int32_field", int32_vec);
|
|
|
|
std::vector<int64_t> int64_vec = {1000, 2000, 3000};
|
|
doc.set<std::vector<int64_t>>("vector_int64_field", int64_vec);
|
|
|
|
std::vector<uint32_t> uint32_vec = {10, 20, 30};
|
|
doc.set<std::vector<uint32_t>>("vector_uint32_field", uint32_vec);
|
|
|
|
std::vector<uint64_t> uint64_vec = {100, 200, 300};
|
|
doc.set<std::vector<uint64_t>>("vector_uint64_field", uint64_vec);
|
|
|
|
std::vector<float> float_vec = {1.1f, 2.2f, 3.3f};
|
|
doc.set<std::vector<float>>("vector_float_field", float_vec);
|
|
|
|
std::vector<double> double_vec = {1.1, 2.2, 3.3};
|
|
doc.set<std::vector<double>>("vector_double_field", double_vec);
|
|
|
|
std::vector<ailego::Float16> float16_vec = {
|
|
ailego::Float16(1.1f), ailego::Float16(2.2f), ailego::Float16(3.3f)};
|
|
doc.set<std::vector<ailego::Float16>>("vector_float16_field", float16_vec);
|
|
|
|
std::vector<std::string> string_vec = {"a", "b", "c"};
|
|
doc.set<std::vector<std::string>>("vector_string_field", string_vec);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<float>> sparse_fp32(
|
|
std::vector<uint32_t>{1, 2, 3}, std::vector<float>{1.1f, 2.2f, 3.3f});
|
|
doc.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"sparse_fp32_field", sparse_fp32);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>> sparse_fp16(
|
|
std::vector<uint32_t>{1, 2, 3},
|
|
std::vector<ailego::Float16>{ailego::Float16(1.1f), ailego::Float16(2.2f),
|
|
ailego::Float16(3.3f)});
|
|
doc.set<std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>>>(
|
|
"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>("bool_field"), true);
|
|
EXPECT_EQ(deserialized_doc->get<int32_t>("int32_field"), 42);
|
|
EXPECT_EQ(deserialized_doc->get<uint32_t>("uint32_field"), 42u);
|
|
EXPECT_EQ(deserialized_doc->get<int64_t>("int64_field"), 42);
|
|
EXPECT_EQ(deserialized_doc->get<uint64_t>("uint64_field"), 42u);
|
|
EXPECT_FLOAT_EQ(deserialized_doc->get<float>("float_field").value(), 3.14f);
|
|
EXPECT_DOUBLE_EQ(deserialized_doc->get<double>("double_field").value(), 3.14);
|
|
EXPECT_EQ(deserialized_doc->get<std::string>("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>("bool_field", true);
|
|
doc.set<int32_t>("int32_field", 42);
|
|
doc.set<uint32_t>("uint32_field", 42);
|
|
doc.set<int64_t>("int64_field", 42);
|
|
doc.set<uint64_t>("uint64_field", 42);
|
|
doc.set<float>("float_field", 3.14f);
|
|
doc.set<double>("double_field", 3.14);
|
|
doc.set<std::string>("string_field", "test");
|
|
|
|
std::vector<bool> bool_vec = {true, false};
|
|
doc.set<std::vector<bool>>("vector_bool_field", bool_vec);
|
|
|
|
std::vector<int8_t> int8_vec = {1, 2};
|
|
doc.set<std::vector<int8_t>>("vector_int8_field", int8_vec);
|
|
|
|
std::vector<int16_t> int16_vec = {10, 20};
|
|
doc.set<std::vector<int16_t>>("vector_int16_field", int16_vec);
|
|
|
|
std::vector<int32_t> int32_vec = {100, 200};
|
|
doc.set<std::vector<int32_t>>("vector_int32_field", int32_vec);
|
|
|
|
std::vector<int64_t> int64_vec = {1000, 2000};
|
|
doc.set<std::vector<int64_t>>("vector_int64_field", int64_vec);
|
|
|
|
std::vector<uint32_t> uint32_vec = {10, 20};
|
|
doc.set<std::vector<uint32_t>>("vector_uint32_field", uint32_vec);
|
|
|
|
std::vector<uint64_t> uint64_vec = {100, 200};
|
|
doc.set<std::vector<uint64_t>>("vector_uint64_field", uint64_vec);
|
|
|
|
std::vector<float> float_vec = {1.1f, 2.2f};
|
|
doc.set<std::vector<float>>("vector_float_field", float_vec);
|
|
|
|
std::vector<double> double_vec = {1.1, 2.2};
|
|
doc.set<std::vector<double>>("vector_double_field", double_vec);
|
|
|
|
std::vector<ailego::Float16> float16_vec = {ailego::Float16(1.1f),
|
|
ailego::Float16(2.2f)};
|
|
doc.set<std::vector<ailego::Float16>>("vector_float16_field", float16_vec);
|
|
|
|
std::vector<std::string> string_vec = {"a", "b"};
|
|
doc.set<std::vector<std::string>>("vector_string_field", string_vec);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<float>> sparse_fp32(
|
|
std::vector<uint32_t>{1, 2}, std::vector<float>{1.1f, 2.2f});
|
|
doc.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"sparse_fp32_field", sparse_fp32);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>> sparse_fp16(
|
|
std::vector<uint32_t>{1, 2},
|
|
std::vector<ailego::Float16>{ailego::Float16(1.1f),
|
|
ailego::Float16(2.2f)});
|
|
doc.set<std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>>>(
|
|
"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>("bool_field", true);
|
|
doc2.set<bool>("bool_field", true);
|
|
|
|
doc1.set<int32_t>("int32_field", 42);
|
|
doc2.set<int32_t>("int32_field", 42);
|
|
|
|
doc1.set<uint32_t>("uint32_field", 42);
|
|
doc2.set<uint32_t>("uint32_field", 42);
|
|
|
|
doc1.set<int64_t>("int64_field", 42);
|
|
doc2.set<int64_t>("int64_field", 42);
|
|
|
|
doc1.set<uint64_t>("uint64_field", 42);
|
|
doc2.set<uint64_t>("uint64_field", 42);
|
|
|
|
doc1.set<float>("float_field", 3.14f);
|
|
doc2.set<float>("float_field", 3.14f);
|
|
|
|
doc1.set<double>("double_field", 3.14);
|
|
doc2.set<double>("double_field", 3.14);
|
|
|
|
doc1.set<std::string>("string_field", "test");
|
|
doc2.set<std::string>("string_field", "test");
|
|
|
|
std::vector<bool> bool_vec = {true, false};
|
|
doc1.set<std::vector<bool>>("vector_bool_field", bool_vec);
|
|
doc2.set<std::vector<bool>>("vector_bool_field", bool_vec);
|
|
|
|
std::vector<int8_t> int8_vec = {1, 2};
|
|
doc1.set<std::vector<int8_t>>("vector_int8_field", int8_vec);
|
|
doc2.set<std::vector<int8_t>>("vector_int8_field", int8_vec);
|
|
|
|
std::vector<int16_t> int16_vec = {10, 20};
|
|
doc1.set<std::vector<int16_t>>("vector_int16_field", int16_vec);
|
|
doc2.set<std::vector<int16_t>>("vector_int16_field", int16_vec);
|
|
|
|
std::vector<int32_t> int32_vec = {100, 200};
|
|
doc1.set<std::vector<int32_t>>("vector_int32_field", int32_vec);
|
|
doc2.set<std::vector<int32_t>>("vector_int32_field", int32_vec);
|
|
|
|
std::vector<int64_t> int64_vec = {1000, 2000};
|
|
doc1.set<std::vector<int64_t>>("vector_int64_field", int64_vec);
|
|
doc2.set<std::vector<int64_t>>("vector_int64_field", int64_vec);
|
|
|
|
std::vector<uint32_t> uint32_vec = {10, 20};
|
|
doc1.set<std::vector<uint32_t>>("vector_uint32_field", uint32_vec);
|
|
doc2.set<std::vector<uint32_t>>("vector_uint32_field", uint32_vec);
|
|
|
|
std::vector<uint64_t> uint64_vec = {100, 200};
|
|
doc1.set<std::vector<uint64_t>>("vector_uint64_field", uint64_vec);
|
|
doc2.set<std::vector<uint64_t>>("vector_uint64_field", uint64_vec);
|
|
|
|
std::vector<float> float_vec = {1.1f, 2.2f};
|
|
doc1.set<std::vector<float>>("vector_float_field", float_vec);
|
|
doc2.set<std::vector<float>>("vector_float_field", float_vec);
|
|
|
|
std::vector<double> double_vec = {1.1, 2.2};
|
|
doc1.set<std::vector<double>>("vector_double_field", double_vec);
|
|
doc2.set<std::vector<double>>("vector_double_field", double_vec);
|
|
|
|
std::vector<ailego::Float16> float16_vec = {ailego::Float16(1.1f),
|
|
ailego::Float16(2.2f)};
|
|
doc1.set<std::vector<ailego::Float16>>("vector_float16_field", float16_vec);
|
|
doc2.set<std::vector<ailego::Float16>>("vector_float16_field", float16_vec);
|
|
|
|
std::vector<std::string> string_vec = {"a", "b"};
|
|
doc1.set<std::vector<std::string>>("vector_string_field", string_vec);
|
|
doc2.set<std::vector<std::string>>("vector_string_field", string_vec);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<float>> sparse_fp32(
|
|
std::vector<uint32_t>{1, 2}, std::vector<float>{1.1f, 2.2f});
|
|
doc1.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"sparse_fp32_field", sparse_fp32);
|
|
doc2.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
|
|
"sparse_fp32_field", sparse_fp32);
|
|
|
|
std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>> sparse_fp16(
|
|
std::vector<uint32_t>{1, 2},
|
|
std::vector<ailego::Float16>{ailego::Float16(1.1f),
|
|
ailego::Float16(2.2f)});
|
|
doc1.set<std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>>>(
|
|
"sparse_fp16_field", sparse_fp16);
|
|
doc2.set<std::pair<std::vector<uint32_t>, std::vector<ailego::Float16>>>(
|
|
"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_t>("int32_field", 43);
|
|
EXPECT_FALSE(doc1 == doc2);
|
|
|
|
doc1.set_pk("test_pk1");
|
|
EXPECT_FALSE(doc1 == doc2);
|
|
|
|
doc1.set_pk("test_pk");
|
|
doc1.set<uint32_t>("int32_field", 42);
|
|
EXPECT_FALSE(doc1 == doc2);
|
|
|
|
doc1.set<int32_t>("int32_field", 42);
|
|
doc1.set<int32_t>("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<int32_t>("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<float> query_vector = {1.0f, 2.0f, 3.0f, 4.0f};
|
|
std::string query_vector_str =
|
|
std::string(reinterpret_cast<char *>(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<std::string>(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<float> query_vector = {1.0f, 2.0f, 3.0f, 4.0f};
|
|
std::string query_vector_str =
|
|
std::string(reinterpret_cast<char *>(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<uint32_t> query_indices(16385);
|
|
std::vector<float> query_values(16385);
|
|
query.target_.set_sparse_vector(
|
|
std::string(reinterpret_cast<char *>(query_indices.data()),
|
|
query_indices.size() * sizeof(uint32_t)),
|
|
std::string(reinterpret_cast<char *>(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<char *>(&one_index), sizeof(uint32_t)),
|
|
std::string(reinterpret_cast<char *>(&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<uint32_t> &v) {
|
|
return std::string(reinterpret_cast<const char *>(v.data()),
|
|
v.size() * sizeof(uint32_t));
|
|
};
|
|
auto pack_val = [](const std::vector<float> &v) {
|
|
return std::string(reinterpret_cast<const char *>(v.data()),
|
|
v.size() * sizeof(float));
|
|
};
|
|
auto decode_idx = [](const std::string &buf) {
|
|
const auto *p = reinterpret_cast<const uint32_t *>(buf.data());
|
|
return std::vector<uint32_t>(p, p + buf.size() / sizeof(uint32_t));
|
|
};
|
|
auto decode_val = [](const std::string &buf) {
|
|
const auto *p = reinterpret_cast<const float *>(buf.data());
|
|
return std::vector<float>(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<uint32_t>{3u, 7u, 42u, 99u, 128u}));
|
|
EXPECT_EQ(decode_val(vc.sparse_values_),
|
|
(std::vector<float>{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<float> query_vector(128, 1.0f);
|
|
query.target_.set_vector(
|
|
std::string(reinterpret_cast<char *>(query_vector.data()),
|
|
query_vector.size() * sizeof(float)));
|
|
FieldSchema schema =
|
|
FieldSchema("embedding", DataType::VECTOR_FP32, 128, false,
|
|
std::make_shared<HnswIndexParams>(MetricType::L2));
|
|
|
|
query.target_.query_params_ = std::make_shared<HnswQueryParams>(150);
|
|
auto s = query.validate(&schema, nullptr);
|
|
EXPECT_TRUE(s.ok());
|
|
|
|
query.target_.query_params_ = std::make_shared<IVFQueryParams>(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<FtsIndexParams>();
|
|
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<HnswIndexParams>(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<float> query_vector = {1.0f, 2.0f, 3.0f, 4.0f};
|
|
std::string vec_data(reinterpret_cast<char *>(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<HnswIndexParams>(MetricType::IP));
|
|
std::vector<uint32_t> idx_vec = {3u, 1u, 2u};
|
|
std::vector<float> val_vec = {0.3f, 0.1f, 0.2f};
|
|
std::string idx_data(reinterpret_cast<const char *>(idx_vec.data()),
|
|
idx_vec.size() * sizeof(uint32_t));
|
|
std::string val_data(reinterpret_cast<const char *>(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<int32_t>("null_field");
|
|
EXPECT_EQ(result.status(), Doc::FieldGetStatus::IS_NULL);
|
|
EXPECT_FALSE(result.ok());
|
|
|
|
// Test get with null field
|
|
auto opt_result = doc.get<int32_t>("null_field");
|
|
EXPECT_FALSE(opt_result.has_value());
|
|
|
|
// Test overwriting null with actual value
|
|
doc.set<int32_t>("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<int32_t>("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<int32_t>("nonexistent");
|
|
EXPECT_EQ(result.status(), Doc::FieldGetStatus::NOT_FOUND);
|
|
EXPECT_FALSE(result.ok());
|
|
|
|
// Test get with non-existent field
|
|
auto opt_result = doc.get<int32_t>("nonexistent");
|
|
EXPECT_FALSE(opt_result.has_value());
|
|
|
|
// Add a field and test again
|
|
doc.set<int32_t>("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<std::string>("existent");
|
|
EXPECT_EQ(type_mismatch_result.status(), Doc::FieldGetStatus::TYPE_MISMATCH);
|
|
EXPECT_FALSE(type_mismatch_result.ok());
|
|
|
|
auto type_mismatch_opt = doc.get<std::string>("existent");
|
|
EXPECT_FALSE(type_mismatch_opt.has_value());
|
|
} |