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


##########
be/src/core/data_type_serde/data_type_serde.cpp:
##########
@@ -496,8 +524,28 @@ Status decode_timestamp_orc_values(IColumn& nested_column, 
const OrcDecodedColum
         }
         auto& value =
                 
reinterpret_cast<DateV2Value<DateTimeV2ValueType>&>(data[old_data_size + row]);
+        RoundedOrcTimestamp timestamp;
+        auto status = round_orc_timestamp_to_microseconds(
+                orc_batch->data[source_row], 
orc_batch->nanoseconds[source_row], &timestamp);
+        if (!status.ok()) {
+            data.resize(old_data_size);
+            return status;
+        }
         value.from_unixtime(orc_batch->data[source_row], timezone);
-        
value.set_microsecond(cast_set<uint64_t>(orc_batch->nanoseconds[source_row] / 
1000));
+        if (!value.is_valid_date()) {

Review Comment:
   [P2] Please validate the effective rounded value rather than rejecting the 
pre-carry second. In UTC, an ORC value at second `-62167219201` with 
`999999500ns` rounds exactly to `-62167219200`, i.e. Doris's supported 
`0000-01-01 00:00:00` boundary. Here `from_unixtime` checks the 
one-second-earlier year-minus-one value and returns before the carry can make 
it valid. The new boundary test uses zero nanos and misses this case; add a 
rounding-into-range lower-bound case.



##########
be/src/exec/operator/file_scan_operator.cpp:
##########
@@ -147,9 +148,18 @@ bool FileScanLocalState::_should_use_file_scanner_v2(const 
TQueryOptions& query_
     const bool is_transactional_hive =
             scan_params.__isset.table_format_params &&
             scan_params.table_format_params.table_format_type == 
"transactional_hive";
-    return query_options.__isset.enable_file_scanner_v2 && 
query_options.enable_file_scanner_v2 &&
-           !is_load && scan_params.format_type != 
TFileFormatType::FORMAT_ES_HTTP &&
-           !is_transactional_hive;
+    const bool has_versioned_parquet_semantics =
+            scan_params.format_type == TFileFormatType::FORMAT_PARQUET &&

Review Comment:
   [P1] Please include Paimon native-Parquet scans in this forced-V2 contract. 
Paimon's scan-level `params.format_type` is always `FORMAT_JNI`; only its 
native `TFileRangeDesc` entries are tagged `FORMAT_PARQUET`. Therefore 
`enable_file_scanner_v2=false` still makes `_init_scanners` instantiate 
FileScannerV1, which cannot consume the new history-schema per-column 
TIMESTAMP/TIMESTAMP_LTZ semantics. The Paimon regression already documents that 
the V1 Parquet timestamp(7/8/9) path returns values eight hours too large, so 
the same versioned plan remains incorrect solely because of the scanner toggle. 
Please route versioned Paimon native-Parquet ranges through V2 as well and 
cover this setting in a regression.



##########
be/src/format/transformer/vparquet_transformer.h:
##########
@@ -88,18 +89,20 @@ struct ParquetFileOptions {
 // a wrapper of parquet output stream
 class VParquetTransformer final : public VFileFormatTransformer {
 public:
-    VParquetTransformer(RuntimeState* state, doris::io::FileWriter* 
file_writer,
-                        const VExprContextSPtrs& output_vexpr_ctxs,
-                        std::vector<std::string> column_names, bool 
output_object_data,
-                        const ParquetFileOptions& parquet_options,
-                        const std::string* iceberg_schema_json = nullptr,
-                        const iceberg::Schema* iceberg_schema = nullptr);
-
-    VParquetTransformer(RuntimeState* state, doris::io::FileWriter* 
file_writer,
-                        const VExprContextSPtrs& output_vexpr_ctxs,
-                        std::vector<TParquetSchema> parquet_schemas, bool 
output_object_data,
-                        const ParquetFileOptions& parquet_options,
-                        const std::string* iceberg_schema_json = nullptr);
+    VParquetTransformer(
+            RuntimeState* state, doris::io::FileWriter* file_writer,
+            const VExprContextSPtrs& output_vexpr_ctxs, 
std::vector<std::string> column_names,
+            bool output_object_data, const ParquetFileOptions& parquet_options,
+            const std::string* iceberg_schema_json = nullptr,
+            const iceberg::Schema* iceberg_schema = nullptr,
+            const ArrowWriteConverter& arrow_write_converter = 
plain_arrow_write_converter());

Review Comment:
   [P1] The plain default breaks both existing Iceberg Variant transformer 
tests, whose constructors pass a non-null `iceberg_schema` but omit this new 
argument. That schema creates Variant's `struct<metadata,value>` storage, while 
`plain_arrow_write_converter()` only binds Doris VARIANT to 
STRING/LARGE_STRING, so both `transformer.write()` assertions now 
deterministically return the plain-binding InvalidArgument. Production Iceberg 
writers pass `iceberg_arrow_write_converter()`; please update the two tests as 
well, or infer/require the target converter whenever `iceberg_schema` is 
non-null.



##########
be/src/core/data_type_serde/data_type_serde.cpp:
##########
@@ -477,6 +477,34 @@ int64_t find_struct_child_index(const ::orc::Type& type, 
const std::string& fiel
     return -1;
 }
 
+struct RoundedOrcTimestamp {
+    int64_t seconds;
+    uint64_t microseconds;
+    bool carry;
+};
+
+Status round_orc_timestamp_to_microseconds(int64_t seconds, int64_t 
nanoseconds,
+                                           RoundedOrcTimestamp* result) {
+    constexpr int64_t NANOS_PER_SECOND = 1000000000;
+    constexpr int64_t NANOS_PER_MICROSECOND = 1000;
+    constexpr int64_t MICROS_PER_SECOND = 1000000;
+    DORIS_CHECK(result != nullptr);
+    DORIS_CHECK(nanoseconds >= 0 && nanoseconds < NANOS_PER_SECOND);

Review Comment:
   [P2] Please return a checked file-data error for an out-of-range nanoseconds 
value instead of using `DORIS_CHECK` here. Both ORC timestamp decoders pass 
`TimestampVectorBatch::nanoseconds` from the external file directly into this 
helper. `DORIS_CHECK` turns malformed file input into a FATAL_ERROR (caught as 
a scan failure in the default release path, but process-fatal in debug or with 
`exit_on_exception=true`) rather than using the helper's Status channel. This 
should be `DataQualityError`/`Corruption`, like the adjacent overflow and 
calendar checks.



##########
be/src/core/data_type_serde/data_type_serde.cpp:
##########
@@ -496,8 +524,28 @@ Status decode_timestamp_orc_values(IColumn& nested_column, 
const OrcDecodedColum
         }
         auto& value =
                 
reinterpret_cast<DateV2Value<DateTimeV2ValueType>&>(data[old_data_size + row]);
+        RoundedOrcTimestamp timestamp;
+        auto status = round_orc_timestamp_to_microseconds(
+                orc_batch->data[source_row], 
orc_batch->nanoseconds[source_row], &timestamp);
+        if (!status.ok()) {
+            data.resize(old_data_size);
+            return status;
+        }
         value.from_unixtime(orc_batch->data[source_row], timezone);
-        
value.set_microsecond(cast_set<uint64_t>(orc_batch->nanoseconds[source_row] / 
1000));
+        if (!value.is_valid_date()) {
+            data.resize(old_data_size);
+            return Status::DataQualityError(
+                    "Decoded ORC timestamp is outside the target timezone 
range");
+        }
+        value.set_microsecond(timestamp.microseconds);
+        // Plain ORC TIMESTAMP is a civil value. Carry after timezone 
conversion so a fractional
+        // round does not jump backward or skip an hour at a daylight-saving 
transition.
+        if (timestamp.carry &&

Review Comment:
   [P1] This civil-time carry also handles `TIMESTAMP_INSTANT` when 
`enable_mapping_timestamp_tz` is false (the default). `DataTypeDateTimeV2SerDe` 
routes both ORC timestamp kinds here. For `1636275599.999999500Z` in 
`America/Los_Angeles`, instant rounding must first reach `1636275600Z` and then 
decode as `01:00:00 PST`; this code converts the prior second to `01:59:59 PDT` 
and returns civil `02:00:00` (and returns nonexistent `02:00` at the spring gap 
instead of `03:00`). Please branch on `file_type`: retain civil carry for plain 
TIMESTAMP, but apply epoch carry before conversion for TIMESTAMP_INSTANT, 
including the unmapped DATETIMEV2 path.



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