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


##########
be/src/format_v2/orc/orc_reader.cpp:
##########
@@ -741,6 +749,88 @@ Status find_projected_minmax_leaf(const ::orc::Type& 
root_type,
 
 } // namespace
 
+// OrcReaderScanState has external linkage, so GCC requires its member types 
to stay
+// outside the anonymous namespace (-Wsubobject-linkage).
+// Keep SDK workarounds and their scan requirements in one plan. SerDes 
receive only the
+// resulting decode context and never need to infer which reader strategy 
produced a batch.
+class OrcTimestampReadPlan {
+public:
+    void configure(const ::orc::Type& file_schema,
+                   const std::vector<format::LocalColumnId>& read_columns,
+                   const cctz::time_zone& session_timezone) {
+        _context = OrcTimestampDecodeContext(session_timezone);
+        // An unprojected NANOS sibling can affect the SDK's ordinary 
timestamp decoding.
+        const bool needs_writer_timezone =
+                orc_serde_utils::has_nanosecond_timestamp(file_schema) &&
+                std::ranges::any_of(read_columns, [&](const auto column_id) {
+                    return !is_virtual_column(column_id) &&
+                           
has_timestamp(*file_schema.getSubtype(column_id.value()), false);

Review Comment:
   [P2] Restrict timestamp planning to projected nested leaves
   
   For an ORC schema 
`struct<ns:timestamp[NANOS],s:struct<i:int,unused:timestamp>>`, `SELECT s.i` 
reaches this check with root column `s`. `has_timestamp` visits the unread 
`s.unused`, enabling `STRIPE_WRITER` even though `includeTypes` later selects 
only `s.i` and no timestamp decoder runs. The scan then discovers every visited 
stripe's writer timezone and creates windowed RowReaders unnecessarily. In the 
pinned SDK, discovery reads/decompresses/parses a fresh stripe footer that the 
normal RowReader reads again; lower caches may absorb I/O, but the duplicate 
parsing remains. This adds avoidable synchronous work to large nested 
projections. Pass the actual nested projection into this check so only a 
projected plain timestamp enables the workaround.



##########
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergTimestampUtils.java:
##########
@@ -0,0 +1,88 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.iceberg;
+
+import org.apache.doris.analysis.Expr;
+import org.apache.doris.analysis.SlotRef;
+import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
+
+import org.apache.iceberg.Schema;
+import org.apache.iceberg.Table;
+import org.apache.iceberg.types.Type;
+import org.apache.iceberg.types.TypeUtil;
+import org.apache.iceberg.types.Types;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Nanosecond conversion and retained DATE-metric safeguards shared by the 
4.2 planners. */
+public final class IcebergTimestampUtils {
+    private IcebergTimestampUtils() {
+    }
+
+    public static long parseNanos(String value, ZoneId zone) {
+        // Static partitions must accept and round exactly the same literals 
as the sink cast.
+        LocalDateTime timestamp = TimeStampNsLiteral.parseDateTime(value, 
zone);
+        // Widen before combining: the minimum timestamp's whole second 
overflows long nanos.
+        return BigDecimal.valueOf(timestamp.toEpochSecond(ZoneOffset.UTC))
+                .multiply(BigDecimal.valueOf(1_000_000_000L))
+                .add(BigDecimal.valueOf(timestamp.getNano())).longValueExact();
+    }
+
+    public static Set<Integer> unsafeNanosFields(Table table, Schema schema) {
+        Set<Integer> unsafe = new HashSet<>();
+        Map<Integer, Schema> history = table.schemas();
+        int maxId = 
history.keySet().stream().mapToInt(Integer::intValue).max().orElse(-1);
+        // Removed history can leave live DATE files whose four-byte bounds 
Iceberg reads as longs.
+        boolean incomplete = history.isEmpty() || history.size() != (long) 
maxId + 1;
+        for (Types.NestedField field : 
TypeUtil.indexById(schema.asStruct()).values()) {
+            if (field.type().typeId() != Type.TypeID.TIMESTAMP_NANO) {
+                continue;
+            }
+            if (incomplete || history.values().stream().anyMatch(old -> {
+                Type type = old.findType(field.fieldId());
+                return type != null && type.typeId() == Type.TypeID.DATE;
+            })) {
+                unsafe.add(field.fieldId());
+            }
+        }
+        return unsafe;
+    }
+
+    private static boolean unsafeName(String name, Schema schema, Set<Integer> 
unsafe) {
+        Types.NestedField field = schema.caseInsensitiveFindField(name);
+        return field != null && unsafe.contains(field.fieldId());
+    }
+
+    public static boolean referencesUnsafe(Expr expr, Schema schema, 
Set<Integer> unsafe) {
+        if (expr instanceof SlotRef && unsafeName(((SlotRef) 
expr).getColumnName(), schema, unsafe)) {
+            return true;
+        }
+        return expr.getChildren().stream().anyMatch(child -> 
referencesUnsafe(child, schema, unsafe));
+    }
+
+    public static boolean 
referencesUnsafe(org.apache.doris.nereids.trees.expressions.Expression expr,
+            Schema schema, Set<Integer> unsafe) {
+        return expr.getInputSlots().stream().anyMatch(slot -> 
unsafeName(slot.getName(), schema, unsafe));

Review Comment:
   [P2] Resolve raw slot names before checking unsafe fields
   
   `rewrite_data_files` passes parser `UnboundSlot`s here. For a quoted 
top-level column named `ts.day`, `getName()` adds backticks, while 
`IcebergNereidsUtils.extractColumnName()` uses the raw single name part. This 
lookup therefore misses an unsafe DATE-to-nanos field although subsequent 
conversion resolves it. After promoting that field and committing a 
nanos-schema snapshot, a rewrite predicate on the quoted column for 
`2024-01-02` silently skips a retained DATE file containing that date: Iceberg 
interprets its four-byte bound as 19724 nanos instead of comparing with 
1704153600000000000. Resolve raw unbound names consistently before the guard so 
this operation rejects unsafe historical bounds just as it does for an ordinary 
identifier.



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