From aee0b66bb4df599f5fcd6da8e4539185535835e5 Mon Sep 17 00:00:00 2001 From: jacktengg Date: Mon, 25 May 2026 00:31:16 +0800 Subject: [PATCH] [fix](be) Handle legacy DecimalV2 segments with missing precision/frac Issue Number: close #xxx Problem Summary: After upgrading from 3.1.4 to 4.0.4, queries on DecimalV2 columns fail with: [INTERNAL_ERROR]meet invalid precision: real_precision=0, max_decimal_precision=27, min_decimal_precision=1 Root cause: Segments written by Doris < 2.1.0 (before #26572) do not persist precision/frac in ColumnMetaPB; they default to 0 when read back. Since #35222, DataTypeFactory passes those raw values as original_precision/original_scale into DataTypeDecimalV2, which calls check_type_precision(0) and throws. 3.1.4 was unaffected because the old code hardcoded (27, 9). Fix: In _create_primitive_data_type for OLAP_FIELD_TYPE_DECIMAL, when precision is not positive (legacy segment), pass UINT32_MAX to DataTypeDecimalV2 to signal that the original precision/scale are unknown. DecimalScaleInfo already treats UINT32_MAX as 'unknown' and falls back to the in-memory (27, 9) representation in get_original_precision()/get_original_scale(). Fix "meet invalid precision: real_precision=0" when querying DecimalV2 columns on segments written by Doris versions older than 2.1.0. - Test: Unit Test - Added DataTypeDecimalTest.create_decimalv2_from_legacy_tablet_column covering: legacy TabletColumn (unset precision/frac), modern TabletColumn with decimal(26,6), and legacy segment_v2::ColumnMetaPB with default-0 precision/frac. PS: error callstack: ``` I20260525 16:41:28.185905 1945583 pipeline_fragment_context.cpp:177] PipelineFragmentContext::cancel|query_id=18982a6c19ee4166-ae3ba44bce094741|fragment_id=1|reason=[INTERNAL_ERROR]meet invalid precision: real_precision=0, max_decimal_precision=27, min_decimal_precision=1 0# doris::get_stack_trace(int, std::__cxx11::basic_string, std::allocator >) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/util/stack_util.cpp:59 1# doris::Exception::Exception(int, std::basic_string_view > const&, bool) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/common/exception.cpp:31 2# doris::Exception::Exception(int, std::basic_string_view > const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/common/exception.h:38 3# doris::Exception::Exception(int, std::basic_string_view > const&, unsigned int const&, unsigned long&&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/common/exception.h:44 4# doris::vectorized::DataTypeDecimal<(doris::PrimitiveType)20>::check_type_precision(unsigned int) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/data_types/data_type_decimal.h:280 5# doris::vectorized::DataTypeDecimal<(doris::PrimitiveType)20>::DataTypeDecimal(unsigned int, unsigned int, unsigned int, unsigned int) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/data_types/data_type_decimal.h:144 6# void std::_Construct, int, int, int&, int&>(doris::vectorized::DataTypeDecimal<(doris::PrimitiveType)20>*, int&&, int&&, int&, int&) at /mnt/disk2/tengjianping/local/ldb_toolchain/bin/../lib/gcc/x86_64-pc-linux-gnu/15/include/g++-v15/bits/stl_construct.h:134 7# std::_Sp_counted_ptr_inplace, std::allocator, (__gnu_cxx::_Lock_policy)2>::_Sp_counted_ptr_inplace(std::allocator, int&&, int&&, int&, int&) at /mnt/disk2/tengjianping/local/ldb_toolchain/bin/../lib/gcc/x86_64-pc-linux-gnu/15/include/g++-v15/bits/alloc_traits.h:805 8# std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count, std::allocator, int, int, int&, int&>(doris::vectorized::DataTypeDecimal<(doris::PrimitiveType)20>*&, std::_Sp_alloc_shared_tag >, int&&, int&&, int&, int&) at /mnt/disk2/tengjianping/local/ldb_toolchain/bin/../lib/gcc/x86_64-pc-linux-gnu/15/include/g++-v15/bits/shared_ptr_base.h:970 9# std::__shared_ptr, (__gnu_cxx::_Lock_policy)2>::__shared_ptr, int, int, int&, int&>(std::_Sp_alloc_shared_tag >, int&&, int&&, int&, int&) at /mnt/disk2/tengjianping/local/ldb_toolchain/bin/../lib/gcc/x86_64-pc-linux-gnu/15/include/g++-v15/bits/shared_ptr_base.h:1719 10# std::shared_ptr >::shared_ptr, int, int, int&, int&>(std::_Sp_alloc_shared_tag >, int&&, int&&, int&, int&) at /mnt/disk2/tengjianping/local/ldb_toolchain/bin/../lib/gcc/x86_64-pc-linux-gnu/15/include/g++-v15/bits/shared_ptr.h:464 11# std::shared_ptr > std::make_shared, int, int, int&, int&>(int&&, int&&, int&, int&) at /mnt/disk2/tengjianping/local/ldb_toolchain/bin/../lib/gcc/x86_64-pc-linux-gnu/15/include/g++-v15/bits/shared_ptr.h:1007 12# doris::vectorized::DataTypeFactory::_create_primitive_data_type(doris::FieldType const&, int, int, int) const at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/data_types/data_type_factory.cpp:196 13# doris::vectorized::DataTypeFactory::create_data_type(doris::segment_v2::ColumnMetaPB const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/data_types/data_type_factory.cpp:394 14# doris::segment_v2::ColumnReader::ColumnReader(doris::segment_v2::ColumnReaderOptions const&, doris::segment_v2::ColumnMetaPB const&, unsigned long, std::shared_ptr) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/column_reader.cpp:290 15# doris::segment_v2::ColumnReader::create(doris::segment_v2::ColumnReaderOptions const&, doris::segment_v2::ColumnMetaPB const&, unsigned long, std::shared_ptr const&, std::shared_ptr*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/column_reader.cpp:242 16# doris::segment_v2::ColumnReaderCache::get_column_reader(int, std::shared_ptr*, doris::OlapReaderStatistics*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/column_reader_cache.cpp:138 17# doris::segment_v2::Segment::get_column_reader(int, std::shared_ptr*, doris::OlapReaderStatistics*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment.cpp:738 18# doris::segment_v2::Segment::new_column_iterator(doris::TabletColumn const&, std::unique_ptr >*, doris::StorageReadOptions const*, std::unordered_map, std::allocator >, std::shared_ptr, std::hash, std::allocator > >, std::equal_to, std::allocator > >, std::allocator, std::allocator > const, std::shared_ptr > > >, std::default_delete, std::allocator >, std::shared_ptr, std::hash, std::allocator > >, std::equal_to, std::allocator > >, std::allocator, std::allocator > const, std::shared_ptr > > > > >, std::hash, std::equal_to, std::allocator, std::allocator >, std::shared_ptr, std::hash, std::allocator > >, std::equal_to, std::allocator > >, std::allocator, std::allocator > const, std::shared_ptr > > >, std::default_delete, std::allocator >, std::shared_ptr, std::hash, std::allocator > >, std::equal_to, std::allocator > >, std::allocator, std::allocator > const, std::shared_ptr > > > > > > > > const*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment.cpp:679 19# doris::segment_v2::SegmentIterator::_init_return_column_iterators() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment_iterator.cpp:1391 20# doris::segment_v2::SegmentIterator::init_iterators() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment_iterator.cpp:396 21# doris::segment_v2::SegmentIterator::_init_impl(doris::StorageReadOptions const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment_iterator.cpp:372 22# doris::segment_v2::SegmentIterator::init(doris::StorageReadOptions const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment_iterator.cpp:290 23# doris::segment_v2::Segment::new_iterator(std::shared_ptr, doris::StorageReadOptions const&, std::unique_ptr >*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/segment.cpp:0 24# doris::segment_v2::LazyInitSegmentIterator::init(doris::StorageReadOptions const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/segment_v2/lazy_init_segment_iterator.cpp:48 25# doris::BetaRowsetReader::_init_iterator() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/beta_rowset_reader.cpp:330 26# doris::BetaRowsetReader::_init_iterator_once()::$_0::operator()() const at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/beta_rowset_reader.cpp:295 27# doris::Status doris::DorisCallOnce::call(doris::BetaRowsetReader::_init_iterator_once()::$_0) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/util/once.h:92 28# doris::BetaRowsetReader::_init_iterator_once() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/beta_rowset_reader.cpp:295 29# doris::Status doris::BetaRowsetReader::_next_batch(doris::vectorized::Block*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/beta_rowset_reader.h:103 30# doris::BetaRowsetReader::next_batch(doris::vectorized::Block*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/olap/rowset/beta_rowset_reader.h:56 31# doris::vectorized::VCollectIterator::Level0Iterator::_refresh() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/vcollect_iterator.h:0 32# doris::vectorized::VCollectIterator::Level0Iterator::refresh_current_row() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/vcollect_iterator.cpp:526 33# doris::vectorized::VCollectIterator::Level0Iterator::ensure_first_row_ref() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/vcollect_iterator.cpp:497 34# doris::vectorized::VCollectIterator::Level1Iterator::ensure_first_row_ref() at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/vcollect_iterator.cpp:715 35# doris::vectorized::VCollectIterator::build_heap(std::vector, std::allocator > >&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/vcollect_iterator.cpp:190 36# doris::vectorized::BlockReader::_init_collect_iter(doris::TabletReader::ReaderParams const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/block_reader.cpp:152 37# doris::vectorized::BlockReader::init(doris::TabletReader::ReaderParams const&) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/olap/block_reader.cpp:226 38# doris::vectorized::OlapScanner::_open_impl(doris::RuntimeState*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/exec/scan/olap_scanner.cpp:266 39# doris::vectorized::Scanner::open(doris::RuntimeState*) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/exec/scan/scanner.h:84 40# doris::vectorized::ScannerScheduler::_scanner_scan(std::shared_ptr, std::shared_ptr) at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/exec/scan/scanner_scheduler.cpp:187 41# doris::vectorized::ScannerScheduler::submit(std::shared_ptr, std::shared_ptr)::$_0::operator()() const::{lambda()#1}::operator()() const::{lambda()#1}::operator()() const at /mnt/disk2/tengjianping/doris-4.0-dev/be/src/vec/exec/scan/scanner_scheduler.cpp:76 ``` Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- be/src/core/data_type/data_type_factory.cpp | 19 +- .../core/data_type/data_type_decimal_test.cpp | 179 ++++++++++++++++++ 2 files changed, 196 insertions(+), 2 deletions(-) diff --git a/be/src/core/data_type/data_type_factory.cpp b/be/src/core/data_type/data_type_factory.cpp index 256493cba04e34..ab6551d23ad5dd 100644 --- a/be/src/core/data_type/data_type_factory.cpp +++ b/be/src/core/data_type/data_type_factory.cpp @@ -379,8 +379,23 @@ DataTypePtr DataTypeFactory::create_data_type(const segment_v2::ColumnMetaPB& pc nested = std::make_shared(dataTypes, names); } else { // TODO add precision and frac - nested = _create_primitive_data_type(static_cast(pcolumn.type()), - pcolumn.precision(), pcolumn.frac(), -1); + auto meta_precision = pcolumn.precision(); + auto meta_scale = pcolumn.frac(); + if (pcolumn.type() == static_cast(FieldType::OLAP_FIELD_TYPE_DECIMAL)) { + // Segments written by Doris < 2.1.0 (before #26572) do not persist + // precision/frac in ColumnMetaPB, so they default to 0 when read back. + // Pass UINT32_MAX to DataTypeDecimalV2 to signal that the original + // precision/scale are unknown; otherwise check_type_precision(0) throws + // "meet invalid precision: real_precision=0". + UInt32 orig_precision = + meta_precision > 0 ? static_cast(meta_precision) : UINT32_MAX; + UInt32 orig_scale = meta_precision > 0 ? static_cast(meta_scale) : UINT32_MAX; + nested = _create_primitive_data_type(static_cast(pcolumn.type()), + orig_precision, orig_scale, -1); + } else { + nested = _create_primitive_data_type(static_cast(pcolumn.type()), + meta_precision, meta_scale, -1); + } } if (pcolumn.is_nullable() && nested) { diff --git a/be/test/core/data_type/data_type_decimal_test.cpp b/be/test/core/data_type/data_type_decimal_test.cpp index 312924b5cee2a6..952f8acb3662a8 100644 --- a/be/test/core/data_type/data_type_decimal_test.cpp +++ b/be/test/core/data_type/data_type_decimal_test.cpp @@ -28,16 +28,24 @@ #include #include +#include "agent/be_exec_version_manager.h" #include "common/exception.h" #include "core/assert_cast.h" #include "core/column/column.h" #include "core/data_type/common_data_type_serder_test.h" #include "core/data_type/common_data_type_test.h" #include "core/data_type/data_type.h" +#include "core/data_type/data_type_agg_state.h" +#include "core/data_type/data_type_array.h" #include "core/data_type/data_type_factory.hpp" +#include "core/data_type/data_type_map.h" +#include "core/data_type/data_type_nullable.h" #include "core/data_type/data_type_number.h" +#include "core/data_type/data_type_struct.h" #include "core/field.h" #include "core/types.h" +#include "gen_cpp/segment_v2.pb.h" +#include "storage/tablet/tablet_schema.h" #include "testutil/test_util.h" namespace doris { @@ -446,6 +454,177 @@ TEST_F(DataTypeDecimalTest, ser_deser) { test_func(dt_decimal256_2, *column_decimal256_2, BeExecVersionManager::max_be_exec_version); test_func(dt_decimal256_3, *column_decimal256_3, BeExecVersionManager::max_be_exec_version); } +// Regression for legacy DecimalV2 segments written by Doris < 2.1.0 (before +// #26572): ColumnMetaPB.precision/frac are absent and default to 0 when read +// back. DataTypeFactory must not pass 0 as the original precision/scale to +// DataTypeDecimalV2, otherwise check_type_precision throws +// "meet invalid precision: real_precision=0". +TEST_F(DataTypeDecimalTest, create_decimalv2_from_legacy_tablet_column) { + // Case 1: legacy segment — precision/frac missing (default 0) + { + TabletColumn col; + col.set_type(FieldType::OLAP_FIELD_TYPE_DECIMAL); + // intentionally do not set precision/frac to mimic old segments + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(col, false)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_DECIMALV2); + const auto* decv2 = assert_cast(dt.get()); + EXPECT_EQ(decv2->get_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_scale(), BeConsts::MAX_DECIMALV2_SCALE); + // When original precision/scale are unknown, get_original_* should fall + // back to the in-memory (27, 9) representation. + EXPECT_EQ(decv2->get_original_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_original_scale(), BeConsts::MAX_DECIMALV2_SCALE); + } + // Case 2: new segment — precision/frac are persisted (e.g. decimal(26,6)) + { + TabletColumn col; + col.set_type(FieldType::OLAP_FIELD_TYPE_DECIMAL); + col.set_precision(26); + col.set_frac(6); + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(col, false)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_DECIMALV2); + const auto* decv2 = assert_cast(dt.get()); + EXPECT_EQ(decv2->get_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_scale(), BeConsts::MAX_DECIMALV2_SCALE); + EXPECT_EQ(decv2->get_original_precision(), 26U); + EXPECT_EQ(decv2->get_original_scale(), 6U); + } + // Case 3: same regression via the segment-read path that consumes + // ColumnMetaPB directly (this is the exact code path that broke after + // PR #35222 when reading old DecimalV2 segments). + { + segment_v2::ColumnMetaPB meta; + meta.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_DECIMAL)); + // precision/frac intentionally unset -> default 0 + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(meta)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_DECIMALV2); + const auto* decv2 = assert_cast(dt.get()); + EXPECT_EQ(decv2->get_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_scale(), BeConsts::MAX_DECIMALV2_SCALE); + EXPECT_EQ(decv2->get_original_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_original_scale(), BeConsts::MAX_DECIMALV2_SCALE); + } +} + +// Regression for complex types wrapping legacy DecimalV2 children whose +// segment ColumnMetaPB.precision/frac default to 0. The fix in +// DataTypeFactory::create_data_type(segment_v2::ColumnMetaPB&) must +// propagate through Array / Map / Struct / AggState recursively. +TEST_F(DataTypeDecimalTest, create_complex_types_with_legacy_decimalv2) { + auto make_legacy_decv2_meta = []() { + segment_v2::ColumnMetaPB child; + child.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_DECIMAL)); + // precision/frac intentionally unset -> default 0 + return child; + }; + auto unwrap_nullable = [](const DataTypePtr& dt) -> DataTypePtr { + if (dt && dt->is_nullable()) { + return assert_cast(dt.get())->get_nested_type(); + } + return dt; + }; + + // Array + { + segment_v2::ColumnMetaPB meta; + meta.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_ARRAY)); + *meta.add_children_columns() = make_legacy_decv2_meta(); + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(meta)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_ARRAY); + const auto* arr = assert_cast(dt.get()); + auto elem = unwrap_nullable(arr->get_nested_type()); + EXPECT_EQ(elem->get_primitive_type(), TYPE_DECIMALV2); + const auto* decv2 = assert_cast(elem.get()); + EXPECT_EQ(decv2->get_original_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_original_scale(), BeConsts::MAX_DECIMALV2_SCALE); + } + + // Map + { + segment_v2::ColumnMetaPB meta; + meta.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_MAP)); + auto* k = meta.add_children_columns(); + k->set_type(static_cast(FieldType::OLAP_FIELD_TYPE_INT)); + *meta.add_children_columns() = make_legacy_decv2_meta(); + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(meta)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_MAP); + const auto* m = assert_cast(dt.get()); + EXPECT_EQ(unwrap_nullable(m->get_key_type())->get_primitive_type(), TYPE_INT); + auto v = unwrap_nullable(m->get_value_type()); + EXPECT_EQ(v->get_primitive_type(), TYPE_DECIMALV2); + const auto* decv2 = assert_cast(v.get()); + EXPECT_EQ(decv2->get_original_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + } + + // Struct + { + segment_v2::ColumnMetaPB meta; + meta.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_STRUCT)); + *meta.add_children_columns() = make_legacy_decv2_meta(); + auto* second = meta.add_children_columns(); + second->set_type(static_cast(FieldType::OLAP_FIELD_TYPE_INT)); + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(meta)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_STRUCT); + const auto* s = assert_cast(dt.get()); + ASSERT_EQ(s->get_elements().size(), 2u); + EXPECT_EQ(unwrap_nullable(s->get_element(0))->get_primitive_type(), TYPE_DECIMALV2); + EXPECT_EQ(unwrap_nullable(s->get_element(1))->get_primitive_type(), TYPE_INT); + } + + // Nested: Array> + { + segment_v2::ColumnMetaPB meta; + meta.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_ARRAY)); + auto* map_child = meta.add_children_columns(); + map_child->set_type(static_cast(FieldType::OLAP_FIELD_TYPE_MAP)); + auto* k = map_child->add_children_columns(); + k->set_type(static_cast(FieldType::OLAP_FIELD_TYPE_INT)); + *map_child->add_children_columns() = make_legacy_decv2_meta(); + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(meta)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_ARRAY); + auto inner = + unwrap_nullable(assert_cast(dt.get())->get_nested_type()); + EXPECT_EQ(inner->get_primitive_type(), TYPE_MAP); + auto v = unwrap_nullable(assert_cast(inner.get())->get_value_type()); + EXPECT_EQ(v->get_primitive_type(), TYPE_DECIMALV2); + } + + // AggState (count) with legacy DecimalV2 sub-type + { + segment_v2::ColumnMetaPB meta; + meta.set_type(static_cast(FieldType::OLAP_FIELD_TYPE_AGG_STATE)); + meta.set_function_name("count"); + meta.set_result_is_nullable(false); + meta.set_be_exec_version(BeExecVersionManager::get_newest_version()); + *meta.add_children_columns() = make_legacy_decv2_meta(); + DataTypePtr dt; + EXPECT_NO_THROW(dt = DataTypeFactory::instance().create_data_type(meta)); + ASSERT_NE(dt, nullptr); + EXPECT_EQ(dt->get_primitive_type(), TYPE_AGG_STATE); + const auto* agg = assert_cast(dt.get()); + ASSERT_EQ(agg->get_sub_types().size(), 1u); + auto sub = unwrap_nullable(agg->get_sub_types()[0]); + EXPECT_EQ(sub->get_primitive_type(), TYPE_DECIMALV2); + const auto* decv2 = assert_cast(sub.get()); + EXPECT_EQ(decv2->get_original_precision(), BeConsts::MAX_DECIMALV2_PRECISION); + EXPECT_EQ(decv2->get_original_scale(), BeConsts::MAX_DECIMALV2_SCALE); + } +} + TEST_F(DataTypeDecimalTest, to_pb_column_meta) { auto test_func = [](auto dt, PGenericType_TypeId expected_type) { auto col_meta = std::make_shared();