Gabriel39 commented on code in PR #68649:
URL: https://github.com/apache/doris/pull/68649#discussion_r4135169722


##########
be/src/core/data_type_serde/data_type_datev2_serde.cpp:
##########
@@ -217,13 +236,12 @@ Status DataTypeDateV2SerDe::write_column_to_arrow(const 
IColumn& column, const N
     const auto& col_data = static_cast<const ColumnDateV2&>(column).get_data();
     auto& date32_builder = assert_cast<arrow::Date32Builder&>(*array_builder);
     for (size_t i = start; i < end; ++i) {
-        auto daynr = col_data[i].daynr() - date_threshold;
         if (null_map && (*null_map)[i]) {
             RETURN_IF_ERROR(checkArrowStatus(date32_builder.AppendNull(), 
column, *array_builder));
         } else {
-            RETURN_IF_ERROR(
-                    checkArrowStatus(date32_builder.Append(cast_set<int, 
int64_t, false>(daynr)),
-                                     column, *array_builder));
+            RETURN_IF_ERROR(checkArrowStatus(
+                    
date32_builder.Append(daynr_to_epoch_days(col_data[i].daynr())), column,

Review Comment:
   Fixed in 0842fe5d56e. Both DATE day and bucket partition transforms now use 
daynr_to_epoch_days, matching the Arrow/Parquet payload. Added a unit test for 
the year-zero boundary and epoch controls, plus a Spark/Iceberg partitioned 
write/read regression that checks partition tuples and selective day/bucket 
predicates. The unit test passes; the external regression compiles and awaits 
execution in external CI.



##########
be/src/format_v2/orc/orc_reader.cpp:
##########
@@ -487,10 +487,26 @@ bool set_date_zone_map(const ::orc::ColumnStatistics& 
statistics, segment_v2::Zo
         !date_statistics->hasMaximum()) {
         return false;
     }
-    auto& date_dict = date_day_offset_dict::get();
-    return set_validated_zone_map(
-            
Field::create_field<TYPE_DATEV2>(date_dict[date_statistics->getMinimum()]),
-            
Field::create_field<TYPE_DATEV2>(date_dict[date_statistics->getMaximum()]), 
zone_map);
+    // ORC DATE statistics are proleptic-Gregorian day ordinals, the same 
domain the row decoder
+    // (DataTypeDateV2SerDe::read_column_from_orc) interprets. Converting them 
through
+    // `date_day_offset_dict` instead would put the year-zero window one day 
off the rows and let a
+    // pushed-down MIN/MAX report a value no row holds. A bound with no Doris 
DATE disables the
+    // statistics, so MIN/MAX falls back to scanning rows.
+    const auto to_date = [](int64_t epoch_days) -> 
std::optional<DateV2Value<DateV2ValueType>> {
+        const int64_t daynr = epoch_days_to_daynr(epoch_days);
+        DateV2Value<DateV2ValueType> value;
+        if (daynr == 0 || 
!value.get_date_from_daynr(static_cast<uint64_t>(daynr))) {
+            return std::nullopt;
+        }
+        return value;
+    };
+    const auto min_value = to_date(date_statistics->getMinimum());

Review Comment:
   Fixed in 0842fe5d56e. Added a shared representability check for the entire 
raw DATE interval, including the Gregorian-only leap day. ORC and Parquet 
MIN/MAX now decline metadata pushdown when the interval can contain an invalid 
DATE, even if both endpoints are valid. New tests use -719528, -719469 and 
-719468 and verify fallback; representable ranges retain their optimization. 
All 240 selected unit tests pass under ASAN.



##########
be/src/core/data_type_serde/data_type_datev2_serde.cpp:
##########
@@ -65,22 +62,44 @@ Status decode_date_orc_values(const DataTypeSerDe& serde, 
IColumn& column,
             orc_serde_utils::orc_decode_row_count(orc_view.rows, 
orc_view.selected_rows);
     std::vector<int32_t> date_values;
     date_values.resize(output_rows);
-    auto& date_dict = date_day_offset_dict::get();
     for (size_t row = 0; row < output_rows; ++row) {
         const auto source_row = orc_serde_utils::orc_source_row_at(row, 
orc_view.selected_rows);
-        const auto date = 
date_dict[cast_set<int>(orc_batch->data[source_row])];
-        date_values[row] = cast_set<int32_t>(date.daynr() - 
DORIS_DATE_EPOCH_DAYNR);
+        // The payload of a null slot is undefined, so it is zeroed rather 
than range-checked;
+        // `read_column_from_decoded_values` never decodes it.
+        if (view.null_map != nullptr && view.null_map[row] != 0) {
+            date_values[row] = 0;
+            continue;
+        }
+        // ORC DATE is days since 1970-01-01 in the proleptic Gregorian 
calendar, the same encoding
+        // `decode_epoch_days()` expects, but ORC hands it over in an int64 
batch. Reject a value
+        // that does not fit the int32 view here, while the real file value is 
still available for
+        // the message, instead of narrowing it blindly.
+        const int64_t file_days = orc_batch->data[source_row];
+        if (file_days < std::numeric_limits<int32_t>::min() ||
+            file_days > std::numeric_limits<int32_t>::max()) {
+            return Status::DataQualityError(
+                    "DATE value {} is outside the Doris DATE range 
(0000-01-01..9999-12-31, "
+                    "0000-02-29 excluded)",
+                    file_days);
+        }
+        date_values[row] = static_cast<int32_t>(file_days);
     }
     view.values = reinterpret_cast<const uint8_t*>(date_values.data());
     RETURN_IF_ERROR(orc_serde_utils::read_decoded_values(serde, column, 
&view));
     return Status::OK();
 }
 
-Status decode_parquet_date(int32_t encoded_date, DateV2Value<DateV2ValueType>* 
value) {
+// Shared by every "days since 1970-01-01" source: Parquet, ORC and Arrow 
date32/date64. The
+// message must therefore not name one format, and it carries the value so a 
broken file can be
+// identified without re-reading it.
+Status decode_epoch_days(int64_t encoded_date, DateV2Value<DateV2ValueType>* 
value) {
     DORIS_CHECK(value != nullptr);
-    const int64_t day_number = static_cast<int64_t>(encoded_date) + 
date_threshold;
-    if (day_number < 0 || 
!value->get_date_from_daynr(static_cast<uint64_t>(day_number))) {
-        return Status::DataQualityError("Parquet DATE value is out of range");
+    const int64_t day_number = epoch_days_to_daynr(encoded_date);

Review Comment:
   Fixed in 0842fe5d56e. ORC checks selected DATE domains against file 
statistics before enabling SDK SARG; an unknown or unsafe range keeps row 
decoding active. Parquet footer and page statistics are unavailable when the 
DATE interval can contain an invalid ordinal. Added ORC SARG on/off and strict 
Parquet pruning on/off tests that preserve the -719469 error, plus explicit 
page-index candidate-retention coverage. These tests fail before the fix and 
pass afterward.



##########
be/src/core/data_type_serde/data_type_datev2_serde.cpp:
##########
@@ -100,7 +119,7 @@ class DateV2ParquetConsumer final : public 
ParquetFixedValueConsumer {
         _data.resize(old_size + num_values);
         for (size_t row = 0; row < num_values; ++row) {
             DateV2Value<DateV2ValueType> value;
-            const auto status = decode_parquet_date(
+            const auto status = decode_epoch_days(

Review Comment:
   Fixed in 0842fe5d56e. Unsafe DATE ranges invalidate physical null statistics 
as well as MIN/MAX. COUNT(DATE) now uses definition levels/footer counts only 
after statistics prove the range is representable (or entirely NULL); otherwise 
it falls back to materialization. Added non-strict IS NULL pruning on/off 
coverage and a COUNT fallback check against materialized NULLs. Additional 
tests confirm valid required/nullable DATE ranges and all-NULL columns retain 
their COUNT shortcut.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to