This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 0cf08938ab2 [fix](be) Fix missing decimal precision and scale for 
legacy decimal data (#65419)
0cf08938ab2 is described below

commit 0cf08938ab2d3cede6a9136d1bea45d2b1fd9638
Author: TengJianPing <[email protected]>
AuthorDate: Mon Jul 13 10:27:00 2026 +0800

    [fix](be) Fix missing decimal precision and scale for legacy decimal data 
(#65419)
    
    ### What problem does this PR solve?
    
    Issue Number: close #xxx
    
    Related PR: #26572, #63569
    
    Problem Summary:
    
    Segments written before decimal ColumnMetaPB precision and scale were
    persisted can miss precision and scale for decimal types in metadata .
    Regular columns, complex decimal children may then build decimal
    DataType instances without the logical precision/scale from
    TabletSchema, and result in error "Wrong precision 0, min: 1, max: 76".
    This change centralizes decimal metadata recovery from TabletSchema,
    applies it before ColumnReader/DataType creation.
    
    ### Release note
    
    Fix reading legacy decimal whose segment metadata is missing decimal
    precision and scale.
    
    ### Check List (For Author)
    
    - Test <!-- At least one of them must be included. -->
        - [ ] Regression test
        - [ ] Unit Test
        - [ ] Manual test (add detailed scripts or steps below)
        - [ ] No need to test or manual test. Explain why:
    - [ ] This is a refactor/code format and no logic has been changed.
            - [ ] Previous test can cover this change.
            - [ ] No code files have been changed.
            - [ ] Other reason <!-- Add your reason?  -->
    
    - Behavior changed:
        - [ ] No.
        - [ ] Yes. <!-- Explain the behavior change -->
    
    - Does this need documentation?
        - [ ] No.
    - [ ] Yes. <!-- Add document PR link here. eg:
    https://github.com/apache/doris-website/pull/1214 -->
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label <!-- Add branch pick label that this PR
    should merge into -->
---
 be/src/core/data_type/data_type_decimal.h          |  13 +-
 be/src/core/data_type/data_type_factory.cpp        |  19 +-
 .../index/inverted/inverted_index_iterator.cpp     |   2 +-
 be/src/storage/olap_common.h                       |   8 +
 be/src/storage/segment/column_reader.cpp           |   2 +-
 be/src/storage/segment/segment.cpp                 |  47 +++++
 be/src/storage/utils.h                             |  26 ---
 be/test/core/data_type/data_type_decimal_test.cpp  | 170 ---------------
 .../storage/segment/column_reader_cache_test.cpp   | 231 ++++++++++++++++++++-
 be/test/storage/segment/mock/mock_segment.h        |  21 +-
 10 files changed, 315 insertions(+), 224 deletions(-)

diff --git a/be/src/core/data_type/data_type_decimal.h 
b/be/src/core/data_type/data_type_decimal.h
index 0aac381d16f..69fae16be82 100644
--- a/be/src/core/data_type/data_type_decimal.h
+++ b/be/src/core/data_type/data_type_decimal.h
@@ -207,10 +207,15 @@ public:
 
     static constexpr size_t max_precision() { return 
max_decimal_precision<T>(); }
 
-    DataTypeDecimal(UInt32 arg_precision = max_decimal_precision<T>(),
-                    UInt32 arg_scale = default_decimal_scale<T>(),
-                    UInt32 arg_original_precision = UINT32_MAX,
-                    UInt32 arg_original_scale = UINT32_MAX)
+    DataTypeDecimal(
+            UInt32 arg_precision = max_decimal_precision<T>(),
+            UInt32 arg_scale = default_decimal_scale<T>(),
+            // For decimalv2 only, record the original(schema) precision and 
scale.
+            // UINT32_MAX means original precision and scale are unknown.
+            // Decimalv2 will be converted to Decimal(27, 9) in memory when 
doing any calculations,
+            // but when casting decimalv2 to string, it's better to keep the 
presion and
+            // scale of it's original value in schema.
+            UInt32 arg_original_precision = UINT32_MAX, UInt32 
arg_original_scale = UINT32_MAX)
             : DecimalScaleInfo<T>(arg_precision, arg_scale, 
arg_original_precision,
                                   arg_original_scale) {}
 
diff --git a/be/src/core/data_type/data_type_factory.cpp 
b/be/src/core/data_type/data_type_factory.cpp
index ab6551d23ad..256493cba04 100644
--- a/be/src/core/data_type/data_type_factory.cpp
+++ b/be/src/core/data_type/data_type_factory.cpp
@@ -379,23 +379,8 @@ DataTypePtr DataTypeFactory::create_data_type(const 
segment_v2::ColumnMetaPB& pc
         nested = std::make_shared<DataTypeStruct>(dataTypes, names);
     } else {
         // TODO add precision and frac
-        auto meta_precision = pcolumn.precision();
-        auto meta_scale = pcolumn.frac();
-        if (pcolumn.type() == 
static_cast<int>(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<UInt32>(meta_precision) : 
UINT32_MAX;
-            UInt32 orig_scale = meta_precision > 0 ? 
static_cast<UInt32>(meta_scale) : UINT32_MAX;
-            nested = 
_create_primitive_data_type(static_cast<FieldType>(pcolumn.type()),
-                                                 orig_precision, orig_scale, 
-1);
-        } else {
-            nested = 
_create_primitive_data_type(static_cast<FieldType>(pcolumn.type()),
-                                                 meta_precision, meta_scale, 
-1);
-        }
+        nested = 
_create_primitive_data_type(static_cast<FieldType>(pcolumn.type()),
+                                             pcolumn.precision(), 
pcolumn.frac(), -1);
     }
 
     if (pcolumn.is_nullable() && nested) {
diff --git a/be/src/storage/index/inverted/inverted_index_iterator.cpp 
b/be/src/storage/index/inverted/inverted_index_iterator.cpp
index 936b82d5d56..6e93d70d025 100644
--- a/be/src/storage/index/inverted/inverted_index_iterator.cpp
+++ b/be/src/storage/index/inverted/inverted_index_iterator.cpp
@@ -273,7 +273,7 @@ Result<InvertedIndexReaderPtr> 
InvertedIndexIterator::select_best_reader(
         return select_for_text(match, query_type, normalized_key);
     }
 
-    if (is_numeric_type(field_type)) {
+    if (field_is_numeric_type(field_type)) {
         return select_for_numeric(match, query_type);
     }
 
diff --git a/be/src/storage/olap_common.h b/be/src/storage/olap_common.h
index 12318a02905..b34f6ce15ea 100644
--- a/be/src/storage/olap_common.h
+++ b/be/src/storage/olap_common.h
@@ -199,6 +199,14 @@ constexpr bool field_is_slice_type(const FieldType& 
field_type) {
            field_type == FieldType::OLAP_FIELD_TYPE_STRING;
 }
 
+constexpr bool field_is_decimal_type(const FieldType& field_type) {
+    return field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL ||
+           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL32 ||
+           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL64 ||
+           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL128I ||
+           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL256;
+}
+
 constexpr bool field_is_numeric_type(const FieldType& field_type) {
     return field_type == FieldType::OLAP_FIELD_TYPE_INT ||
            field_type == FieldType::OLAP_FIELD_TYPE_UNSIGNED_INT ||
diff --git a/be/src/storage/segment/column_reader.cpp 
b/be/src/storage/segment/column_reader.cpp
index f15e7462e6c..2021793be20 100644
--- a/be/src/storage/segment/column_reader.cpp
+++ b/be/src/storage/segment/column_reader.cpp
@@ -740,7 +740,7 @@ Status ColumnReader::_load_index(const 
std::shared_ptr<IndexFileReader>& index_f
                         "create StringTypeInvertedIndexReader error: {}", 
e.what());
             }
         }
-    } else if (is_numeric_type(type)) {
+    } else if (field_is_numeric_type(type)) {
         try {
             index_reader = BkdIndexReader::create_shared(index_meta, 
index_file_reader);
         } catch (const CLuceneError& e) {
diff --git a/be/src/storage/segment/segment.cpp 
b/be/src/storage/segment/segment.cpp
index 251452babe5..054f21cec6f 100644
--- a/be/src/storage/segment/segment.cpp
+++ b/be/src/storage/segment/segment.cpp
@@ -144,6 +144,49 @@ Status build_segment_zonemap_context(Segment* segment, 
const Schema& schema,
     return Status::OK();
 }
 
+void fill_missing_decimal_precision(const TabletColumn& column, ColumnMetaPB* 
meta) {
+    auto meta_type = static_cast<FieldType>(meta->type());
+    if (meta_type != column.type()) {
+        return;
+    }
+
+    if (field_is_decimal_type(meta_type)) {
+        if ((!meta->has_precision() || meta->precision() <= 0) && 
column.precision() > 0) {
+            meta->set_precision(column.precision());
+        }
+        if ((!meta->has_frac() || meta->frac() < 0) && column.frac() >= 0) {
+            meta->set_frac(column.frac());
+        }
+    }
+
+    // Complex column meta may also include storage helper children, such as
+    // array offsets. Only schema children have matching TabletColumn subtypes.
+    int child_count =
+            std::min(meta->children_columns_size(), 
static_cast<int>(column.get_subtype_count()));
+    for (int i = 0; i < child_count; ++i) {
+        fill_missing_decimal_precision(column.get_sub_column(i), 
meta->mutable_children_columns(i));
+    }
+}
+
+void fill_missing_decimal_precision_from_schema(const TabletSchemaSPtr& 
tablet_schema,
+                                                ColumnMetaPB* meta) {
+    if (!meta->has_unique_id()) {
+        return;
+    }
+    int32_t col_idx = 
tablet_schema->field_index(static_cast<int32_t>(meta->unique_id()));
+    if (col_idx < 0) {
+        return;
+    }
+    fill_missing_decimal_precision(tablet_schema->column(col_idx), meta);
+}
+
+void fill_footer_missing_decimal_precision(const TabletSchemaSPtr& 
tablet_schema,
+                                           SegmentFooterPB* footer) {
+    for (int i = 0; i < footer->columns_size(); ++i) {
+        fill_missing_decimal_precision_from_schema(tablet_schema, 
footer->mutable_columns(i));
+    }
+}
+
 } // namespace
 
 Status Segment::open(io::FileSystemSPtr fs, const std::string& path, int64_t 
tablet_id,
@@ -604,6 +647,10 @@ Status 
Segment::_parse_footer(std::shared_ptr<SegmentFooterPB>& footer,
                 _file_reader->path().native(), file_size,
                 file_cache_key_str(_file_reader->path().native()));
     }
+    // Segments written before #26572 do not persist decimal precision/frac in
+    // ColumnMetaPB, so recover the logical p/s from TabletSchema before
+    // ColumnReader builds DataTypeDecimal.
+    fill_footer_missing_decimal_precision(_tablet_schema, footer.get());
 
     VLOG_DEBUG << fmt::format("Loading segment footer from {} finished",
                               _file_reader->path().native());
diff --git a/be/src/storage/utils.h b/be/src/storage/utils.h
index 9772be32589..69c7ddbff00 100644
--- a/be/src/storage/utils.h
+++ b/be/src/storage/utils.h
@@ -185,32 +185,6 @@ constexpr bool is_string_type(const FieldType& field_type) 
{
            field_type == FieldType::OLAP_FIELD_TYPE_STRING;
 }
 
-constexpr bool is_numeric_type(const FieldType& field_type) {
-    return field_type == FieldType::OLAP_FIELD_TYPE_INT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_UNSIGNED_INT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_BIGINT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_SMALLINT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_UNSIGNED_TINYINT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_UNSIGNED_SMALLINT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_TINYINT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DOUBLE ||
-           field_type == FieldType::OLAP_FIELD_TYPE_FLOAT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DATE ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DATEV2 ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DATETIME ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DATETIMEV2 ||
-           field_type == FieldType::OLAP_FIELD_TYPE_TIMESTAMPTZ ||
-           field_type == FieldType::OLAP_FIELD_TYPE_LARGEINT ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL32 ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL64 ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL128I ||
-           field_type == FieldType::OLAP_FIELD_TYPE_DECIMAL256 ||
-           field_type == FieldType::OLAP_FIELD_TYPE_BOOL ||
-           field_type == FieldType::OLAP_FIELD_TYPE_IPV4 ||
-           field_type == FieldType::OLAP_FIELD_TYPE_IPV6;
-}
-
 // Util used to get string name of thrift enum item
 #define EnumToString(enum_type, index, out)                   \
     do {                                                      \
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 952f8acb366..64108bb31e7 100644
--- a/be/test/core/data_type/data_type_decimal_test.cpp
+++ b/be/test/core/data_type/data_type_decimal_test.cpp
@@ -454,176 +454,6 @@ 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<const DataTypeDecimalV2*>(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<const DataTypeDecimalV2*>(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<int>(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<const DataTypeDecimalV2*>(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<int>(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<const 
DataTypeNullable*>(dt.get())->get_nested_type();
-        }
-        return dt;
-    };
-
-    // Array<DecimalV2>
-    {
-        segment_v2::ColumnMetaPB meta;
-        meta.set_type(static_cast<int>(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<const DataTypeArray*>(dt.get());
-        auto elem = unwrap_nullable(arr->get_nested_type());
-        EXPECT_EQ(elem->get_primitive_type(), TYPE_DECIMALV2);
-        const auto* decv2 = assert_cast<const DataTypeDecimalV2*>(elem.get());
-        EXPECT_EQ(decv2->get_original_precision(), 
BeConsts::MAX_DECIMALV2_PRECISION);
-        EXPECT_EQ(decv2->get_original_scale(), BeConsts::MAX_DECIMALV2_SCALE);
-    }
-
-    // Map<Int, DecimalV2>
-    {
-        segment_v2::ColumnMetaPB meta;
-        meta.set_type(static_cast<int>(FieldType::OLAP_FIELD_TYPE_MAP));
-        auto* k = meta.add_children_columns();
-        k->set_type(static_cast<int>(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<const DataTypeMap*>(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<const DataTypeDecimalV2*>(v.get());
-        EXPECT_EQ(decv2->get_original_precision(), 
BeConsts::MAX_DECIMALV2_PRECISION);
-    }
-
-    // Struct<DecimalV2, Int>
-    {
-        segment_v2::ColumnMetaPB meta;
-        meta.set_type(static_cast<int>(FieldType::OLAP_FIELD_TYPE_STRUCT));
-        *meta.add_children_columns() = make_legacy_decv2_meta();
-        auto* second = meta.add_children_columns();
-        second->set_type(static_cast<int>(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<const DataTypeStruct*>(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<Map<Int, DecimalV2>>
-    {
-        segment_v2::ColumnMetaPB meta;
-        meta.set_type(static_cast<int>(FieldType::OLAP_FIELD_TYPE_ARRAY));
-        auto* map_child = meta.add_children_columns();
-        map_child->set_type(static_cast<int>(FieldType::OLAP_FIELD_TYPE_MAP));
-        auto* k = map_child->add_children_columns();
-        k->set_type(static_cast<int>(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<const 
DataTypeArray*>(dt.get())->get_nested_type());
-        EXPECT_EQ(inner->get_primitive_type(), TYPE_MAP);
-        auto v = unwrap_nullable(assert_cast<const 
DataTypeMap*>(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<int>(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<const DataTypeAggState*>(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<const DataTypeDecimalV2*>(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) {
diff --git a/be/test/storage/segment/column_reader_cache_test.cpp 
b/be/test/storage/segment/column_reader_cache_test.cpp
index da8aa13d3bf..e080e5f6687 100644
--- a/be/test/storage/segment/column_reader_cache_test.cpp
+++ b/be/test/storage/segment/column_reader_cache_test.cpp
@@ -16,24 +16,32 @@
 // 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 {
@@ -42,6 +50,25 @@ 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:
@@ -69,6 +96,9 @@ protected:
     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>>();
@@ -94,6 +124,7 @@ protected:
     void TearDown() override {
         _cache.reset();
         _mock_segment.reset();
+        
static_cast<void>(io::global_local_filesystem()->delete_directory(kTestDir));
     }
 
     void setup_basic_mocks() {
@@ -132,10 +163,36 @@ protected:
         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
@@ -490,4 +547,176 @@ TEST_F(ColumnReaderCacheTest, EmptyPath) {
     EXPECT_TRUE(status.is<ErrorCode::NOT_FOUND>());
 }
 
-} // namespace doris::segment_v2
\ No newline at end of file
+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
diff --git a/be/test/storage/segment/mock/mock_segment.h 
b/be/test/storage/segment/mock/mock_segment.h
index 6c715a53dee..1c145a55827 100644
--- a/be/test/storage/segment/mock/mock_segment.h
+++ b/be/test/storage/segment/mock/mock_segment.h
@@ -21,7 +21,9 @@
 #include <gmock/gmock.h>
 
 #include <memory>
+#include <string>
 #include <unordered_map>
+#include <utility>
 
 #include "io/fs/file_reader.h"
 #include "storage/segment/segment.h"
@@ -49,13 +51,24 @@ public:
 
     // Helper methods for test setup
     void add_column_uid_mapping(int32_t col_uid, int32_t footer_ordinal) {
-        _tablet_schema->_cols.push_back(std::make_shared<TabletColumn>());
-        _tablet_schema->_cols.back()->set_unique_id(col_uid);
-        _tablet_schema->_field_uniqueid_to_index[col_uid] = footer_ordinal;
+        (void)footer_ordinal;
+        TabletColumn column;
+        column.set_unique_id(col_uid);
+        column.set_name(std::to_string(col_uid));
+        _tablet_schema->append_column(column);
     }
 
     void set_footer(std::shared_ptr<SegmentFooterPB> footer) { _footer = 
footer; }
 
+    void set_file_reader_for_test(io::FileReaderSPtr file_reader) {
+        _file_reader = std::move(file_reader);
+    }
+
+    Status parse_footer_for_test(std::shared_ptr<SegmentFooterPB>& footer,
+                                 OlapReaderStatistics* stats = nullptr) {
+        return _parse_footer(footer, stats);
+    }
+
     void set_column_raw_data_bytes(int32_t uid, uint64_t bytes) {
         _column_uid_to_raw_bytes[uid] = bytes;
     }
@@ -68,4 +81,4 @@ public:
     friend class ColumnReaderCache;
 };
 
-} // namespace doris::segment_v2
\ No newline at end of file
+} // namespace doris::segment_v2


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to