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

804 lines
32 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 "utils.h"
#include <cstdint>
#include <memory>
#include <vector>
#include <zvec/ailego/logger/logger.h>
#include "zvec/db/collection.h"
#include "zvec/db/doc.h"
#include "zvec/db/index_params.h"
#include "zvec/db/schema.h"
#include "zvec/db/status.h"
#include "zvec/db/type.h"
using namespace zvec;
using namespace zvec::test;
CollectionSchema::Ptr TestHelper::CreateTempSchema() {
auto schema = std::make_shared<CollectionSchema>("demo");
schema->set_max_doc_count_per_segment(1000);
schema->add_field(std::make_shared<FieldSchema>(
"id", DataType::INT64, false, std::make_shared<InvertIndexParams>(true)));
schema->add_field(std::make_shared<FieldSchema>(
"name", DataType::STRING, false,
std::make_shared<InvertIndexParams>(false)));
schema->add_field(
std::make_shared<FieldSchema>("weight", DataType::FLOAT, true));
schema->add_field(std::make_shared<FieldSchema>(
"dense", DataType::VECTOR_FP32, 128, false,
std::make_shared<HnswIndexParams>(MetricType::IP)));
schema->add_field(std::make_shared<FieldSchema>(
"sparse", DataType::SPARSE_VECTOR_FP32, 0, false,
std::make_shared<HnswIndexParams>(MetricType::IP)));
return schema;
}
CollectionSchema::Ptr TestHelper::CreateScalarSchema() {
auto schema = std::make_shared<CollectionSchema>("demo");
// scalar
schema->add_field(std::make_shared<FieldSchema>("int32", DataType::INT32));
schema->add_field(std::make_shared<FieldSchema>("string", DataType::STRING));
return schema;
}
// Helper function
CollectionSchema::Ptr TestHelper::CreateNormalSchema(
bool nullable, std::string name, IndexParams::Ptr scalar_index_params,
IndexParams::Ptr vector_index_params, uint64_t max_doc_count) {
auto schema = std::make_shared<CollectionSchema>(name);
schema->set_max_doc_count_per_segment(max_doc_count);
// scalar
schema->add_field(std::make_shared<FieldSchema>(
"int32", DataType::INT32, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"string", DataType::STRING, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"uint32", DataType::UINT32, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"bool", DataType::BOOL, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"float", DataType::FLOAT, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"double", DataType::DOUBLE, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"int64", DataType::INT64, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"uint64", DataType::UINT64, nullable, scalar_index_params));
// array
schema->add_field(std::make_shared<FieldSchema>(
"array_int32", DataType::ARRAY_INT32, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_string", DataType::ARRAY_STRING, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_uint32", DataType::ARRAY_UINT32, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_bool", DataType::ARRAY_BOOL, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_float", DataType::ARRAY_FLOAT, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_double", DataType::ARRAY_DOUBLE, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_int64", DataType::ARRAY_INT64, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"array_uint64", DataType::ARRAY_UINT64, nullable, scalar_index_params));
schema->add_field(std::make_shared<FieldSchema>(
"dense_fp32", DataType::VECTOR_FP32, 128, false,
vector_index_params ? vector_index_params
: 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_int8", DataType::VECTOR_INT8, 128, false,
std::make_shared<FlatIndexParams>(MetricType::IP)));
// IVF, HNSW_RABITQ and DISKANN do not support sparse vectors, always use
// Flat for sparse fields in those cases.
auto supports_sparse = [](const IndexParams::Ptr &params) {
auto type = params->type();
return type != IndexType::IVF && type != IndexType::HNSW_RABITQ &&
type != IndexType::DISKANN;
};
IndexParams::Ptr sparse_index_params;
if (vector_index_params && supports_sparse(vector_index_params)) {
sparse_index_params = vector_index_params->clone();
auto v = std::dynamic_pointer_cast<VectorIndexParams>(sparse_index_params);
// sparse always use IP
v->set_metric_type(MetricType::IP);
}
schema->add_field(std::make_shared<FieldSchema>(
"sparse_fp32", DataType::SPARSE_VECTOR_FP32, 128, false,
sparse_index_params ? sparse_index_params
: 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)));
return schema;
}
CollectionSchema::Ptr TestHelper::CreateSchemaWithScalarIndex(
bool nullable, bool enable_optimize, std::string name) {
return CreateNormalSchema(
nullable, name, std::make_shared<InvertIndexParams>(enable_optimize));
}
CollectionSchema::Ptr TestHelper::CreateSchemaWithVectorIndex(
bool nullable, std::string name, IndexParams::Ptr vector_index_params) {
return CreateNormalSchema(
nullable, name, nullptr,
vector_index_params ? vector_index_params
: std::make_shared<HnswIndexParams>(MetricType::IP));
}
CollectionSchema::Ptr TestHelper::CreateSchemaWithMaxDocCount(
uint64_t doc_count) {
return CreateNormalSchema(false, "demo", nullptr, nullptr, doc_count);
}
std::string TestHelper::MakePK(const uint64_t doc_id) {
return "pk_" + std::to_string(doc_id);
}
uint64_t TestHelper::ExtractDocId(const std::string &pk) {
return std::stoull(pk.substr(3));
}
Doc TestHelper::CreateDoc(const uint64_t doc_id, const CollectionSchema &schema,
std::string pk) {
Doc new_doc;
if (pk.empty()) {
pk = MakePK(doc_id);
}
new_doc.set_pk(pk);
for (auto &field : schema.fields()) {
switch (field->data_type()) {
case DataType::BINARY: {
std::string binary_str("binary_" + std::to_string(doc_id));
new_doc.set<std::string>(field->name(), binary_str);
break;
}
case DataType::BOOL:
new_doc.set<bool>(field->name(), doc_id % 10 == 0);
break;
case DataType::INT32:
new_doc.set<int32_t>(field->name(), (int32_t)doc_id);
break;
case DataType::INT64:
new_doc.set<int64_t>(field->name(), (int64_t)doc_id);
break;
case DataType::UINT32:
new_doc.set<uint32_t>(field->name(), (uint32_t)doc_id);
break;
case DataType::UINT64:
new_doc.set<uint64_t>(field->name(), (uint64_t)doc_id);
break;
case DataType::FLOAT:
new_doc.set<float>(field->name(), (float)doc_id);
break;
case DataType::DOUBLE:
new_doc.set<double>(field->name(), (double)doc_id);
break;
case DataType::STRING:
new_doc.set<std::string>(field->name(),
"value_" + std::to_string(doc_id));
break;
case DataType::ARRAY_BINARY: {
std::vector<std::string> bin_vec;
for (size_t i = 0; i < (doc_id % 10); i++) {
bin_vec.push_back("bin_" + std::to_string(i));
}
new_doc.set<std::vector<std::string>>(field->name(), bin_vec);
break;
}
case DataType::ARRAY_BOOL:
new_doc.set<std::vector<bool>>(field->name(),
std::vector<bool>(10, doc_id % 10 == 0));
break;
case DataType::ARRAY_INT32:
new_doc.set<std::vector<int32_t>>(
field->name(), std::vector<int32_t>(10, (int32_t)doc_id));
break;
case DataType::ARRAY_INT64:
new_doc.set<std::vector<int64_t>>(
field->name(), std::vector<int64_t>(10, (int64_t)doc_id));
break;
case DataType::ARRAY_UINT32:
new_doc.set<std::vector<uint32_t>>(
field->name(), std::vector<uint32_t>(10, (uint32_t)doc_id));
break;
case DataType::ARRAY_UINT64:
new_doc.set<std::vector<uint64_t>>(
field->name(), std::vector<uint64_t>(10, (uint64_t)doc_id));
break;
case DataType::ARRAY_FLOAT:
new_doc.set<std::vector<float>>(field->name(),
std::vector<float>(10, (float)doc_id));
break;
case DataType::ARRAY_DOUBLE:
new_doc.set<std::vector<double>>(
field->name(), std::vector<double>(10, (double)doc_id));
break;
case DataType::ARRAY_STRING:
new_doc.set<std::vector<std::string>>(
field->name(),
std::vector<std::string>(10, "value_" + std::to_string(doc_id)));
break;
case DataType::VECTOR_BINARY32:
new_doc.set<std::vector<uint32_t>>(
field->name(),
std::vector<uint32_t>(field->dimension(), uint32_t(doc_id + 0.1)));
break;
case DataType::VECTOR_BINARY64:
new_doc.set<std::vector<uint64_t>>(
field->name(),
std::vector<uint64_t>(field->dimension(), uint64_t(doc_id + 0.1)));
break;
case DataType::VECTOR_FP32:
new_doc.set<std::vector<float>>(
field->name(),
std::vector<float>(field->dimension(), float(doc_id + 0.1)));
break;
case DataType::VECTOR_FP64:
new_doc.set<std::vector<double>>(
field->name(),
std::vector<double>(field->dimension(), double(doc_id + 0.1)));
break;
case DataType::VECTOR_FP16:
new_doc.set<std::vector<float16_t>>(
field->name(), std::vector<float16_t>(
field->dimension(),
static_cast<float16_t>(float(doc_id + 0.1))));
break;
case DataType::VECTOR_INT8:
new_doc.set<std::vector<int8_t>>(
field->name(),
std::vector<int8_t>(field->dimension(), (int8_t)doc_id));
break;
case DataType::VECTOR_INT16:
new_doc.set<std::vector<int16_t>>(
field->name(),
std::vector<int16_t>(field->dimension(), (int16_t)doc_id));
break;
case DataType::SPARSE_VECTOR_FP16: {
std::vector<uint32_t> indices;
std::vector<float16_t> values;
for (uint32_t i = 0; i < 100; i++) {
indices.push_back(i);
values.push_back(float16_t(float(doc_id + 0.1)));
}
std::pair<std::vector<uint32_t>, std::vector<float16_t>>
sparse_float_vec;
sparse_float_vec.first = indices;
sparse_float_vec.second = values;
new_doc.set<std::pair<std::vector<uint32_t>, std::vector<float16_t>>>(
field->name(), sparse_float_vec);
break;
}
case DataType::SPARSE_VECTOR_FP32: {
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(doc_id + 0.1));
}
std::pair<std::vector<uint32_t>, std::vector<float>> sparse_float_vec;
sparse_float_vec.first = indices;
sparse_float_vec.second = values;
new_doc.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
field->name(), sparse_float_vec);
break;
}
default:
std::cout << "Unsupported data type: " << field->name() << std::endl;
throw std::runtime_error("Unsupported vector data type");
}
}
return new_doc;
}
Doc TestHelper::CreateDocNull(const uint64_t doc_id,
const CollectionSchema &schema, std::string pk) {
Doc new_doc;
if (pk.empty()) {
pk = "pk_" + std::to_string(doc_id);
}
new_doc.set_pk(pk);
for (auto &field : schema.fields()) {
switch (field->data_type()) {
case DataType::BINARY:
case DataType::BOOL:
case DataType::INT32:
case DataType::INT64:
case DataType::UINT32:
case DataType::UINT64:
case DataType::FLOAT:
case DataType::DOUBLE:
case DataType::STRING:
case DataType::ARRAY_BINARY:
case DataType::ARRAY_BOOL:
case DataType::ARRAY_INT32:
case DataType::ARRAY_INT64:
case DataType::ARRAY_UINT32:
case DataType::ARRAY_UINT64:
case DataType::ARRAY_FLOAT:
case DataType::ARRAY_DOUBLE:
case DataType::ARRAY_STRING:
break;
case DataType::VECTOR_FP32:
new_doc.set<std::vector<float>>(
field->name(),
std::vector<float>(field->dimension(), float(doc_id + 0.1)));
break;
case DataType::VECTOR_FP64:
new_doc.set<std::vector<double>>(
field->name(),
std::vector<double>(field->dimension(), double(doc_id + 0.1)));
break;
case DataType::VECTOR_FP16:
new_doc.set<std::vector<float16_t>>(
field->name(), std::vector<float16_t>(
field->dimension(),
static_cast<float16_t>(float(doc_id + 0.1))));
break;
case DataType::VECTOR_INT8:
new_doc.set<std::vector<int8_t>>(
field->name(),
std::vector<int8_t>(field->dimension(), (int8_t)doc_id));
break;
case DataType::VECTOR_INT16:
new_doc.set<std::vector<int16_t>>(
field->name(),
std::vector<int16_t>(field->dimension(), (int16_t)doc_id));
break;
case DataType::SPARSE_VECTOR_FP16: {
std::vector<uint32_t> indices;
std::vector<float16_t> values;
for (uint32_t i = 0; i < 100; i++) {
indices.push_back(i);
values.push_back(float16_t(float(doc_id + 0.1)));
}
std::pair<std::vector<uint32_t>, std::vector<float16_t>>
sparse_float_vec;
sparse_float_vec.first = indices;
sparse_float_vec.second = values;
new_doc.set<std::pair<std::vector<uint32_t>, std::vector<float16_t>>>(
field->name(), sparse_float_vec);
break;
}
case DataType::SPARSE_VECTOR_FP32: {
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(doc_id + 0.1));
}
std::pair<std::vector<uint32_t>, std::vector<float>> sparse_float_vec;
sparse_float_vec.first = indices;
sparse_float_vec.second = values;
new_doc.set<std::pair<std::vector<uint32_t>, std::vector<float>>>(
field->name(), sparse_float_vec);
break;
}
default:
throw std::runtime_error("Unsupported vector data type");
}
}
return new_doc;
}
Status TestHelper::SegmentInsertDoc(const Segment::Ptr &segment,
const CollectionSchema &schema,
const uint64_t start_doc_id,
const uint64_t end_doc_id, bool nullable,
bool upsert, bool batch) {
for (auto doc_id = start_doc_id; doc_id < end_doc_id; doc_id++) {
if (segment) {
Doc new_doc;
if (nullable) {
new_doc = CreateDocNull(doc_id, schema);
} else {
new_doc = CreateDoc(doc_id, schema);
}
Status s;
if (upsert) {
s = segment->Upsert(new_doc);
CHECK_RETURN_STATUS(s);
} else {
s = segment->Insert(new_doc);
CHECK_RETURN_STATUS(s);
}
}
}
return Status::OK();
}
Status TestHelper::CollectionInsertDoc(const Collection::Ptr &collection,
const uint64_t start_doc_id,
const uint64_t end_doc_id, bool nullable,
bool upsert, bool batch) {
if (!collection) {
return Status::InvalidArgument("collection is nullptr");
}
auto schema = collection->Schema().value();
auto make_doc = [&](uint64_t doc_id) -> Doc {
return nullable ? CreateDocNull(doc_id, schema) : CreateDoc(doc_id, schema);
};
auto exec_write = [&](std::vector<Doc> &docs) -> Status {
Result<WriteResults> result =
upsert ? collection->Upsert(docs) : collection->Insert(docs);
if (!result.has_value()) {
LOG_ERROR("Failed to %s docs (count=%zu), error: %s.",
upsert ? "upsert" : "insert", docs.size(),
result.error().message().c_str());
return result.error();
}
const auto &write_results = result.value();
if (write_results.empty()) {
return Status::InternalError("WriteResults is unexpectedly empty");
}
for (const auto &wr : write_results) {
if (!wr.ok()) {
return wr;
}
}
return Status::OK();
};
if (batch) {
std::vector<Doc> docs;
docs.reserve(end_doc_id - start_doc_id);
for (uint64_t doc_id = start_doc_id; doc_id < end_doc_id; ++doc_id) {
docs.emplace_back(make_doc(doc_id));
}
return exec_write(docs);
} else {
std::vector<Doc> single_doc;
single_doc.reserve(1); // 可选优化
for (uint64_t doc_id = start_doc_id; doc_id < end_doc_id; ++doc_id) {
single_doc.clear();
single_doc.push_back(make_doc(doc_id));
Status s = exec_write(single_doc);
if (!s.ok()) {
LOG_ERROR("Failed at doc_id=%" PRIu64 ", doc: %s", doc_id,
single_doc[0].to_detail_string().c_str());
return s;
}
}
}
return Status::OK();
}
Status TestHelper::CollectionUpsertDoc(const Collection::Ptr &collection,
const uint64_t start_doc_id,
const uint64_t end_doc_id, bool nullable,
bool batch) {
return CollectionInsertDoc(collection, start_doc_id, end_doc_id, nullable,
true, batch);
}
Segment::Ptr TestHelper::CreateSegmentWithDoc(
const std::string &col_path, const CollectionSchema &schema,
SegmentID segment_id, uint64_t min_doc_id, const IDMap::Ptr &id_map,
const DeleteStore::Ptr &delete_store,
const VersionManager::Ptr &version_manager, const SegmentOptions &options,
uint64_t start_doc_id, uint32_t doc_count, bool nullable, bool upsert) {
auto result =
Segment::CreateAndOpen(col_path, schema, segment_id, min_doc_id, id_map,
delete_store, version_manager, options);
if (!result.has_value()) {
return nullptr;
}
auto segment = std::move(result).value();
auto s = SegmentInsertDoc(segment, schema, start_doc_id,
start_doc_id + doc_count, nullable, upsert);
if (!s.ok()) {
LOG_ERROR("Failed to insert doc, err: %s", s.message().c_str());
return nullptr;
}
return segment;
}
Collection::Ptr TestHelper::CreateCollectionWithDoc(
const std::string &path, const CollectionSchema &schema,
const CollectionOptions &options, uint64_t start_doc_id, uint32_t doc_count,
bool nullable, bool upsert) {
auto result = Collection::CreateAndOpen(path, schema, options);
if (!result.has_value()) {
LOG_ERROR("Failed to create collection, err: %s",
result.error().message().c_str());
return nullptr;
}
auto collection = std::move(result).value();
auto s = CollectionInsertDoc(collection, start_doc_id,
start_doc_id + doc_count, nullable, upsert);
if (!s.ok()) {
LOG_ERROR("Failed to insert doc, err: %s", s.message().c_str());
return nullptr;
}
return collection;
}
arrow::Status TestHelper::WriteTestFile(const std::string &filepath,
FileFormat format,
uint32_t start_doc_id,
uint32_t end_doc_id,
uint32_t batch_size) {
// Define schema with additional list types
auto schema = arrow::schema(
{arrow::field(GLOBAL_DOC_ID, arrow::uint64()),
arrow::field(USER_ID, arrow::utf8()), arrow::field("id", arrow::int32()),
arrow::field("name", arrow::utf8()),
arrow::field("score", arrow::float64()),
arrow::field("list_binary", arrow::list(arrow::binary())),
arrow::field("list_utf8", arrow::list(arrow::utf8())),
arrow::field("list_boolean", arrow::list(arrow::boolean())),
arrow::field("list_int32", arrow::list(arrow::int32())),
arrow::field("list_int64", arrow::list(arrow::int64())),
arrow::field("list_uint32", arrow::list(arrow::uint32())),
arrow::field("list_uint64", arrow::list(arrow::uint64())),
arrow::field("list_float32", arrow::list(arrow::float32())),
arrow::field("list_float64", arrow::list(arrow::float64()))});
// Create builders
auto g_doc_id_builder = std::make_shared<arrow::UInt64Builder>();
auto uid_builder = std::make_shared<arrow::StringBuilder>();
auto id_builder = std::make_shared<arrow::Int32Builder>();
auto name_builder = std::make_shared<arrow::StringBuilder>();
auto score_builder = std::make_shared<arrow::DoubleBuilder>();
// Array field builders
auto list_binary_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::BinaryBuilder>());
auto list_utf8_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::StringBuilder>());
auto list_boolean_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::BooleanBuilder>());
auto list_int32_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::Int32Builder>());
auto list_int64_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::Int64Builder>());
auto list_uint32_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::UInt32Builder>());
auto list_uint64_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::UInt64Builder>());
auto list_float32_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::FloatBuilder>());
auto list_float64_builder = std::make_shared<arrow::ListBuilder>(
arrow::default_memory_pool(), std::make_shared<arrow::DoubleBuilder>());
// Cast child builders for easier access
auto binary_builder =
static_cast<arrow::BinaryBuilder *>(list_binary_builder->value_builder());
auto utf8_child_builder =
static_cast<arrow::StringBuilder *>(list_utf8_builder->value_builder());
auto boolean_child_builder = static_cast<arrow::BooleanBuilder *>(
list_boolean_builder->value_builder());
auto int32_child_builder =
static_cast<arrow::Int32Builder *>(list_int32_builder->value_builder());
auto int64_child_builder =
static_cast<arrow::Int64Builder *>(list_int64_builder->value_builder());
auto uint32_child_builder =
static_cast<arrow::UInt32Builder *>(list_uint32_builder->value_builder());
auto uint64_child_builder =
static_cast<arrow::UInt64Builder *>(list_uint64_builder->value_builder());
auto float32_child_builder =
static_cast<arrow::FloatBuilder *>(list_float32_builder->value_builder());
auto float64_child_builder = static_cast<arrow::DoubleBuilder *>(
list_float64_builder->value_builder());
// Fill data
for (uint32_t i = start_doc_id; i < end_doc_id; ++i) {
ARROW_RETURN_NOT_OK(g_doc_id_builder->Append(i + 1));
ARROW_RETURN_NOT_OK(uid_builder->Append("user_" + std::to_string(i + 1)));
ARROW_RETURN_NOT_OK(id_builder->Append(i + 1));
ARROW_RETURN_NOT_OK(name_builder->Append("Name" + std::to_string(i)));
ARROW_RETURN_NOT_OK(score_builder->Append(80.0 + i));
const int dim = 128;
// Append list_binary data
ARROW_RETURN_NOT_OK(list_binary_builder->Append());
for (int j = 0; j < dim; ++j) {
std::string binary_data =
"binary_" + std::to_string(i) + "_" + std::to_string(j);
ARROW_RETURN_NOT_OK(binary_builder->Append(binary_data));
}
// Append list_utf8 data
ARROW_RETURN_NOT_OK(list_utf8_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(utf8_child_builder->Append(
"string_" + std::to_string(i) + "_" + std::to_string(j)));
}
// Append list_boolean data
ARROW_RETURN_NOT_OK(list_boolean_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(boolean_child_builder->Append((i + j) % 2 == 0));
}
// Append list_int32 data
ARROW_RETURN_NOT_OK(list_int32_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(int32_child_builder->Append(i * 10 + j));
}
// Append list_int64 data
ARROW_RETURN_NOT_OK(list_int64_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(
int64_child_builder->Append(static_cast<int64_t>(i) * 100 + j));
}
// Append list_uint32 data
ARROW_RETURN_NOT_OK(list_uint32_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(
uint32_child_builder->Append(static_cast<uint32_t>(i) * 10 + j));
}
// Append list_uint64 data
ARROW_RETURN_NOT_OK(list_uint64_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(
uint64_child_builder->Append(static_cast<uint64_t>(i) * 100 + j));
}
// Append list_float32 data
ARROW_RETURN_NOT_OK(list_float32_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(
float32_child_builder->Append(static_cast<float>(i) + j * 0.1f));
}
// Append list_float64 data
ARROW_RETURN_NOT_OK(list_float64_builder->Append());
for (int j = 0; j < dim; ++j) {
ARROW_RETURN_NOT_OK(
float64_child_builder->Append(static_cast<double>(i) + j * 0.01));
}
}
// Construct arrays
std::shared_ptr<arrow::Array> g_doc_id_array, uid_array, id_array, name_array,
score_array, list_binary_array, list_utf8_array, list_boolean_array,
list_int32_array, list_int64_array, list_uint32_array, list_uint64_array,
list_float32_array, list_float64_array;
ARROW_RETURN_NOT_OK(g_doc_id_builder->Finish(&g_doc_id_array));
ARROW_RETURN_NOT_OK(uid_builder->Finish(&uid_array));
ARROW_RETURN_NOT_OK(id_builder->Finish(&id_array));
ARROW_RETURN_NOT_OK(name_builder->Finish(&name_array));
ARROW_RETURN_NOT_OK(score_builder->Finish(&score_array));
ARROW_RETURN_NOT_OK(list_binary_builder->Finish(&list_binary_array));
ARROW_RETURN_NOT_OK(list_utf8_builder->Finish(&list_utf8_array));
ARROW_RETURN_NOT_OK(list_boolean_builder->Finish(&list_boolean_array));
ARROW_RETURN_NOT_OK(list_int32_builder->Finish(&list_int32_array));
ARROW_RETURN_NOT_OK(list_int64_builder->Finish(&list_int64_array));
ARROW_RETURN_NOT_OK(list_uint32_builder->Finish(&list_uint32_array));
ARROW_RETURN_NOT_OK(list_uint64_builder->Finish(&list_uint64_array));
ARROW_RETURN_NOT_OK(list_float32_builder->Finish(&list_float32_array));
ARROW_RETURN_NOT_OK(list_float64_builder->Finish(&list_float64_array));
// Set rows per batch
std::vector<std::shared_ptr<arrow::RecordBatch>> batches;
// Split data into multiple batches
auto doc_count = (int)(end_doc_id - start_doc_id);
for (int start = 0; start < doc_count; start += batch_size) {
int current_batch_size = std::min((int)batch_size, doc_count - start);
auto g_doc_id_slice = g_doc_id_array->Slice(start, current_batch_size);
auto uid_slice = uid_array->Slice(start, current_batch_size);
auto id_slice = id_array->Slice(start, current_batch_size);
auto name_slice = name_array->Slice(start, current_batch_size);
auto score_slice = score_array->Slice(start, current_batch_size);
auto list_binary_slice =
list_binary_array->Slice(start, current_batch_size);
auto list_utf8_slice = list_utf8_array->Slice(start, current_batch_size);
auto list_boolean_slice =
list_boolean_array->Slice(start, current_batch_size);
auto list_int32_slice = list_int32_array->Slice(start, current_batch_size);
auto list_int64_slice = list_int64_array->Slice(start, current_batch_size);
auto list_uint32_slice =
list_uint32_array->Slice(start, current_batch_size);
auto list_uint64_slice =
list_uint64_array->Slice(start, current_batch_size);
auto list_float32_slice =
list_float32_array->Slice(start, current_batch_size);
auto list_float64_slice =
list_float64_array->Slice(start, current_batch_size);
auto batch = arrow::RecordBatch::Make(
schema, current_batch_size,
{g_doc_id_slice, uid_slice, id_slice, name_slice, score_slice,
list_binary_slice, list_utf8_slice, list_boolean_slice,
list_int32_slice, list_int64_slice, list_uint32_slice,
list_uint64_slice, list_float32_slice, list_float64_slice});
batches.push_back(batch);
}
// Open output stream
ARROW_ASSIGN_OR_RAISE(auto out, arrow::io::FileOutputStream::Open(filepath));
if (format == FileFormat::PARQUET) {
// Parquet write logic - create table with multiple record batches
auto table = arrow::Table::Make(
schema, {g_doc_id_array, uid_array, id_array, name_array, score_array,
list_binary_array, list_utf8_array, list_boolean_array,
list_int32_array, list_int64_array, list_uint32_array,
list_uint64_array, list_float32_array, list_float64_array});
parquet::WriterProperties::Builder builder;
builder.data_pagesize(1024);
// 3 rows per row group
builder.max_row_group_length(batch_size);
auto props = builder.build();
auto status = parquet::arrow::WriteTable(
*table, arrow::default_memory_pool(), out, batch_size, props);
if (!status.ok()) {
std::cerr << "Write failed: " << status.ToString() << std::endl;
return status;
}
std::cout << "Wrote test Parquet file with multiple row groups: "
<< filepath << std::endl;
} else if (format == FileFormat::IPC) {
// IPC write logic - write multiple record batches
auto writer_result = arrow::ipc::MakeFileWriter(out, schema);
ARROW_RETURN_NOT_OK(writer_result.status());
auto writer = std::move(writer_result).ValueOrDie();
// Write multiple batches
for (const auto &batch : batches) {
ARROW_RETURN_NOT_OK(writer->WriteRecordBatch(*batch));
}
ARROW_RETURN_NOT_OK(writer->Close());
std::cout << "Wrote test IPC file with " << batches.size()
<< " batches: " << filepath << std::endl;
}
ARROW_RETURN_NOT_OK(out->Close());
return arrow::Status::OK();
}