blob: 7fc63e6fcd0b78bd13a3b08904d9aa8ae4efccf9 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 <CLucene.h>
#include <CLucene/config/repl_wchar.h>
#include <CLucene/index/IndexReader.h>
#include <gen_cpp/olap_file.pb.h>
#include <gtest/gtest-message.h>
#include <gtest/gtest-test-part.h>
#include <gtest/gtest.h>
#include <string.h>
#include <map>
#include <memory>
#include <string>
#include "core/block/block.h"
#include "core/column/column_array.h"
#include "core/column/column_nullable.h"
#include "core/column/column_string.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_factory.hpp"
#include "core/data_type/data_type_number.h"
#include "core/field.h"
#include "core/pod_array_fwd.h"
#include "core/types.h"
#include "gtest/gtest_pred_impl.h"
#include "io/fs/file_writer.h"
#include "io/fs/local_file_system.h"
#include "runtime/exec_env.h"
#include "storage/index/index_file_reader.h"
#include "storage/index/index_file_writer.h"
#include "storage/index/inverted/inverted_index_compound_reader.h"
#include "storage/index/inverted/inverted_index_desc.h"
#include "storage/index/inverted/inverted_index_fs_directory.h"
#include "storage/index/inverted/inverted_index_writer.h"
#include "storage/index/zone_map/zone_map_index.h"
#include "storage/iterator/olap_data_convertor.h"
#include "storage/tablet/tablet_schema.h"
#include "storage/tablet/tablet_schema_helper.h"
#include "storage/types.h"
#include "util/faststring.h"
#include "util/slice.h"
using namespace lucene::index;
using doris::segment_v2::IndexFileWriter;
namespace doris::segment_v2 {
class InvertedIndexArrayTest : public testing::Test {
using ExpectedDocMap = std::map<std::string, std::vector<int>>;
public:
const std::string kTestDir = "./ut_dir/inverted_index_array_test";
void check_terms_stats(std::string index_prefix, ExpectedDocMap* expected,
std::vector<int> expected_null_bitmap = {},
InvertedIndexStorageFormatPB format = InvertedIndexStorageFormatPB::V1,
const TabletIndex* index_meta = nullptr) {
std::string file_str;
if (format == InvertedIndexStorageFormatPB::V1) {
file_str = InvertedIndexDescriptor::get_index_file_path_v1(index_prefix,
index_meta->index_id(), "");
} else if (format == InvertedIndexStorageFormatPB::V2) {
file_str = InvertedIndexDescriptor::get_index_file_path_v2(index_prefix);
}
std::unique_ptr<IndexFileReader> reader = std::make_unique<IndexFileReader>(
io::global_local_filesystem(), index_prefix, format);
auto st = reader->init();
EXPECT_EQ(st, Status::OK());
auto result = reader->open(index_meta);
EXPECT_TRUE(result.has_value()) << "Failed to open compound reader" << result.error();
auto compound_reader = std::move(result.value());
try {
CLuceneError err;
CL_NS(store)::IndexInput* index_input = nullptr;
auto ok = DorisFSDirectory::FSIndexInput::open(
io::global_local_filesystem(), file_str.c_str(), index_input, err, 4096);
if (!ok) {
throw err;
}
std::shared_ptr<roaring::Roaring> null_bitmap = std::make_shared<roaring::Roaring>();
const char* null_bitmap_file_name =
InvertedIndexDescriptor::get_temporary_null_bitmap_file_name();
if (compound_reader->fileExists(null_bitmap_file_name)) {
std::unique_ptr<lucene::store::IndexInput> null_bitmap_in;
assert(compound_reader->openInput(null_bitmap_file_name, null_bitmap_in, err,
4096));
size_t null_bitmap_size = null_bitmap_in->length();
doris::faststring buf;
buf.resize(null_bitmap_size);
null_bitmap_in->readBytes(reinterpret_cast<uint8_t*>(buf.data()), null_bitmap_size);
*null_bitmap = roaring::Roaring::read(reinterpret_cast<char*>(buf.data()), false);
EXPECT_TRUE(expected_null_bitmap.size() == null_bitmap->cardinality());
for (int i : expected_null_bitmap) {
EXPECT_TRUE(null_bitmap->contains(i));
}
}
index_input->close();
_CLLDELETE(index_input);
} catch (const CLuceneError& e) {
EXPECT_TRUE(false) << "CLuceneError: " << e.what();
}
std::cout << "Term statistics for " << file_str << std::endl;
std::cout << "==================================" << std::endl;
lucene::store::Directory* dir = compound_reader.get();
lucene::index::IndexReader* r = lucene::index::IndexReader::open(dir);
printf("Max Docs: %d\n", r->maxDoc());
printf("Num Docs: %d\n", r->numDocs());
TermEnum* te = r->terms();
int32_t nterms;
for (nterms = 0; te->next(); nterms++) {
/* empty */
std::string token =
lucene_wcstoutf8string(te->term(false)->text(), te->term(false)->textLength());
printf("Term: %s ", token.c_str());
if (expected) {
auto it = expected->find(token);
if (it != expected->end()) {
TermDocs* td = r->termDocs(te->term(false));
std::vector<int> actual_docs;
while (td->next()) {
actual_docs.push_back(td->doc());
}
td->close();
_CLLDELETE(td);
EXPECT_EQ(actual_docs, it->second) << "Term: " << token;
}
}
printf("Freq: %d\n", te->docFreq());
}
printf("Term count: %d\n\n", nterms);
if (expected) {
ASSERT_EQ(nterms, expected->size());
}
te->close();
_CLLDELETE(te);
r->close();
_CLLDELETE(r);
compound_reader->close();
}
void SetUp() override {
auto st = io::global_local_filesystem()->delete_directory(kTestDir);
ASSERT_TRUE(st.ok()) << st;
st = io::global_local_filesystem()->create_directory(kTestDir);
ASSERT_TRUE(st.ok()) << st;
std::vector<StorePath> paths;
paths.emplace_back(kTestDir, 1024);
auto tmp_file_dirs = std::make_unique<segment_v2::TmpFileDirs>(paths);
st = tmp_file_dirs->init();
if (!st.ok()) {
std::cout << "init tmp file dirs error:" << st.to_string() << std::endl;
return;
}
ExecEnv::GetInstance()->set_tmp_file_dir(std::move(tmp_file_dirs));
}
void TearDown() override {
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(kTestDir).ok());
}
// create a TabletSchema with an array column (and a normal int column as key)
TabletSchemaSPtr create_schema_with_array(KeysType keys_type = DUP_KEYS) {
TabletSchemaSPtr tablet_schema = std::make_shared<TabletSchema>();
TabletSchemaPB tablet_schema_pb;
tablet_schema_pb.set_keys_type(keys_type);
tablet_schema->init_from_pb(tablet_schema_pb);
TabletColumn array;
array.set_name("arr1");
array.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
array.set_length(0);
array.set_index_length(0);
array.set_is_nullable(false);
array.set_is_bf_column(false);
TabletColumn child;
child.set_name("arr_sub_string");
child.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
child.set_length(INT_MAX);
array.add_sub_column(child);
tablet_schema->append_column(array);
return tablet_schema;
}
void test_non_null_string(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26033;
std::string index_path =
InvertedIndexDescriptor::get_index_file_path_v1(index_path_prefix, index_id, "");
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr1");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.index_type();
idx_meta.init_from_pb(*index_meta_pb.get());
auto index_file_writer =
std::make_unique<IndexFileWriter>(fs, index_path_prefix, std::string {rowset_id},
seg_id, InvertedIndexStorageFormatPB::V1);
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
// Construct two arrays: The first row is ["amory","doris"], and the second row is ["amory", "commiter"]
Array a1, a2;
a1.push_back(Field::create_field<TYPE_STRING>("amory"));
a1.push_back(Field::create_field<TYPE_STRING>("doris"));
a2.push_back(Field::create_field<TYPE_STRING>("amory"));
a2.push_back(Field::create_field<TYPE_STRING>("commiter"));
// Construct array type: DataTypeArray(DataTypeString)
DataTypePtr s1 = std::make_shared<DataTypeString>();
DataTypePtr array_type = std::make_shared<DataTypeArray>(s1);
MutableColumnPtr col = array_type->create_column();
col->insert(Field::create_field<TYPE_ARRAY>(a1));
col->insert(Field::create_field<TYPE_ARRAY>(a2));
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, array_type, "arr1");
// Put the array column into the Block (assuming only this column)
Block block;
block.insert(type_and_name);
// block.rows() should be 2
// Use OlapBlockDataConvertor to convert
// Note: Here we need a TabletSchema object, in this example we construct a simple schema,
// Assuming that the 0th column in the schema is our array column (the actual UT has the corresponding TabletColumn)
TabletSchemaSPtr tablet_schema = create_schema_with_array();
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
// The conversion result is actually an array of 4 pointers:
// [0]: Total number of elements (elem_cnt)
// [1]: Offsets array pointer
// [2]: Nested item data pointer
// [3]: Nested nullmap pointer
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
// Get the length of the subfield, used for inverted index writing
auto field_size = field_type_size(field->get_sub_column(0).type());
// Call the inverted index writing interface, passing in item_data, item_nullmap, offsets_ptr, and the number of rows (the number of array rows in the Block)
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, block.rows());
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, block.rows());
EXPECT_EQ(st, Status::OK());
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
ExpectedDocMap expected = {{"amory", {0, 1}}, {"doris", {0}}, {"commiter", {1}}};
check_terms_stats(index_path_prefix, &expected, {}, InvertedIndexStorageFormatPB::V1,
&idx_meta);
}
void test_string(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26033;
std::string index_path =
InvertedIndexDescriptor::get_index_file_path_v1(index_path_prefix, index_id, "");
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr1");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.index_type();
idx_meta.init_from_pb(*index_meta_pb.get());
auto index_file_writer =
std::make_unique<IndexFileWriter>(fs, index_path_prefix, std::string {rowset_id},
seg_id, InvertedIndexStorageFormatPB::V1);
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
// Construct two arrays: The first row is ["amory","doris"], and the second row is [NULL, "amory", "commiter"]
Array a1, a2;
a1.push_back(Field::create_field<TYPE_STRING>("amory"));
a1.push_back(Field::create_field<TYPE_STRING>("doris"));
a2.push_back(Field());
a2.push_back(Field::create_field<TYPE_STRING>("amory"));
a2.push_back(Field::create_field<TYPE_STRING>("commiter"));
// Construct array type: DataTypeArray(DataTypeNullable(DataTypeString))
DataTypePtr s1 = std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(s1);
MutableColumnPtr col = array_type->create_column();
col->insert(Field::create_field<TYPE_ARRAY>(a1));
col->insert(Field::create_field<TYPE_ARRAY>(a2));
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, array_type, "arr1");
// Put the array column into the Block (assuming only this column)
Block block;
block.insert(type_and_name);
// block.rows() should be 2
// Use OlapBlockDataConvertor to convert
// Note: Here we need a TabletSchema object, in this example we construct a simple schema,
// Assuming that the 0th column in the schema is our array column (the actual UT has the corresponding TabletColumn)
TabletSchemaSPtr tablet_schema = create_schema_with_array();
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
// The conversion result is actually an array of 4 pointers:
// [0]: Total number of elements (elem_cnt)
// [1]: Offsets array pointer
// [2]: Nested item data pointer
// [3]: Nested nullmap pointer
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
// Get the length of the subfield, used for inverted index writing
auto field_size = field_type_size(field->get_sub_column(0).type());
// Call the inverted index writing interface, passing in item_data, item_nullmap, offsets_ptr, and the number of rows (the number of array rows in the Block)
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, block.rows());
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, block.rows());
EXPECT_EQ(st, Status::OK());
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
ExpectedDocMap expected = {{"amory", {0, 1}}, {"doris", {0}}, {"commiter", {1}}};
check_terms_stats(index_path_prefix, &expected, {}, InvertedIndexStorageFormatPB::V1,
&idx_meta);
}
void test_null_write_v2(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26033;
std::string index_path = InvertedIndexDescriptor::get_index_file_path_v2(index_path_prefix);
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr1");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.index_type();
idx_meta.init_from_pb(*index_meta_pb.get());
io::FileWriterPtr file_writer;
io::FileWriterOptions opts;
Status sts = fs->create_file(index_path, &file_writer, &opts);
ASSERT_TRUE(sts.ok());
auto index_file_writer = std::make_unique<IndexFileWriter>(
fs, index_path_prefix, std::string {rowset_id}, seg_id,
InvertedIndexStorageFormatPB::V2, std::move(file_writer));
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
// Simulate outer null cases: 5 rows, outer null map = {1, 0, 0, 1, 0}, i.e., rows 0 and 3 are null
std::vector<uint8_t> outer_null_map = {1, 0, 0, 1, 0};
// Construct inner array type: DataTypeArray(DataTypeNullable(DataTypeString))
DataTypePtr inner_string_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_string_type);
// To support outer array null values, wrap it in a Nullable type
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
// Construct 5 rows of data:
// Row 0: null
// Row 1: a2 = [Null, "test"]
// Row 2: a3 = ["mixed", Null, "data"]
// Row 3: null
// Row 4: a5 = ["non-null"]
MutableColumnPtr col = final_type->create_column();
// Row 0: insert null
col->insert(Field());
// Row 1: insert a2
Array a2;
a2.push_back(Field());
a2.push_back(Field::create_field<TYPE_STRING>("test"));
col->insert(Field::create_field<TYPE_ARRAY>(a2));
// Row 2: insert a3
Array a3;
a3.push_back(Field::create_field<TYPE_STRING>("mixed"));
a3.push_back(Field());
a3.push_back(Field::create_field<TYPE_STRING>("data"));
col->insert(Field::create_field<TYPE_ARRAY>(a3));
// Row 3: insert null
col->insert(Field());
// Row 4: insert a5
Array a5;
a5.push_back(Field::create_field<TYPE_STRING>("non-null"));
col->insert(Field::create_field<TYPE_ARRAY>(a5));
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr1");
// Construct Block, containing only the array column, with 5 rows
Block block;
block.insert(type_and_name);
// Construct TabletSchema (containing the array column) - reference the existing helper function
TabletSchemaSPtr tablet_schema = create_schema_with_array();
// In this schema, assume the 0th column is the key, and the arr1 column is the non-key column with index 1
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
// Convert array column data
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
// OlapColumnDataConvertorArray conversion result is a 4-tuple:
// [0]: element total count (elem_cnt, not used directly)
// [1]: offsets array pointer
// [2]: nested item data conversion result pointer
// [3]: nested nullmap pointer
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
// Call the inverted index writing interface, passing in the converted nested data, nullmap, and offsets
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, block.rows());
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, block.rows());
EXPECT_EQ(st, Status::OK());
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
// Expected inverted index result: only index non-null elements
// Row 1: non-null in a2 is "test"
// Row 2: non-null in a3 is "mixed" and "data"
// Row 4: non-null in a5 is "non-null"
ExpectedDocMap expected = {{"test", {1}}, {"mixed", {2}}, {"data", {2}}, {"non-null", {4}}};
std::vector<int> expected_null_bitmap = {0, 3};
check_terms_stats(index_path_prefix, &expected, expected_null_bitmap,
InvertedIndexStorageFormatPB::V2, &idx_meta);
}
void test_null_write(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26033;
std::string index_path =
InvertedIndexDescriptor::get_index_file_path_v1(index_path_prefix, index_id, "");
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr1");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.index_type();
idx_meta.init_from_pb(*index_meta_pb.get());
auto index_file_writer =
std::make_unique<IndexFileWriter>(fs, index_path_prefix, std::string {rowset_id},
seg_id, InvertedIndexStorageFormatPB::V1);
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
// Simulate outer null cases: 5 rows, outer null map = {1, 0, 0, 1, 0}, i.e., rows 0 and 3 are null
std::vector<uint8_t> outer_null_map = {1, 0, 0, 1, 0};
// Construct inner array type: DataTypeArray(DataTypeNullable(DataTypeString))
DataTypePtr inner_string_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_string_type);
// To support outer array null values, wrap it in a Nullable type
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
// Construct 5 rows of data:
// Row 0: null
// Row 1: a2 = [Null, "test"]
// Row 2: a3 = ["mixed", Null, "data"]
// Row 3: null
// Row 4: a5 = ["non-null"]
MutableColumnPtr col = final_type->create_column();
// Row 0: insert null
col->insert(Field());
// Row 1: insert a2
Array a2;
a2.push_back(Field());
a2.push_back(Field::create_field<TYPE_STRING>("test"));
col->insert(Field::create_field<TYPE_ARRAY>(a2));
// Row 2: insert a3
Array a3;
a3.push_back(Field::create_field<TYPE_STRING>("mixed"));
a3.push_back(Field());
a3.push_back(Field::create_field<TYPE_STRING>("data"));
col->insert(Field::create_field<TYPE_ARRAY>(a3));
// Row 3: insert null
col->insert(Field());
// Row 4: insert a5
Array a5;
a5.push_back(Field::create_field<TYPE_STRING>("non-null"));
col->insert(Field::create_field<TYPE_ARRAY>(a5));
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr1");
// Construct Block, containing only the array column, with 5 rows
Block block;
block.insert(type_and_name);
// Construct TabletSchema (containing the array column) - reference the existing helper function
TabletSchemaSPtr tablet_schema = create_schema_with_array();
// In this schema, assume the 0th column is the key, and the arr1 column is the non-key column with index 1
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
// Convert array column data
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
// OlapColumnDataConvertorArray conversion result is a 4-tuple:
// [0]: element total count (elem_cnt, not used directly)
// [1]: offsets array pointer
// [2]: nested item data conversion result pointer
// [3]: nested nullmap pointer
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
// Call the inverted index writing interface, passing in the converted nested data, nullmap, and offsets
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, block.rows());
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, block.rows());
EXPECT_EQ(st, Status::OK());
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
// Expected inverted index result: only index non-null elements
// Row 1: non-null in a2 is "test"
// Row 2: non-null in a3 is "mixed" and "data"
// Row 4: non-null in a5 is "non-null"
ExpectedDocMap expected = {{"test", {1}}, {"mixed", {2}}, {"data", {2}}, {"non-null", {4}}};
std::vector<int> expected_null_bitmap = {0, 3};
check_terms_stats(index_path_prefix, &expected, expected_null_bitmap,
InvertedIndexStorageFormatPB::V1, &idx_meta);
}
void test_multi_block_write(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26033;
std::string index_path =
InvertedIndexDescriptor::get_index_file_path_v1(index_path_prefix, index_id, "");
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr1");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.init_from_pb(*index_meta_pb.get());
auto index_file_writer = std::make_unique<IndexFileWriter>(
fs, index_path_prefix, "multi_block", 0, InvertedIndexStorageFormatPB::V1);
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
ExpectedDocMap merged_expected;
// --- Block 1 ---
{
const int row_num = 4;
// construct data type: Nullable( Array( Nullable(String) ) )
DataTypePtr inner_string =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_string);
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
// construct MutableColumn
MutableColumnPtr col = final_type->create_column();
// simulate outer null: row0 and row3 are null, the rest are non-null
col->insert(Field()); // row0: null
{
// row1: non-null, array with 1 element: "block1_data1"
Array arr;
arr.push_back(Field::create_field<TYPE_STRING>("block1_data1"));
col->insert(Field::create_field<TYPE_ARRAY>(arr));
}
{
// row2: non-null, array with 1 element: "block1_data2"
Array arr;
arr.push_back(Field::create_field<TYPE_STRING>("block1_data2"));
col->insert(Field::create_field<TYPE_ARRAY>(arr));
}
col->insert(Field()); // row3: null
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr1");
// construct Block (containing only the arr1 column)
Block block;
block.insert(type_and_name);
// use TabletSchema containing the array column (arr1 is the non-key column with index 1 in the schema)
TabletSchemaSPtr tablet_schema = create_schema_with_array();
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
// convert the arr1 column in the block
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
// the conversion result is a 4-tuple: [0]: element count, [1]: offsets pointer, [2]: item data, [3]: item nullmap
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, row_num);
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, row_num);
EXPECT_EQ(st, Status::OK());
// for Block1, the expected non-null behavior is row1 and row2
ExpectedDocMap expected = {{"block1_data1", {1}}, {"block1_data2", {2}}};
merged_expected.insert(expected.begin(), expected.end());
}
// --- Block 2 ---
{
const int row_num = 2;
DataTypePtr inner_string =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_string);
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
MutableColumnPtr col = final_type->create_column();
// row0: non-null, array with 1 element: "block2_data1"
{
Array arr;
arr.push_back(Field::create_field<TYPE_STRING>("block2_data1"));
col->insert(Field::create_field<TYPE_ARRAY>(arr));
}
// row1: null
col->insert(Field());
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr1");
Block block;
block.insert(type_and_name);
TabletSchemaSPtr tablet_schema = create_schema_with_array();
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, row_num);
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, row_num);
EXPECT_EQ(st, Status::OK());
ExpectedDocMap expected = {{"block2_data1", {4}}};
merged_expected.insert(expected.begin(), expected.end());
}
// --- Block 3 ---
{
const int row_num = 2;
DataTypePtr inner_string =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_string);
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
MutableColumnPtr col = final_type->create_column();
// row0: non-null, array with 1 element: "block3_data1"
{
Array arr;
arr.push_back(Field::create_field<TYPE_STRING>("block3_data1"));
col->insert(Field::create_field<TYPE_ARRAY>(arr));
}
// row1: null
col->insert(Field());
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr1");
Block block;
block.insert(type_and_name);
TabletSchemaSPtr tablet_schema = create_schema_with_array();
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, row_num);
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, row_num);
EXPECT_EQ(st, Status::OK());
ExpectedDocMap expected = {{"block3_data1", {6}}};
merged_expected.insert(expected.begin(), expected.end());
}
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
std::vector<int> expected_null_bitmap = {0, 3, 5, 7};
check_terms_stats(index_path_prefix, &merged_expected, expected_null_bitmap,
InvertedIndexStorageFormatPB::V1, &idx_meta);
}
void test_array_numeric(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26033;
std::string index_path =
InvertedIndexDescriptor::get_index_file_path_v1(index_path_prefix, index_id, "");
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr_numeric");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.init_from_pb(*index_meta_pb.get());
auto index_file_writer =
std::make_unique<IndexFileWriter>(fs, index_path_prefix, std::string {rowset_id},
seg_id, InvertedIndexStorageFormatPB::V1);
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
DataTypePtr inner_int = std::make_shared<DataTypeInt32>();
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_int);
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
// create a MutableColumnPtr
MutableColumnPtr col = final_type->create_column();
// row0: non-null, array [123, 456]
{
Array arr;
arr.push_back(Field::create_field<TYPE_INT>(123));
arr.push_back(Field::create_field<TYPE_INT>(456));
col->insert(Field::create_field<TYPE_ARRAY>(arr));
}
// row1: null
col->insert(Field());
// row2: non-null, array [789, 101112]
{
Array arr;
arr.push_back(Field::create_field<TYPE_INT>(789));
arr.push_back(Field::create_field<TYPE_INT>(101112));
col->insert(Field::create_field<TYPE_ARRAY>(arr));
}
// wrap the constructed column into a ColumnWithTypeAndName
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr_num");
// construct Block (containing only this column), with 3 rows
Block block;
block.insert(type_and_name);
TabletSchemaSPtr tablet_schema = std::make_shared<TabletSchema>();
TabletSchemaPB tablet_schema_pb;
tablet_schema_pb.set_keys_type(KeysType::DUP_KEYS);
tablet_schema->init_from_pb(tablet_schema_pb);
TabletColumn array;
array.set_name("arr1");
array.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
array.set_length(0);
array.set_index_length(0);
array.set_is_nullable(false);
array.set_is_bf_column(false);
TabletColumn child;
child.set_name("arr_sub_int");
child.set_type(FieldType::OLAP_FIELD_TYPE_INT);
child.set_length(INT_MAX);
array.add_sub_column(child);
tablet_schema->append_column(array);
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
// the conversion result is a 4-tuple: [0]: element total count, [1]: offsets pointer, [2]: item data, [3]: item nullmap
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
// get the size of the sub field (4 bytes for INT type)
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, block.rows());
EXPECT_EQ(st, Status::OK());
const auto* null_map = accessor->get_nullmap();
// add nulls
st = _inverted_index_builder->add_array_nulls(null_map, block.rows());
EXPECT_EQ(st, Status::OK());
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
// expected inverted index: row0 contains "123" and "456" (doc id 0), row1 is null, row2 contains "789" and "101112" (doc id 2)
ExpectedDocMap expected = {{"123", {0}}, {"456", {0}}, {"789", {2}}, {"101112", {2}}};
std::vector<int> expected_null_bitmap = {1};
std::unique_ptr<IndexFileReader> reader = std::make_unique<IndexFileReader>(
io::global_local_filesystem(), index_path_prefix, InvertedIndexStorageFormatPB::V1);
auto sts = reader->init();
EXPECT_EQ(sts, Status::OK());
auto result = reader->open(&idx_meta);
EXPECT_TRUE(result.has_value()) << "Failed to open compound reader" << result.error();
auto compound_reader = std::move(result.value());
try {
CLuceneError err;
CL_NS(store)::IndexInput* index_input = nullptr;
auto ok = DorisFSDirectory::FSIndexInput::open(
io::global_local_filesystem(), index_path.c_str(), index_input, err, 4096);
if (!ok) {
throw err;
}
std::shared_ptr<roaring::Roaring> null_bitmap = std::make_shared<roaring::Roaring>();
const char* null_bitmap_file_name =
InvertedIndexDescriptor::get_temporary_null_bitmap_file_name();
if (compound_reader->fileExists(null_bitmap_file_name)) {
std::unique_ptr<lucene::store::IndexInput> null_bitmap_in;
assert(compound_reader->openInput(null_bitmap_file_name, null_bitmap_in, err,
4096));
size_t null_bitmap_size = null_bitmap_in->length();
doris::faststring buf;
buf.resize(null_bitmap_size);
null_bitmap_in->readBytes(reinterpret_cast<uint8_t*>(buf.data()), null_bitmap_size);
*null_bitmap = roaring::Roaring::read(reinterpret_cast<char*>(buf.data()), false);
assert(expected_null_bitmap.size() == null_bitmap->cardinality());
for (int i : expected_null_bitmap) {
EXPECT_TRUE(null_bitmap->contains(i));
}
}
index_input->close();
_CLLDELETE(index_input);
} catch (const CLuceneError& e) {
EXPECT_TRUE(false) << "CLuceneError: " << e.what();
}
}
void test_array_all_null(std::string_view rowset_id, int seg_id, const TabletColumn* field) {
EXPECT_TRUE(field->type() == FieldType::OLAP_FIELD_TYPE_ARRAY);
std::string index_path_prefix {InvertedIndexDescriptor::get_index_file_path_prefix(
local_segment_path(kTestDir, rowset_id, seg_id))};
int index_id = 26034;
std::string index_path =
InvertedIndexDescriptor::get_index_file_path_v1(index_path_prefix, index_id, "");
auto fs = io::global_local_filesystem();
auto index_meta_pb = std::make_unique<TabletIndexPB>();
index_meta_pb->set_index_type(IndexType::INVERTED);
index_meta_pb->set_index_id(index_id);
index_meta_pb->set_index_name("index_inverted_arr_all_null");
index_meta_pb->clear_col_unique_id();
index_meta_pb->add_col_unique_id(0);
TabletIndex idx_meta;
idx_meta.init_from_pb(*index_meta_pb.get());
auto index_file_writer =
std::make_unique<IndexFileWriter>(fs, index_path_prefix, std::string {rowset_id},
seg_id, InvertedIndexStorageFormatPB::V1);
std::unique_ptr<segment_v2::IndexColumnWriter> _inverted_index_builder = nullptr;
EXPECT_EQ(IndexColumnWriter::create(field, &_inverted_index_builder,
index_file_writer.get(), &idx_meta),
Status::OK());
// Construct inner array type: DataTypeArray(DataTypeNullable(DataTypeString))
DataTypePtr inner_string_type =
std::make_shared<DataTypeNullable>(std::make_shared<DataTypeString>());
DataTypePtr array_type = std::make_shared<DataTypeArray>(inner_string_type);
// To support outer array null values, wrap it in a Nullable type
DataTypePtr final_type = std::make_shared<DataTypeNullable>(array_type);
MutableColumnPtr col = final_type->create_column();
col->insert(Field());
col->insert(Field());
ColumnPtr column_array = std::move(col);
ColumnWithTypeAndName type_and_name(column_array, final_type, "arr1");
Block block;
block.insert(type_and_name);
TabletSchemaSPtr tablet_schema = create_schema_with_array();
OlapBlockDataConvertor convertor(tablet_schema.get(), {0});
convertor.set_source_content(&block, 0, block.rows());
auto [st, accessor] = convertor.convert_column_data(0);
EXPECT_EQ(st, Status::OK());
const auto* data_ptr = reinterpret_cast<const uint64_t*>(accessor->get_data());
const auto* offsets_ptr = reinterpret_cast<const uint8_t*>(data_ptr[1]);
const void* item_data = reinterpret_cast<const void*>(data_ptr[2]);
const auto* item_nullmap = reinterpret_cast<const uint8_t*>(data_ptr[3]);
const auto* null_map = accessor->get_nullmap();
auto field_size = field_type_size(field->get_sub_column(0).type());
st = _inverted_index_builder->add_array_values(field_size, item_data, item_nullmap,
offsets_ptr, block.rows());
EXPECT_EQ(st, Status::OK());
st = _inverted_index_builder->add_array_nulls(null_map, block.rows());
EXPECT_EQ(st, Status::OK());
EXPECT_EQ(_inverted_index_builder->finish(), Status::OK());
EXPECT_EQ(index_file_writer->begin_close(), Status::OK());
EXPECT_EQ(index_file_writer->finish_close(), Status::OK());
std::vector<int> expected_null_bitmap = {0, 1};
ExpectedDocMap expected {};
check_terms_stats(index_path_prefix, &expected, expected_null_bitmap,
InvertedIndexStorageFormatPB::V1, &idx_meta);
}
private:
static void build_slices(PaddedPODArray<Slice>& slices, const ColumnPtr& column_array,
size_t num_strings) {
const auto* col_arr = assert_cast<const ColumnArray*>(column_array.get());
const UInt8* nested_null_map =
assert_cast<const ColumnNullable*>(col_arr->get_data_ptr().get())
->get_null_map_column()
.get_data()
.data();
const auto* col_arr_str = assert_cast<const ColumnString*>(
assert_cast<const ColumnNullable*>(col_arr->get_data_ptr().get())
->get_nested_column_ptr()
.get());
const char* char_data = (const char*)(col_arr_str->get_chars().data());
const ColumnString::Offset* offset_cur = col_arr_str->get_offsets().data();
const ColumnString::Offset* offset_end = offset_cur + num_strings;
Slice* slice = slices.data();
size_t string_offset = *(offset_cur - 1);
const UInt8* nullmap_cur = nested_null_map;
while (offset_cur != offset_end) {
if (!*nullmap_cur) {
slice->data = const_cast<char*>(char_data + string_offset);
slice->size = *offset_cur - string_offset;
} else {
slice->data = nullptr;
slice->size = 0;
}
string_offset = *offset_cur;
++nullmap_cur;
++slice;
++offset_cur;
}
}
};
TEST_F(InvertedIndexArrayTest, ArrayString) {
TabletColumn arrayTabletColumn;
arrayTabletColumn.set_unique_id(0);
arrayTabletColumn.set_name("arr1");
arrayTabletColumn.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
TabletColumn arraySubColumn;
arraySubColumn.set_unique_id(1);
arraySubColumn.set_name("arr_sub_string");
arraySubColumn.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
arrayTabletColumn.add_sub_column(arraySubColumn);
const TabletColumn* field = &(arrayTabletColumn);
test_string("rowset_id", 0, field);
test_non_null_string("rowset_id_non_null", 0, field);
}
TEST_F(InvertedIndexArrayTest, ComplexNullCases) {
TabletColumn arrayTabletColumn;
arrayTabletColumn.set_unique_id(0);
arrayTabletColumn.set_name("arr1");
arrayTabletColumn.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
TabletColumn arraySubColumn;
arraySubColumn.set_unique_id(1);
arraySubColumn.set_name("arr_sub_string");
arraySubColumn.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
arrayTabletColumn.add_sub_column(arraySubColumn);
const TabletColumn* field = &(arrayTabletColumn);
test_null_write("complex_null", 0, field);
test_null_write_v2("complex_null_v2", 0, field);
test_array_all_null("complex_null_all_null", 0, field);
}
TEST_F(InvertedIndexArrayTest, MultiBlockWrite) {
TabletColumn arrayTabletColumn;
arrayTabletColumn.set_unique_id(0);
arrayTabletColumn.set_name("arr1");
arrayTabletColumn.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
TabletColumn arraySubColumn;
arraySubColumn.set_unique_id(1);
arraySubColumn.set_name("arr_sub_string");
arraySubColumn.set_type(FieldType::OLAP_FIELD_TYPE_STRING);
arrayTabletColumn.add_sub_column(arraySubColumn);
const TabletColumn* field = &(arrayTabletColumn);
test_multi_block_write("multi_block", 0, field);
}
TEST_F(InvertedIndexArrayTest, ArrayInt) {
TabletColumn arrayTabletColumn;
arrayTabletColumn.set_unique_id(0);
arrayTabletColumn.set_name("arr1");
arrayTabletColumn.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
TabletColumn arraySubColumn;
arraySubColumn.set_unique_id(1);
arraySubColumn.set_name("arr_sub_int");
arraySubColumn.set_type(FieldType::OLAP_FIELD_TYPE_INT);
arrayTabletColumn.add_sub_column(arraySubColumn);
const TabletColumn* field = &(arrayTabletColumn);
test_array_numeric("int_test", 0, field);
}
} // namespace doris::segment_v2