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

morningman pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 57409f84179 [fix](be) Fix missing decimal precision and scale for 
legacy decimal data (#65419) (#65521)
57409f84179 is described below

commit 57409f841792e1d8bdc368ef2028bf688d881d3c
Author: TengJianPing <[email protected]>
AuthorDate: Thu Jul 16 11:17:00 2026 +0800

    [fix](be) Fix missing decimal precision and scale for legacy decimal data 
(#65419) (#65521)
    
    Issue Number: close #xxx
    
    Pick #65419
---
 be/src/olap/olap_common.h                          |   8 +
 be/src/olap/rowset/segment_v2/column_reader.cpp    |   2 +-
 .../rowset/segment_v2/inverted_index_iterator.cpp  |   2 +-
 be/src/olap/rowset/segment_v2/segment.cpp          |  52 +++++
 be/src/olap/utils.h                                |  26 ---
 be/src/vec/data_types/data_type_decimal.h          |  13 +-
 be/src/vec/data_types/data_type_factory.cpp        |  19 +-
 .../rowset/segment_v2/column_reader_cache_test.cpp | 241 ++++++++++++++++++++-
 be/test/olap/rowset/segment_v2/mock/mock_segment.h |  21 +-
 be/test/vec/data_types/data_type_decimal_test.cpp  | 170 ---------------
 10 files changed, 329 insertions(+), 225 deletions(-)

diff --git a/be/src/olap/olap_common.h b/be/src/olap/olap_common.h
index 6ffa1b95e1c..edf1b70dabc 100644
--- a/be/src/olap/olap_common.h
+++ b/be/src/olap/olap_common.h
@@ -195,6 +195,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/olap/rowset/segment_v2/column_reader.cpp 
b/be/src/olap/rowset/segment_v2/column_reader.cpp
index 92271d55ccf..63313ac4458 100644
--- a/be/src/olap/rowset/segment_v2/column_reader.cpp
+++ b/be/src/olap/rowset/segment_v2/column_reader.cpp
@@ -721,7 +721,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/olap/rowset/segment_v2/inverted_index_iterator.cpp 
b/be/src/olap/rowset/segment_v2/inverted_index_iterator.cpp
index 9b3665904cb..4fc85d7dd22 100644
--- a/be/src/olap/rowset/segment_v2/inverted_index_iterator.cpp
+++ b/be/src/olap/rowset/segment_v2/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/olap/rowset/segment_v2/segment.cpp 
b/be/src/olap/rowset/segment_v2/segment.cpp
index 738db4fd859..a0cf70d36b0 100644
--- a/be/src/olap/rowset/segment_v2/segment.cpp
+++ b/be/src/olap/rowset/segment_v2/segment.cpp
@@ -23,6 +23,7 @@
 #include <gen_cpp/olap_file.pb.h>
 #include <gen_cpp/segment_v2.pb.h>
 
+#include <algorithm>
 #include <cstring>
 #include <memory>
 #include <sstream>
@@ -84,6 +85,53 @@ namespace doris::segment_v2 {
 
 class InvertedIndexIterator;
 
+namespace {
+
+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,
                      uint32_t segment_id, RowsetId rowset_id, TabletSchemaSPtr 
tablet_schema,
                      const io::FileReaderOptions& reader_options, 
std::shared_ptr<Segment>* output,
@@ -479,6 +527,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/olap/utils.h b/be/src/olap/utils.h
index 4423ed6c216..bc6abf6058d 100644
--- a/be/src/olap/utils.h
+++ b/be/src/olap/utils.h
@@ -182,32 +182,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/src/vec/data_types/data_type_decimal.h 
b/be/src/vec/data_types/data_type_decimal.h
index da34beb754e..9e655e91323 100644
--- a/be/src/vec/data_types/data_type_decimal.h
+++ b/be/src/vec/data_types/data_type_decimal.h
@@ -125,10 +125,15 @@ public:
 
     static constexpr size_t max_precision() { return 
max_decimal_precision<T>(); }
 
-    DataTypeDecimal(UInt32 precision = max_decimal_precision<T>(),
-                    UInt32 scale = default_decimal_scale<T>(),
-                    UInt32 arg_original_precision = UINT32_MAX,
-                    UInt32 arg_original_scale = UINT32_MAX)
+    DataTypeDecimal(
+            UInt32 precision = max_decimal_precision<T>(),
+            UInt32 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)
             : precision(precision),
               scale(scale),
               original_precision(arg_original_precision),
diff --git a/be/src/vec/data_types/data_type_factory.cpp 
b/be/src/vec/data_types/data_type_factory.cpp
index e6bfadd32a4..5849781a2e5 100644
--- a/be/src/vec/data_types/data_type_factory.cpp
+++ b/be/src/vec/data_types/data_type_factory.cpp
@@ -385,23 +385,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/test/olap/rowset/segment_v2/column_reader_cache_test.cpp 
b/be/test/olap/rowset/segment_v2/column_reader_cache_test.cpp
index 75afa597302..b0c2bcbf75a 100644
--- a/be/test/olap/rowset/segment_v2/column_reader_cache_test.cpp
+++ b/be/test/olap/rowset/segment_v2/column_reader_cache_test.cpp
@@ -16,23 +16,33 @@
 // under the License.
 #include "olap/rowset/segment_v2/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 "gen_cpp/segment_v2.pb.h"
 #include "io/fs/file_reader.h"
+#include "io/fs/local_file_system.h"
 #include "mock/mock_segment.h"
 #include "olap/rowset/segment_v2/column_meta_accessor.h"
 #include "olap/rowset/segment_v2/column_reader.h"
+#include "olap/rowset/segment_v2/encoding_info.h"
 #include "olap/rowset/segment_v2/segment.h"
+#include "olap/rowset/segment_v2/segment_writer.h"
 #include "olap/rowset/segment_v2/variant/variant_column_reader.h"
 #include "olap/tablet_schema.h"
+#include "util/coding.h"
+#include "vec/common/assert_cast.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_decimal.h"
+#include "vec/data_types/data_type_nullable.h"
 #include "vec/json/path_in_data.h"
 
 namespace doris::segment_v2 {
@@ -40,6 +50,28 @@ namespace doris::segment_v2 {
 using ::testing::_;
 using ::testing::Return;
 using ::testing::StrictMock;
+using vectorized::DataTypeArray;
+using vectorized::DataTypeDecimalV2;
+using vectorized::DataTypeNullable;
+
+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 {
@@ -60,6 +92,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>>();
@@ -85,6 +120,7 @@ protected:
     void TearDown() override {
         _cache.reset();
         _mock_segment.reset();
+        
static_cast<void>(io::global_local_filesystem()->delete_directory(kTestDir));
     }
 
     void setup_basic_mocks() {
@@ -123,10 +159,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
@@ -481,4 +543,179 @@ 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(
+            
EncodingInfo::get_default_encoding(static_cast<FieldType>(col_meta.type()), {}, 
false));
+    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(EncodingInfo::get_default_encoding(
+            static_cast<FieldType>(item_meta->type()), {}, false));
+    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(EncodingInfo::get_default_encoding(
+            static_cast<FieldType>(offset_meta->type()), {}, false));
+    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 =
+            assert_cast<const 
DataTypeDecimalV2*>(array_type->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(
+            
EncodingInfo::get_default_encoding(static_cast<FieldType>(col_meta.type()), {}, 
false));
+    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(EncodingInfo::get_default_encoding(
+            static_cast<FieldType>(item_meta->type()), {}, false));
+    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(EncodingInfo::get_default_encoding(
+            static_cast<FieldType>(offset_meta->type()), {}, false));
+    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/olap/rowset/segment_v2/mock/mock_segment.h 
b/be/test/olap/rowset/segment_v2/mock/mock_segment.h
index 99bc363e010..9684ceb09ad 100644
--- a/be/test/olap/rowset/segment_v2/mock/mock_segment.h
+++ b/be/test/olap/rowset/segment_v2/mock/mock_segment.h
@@ -20,7 +20,9 @@
 #include <gmock/gmock.h>
 
 #include <memory>
+#include <string>
 #include <unordered_map>
+#include <utility>
 
 #include "gen_cpp/segment_v2.pb.h"
 #include "io/fs/file_reader.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);
+    }
+
     std::shared_ptr<SegmentFooterPB> get_footer() const { return _footer; }
 
     std::shared_ptr<SegmentFooterPB> _footer;
@@ -64,4 +77,4 @@ public:
     friend class ColumnReaderCache;
 };
 
-} // namespace doris::segment_v2
\ No newline at end of file
+} // namespace doris::segment_v2
diff --git a/be/test/vec/data_types/data_type_decimal_test.cpp 
b/be/test/vec/data_types/data_type_decimal_test.cpp
index c462d0414e3..3cf0b54ce07 100644
--- a/be/test/vec/data_types/data_type_decimal_test.cpp
+++ b/be/test/vec/data_types/data_type_decimal_test.cpp
@@ -479,176 +479,6 @@ TEST_F(DataTypeDecimalTest, ser_deser) {
     test_func(dt_decimal256_3, *column_decimal256_3, USE_CONST_SERDE);
     test_func(dt_decimal256_3, *column_decimal256_3, AGGREGATION_2_1_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) {


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


Reply via email to