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


##########
be/src/format/transformer/vorc_transformer.cpp:
##########
@@ -428,18 +432,71 @@ Status 
VOrcTransformer::collect_file_statistics_after_close(TIcebergColumnStats*
     }
 }
 
-bool VOrcTransformer::_collect_column_bounds(const orc::ColumnStatistics* 
col_stats,
-                                             int32_t field_id, const 
DataTypePtr& data_type,
-                                             std::map<int32_t, std::string>* 
lower_bounds,
-                                             std::map<int32_t, std::string>* 
upper_bounds) {
-    bool has_bounds = false;
-    auto primitive_type = remove_nullable(data_type)->get_primitive_type();
+Status VOrcTransformer::_collect_iceberg_metrics(
+        const orc::Reader* reader, const orc::Statistics* file_stats,
+        std::map<int32_t, int64_t>* column_sizes, std::map<int32_t, int64_t>* 
value_counts,
+        std::map<int32_t, int64_t>* null_value_counts, std::map<int32_t, 
std::string>* lower_bounds,
+        std::map<int32_t, std::string>* upper_bounds) const {
+    std::map<uint64_t, int32_t> orc_to_iceberg_field_ids;
+    for (uint32_t orc_col_id = 1; orc_col_id < 
file_stats->getNumberOfColumns(); ++orc_col_id) {
+        const orc::Type* orc_type = _schema->getTypeByColumnId(orc_col_id);
+        if (!orc_type->hasAttributeKey(ORC_ICEBERG_ID_KEY)) {
+            continue;
+        }
+
+        int32_t field_id = 
std::stoi(orc_type->getAttributeValue(ORC_ICEBERG_ID_KEY));
+        iceberg::Type* field_type = _iceberg_schema->find_type(field_id);
+        if (field_type == nullptr) {
+            return Status::InternalError("Can not find Iceberg field for ORC 
metrics: {}",
+                                         field_id);
+        }
+        if (_iceberg_schema->is_nested_in_list_or_map(field_id)) {
+            continue;
+        }
+
+        orc_to_iceberg_field_ids.emplace(orc_col_id, field_id);
+        (*column_sizes)[field_id] = 0;
+        const orc::ColumnStatistics* col_stats = 
file_stats->getColumnStatistics(orc_col_id);
+        if (col_stats != nullptr) {
+            int64_t non_null_count = 
cast_set<int64_t>(col_stats->getNumberOfValues());
+            DORIS_CHECK_GE(_cur_written_rows, non_null_count);
+            int64_t null_count = col_stats->hasNull() ? _cur_written_rows - 
non_null_count : 0;
+            (*null_value_counts)[field_id] = null_count;
+            (*value_counts)[field_id] = non_null_count + null_count;
+            if (field_type->is_primitive_type()) {
+                _collect_primitive_column_bounds(col_stats, field_id, 
field_type, lower_bounds,
+                                                 upper_bounds);
+            }
+        }
+    }
+
+    for (uint64_t stripe_id = 0; stripe_id < reader->getNumberOfStripes(); 
++stripe_id) {
+        std::unique_ptr<orc::StripeInformation> stripe = 
reader->getStripe(stripe_id);
+        for (uint64_t stream_id = 0; stream_id < stripe->getNumberOfStreams(); 
++stream_id) {
+            std::unique_ptr<orc::StreamInformation> stream =
+                    stripe->getStreamInformation(stream_id);

Review Comment:
   [P2] Avoid quadratic stream traversal on close. In the pinned ORC 1.9.0, 
each `getStreamInformation(i)` recomputes its offset by summing all earlier 
stream lengths. Calling it for every stream makes this new collector 
O(streams^2) per stripe (plus one allocation per call), which is costly for 
wide Iceberg schemas on the synchronous commit path. Please aggregate directly 
from the stripe footer or provide a single-pass/bulk accessor.



##########
be/src/format/transformer/vorc_transformer.cpp:
##########
@@ -482,13 +546,11 @@ bool VOrcTransformer::_collect_column_bounds(const 
orc::ColumnStatistics* col_st
     } else if (const auto* string_stats =
                        dynamic_cast<const 
orc::StringColumnStatistics*>(col_stats)) {
         if (string_stats->hasMinimum() && string_stats->hasMaximum()) {
-            has_bounds = true;
             (*lower_bounds)[field_id] = string_stats->getMinimum();

Review Comment:
   [P1] Encode nested UUID bounds as 16 bytes. With 
`enable.mapping.varbinary=false`, UUID is written to ORC as its 36-byte STRING 
form, and this newly recursive path copies that text directly into Iceberg 
bounds. Iceberg UUID bounds are 16-byte values (the Parquet path explicitly 
enforces width 16), so readers decode different metadata and may prune a file 
incorrectly. Please convert canonical UUID text to the Iceberg binary 
representation, or omit UUID bounds, and cover a UUID struct child with 
varbinary mapping disabled.



##########
be/src/format/transformer/vorc_transformer.cpp:
##########
@@ -482,13 +546,11 @@ bool VOrcTransformer::_collect_column_bounds(const 
orc::ColumnStatistics* col_st
     } else if (const auto* string_stats =
                        dynamic_cast<const 
orc::StringColumnStatistics*>(col_stats)) {
         if (string_stats->hasMinimum() && string_stats->hasMaximum()) {
-            has_bounds = true;
             (*lower_bounds)[field_id] = string_stats->getMinimum();
             (*upper_bounds)[field_id] = string_stats->getMaximum();

Review Comment:
   [P1] Do not publish ORC's embedded-NUL string extrema. Doris preserves 
embedded NUL bytes when writing, but pinned ORC 1.9 computes STRING min/max 
with `strncmp`. If a nested child writes `\x00a` before `\x00z`, ORC leaves 
`\x00a` as the maximum; this newly recursive copy therefore commits an upper 
bound below an actual value and Iceberg can prune the file incorrectly. Please 
compute length-aware extrema or conservatively omit these bounds, and add a 
struct-string regression containing U+0000.



##########
be/src/format/transformer/vorc_transformer.cpp:
##########
@@ -165,6 +185,18 @@ Status VOrcTransformer::open() {
     return Status::OK();
 }
 
+const iceberg::NestedField* VOrcTransformer::_iceberg_field_for_column(
+        const std::string& column_name) const {
+    if (_iceberg_schema == nullptr) {
+        return nullptr;
+    }
+    const auto& fields = _iceberg_schema->root_struct().fields();
+    auto it = std::find_if(fields.begin(), fields.end(), [&](const 
iceberg::NestedField& field) {

Review Comment:
   [P2] Index root fields instead of rescanning them per column. `open()` 
invokes this helper for every output name, and each call linearly scans all 
root fields, so the normal N-column writer setup performs O(N^2) string 
comparisons for every partition/file rollover. Please add a non-inserting 
root-name index or pass the already resolved fields/IDs into the transformer.



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