blob: 61a8b3f94243741d3aa346f703f44b8bbb5da2a2 [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 "storage/segment/column_reader_cache.h"
#include <crc32c/crc32c.h>
#include <gen_cpp/segment_v2.pb.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <chrono>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include "common/config.h"
#include "core/assert_cast.h"
#include "core/data_type/data_type_array.h"
#include "core/data_type/data_type_nullable.h"
#include "io/fs/file_reader.h"
#include "io/fs/local_file_system.h"
#include "storage/segment/column_meta_accessor.h"
#include "storage/segment/column_reader.h"
#include "storage/segment/encoding_info.h"
#include "storage/segment/mock/mock_segment.h"
#include "storage/segment/segment.h"
#include "storage/segment/segment_writer.h"
#include "storage/segment/variant/variant_column_reader.h"
#include "storage/tablet/tablet_schema.h"
#include "util/coding.h"
#include "util/json/path_in_data.h"
namespace doris::segment_v2 {
using ::testing::_;
using ::testing::Return;
using ::testing::StrictMock;
namespace {
constexpr std::string_view kTestDir = "./ut_dir/column_reader_cache_test";
Status append_footer_trailer(io::FileWriter* file_writer, SegmentFooterPB* footer) {
std::string footer_buf;
if (!footer->SerializeToString(&footer_buf)) {
return Status::InternalError("failed to serialize SegmentFooterPB for test");
}
faststring fixed_buf;
put_fixed32_le(&fixed_buf, static_cast<uint32_t>(footer_buf.size()));
put_fixed32_le(&fixed_buf, crc32c::Crc32c(footer_buf.data(), footer_buf.size()));
fixed_buf.append(k_segment_magic, k_segment_magic_length);
std::vector<Slice> slices {Slice(footer_buf), Slice(fixed_buf)};
return file_writer->appendv(slices.data(), slices.size());
}
} // namespace
// Mock classes for testing
class MockColumnReader : public ColumnReader {
public:
MockColumnReader(int32_t col_uid, FieldType type = FieldType::OLAP_FIELD_TYPE_INT)
: _col_uid(col_uid), _type(type) {}
FieldType get_meta_type() override { return _type; }
int32_t get_col_uid() const { return _col_uid; }
private:
int32_t _col_uid;
FieldType _type;
};
class ColumnReaderCacheTest : public ::testing::Test {
protected:
// EncodingInfo friended ColumnReaderCacheTest so the fixture can pick a valid V2
// default encoding for the synthetic ColumnMetaPBs constructed by these tests.
// TEST_F bodies subclass this fixture and call the helper (friendship doesn't carry
// to subclasses).
static segment_v2::EncodingTypePB get_v2_default_encoding(FieldType t) {
return segment_v2::EncodingInfo::get_v2_default_encoding(t);
}
void SetUp() override {
// Set up test configuration
config::max_segment_partial_column_cache_size = 3;
auto fs = io::global_local_filesystem();
static_cast<void>(fs->delete_directory(kTestDir));
CHECK(fs->create_directory(kTestDir).ok());
// Create mock segment
_mock_segment = std::make_unique<StrictMock<MockSegment>>();
// Set up basic mock expectations before constructing the cache, because
// ColumnReaderCache's ctor reads _mock_segment->num_rows().
setup_basic_mocks();
// Create cache bound to mock segment context.
// ColumnMetaAccessor will be initialized later in setup_segment_footer()
// once the test-specific footer has been constructed.
io::FileReaderSPtr file_reader; // nullptr is fine for inline-only tests
auto footer_cb = [this](std::shared_ptr<SegmentFooterPB>& footer_pb_shared,
OlapReaderStatistics* stats,
const io::IOContext* io_ctx) -> Status {
// Delegate to MockSegment::_get_segment_footer
return _mock_segment->_get_segment_footer(footer_pb_shared, stats, io_ctx);
};
_cache = std::make_unique<ColumnReaderCache>(&_accessor, _mock_segment->tablet_schema(),
file_reader, _mock_segment->num_rows(),
footer_cb);
}
void TearDown() override {
_cache.reset();
_mock_segment.reset();
static_cast<void>(io::global_local_filesystem()->delete_directory(kTestDir));
}
void setup_basic_mocks() {
// Set up file reader
io::FileReaderSPtr file_reader;
EXPECT_CALL(*_mock_segment, file_reader()).WillRepeatedly(Return(file_reader));
// Set up num rows
EXPECT_CALL(*_mock_segment, num_rows()).WillRepeatedly(Return(1000));
// mock _get_segment_footer
EXPECT_CALL(*_mock_segment, _get_segment_footer(_, _, _))
.WillRepeatedly(
testing::Invoke([this](std::shared_ptr<SegmentFooterPB>& footer_pb_shared,
OlapReaderStatistics*, const io::IOContext*) {
if (_mock_segment->_footer) {
footer_pb_shared = _mock_segment->_footer;
return Status::OK();
}
return Status::NotFound("Footer not set");
}));
}
void setup_column_uid_mapping(int32_t col_uid, int32_t footer_ordinal) {
_mock_segment->add_column_uid_mapping(col_uid, footer_ordinal);
}
void setup_segment_footer(const std::vector<ColumnMetaPB>& columns) {
auto footer = std::make_shared<SegmentFooterPB>();
for (const auto& col : columns) {
*footer->add_columns() = col;
}
_mock_segment->set_footer(footer);
// Initialize ColumnMetaAccessor with the current footer; external meta is not
// used in these tests so we can pass a null file reader.
CHECK(_accessor.init(*footer, nullptr).ok());
}
void setup_parsed_segment_footer(const std::vector<ColumnMetaPB>& columns) {
SegmentFooterPB footer;
for (const auto& col : columns) {
*footer.add_columns() = col;
}
auto fs = io::global_local_filesystem();
std::string file_path = std::string(kTestDir) + "/parsed_footer_" +
std::to_string(_parsed_footer_file_id++) + ".dat";
io::FileWriterPtr file_writer;
CHECK(fs->create_file(file_path, &file_writer).ok());
CHECK(append_footer_trailer(file_writer.get(), &footer).ok());
CHECK(file_writer->close().ok());
io::FileReaderSPtr file_reader;
io::FileReaderOptions opts;
CHECK(fs->open_file(file_path, &file_reader, &opts).ok());
_mock_segment->set_file_reader_for_test(file_reader);
std::shared_ptr<SegmentFooterPB> parsed_footer;
CHECK(_mock_segment->parse_footer_for_test(parsed_footer, &_stats).ok());
_mock_segment->set_footer(parsed_footer);
CHECK(_accessor.init(*parsed_footer, nullptr).ok());
}
std::unique_ptr<StrictMock<MockSegment>> _mock_segment;
ColumnMetaAccessor _accessor;
std::unique_ptr<ColumnReaderCache> _cache;
OlapReaderStatistics _stats;
int _parsed_footer_file_id = 0;
};
// Test basic cache functionality
TEST_F(ColumnReaderCacheTest, BasicCacheOperations) {
// Test empty cache
auto readers = _cache->get_available_readers(false);
EXPECT_TRUE(readers.empty());
// Test cache insertion and retrieval
setup_column_uid_mapping(1, 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta.set_unique_id(1);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta});
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(1, &reader, &_stats);
EXPECT_TRUE(status.ok()) << status.to_string();
EXPECT_NE(reader, nullptr);
// Verify cache hit
std::shared_ptr<ColumnReader> cached_reader;
status = _cache->get_column_reader(1, &cached_reader, &_stats);
EXPECT_TRUE(status.ok()) << status.to_string();
EXPECT_EQ(reader, cached_reader);
// Verify cache contents
readers = _cache->get_available_readers(false);
EXPECT_EQ(readers.size(), 1);
EXPECT_EQ(readers[1], reader);
}
// Test LRU eviction
TEST_F(ColumnReaderCacheTest, LRUEviction) {
// Set cache size to 2
config::max_segment_partial_column_cache_size = 2;
// Add 3 columns to trigger eviction
for (int i = 1; i <= 3; ++i) {
setup_column_uid_mapping(i, 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta.set_unique_id(i);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta});
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(i, &reader, &_stats);
EXPECT_TRUE(status.ok());
}
// Verify only 2 readers remain (LRU eviction)
auto readers = _cache->get_available_readers(false);
EXPECT_EQ(readers.size(), 2);
// Column 1 should be evicted (least recently used)
EXPECT_EQ(readers.find(1), readers.end());
EXPECT_NE(readers.find(2), readers.end());
EXPECT_NE(readers.find(3), readers.end());
}
// Test LRU order maintenance
TEST_F(ColumnReaderCacheTest, LRUOrderMaintenance) {
config::max_segment_partial_column_cache_size = 2;
setup_column_uid_mapping(1, 0);
setup_column_uid_mapping(2, 1);
ColumnMetaPB col_meta1, col_meta2;
col_meta1.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta1.set_unique_id(1);
col_meta1.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta1.type())));
col_meta1.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
col_meta2.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta2.set_unique_id(2);
col_meta2.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta2.type())));
col_meta2.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta1, col_meta2});
// Insert column 1
std::shared_ptr<ColumnReader> reader1;
Status status = _cache->get_column_reader(1, &reader1, &_stats);
EXPECT_TRUE(status.ok());
// Insert column 2
std::shared_ptr<ColumnReader> reader2;
status = _cache->get_column_reader(2, &reader2, &_stats);
EXPECT_TRUE(status.ok());
// Access column 1 again (should move to front)
std::shared_ptr<ColumnReader> reader1_again;
status = _cache->get_column_reader(1, &reader1_again, &_stats);
EXPECT_TRUE(status.ok());
// Add column 3 to trigger eviction
setup_column_uid_mapping(3, 2);
ColumnMetaPB col_meta3;
col_meta3.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta3.set_unique_id(3);
col_meta3.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta3.type())));
col_meta3.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta1, col_meta2, col_meta3});
std::shared_ptr<ColumnReader> reader3;
status = _cache->get_column_reader(3, &reader3, &_stats);
EXPECT_TRUE(status.ok());
// Column 2 should be evicted (least recently used)
auto readers = _cache->get_available_readers(false);
EXPECT_EQ(readers.size(), 2);
EXPECT_NE(readers.find(1), readers.end());
EXPECT_EQ(readers.find(2), readers.end()); // Should be evicted
EXPECT_NE(readers.find(3), readers.end());
}
// Test variant column path reading
TEST_F(ColumnReaderCacheTest, VariantColumnPathReading) {
setup_column_uid_mapping(1, 0);
// Create variant column meta
ColumnMetaPB variant_meta;
variant_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_VARIANT));
variant_meta.set_unique_id(1);
variant_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(variant_meta.type())));
variant_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
// Create subcolumn meta
ColumnMetaPB subcol_meta;
subcol_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_STRING));
subcol_meta.set_unique_id(2);
subcol_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(subcol_meta.type())));
subcol_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({variant_meta, subcol_meta});
// Test path column reader
PathInData path("field1");
std::shared_ptr<ColumnReader> path_reader;
Status status = _cache->get_path_column_reader(1, path, &path_reader, &_stats);
EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
EXPECT_EQ(path_reader, nullptr);
}
// Test non-existent column
TEST_F(ColumnReaderCacheTest, NonExistentColumn) {
// Don't set up any column mapping
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(999, &reader, &_stats);
EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
}
// Test non-existent variant path
TEST_F(ColumnReaderCacheTest, NonExistentVariantPath) {
setup_column_uid_mapping(1, 0);
ColumnMetaPB variant_meta;
variant_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_VARIANT));
variant_meta.set_unique_id(1);
variant_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(variant_meta.type())));
variant_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({variant_meta});
PathInData non_existent_path("non_existent_field");
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_path_column_reader(1, non_existent_path, &reader, &_stats);
EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
EXPECT_EQ(reader, nullptr);
}
// Test concurrent access
TEST_F(ColumnReaderCacheTest, ConcurrentAccess) {
setup_column_uid_mapping(1, 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta.set_unique_id(1);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta});
const int num_threads = 10;
const int num_operations = 100;
std::vector<std::thread> threads;
std::atomic<int> success_count {0};
for (int i = 0; i < num_threads; ++i) {
threads.emplace_back([this, &success_count]() {
for (int j = 0; j < num_operations; ++j) {
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(1, &reader, &_stats);
if (status.ok() && reader != nullptr) {
success_count++;
}
}
});
}
for (auto& thread : threads) {
thread.join();
}
EXPECT_EQ(success_count.load(), num_threads * num_operations);
}
// Test cache statistics
TEST_F(ColumnReaderCacheTest, CacheStatistics) {
setup_column_uid_mapping(1, 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta.set_unique_id(1);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta});
// First access (cache miss)
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(1, &reader, &_stats);
EXPECT_TRUE(status.ok());
// Second access (cache hit)
status = _cache->get_column_reader(1, &reader, &_stats);
EXPECT_TRUE(status.ok());
// Verify cache contents
auto readers = _cache->get_available_readers(false);
EXPECT_EQ(readers.size(), 1);
}
// Test cache with different column types
TEST_F(ColumnReaderCacheTest, DifferentColumnTypes) {
std::vector<FieldType> types = {
FieldType::OLAP_FIELD_TYPE_INT, FieldType::OLAP_FIELD_TYPE_STRING,
FieldType::OLAP_FIELD_TYPE_DOUBLE, FieldType::OLAP_FIELD_TYPE_BOOL};
for (size_t i = 0; i < types.size(); ++i) {
setup_column_uid_mapping(static_cast<int32_t>(i + 1), 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(types[i]));
col_meta.set_unique_id(static_cast<int32_t>(i + 1));
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta});
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(static_cast<int32_t>(i + 1), &reader, &_stats);
EXPECT_TRUE(status.ok());
EXPECT_NE(reader, nullptr);
}
auto readers = _cache->get_available_readers(false);
EXPECT_EQ(readers.size(), config::max_segment_partial_column_cache_size);
}
// Test cache with node_hint parameter
TEST_F(ColumnReaderCacheTest, NodeHintParameter) {
setup_column_uid_mapping(1, 0);
ColumnMetaPB variant_meta, subcol_meta;
variant_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_VARIANT));
variant_meta.set_unique_id(1);
variant_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(variant_meta.type())));
variant_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
subcol_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_STRING));
subcol_meta.set_unique_id(2);
subcol_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(subcol_meta.type())));
subcol_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({variant_meta, subcol_meta});
// Create node hint
SubcolumnColumnMetaInfo::Node node_hint(SubcolumnColumnMetaInfo::Node::SCALAR);
node_hint.data.footer_ordinal = 1;
node_hint.path = PathInData("hint_field");
PathInData path("hint_field");
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_path_column_reader(1, path, &reader, &_stats, &node_hint);
EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
EXPECT_EQ(reader, nullptr);
// Test cache hit
status = _cache->get_path_column_reader(1, path, &reader, &_stats, &node_hint);
EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
EXPECT_EQ(reader, nullptr);
}
// Test cache performance under load
TEST_F(ColumnReaderCacheTest, PerformanceUnderLoad) {
const int num_columns = 100;
// Set up many columns in a single footer so that ColumnMetaAccessor and
// ColumnReaderCache both see a consistent multi-column layout.
std::vector<ColumnMetaPB> all_columns;
for (int i = 1; i <= num_columns; ++i) {
setup_column_uid_mapping(i, 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_INT));
col_meta.set_unique_id(i);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
all_columns.push_back(col_meta);
}
setup_segment_footer(all_columns);
auto start_time = std::chrono::high_resolution_clock::now();
// Access all columns
for (int i = 1; i <= num_columns; ++i) {
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(i, &reader, &_stats);
EXPECT_TRUE(status.ok());
}
// Access again (should be cache hits)
for (int i = 1; i <= num_columns; ++i) {
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(i, &reader, &_stats);
EXPECT_TRUE(status.ok());
}
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
// Performance should be reasonable (less than 1 second for 100 columns)
EXPECT_LT(duration.count(), 1000);
// Verify cache size is limited
auto readers = _cache->get_available_readers(false);
EXPECT_LE(readers.size(), config::max_segment_partial_column_cache_size);
}
// Test cache with empty path
TEST_F(ColumnReaderCacheTest, EmptyPath) {
setup_column_uid_mapping(1, 0);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_VARIANT));
col_meta.set_unique_id(1);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_segment_footer({col_meta});
PathInData empty_path("");
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_path_column_reader(1, empty_path, &reader, &_stats);
EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
}
TEST_F(ColumnReaderCacheTest, FillMissingDecimalV2PrecisionFromTabletSchema) {
constexpr int32_t col_uid = 10;
setup_column_uid_mapping(col_uid, 0);
auto& tablet_column = _mock_segment->tablet_schema()->mutable_column_by_uid(col_uid);
tablet_column.set_type(FieldType::OLAP_FIELD_TYPE_DECIMAL);
tablet_column.set_precision(20);
tablet_column.set_frac(6);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL));
col_meta.set_unique_id(col_uid);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
ASSERT_FALSE(col_meta.has_precision());
ASSERT_FALSE(col_meta.has_frac());
setup_parsed_segment_footer({col_meta});
const auto& parsed_col_meta = _mock_segment->get_footer()->columns(0);
EXPECT_EQ(parsed_col_meta.precision(), 20);
EXPECT_EQ(parsed_col_meta.frac(), 6);
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(col_uid, &reader, &_stats);
ASSERT_TRUE(status.ok()) << status.to_string();
ASSERT_NE(reader, nullptr);
const auto* data_type =
assert_cast<const DataTypeDecimalV2*>(reader->get_vec_data_type().get());
ASSERT_NE(data_type, nullptr);
EXPECT_EQ(data_type->get_original_precision(), 20);
EXPECT_EQ(data_type->get_original_scale(), 6);
EXPECT_EQ(data_type->get_precision(), 27);
EXPECT_EQ(data_type->get_scale(), 9);
}
TEST_F(ColumnReaderCacheTest, FillMissingDecimalV2PrecisionForComplexTypeFromTabletSchema) {
constexpr int32_t col_uid = 11;
setup_column_uid_mapping(col_uid, 0);
auto& tablet_column = _mock_segment->tablet_schema()->mutable_column_by_uid(col_uid);
tablet_column.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
TabletColumn item_column;
item_column.set_type(FieldType::OLAP_FIELD_TYPE_DECIMAL);
item_column.set_precision(18);
item_column.set_frac(4);
tablet_column.add_sub_column(item_column);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_ARRAY));
col_meta.set_unique_id(col_uid);
auto* item_meta = col_meta.add_children_columns();
item_meta->set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL));
item_meta->set_encoding(get_v2_default_encoding(static_cast<FieldType>(item_meta->type())));
item_meta->set_num_rows(1000);
item_meta->mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
ASSERT_FALSE(item_meta->has_precision());
ASSERT_FALSE(item_meta->has_frac());
auto* offset_meta = col_meta.add_children_columns();
offset_meta->set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_UNSIGNED_BIGINT));
offset_meta->set_encoding(get_v2_default_encoding(static_cast<FieldType>(offset_meta->type())));
offset_meta->set_num_rows(1000);
offset_meta->mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_parsed_segment_footer({col_meta});
const auto& parsed_item_meta = _mock_segment->get_footer()->columns(0).children_columns(0);
EXPECT_EQ(parsed_item_meta.precision(), 18);
EXPECT_EQ(parsed_item_meta.frac(), 4);
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(col_uid, &reader, &_stats);
ASSERT_TRUE(status.ok()) << status.to_string();
ASSERT_NE(reader, nullptr);
auto data_type = reader->get_vec_data_type();
ASSERT_NE(data_type, nullptr);
EXPECT_EQ(data_type->get_primitive_type(), TYPE_ARRAY);
const auto* array_type = assert_cast<const DataTypeArray*>(data_type.get());
const auto* nested_type_nullable =
assert_cast<const DataTypeNullable*>(array_type->get_nested_type().get());
ASSERT_NE(nested_type_nullable, nullptr);
const auto* nested_type =
assert_cast<const DataTypeDecimalV2*>(nested_type_nullable->get_nested_type().get());
ASSERT_NE(nested_type, nullptr);
EXPECT_EQ(nested_type->get_original_precision(), 18);
EXPECT_EQ(nested_type->get_original_scale(), 4);
EXPECT_EQ(nested_type->get_precision(), 27);
EXPECT_EQ(nested_type->get_scale(), 9);
}
TEST_F(ColumnReaderCacheTest, FillMissingDecimalV3PrecisionFromTabletSchema) {
constexpr int32_t col_uid = 10;
setup_column_uid_mapping(col_uid, 0);
auto& tablet_column = _mock_segment->tablet_schema()->mutable_column_by_uid(col_uid);
tablet_column.set_type(FieldType::OLAP_FIELD_TYPE_DECIMAL128I);
tablet_column.set_precision(20);
tablet_column.set_frac(6);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL128I));
col_meta.set_unique_id(col_uid);
col_meta.set_encoding(get_v2_default_encoding(static_cast<FieldType>(col_meta.type())));
col_meta.mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
ASSERT_FALSE(col_meta.has_precision());
ASSERT_FALSE(col_meta.has_frac());
setup_parsed_segment_footer({col_meta});
const auto& parsed_col_meta = _mock_segment->get_footer()->columns(0);
EXPECT_EQ(parsed_col_meta.precision(), 20);
EXPECT_EQ(parsed_col_meta.frac(), 6);
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(col_uid, &reader, &_stats);
ASSERT_TRUE(status.ok()) << status.to_string();
ASSERT_NE(reader, nullptr);
auto data_type = reader->get_vec_data_type();
ASSERT_NE(data_type, nullptr);
EXPECT_EQ(data_type->get_primitive_type(), TYPE_DECIMAL128I);
EXPECT_EQ(data_type->get_precision(), 20);
EXPECT_EQ(data_type->get_scale(), 6);
}
TEST_F(ColumnReaderCacheTest, FillMissingDecimalV3PrecisionForComplexTypeFromTabletSchema) {
constexpr int32_t col_uid = 11;
setup_column_uid_mapping(col_uid, 0);
auto& tablet_column = _mock_segment->tablet_schema()->mutable_column_by_uid(col_uid);
tablet_column.set_type(FieldType::OLAP_FIELD_TYPE_ARRAY);
TabletColumn item_column;
item_column.set_type(FieldType::OLAP_FIELD_TYPE_DECIMAL64);
item_column.set_precision(18);
item_column.set_frac(4);
tablet_column.add_sub_column(item_column);
ColumnMetaPB col_meta;
col_meta.set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_ARRAY));
col_meta.set_unique_id(col_uid);
auto* item_meta = col_meta.add_children_columns();
item_meta->set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_DECIMAL64));
item_meta->set_encoding(get_v2_default_encoding(static_cast<FieldType>(item_meta->type())));
item_meta->set_num_rows(1000);
item_meta->mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
ASSERT_FALSE(item_meta->has_precision());
ASSERT_FALSE(item_meta->has_frac());
auto* offset_meta = col_meta.add_children_columns();
offset_meta->set_type(static_cast<int32_t>(FieldType::OLAP_FIELD_TYPE_UNSIGNED_BIGINT));
offset_meta->set_encoding(get_v2_default_encoding(static_cast<FieldType>(offset_meta->type())));
offset_meta->set_num_rows(1000);
offset_meta->mutable_indexes()->Add()->set_type(ORDINAL_INDEX);
setup_parsed_segment_footer({col_meta});
const auto& parsed_item_meta = _mock_segment->get_footer()->columns(0).children_columns(0);
EXPECT_EQ(parsed_item_meta.precision(), 18);
EXPECT_EQ(parsed_item_meta.frac(), 4);
std::shared_ptr<ColumnReader> reader;
Status status = _cache->get_column_reader(col_uid, &reader, &_stats);
ASSERT_TRUE(status.ok()) << status.to_string();
ASSERT_NE(reader, nullptr);
auto data_type = reader->get_vec_data_type();
ASSERT_NE(data_type, nullptr);
EXPECT_EQ(data_type->get_primitive_type(), TYPE_ARRAY);
const auto* array_type = assert_cast<const DataTypeArray*>(data_type.get());
auto nested_type = array_type->get_nested_type();
ASSERT_NE(nested_type, nullptr);
EXPECT_EQ(nested_type->get_primitive_type(), TYPE_DECIMAL64);
EXPECT_EQ(nested_type->get_precision(), 18);
EXPECT_EQ(nested_type->get_scale(), 4);
}
} // namespace doris::segment_v2