github-actions[bot] commented on code in PR #68934:
URL: https://github.com/apache/doris/pull/68934#discussion_r4254681379


##########
be/src/format_v2/orc/orc_reader.cpp:
##########
@@ -1646,24 +1715,91 @@ Status OrcReader::_select_stripe_ranges_by_statistics() 
{
     return Status::OK();
 }
 
-void OrcReader::_apply_current_stripe_range() {
-    if (!_state->stripe_pruning_applied || 
_state->selected_stripe_ranges.empty()) {
-        return;
+Status OrcReader::_prepare_timestamp_stripe_ranges() {
+    if (!_state->match_writer_timezone) {
+        return Status::OK();
+    }
+    std::vector<uint64_t> stripes;
+    if (_state->stripe_ranges_applied) {
+        for (const auto& range : _state->selected_stripe_ranges) {
+            for (auto stripe = range.first_stripe; stripe < range.last_stripe; 
++stripe) {
+                stripes.push_back(stripe);
+            }
+        }
+    } else {
+        RETURN_IF_ERROR(_collect_split_stripes(&stripes));
+    }
+    _state->selected_stripe_ranges.clear();
+    _state->stripe_ranges_applied = true;
+    _reader_statistics.read_row_groups = cast_set<int32_t>(stripes.size());
+    if (stripes.empty()) {
+        return Status::OK();
+    }
+    try {
+        uint64_t first_row = 0;
+        size_t selected = 0;
+        for (uint64_t index = 0; index <= stripes.back(); ++index) {
+            const auto stripe = _state->reader->getStripe(index);
+            const auto end_row = first_row + stripe->getNumberOfRows();
+            if (index == stripes[selected]) {
+                // A RowReader has one timezone, whereas writer timezones 
belong to stripes.
+                // Read footer timezone metadata only when entering the 
selected stripe.
+                _state->selected_stripe_ranges.push_back({
+                        .first_stripe = index,
+                        .last_stripe = index + 1,
+                        .offset = stripe->getOffset(),
+                        .length = stripe->getLength(),
+                        .first_row = first_row,
+                        .end_row = end_row,
+                });
+                ++selected;
+            }
+            first_row = end_row;
+        }
+    } catch (const std::exception& e) {
+        return Status::InternalError("Failed to prepare ORC timestamp stripe 
ranges: {}", e.what());
+    }
+    return Status::OK();
+}
+
+Status OrcReader::_apply_current_stripe_range() {
+    if (!_state->stripe_ranges_applied || 
_state->selected_stripe_ranges.empty()) {
+        return Status::OK();
     }
     DORIS_CHECK(_state->current_stripe_range < 
_state->selected_stripe_ranges.size());
     const auto& stripe_range = 
_state->selected_stripe_ranges[_state->current_stripe_range];
     _state->row_reader_options.range(stripe_range.offset, stripe_range.length);
+    if (_state->match_writer_timezone) {
+        try {
+            const auto stripe = 
_state->reader->getStripe(stripe_range.first_stripe);
+            const auto& name = stripe->getWriterTimezone();
+            const auto writer_timezone = name.empty() ? "GMT" : name;
+            if (!TimezoneUtils::find_cctz_time_zone(writer_timezone, 
_state->writer_timezone_obj)) {
+                return Status::InvalidArgument("Unknown ORC stripe writer 
timezone: {}",
+                                               writer_timezone);
+            }
+            // Matching zones preserves the SDK's UTC carrier until its 
negative-fraction
+            // correction is complete. cctz then applies historical offsets to 
that original second.
+            _state->row_reader_options.setTimezoneName(writer_timezone);

Review Comment:
   [P2] Keep ORC MIN/MAX consistent with the repaired row timestamps. With a 
C++ ORC New York file containing an ordinary timestamp at source second 
`-5364644638` and fraction `123456789`, plus a hidden NANOS sibling, this 
matching-zone path now correctly returns `1800-01-01 00:00:00.123457`. However, 
[the pinned writer's 
statistics](https://github.com/apache/doris-thirdparty/blob/5860f94768b94406f31698ac66f4cc3d2a781324/c%2B%2B/src/ColumnWriter.cc#L1713-L1722)
 use ORC's ancient EST offset, and `get_aggregate_result` still interprets 
those bounds in UTC: pushed `MIN(plain)`/`MAX(plain)` return `1799-12-31 
23:56:02.123457`. The nonzero nanos tail passes the sentinel fallback. This is 
a distinct metadata-aggregate path from the earlier row-carrier thread; 
negative timestamp predicates already remain residual. Reconcile these ordinary 
timestamp statistics with the selected producer/carrier semantics or fall back 
to row aggregation, and add a pushdown-enabled/disabled differential test.



##########
be/src/core/data_type_serde/data_type_datetimev2_serde.cpp:
##########
@@ -1049,7 +1050,11 @@ Status 
DataTypeDateTimeV2SerDe::read_column_from_orc(IColumn& column,
     if (orc_serde_utils::orc_decode_row_count(view.rows, view.selected_rows) 
== 0) {
         return Status::OK();
     }
-    return decode_timestamp_orc_values(column, view, *view.timezone);
+    const auto* timezone = view.file_type->getKind() == ::orc::TIMESTAMP &&
+                                           view.plain_timestamp_timezone != 
nullptr
+                                   ? view.plain_timestamp_timezone

Review Comment:
   [P2] Preserve ancient timestamps produced by the Java ORC writer. A 
supported non-UTC Java ORC 1.9 writer in `America/New_York` stores 
`Timestamp.valueOf("1800-01-01 00:00:00")` as second `-5364644400`: [Java's 
historical timezone carrier uses raw 
EST](https://github.com/openjdk/jdk17u/blob/master/src/java.base/share/classes/sun/util/calendar/ZoneInfo.java#L250-L261).
 With a hidden NANOS-annotated sibling, the new matching-zone reader preserves 
that second, and this branch applies cctz's UTC-04:56:02 LMT offset, returning 
`1800-01-01 00:03:58`. The prior UTC-session reader returned midnight. The 
[Java ORC 
writer](https://github.com/apache/orc/blob/rel/release-1.9.0/java/core/src/java/org/apache/orc/impl/writer/TimestampTreeWriter.java#L128-L141)
 and public Iceberg schema converter support this mixed file. The existing C++ 
ancient fixture uses a different carrier, so fixing its earlier thread does not 
cover this regression. Preserve both producer representations and add a 
Java-prod
 uced fixture read in a UTC session.



##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergPartitionUtils.java:
##########
@@ -801,6 +828,11 @@ static boolean isValidRelatedTable(Table table) {
                 return false;
             }
             PartitionField partitionField = fields.get(0);
+            if (spec.schema().findType(partitionField.sourceId()).typeId() == 
Type.TypeID.TIMESTAMP_NANO) {

Review Comment:
   [P2] Handle dropped partition sources before inspecting their type. A valid 
v1 table can remove its sole `day(ts)` partition field, leaving `void(ts)`, and 
then drop `ts`. [Iceberg rebuilds historical specs against the new 
schema](https://github.com/apache/iceberg/blob/apache-iceberg-1.11.0/core/src/main/java/org/apache/iceberg/TableMetadata.java#L756-L768),
 preserving the removed source ID, so `findType(sourceId)` is null in both the 
old calendar spec and current void spec. This new dereference runs before 
rejecting non-calendar transforms. `IcebergConnectorMetadata.latestSnapshotPin` 
calls it unconditionally during `beginQuerySnapshot`, so even a normal SELECT 
of a surviving column now fails with a wrapped null-pointer error; the previous 
code returned an unpartitioned view. Treat a missing source type as ineligible 
for RANGE partitioning, and cover a real v1 remove-partition/drop-source 
lifecycle through snapshot planning.



##########
be/src/format_v2/orc/orc_reader.cpp:
##########
@@ -1646,24 +1715,91 @@ Status OrcReader::_select_stripe_ranges_by_statistics() 
{
     return Status::OK();
 }
 
-void OrcReader::_apply_current_stripe_range() {
-    if (!_state->stripe_pruning_applied || 
_state->selected_stripe_ranges.empty()) {
-        return;
+Status OrcReader::_prepare_timestamp_stripe_ranges() {
+    if (!_state->match_writer_timezone) {
+        return Status::OK();
+    }
+    std::vector<uint64_t> stripes;
+    if (_state->stripe_ranges_applied) {
+        for (const auto& range : _state->selected_stripe_ranges) {
+            for (auto stripe = range.first_stripe; stripe < range.last_stripe; 
++stripe) {
+                stripes.push_back(stripe);
+            }
+        }
+    } else {
+        RETURN_IF_ERROR(_collect_split_stripes(&stripes));
+    }
+    _state->selected_stripe_ranges.clear();
+    _state->stripe_ranges_applied = true;
+    _reader_statistics.read_row_groups = cast_set<int32_t>(stripes.size());
+    if (stripes.empty()) {
+        return Status::OK();
+    }
+    try {
+        uint64_t first_row = 0;
+        size_t selected = 0;
+        for (uint64_t index = 0; index <= stripes.back(); ++index) {
+            const auto stripe = _state->reader->getStripe(index);
+            const auto end_row = first_row + stripe->getNumberOfRows();
+            if (index == stripes[selected]) {
+                // A RowReader has one timezone, whereas writer timezones 
belong to stripes.
+                // Read footer timezone metadata only when entering the 
selected stripe.
+                _state->selected_stripe_ranges.push_back({
+                        .first_stripe = index,
+                        .last_stripe = index + 1,

Review Comment:
   [P2] Reuse row readers for adjacent stripes with the same timezone. Every 
selected stripe becomes its own range here, including unfiltered all-GMT nanos 
files, and each range transition constructs a new `RowReader`. The [pinned 
constructor](https://github.com/apache/doris-thirdparty/blob/5860f94768b94406f31698ac66f4cc3d2a781324/c%2B%2B/src/Reader.cc#L263-L307)
 allocates `firstRowOfStripe` for the entire file and copies/scans every footer 
stripe even for a one-stripe range. For N small flushed stripes in one split 
this changes O(N) setup to O(N²), plus repeated schema and batch 
initialization; projecting only a plain timestamp beside a hidden NANOS field 
also triggers it. Coalesce adjacent selected stripes with identical writer 
zones or retain reusable SDK setup, and cover many-stripe files while 
preserving different-zone correctness.



##########
fe/fe-connector/fe-connector-iceberg/src/main/java/org/apache/doris/connector/iceberg/IcebergConnectorTransaction.java:
##########
@@ -929,14 +930,19 @@ private Expression buildPartitionFilter(Map<String, 
String> staticPartitions, Pa
      * both paths that build this predicate: DELETE/UPDATE/MERGE conflict 
detection and
      * {@code INSERT OVERWRITE ... PARTITION(d='nan')}.
      */
-    private static Expression identityPartitionPredicate(String sourceColName, 
Object value) {
+    private static Expression identityPartitionPredicate(Types.NestedField 
sourceField, Object value) {
+        String sourceColName = sourceField.name();
         if (value == null) {
             return Expressions.isNull(sourceColName);
         }
         if ((value instanceof Double || value instanceof Float)
                 && Double.isNaN(((Number) value).doubleValue())) {
             return Expressions.isNaN(sourceColName);
         }
+        if (sourceField.type().typeId() == TypeID.TIMESTAMP_NANO) {
+            // Partition values are already nanos; a raw Long would be rebound 
as microseconds.
+            return Expressions.equal(sourceColName, Expressions.nanos((Long) 
value));

Review Comment:
   [P1] Guard generated overwrite predicates against retained DATE bounds. A v3 
table can retain an unpartitioned DATE file containing `2024-01-02`, promote 
that field to `timestamp_ns`, then add an identity partition. `INSERT OVERWRITE 
... PARTITION(ts='2024-01-02')` reaches this direct nanos equality without 
`forTable`'s history safeguard. Iceberg 1.11 interprets the retained four-byte 
bounds as the unscaled long `19724`, so [its deletion 
evaluator](https://github.com/apache/iceberg/blob/apache-iceberg-1.11.0/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java#L580-L620)
 excludes the old file from deletion while the replacement is committed; the 
overwritten rows remain. Generated identity conflict filters use the same 
helper. This is a separate commit-time instance from the earlier user-predicate 
pruning thread. Reject unsafe static overwrites or safely promote old bounds; 
conflict-only filters can conservatively omit the unsafe conjunct. Cover 
retained files from the 
 previous unpartitioned spec.



-- 
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