This is an automated email from the ASF dual-hosted git repository.

Gabriel39 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new 66a421b1760 [fix](be) Truncate native ORC timestamps to microseconds 
(#68024)
66a421b1760 is described below

commit 66a421b1760ddd0cc44f86074daab3aff95b576f
Author: Oliveira <[email protected]>
AuthorDate: Sun Sep 20 09:41:14 2026 +0800

    [fix](be) Truncate native ORC timestamps to microseconds (#68024)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #67904
    
    Problem Summary:
    
    Paimon ORC tables can store timestamp values with nanosecond precision,
    while Doris exposes timestamps at microsecond precision. The Paimon JNI
    reader truncates sub-microsecond digits, but the native ORC reader
    rounded them. For example, `2024-01-02 10:04:05.123456789` was exposed
    as `.123456` by JNI and `.123457` by the native reader, causing equality
    predicates to return inconsistent results.
    
    The existing Paimon predicate conversion keeps `TIMESTAMP(7/8/9)`
    comparisons as residual predicates. This change makes the native ORC
    path use the same truncation semantics as Doris and the JNI reader for
    row decoding, stripe statistics, and ORC search argument bounds.
    
    ### Release note
    
    Fix inconsistent timestamp values and predicate results between native
    ORC and JNI readers for sub-microsecond timestamps.
    
    ### Check List (For Author)
    
    - Test
        - [x] Regression test
    - `./run-regression-test.sh --conf /tmp/yuze-regression-full-conf.groovy
    --run -d external_table_p0/paimon -s paimon_timestamp_types`
        - [x] Unit Test
    - `./run-be-ut.sh --run
    
--filter=NewOrcReaderTest.*Timestamp*:NewOrcReaderTest.ReadTimestampNanosecondsTruncatesToMicroseconds`
        - [ ] Manual test
        - [ ] No need to test or manual test
    - Behavior changed:
    - [x] Yes. Native ORC timestamps now truncate sub-microsecond digits
    consistently with Doris precision and the JNI reader.
        - [ ] No.
    - Does this need documentation?
        - [x] No.
        - [ ] Yes.
    
    ### Check List (For Reviewer who merge this PR)
    
    - [ ] Confirm the release note
    - [ ] Confirm test cases
    - [ ] Confirm document
    - [ ] Add branch pick label
---
 .../data_type_serde/data_type_datetimev2_serde.cpp |  12 +-
 .../data_type_timestamptz_serde.cpp                |   4 +-
 be/src/core/data_type_serde/orc_serde_utils.cpp    |  21 +--
 be/src/core/data_type_serde/orc_serde_utils.h      |   7 +-
 be/src/format_v2/orc/orc_reader.cpp                |  25 ++-
 be/src/format_v2/orc/orc_search_argument.cpp       |  25 ++-
 be/test/format_v2/orc/orc_reader_test.cpp          | 175 ++++++++++++++-------
 .../paimon/paimon_timestamp_types.out              |  47 +++++-
 .../external_table_p0/paimon/test_paimon_minio.out |  16 +-
 .../tvf/orc_tvf/test_hdfs_orc_group4_orc_files.out |   2 +-
 .../tvf/orc_tvf/test_hdfs_orc_group6_orc_files.out |   4 +-
 .../paimon/paimon_timestamp_types.groovy           |  44 ++++++
 12 files changed, 258 insertions(+), 124 deletions(-)

diff --git a/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp 
b/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
index a4b11788986..b695d2ebdfc 100644
--- a/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_datetimev2_serde.cpp
@@ -72,8 +72,8 @@ Status decode_timestamp_orc_values(IColumn& nested_column, 
const OrcDecodedColum
         }
         auto& value =
                 
reinterpret_cast<DateV2Value<DateTimeV2ValueType>&>(data[old_data_size + row]);
-        orc_serde_utils::RoundedOrcTimestamp timestamp;
-        auto status = orc_serde_utils::round_orc_timestamp_to_microseconds(
+        orc_serde_utils::TruncatedOrcTimestamp timestamp;
+        auto status = orc_serde_utils::truncate_orc_timestamp_to_microseconds(
                 orc_batch->data[source_row], 
orc_batch->nanoseconds[source_row], &timestamp);
         if (!status.ok()) {
             data.resize(old_data_size);
@@ -86,14 +86,6 @@ Status decode_timestamp_orc_values(IColumn& nested_column, 
const OrcDecodedColum
                     "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 &&
-            !value.date_add_interval<TimeUnit::SECOND>(TimeInterval 
{TimeUnit::SECOND, 1, false})) {
-            data.resize(old_data_size);
-            return Status::DataQualityError(
-                    "Decoded ORC timestamp is outside the target timezone 
range");
-        }
     }
     return Status::OK();
 }
diff --git a/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp 
b/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
index d83bc0745d9..f75755f2f52 100644
--- a/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
+++ b/be/src/core/data_type_serde/data_type_timestamptz_serde.cpp
@@ -56,8 +56,8 @@ Status decode_timestamp_tz_orc_values(IColumn& nested_column,
             continue;
         }
         auto& value = data[old_data_size + row];
-        orc_serde_utils::RoundedOrcTimestamp timestamp;
-        auto status = orc_serde_utils::round_orc_timestamp_to_microseconds(
+        orc_serde_utils::TruncatedOrcTimestamp timestamp;
+        auto status = orc_serde_utils::truncate_orc_timestamp_to_microseconds(
                 orc_batch->data[source_row], 
orc_batch->nanoseconds[source_row], &timestamp);
         if (!status.ok()) {
             data.resize(old_data_size);
diff --git a/be/src/core/data_type_serde/orc_serde_utils.cpp 
b/be/src/core/data_type_serde/orc_serde_utils.cpp
index 5b7c88dbd63..74f910798c7 100644
--- a/be/src/core/data_type_serde/orc_serde_utils.cpp
+++ b/be/src/core/data_type_serde/orc_serde_utils.cpp
@@ -41,25 +41,16 @@ bool orc_row_is_null(const ::orc::ColumnVectorBatch& batch, 
size_t row) {
     return batch.hasNulls && !batch.notNull[row];
 }
 
-Status round_orc_timestamp_to_microseconds(int64_t seconds, int64_t 
nanoseconds,
-                                           RoundedOrcTimestamp* result) {
+Status truncate_orc_timestamp_to_microseconds(int64_t seconds, int64_t 
nanoseconds,
+                                              TruncatedOrcTimestamp* 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);
-    // Doris stores six fractional digits, so use half-up rounding and carry 
999999500ns into the
-    // next second instead of silently truncating the ORC value.
-    const auto rounded_microseconds =
-            (nanoseconds + NANOS_PER_MICROSECOND / 2) / NANOS_PER_MICROSECOND;
-    // Validate the carry here, but validate Doris' calendar range after 
timezone conversion:
-    // a valid year-zero local timestamp may have a UTC epoch before year zero.
-    if (__builtin_add_overflow(seconds, rounded_microseconds / 
MICROS_PER_SECOND,
-                               &result->seconds)) {
-        return Status::DataQualityError("ORC timestamp overflows after 
microsecond rounding");
-    }
-    result->microseconds = cast_set<uint64_t>(rounded_microseconds % 
MICROS_PER_SECOND);
-    result->carry = rounded_microseconds >= MICROS_PER_SECOND;
+    // Doris exposes ORC timestamps at microsecond precision. Discard 
sub-microsecond digits so
+    // native ORC and JNI readers retain the same visible value and predicate 
semantics.
+    result->seconds = seconds;
+    result->microseconds = cast_set<uint64_t>(nanoseconds / 
NANOS_PER_MICROSECOND);
     return Status::OK();
 }
 
diff --git a/be/src/core/data_type_serde/orc_serde_utils.h 
b/be/src/core/data_type_serde/orc_serde_utils.h
index 06b9c4406ee..1adcf8faafb 100644
--- a/be/src/core/data_type_serde/orc_serde_utils.h
+++ b/be/src/core/data_type_serde/orc_serde_utils.h
@@ -32,14 +32,13 @@ size_t orc_decode_row_count(size_t rows, const 
std::vector<size_t>* selected_row
 size_t orc_source_row_at(size_t row, const std::vector<size_t>* selected_rows);
 bool orc_row_is_null(const ::orc::ColumnVectorBatch& batch, size_t row);
 
-struct RoundedOrcTimestamp {
+struct TruncatedOrcTimestamp {
     int64_t seconds;
     uint64_t microseconds;
-    bool carry;
 };
 
-Status round_orc_timestamp_to_microseconds(int64_t seconds, int64_t 
nanoseconds,
-                                           RoundedOrcTimestamp* result);
+Status truncate_orc_timestamp_to_microseconds(int64_t seconds, int64_t 
nanoseconds,
+                                              TruncatedOrcTimestamp* result);
 
 DecodedColumnView make_orc_decoded_view(const OrcDecodedColumnView& orc_view,
                                         DecodedValueKind value_kind);
diff --git a/be/src/format_v2/orc/orc_reader.cpp 
b/be/src/format_v2/orc/orc_reader.cpp
index b7ffc1c9134..9407e7ef31c 100644
--- a/be/src/format_v2/orc/orc_reader.cpp
+++ b/be/src/format_v2/orc/orc_reader.cpp
@@ -502,22 +502,12 @@ std::optional<DateV2Value<DateTimeV2ValueType>> 
datetime_v2_from_orc_millis(
     }
     const auto extra_nanos = std::max<int32_t>(nanos_tail, 0);
     constexpr int64_t NANOS_PER_MICROSECOND = 1000;
-    constexpr int64_t MICROS_PER_SECOND = 1000000;
     // Stripe statistics split the timestamp into milliseconds and the 
remaining nanoseconds. Use
-    // the same half-up rule as row decoding so zone-map pruning observes 
identical values.
-    const auto rounded_extra_microseconds =
-            (extra_nanos + NANOS_PER_MICROSECOND / 2) / NANOS_PER_MICROSECOND;
-    const auto microseconds_with_carry = millis_remainder * 1000 + 
rounded_extra_microseconds;
-    // Calendar bounds depend on the target timezone, so only reject 
arithmetic overflow here and
-    // let the converted value below decide whether the statistic is 
representable by Doris.
-    int64_t rounded_seconds;
-    if (__builtin_add_overflow(seconds, microseconds_with_carry / 
MICROS_PER_SECOND,
-                               &rounded_seconds)) {
-        return std::nullopt;
-    }
-    const auto microseconds = cast_set<uint64_t>(microseconds_with_carry % 
MICROS_PER_SECOND);
+    // the same truncation as row decoding so zone-map pruning observes 
identical values.
+    const auto microseconds =
+            cast_set<uint64_t>(millis_remainder * 1000 + extra_nanos / 
NANOS_PER_MICROSECOND);
     DateV2Value<DateTimeV2ValueType> value;
-    value.from_unixtime(rounded_seconds, timezone);
+    value.from_unixtime(seconds, timezone);
     value.set_microsecond(microseconds);
     if (!value.is_valid_date()) {
         return std::nullopt;
@@ -543,6 +533,13 @@ bool set_timestamp_zone_map(const ::orc::ColumnStatistics& 
statistics,
         !timestamp_statistics->hasMaximum()) {
         return false;
     }
+    // ORC substitutes these conservative tails when ORC-611 nanos fields are 
absent. The public
+    // statistics API cannot distinguish those sentinels from equal serialized 
values, so fall
+    // back to a row scan rather than expose an inexact bound as an exact 
aggregate result.
+    if (timestamp_statistics->getMinimumNanos() == 0 ||
+        timestamp_statistics->getMaximumNanos() == 999999) {
+        return false;
+    }
     const auto min_endpoint =
             std::pair(timestamp_statistics->getMinimum(), 
timestamp_statistics->getMinimumNanos());
     const auto max_endpoint =
diff --git a/be/src/format_v2/orc/orc_search_argument.cpp 
b/be/src/format_v2/orc/orc_search_argument.cpp
index 5cee9cf46c1..3c7094ec38c 100644
--- a/be/src/format_v2/orc/orc_search_argument.cpp
+++ b/be/src/format_v2/orc/orc_search_argument.cpp
@@ -984,9 +984,8 @@ std::optional<OrcSargComparisonLiteral> 
make_comparison_literal_for_sarg(
     if (column.predicate_type == ::orc::PredicateDataType::TIMESTAMP) {
         const auto timestamp = literal->getTimestamp();
         // ORC reconstructs negative timestamp statistics with truncating 
division, which can
-        // produce a non-canonical nanos component. The exact epoch is unsafe 
too because its
-        // half-up lower bound is -500ns. Leave these predicates to Doris row 
filtering.
-        if (timestamp.second < 0 || (timestamp.second == 0 && timestamp.nanos 
== 0)) {
+        // produce a non-canonical nanos component. Leave negative timestamps 
to row filtering.
+        if (timestamp.second < 0) {
             return std::nullopt;
         }
     }
@@ -1066,7 +1065,7 @@ std::optional<std::vector<::orc::Literal>> 
make_in_literals_for_sarg(
         }
         if (column.predicate_type == ::orc::PredicateDataType::TIMESTAMP) {
             const auto timestamp = literal->getTimestamp();
-            if (timestamp.second < 0 || (timestamp.second == 0 && 
timestamp.nanos == 0)) {
+            if (timestamp.second < 0) {
                 return std::nullopt;
             }
         }
@@ -1248,17 +1247,17 @@ OrcSargNode make_and_node(std::vector<OrcSargNode> 
children) {
     return shift_timestamp_literal(literal, NANOS_PER_MILLISECOND - remainder);
 }
 
-std::pair<::orc::Literal, ::orc::Literal> timestamp_rounding_bounds(const 
::orc::Literal& literal) {
-    constexpr int32_t HALF_MICROSECOND_NANOS = 500;
-    // All raw ORC values in [lower, upper) round half-up to this Doris 
microsecond. Round these
+std::pair<::orc::Literal, ::orc::Literal> timestamp_truncation_bounds(
+        const ::orc::Literal& literal) {
+    constexpr int32_t NANOS_PER_MICROSECOND = 1000;
+    // All raw ORC values in [lower, upper) truncate to this Doris 
microsecond. Expand these
     // boundaries toward the side that enlarges the SARG match set because ORC 
statistics retain
     // only millisecond precision.
-    return {shift_timestamp_literal(literal, -HALF_MICROSECOND_NANOS),
-            shift_timestamp_literal(literal, HALF_MICROSECOND_NANOS)};
+    return {literal, shift_timestamp_literal(literal, NANOS_PER_MICROSECOND)};
 }
 
 OrcSargNode make_timestamp_equals_node(const OrcSargColumn& column, const 
::orc::Literal& literal) {
-    const auto [lower_bound, upper_bound] = timestamp_rounding_bounds(literal);
+    const auto [lower_bound, upper_bound] = 
timestamp_truncation_bounds(literal);
     std::vector<OrcSargNode> children;
     children.push_back(make_not_node(make_literal_node(
             OrcSargNodeKind::LESS_THAN, column, 
floor_timestamp_literal_to_millis(lower_bound))));
@@ -1268,7 +1267,7 @@ OrcSargNode make_timestamp_equals_node(const 
OrcSargColumn& column, const ::orc:
 }
 
 std::optional<OrcSargNode> make_timestamp_comparison_node(const 
OrcSargComparison& comparison) {
-    const auto [lower_bound, upper_bound] = 
timestamp_rounding_bounds(comparison.literal);
+    const auto [lower_bound, upper_bound] = 
timestamp_truncation_bounds(comparison.literal);
     switch (comparison.normalized_op) {
     case TExprOpcode::GE:
         return make_not_node(make_literal_node(OrcSargNodeKind::LESS_THAN, 
comparison.column,
@@ -1310,9 +1309,9 @@ OrcSargNode make_timestamp_in_node(const OrcSargColumn& 
column,
         }
     }
     const auto lower_bound =
-            
floor_timestamp_literal_to_millis(timestamp_rounding_bounds(min_literal).first);
+            
floor_timestamp_literal_to_millis(timestamp_truncation_bounds(min_literal).first);
     const auto upper_bound =
-            
ceil_timestamp_literal_to_millis(timestamp_rounding_bounds(max_literal).second);
+            
ceil_timestamp_literal_to_millis(timestamp_truncation_bounds(max_literal).second);
     std::vector<OrcSargNode> children;
     children.push_back(
             make_not_node(make_literal_node(OrcSargNodeKind::LESS_THAN, 
column, lower_bound)));
diff --git a/be/test/format_v2/orc/orc_reader_test.cpp 
b/be/test/format_v2/orc/orc_reader_test.cpp
index d9be5a5e6d0..5cad401a06c 100644
--- a/be/test/format_v2/orc/orc_reader_test.cpp
+++ b/be/test/format_v2/orc/orc_reader_test.cpp
@@ -4320,7 +4320,7 @@ void write_multi_stripe_orc_sarg_types_file(const 
std::string& file_path) {
 
 void write_multi_stripe_orc_timestamp_instant_sarg_file(
         const std::string& file_path, int64_t first_timestamp_second = 0,
-        int64_t second_timestamp_second = 1609459200) {
+        int64_t second_timestamp_second = 1609459200, int64_t nanoseconds = 
123000000) {
     auto type = std::unique_ptr<::orc::Type>(::orc::Type::buildTypeFromString(
             "struct<timestamp_instant_col:timestamp with local time 
zone,payload:string>"));
 
@@ -4342,7 +4342,7 @@ void write_multi_stripe_orc_timestamp_instant_sarg_file(
         payloads.reserve(ROWS_PER_STRIPE);
         for (int64_t row = 0; row < ROWS_PER_STRIPE; ++row) {
             timestamp_batch.data[row] = first_timestamp_second + row;
-            timestamp_batch.nanoseconds[row] = 123000000;
+            timestamp_batch.nanoseconds[row] = nanoseconds;
             payloads.push_back(std::string(2048, static_cast<char>('a' + row % 
26)));
             set_string_value(payload_batch, row, payloads.back());
         }
@@ -4941,7 +4941,7 @@ TEST_F(NewOrcReaderTest, 
AggregatePushdownUsesPrunedStripes) {
 TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxMatchesScanInSessionTimezone) {
     const auto multi_stripe_file_path = (_test_dir / 
"aggregate_timestamp_timezone.orc").string();
     write_two_stripe_constant_timestamp_file(multi_stripe_file_path, 
1609430400, 1609434000,
-                                             "Asia/Shanghai");
+                                             "Asia/Shanghai", 123456789);
 
     RuntimeState state {TQueryOptions(), TQueryGlobals()};
     state.set_timezone("Asia/Shanghai");
@@ -4976,8 +4976,8 @@ TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxMatchesScanInSessionTim
     ASSERT_EQ(scan_rows, 400);
     ASSERT_TRUE(scan_min.has_value());
     ASSERT_TRUE(scan_max.has_value());
-    EXPECT_EQ(*scan_min, make_datetime_v2(2021, 1, 1, 0, 0, 0, 123000));
-    EXPECT_EQ(*scan_max, make_datetime_v2(2021, 1, 1, 1, 0, 0, 123000));
+    EXPECT_EQ(*scan_min, make_datetime_v2(2021, 1, 1, 0, 0, 0, 123456));
+    EXPECT_EQ(*scan_max, make_datetime_v2(2021, 1, 1, 1, 0, 0, 123456));
 
     auto reader = create_reader_for_path(multi_stripe_file_path);
     ASSERT_TRUE(reader->init(&state).ok());
@@ -5082,8 +5082,8 @@ TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxFallsBackForPreOrc135Wr
 
 TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxMatchesNegativeRowDecoding) {
     const auto file_path = (_test_dir / 
"aggregate_negative_timestamp.orc").string();
-    // Unlike ORC SARG reconstruction, the aggregate path canonicalizes 
negative millisecond
-    // remainders before rounding. Its statistics result must match the 
decoded row value.
+    // The aggregate path canonicalizes negative millisecond remainders before 
truncation. Its
+    // statistics result must match the decoded row value.
     write_timestamp_rounding_orc_file(file_path, 1, 3);
 
     auto reader = create_reader_for_path(file_path);
@@ -5106,11 +5106,42 @@ TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxMatchesNegativeRowDecod
     EXPECT_EQ(aggregate_result.count, 1);
     EXPECT_TRUE(aggregate_result.columns[0].has_min);
     EXPECT_TRUE(aggregate_result.columns[0].has_max);
-    const auto expected = make_datetime_v2(1969, 12, 31, 23, 59, 58, 999999);
+    const auto expected = make_datetime_v2(1969, 12, 31, 23, 59, 58, 999998);
     EXPECT_EQ(aggregate_result.columns[0].min_value.get<TYPE_DATETIMEV2>(), 
expected);
     EXPECT_EQ(aggregate_result.columns[0].max_value.get<TYPE_DATETIMEV2>(), 
expected);
 }
 
+TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxFallsBackWithoutExactNanosTails) {
+    const auto assert_fallback = [&](std::string_view file_name, bool 
timestamp_instant) {
+        const auto file_path = (_test_dir / file_name).string();
+        if (timestamp_instant) {
+            write_multi_stripe_orc_timestamp_instant_sarg_file(file_path);
+        } else {
+            write_two_stripe_constant_timestamp_file(file_path, 1, 2);
+        }
+
+        auto reader = create_reader_for_path(file_path, nullptr, nullptr, 
std::nullopt,
+                                             timestamp_instant);
+        RuntimeState state {TQueryOptions(), TQueryGlobals()};
+        state.set_timezone("+00:00");
+        EXPECT_TRUE(reader->init(&state).ok());
+        auto request = std::make_shared<format::FileScanRequest>();
+        request->non_predicate_columns = {field_projection(0)};
+        EXPECT_TRUE(reader->open(request).ok());
+
+        format::FileAggregateRequest aggregate_request;
+        aggregate_request.agg_type = TPushAggOp::type::MINMAX;
+        aggregate_request.columns.push_back(
+                {.projection = 
format::LocalColumnIndex::top_level(format::LocalColumnId(0))});
+        format::FileAggregateResult aggregate_result;
+        const auto status = reader->get_aggregate_result(aggregate_request, 
&aggregate_result);
+        EXPECT_TRUE(status.is<ErrorCode::NOT_IMPLEMENTED_ERROR>()) << status;
+    };
+
+    assert_fallback("aggregate_timestamp_no_nanos_tail.orc", false);
+    assert_fallback("aggregate_timestamp_instant_no_nanos_tail.orc", true);
+}
+
 TEST_F(NewOrcReaderTest, AggregatePushdownTimestampMinMaxRejectsNamedTimezone) 
{
     const auto multi_stripe_file_path = (_test_dir / 
"aggregate_timestamp_dst_fold.orc").string();
     // 2021-11-07 08:30Z and 09:00Z straddle the America/Los_Angeles fall-back 
transition.
@@ -5137,7 +5168,8 @@ TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampMinMaxRejectsNamedTimezone) {
 
 TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampInstantMinMaxUsesTimestampTzWhenMapped) {
     const auto multi_stripe_file_path = (_test_dir / 
"aggregate_timestamp_instant_tz.orc").string();
-    write_multi_stripe_orc_timestamp_instant_sarg_file(multi_stripe_file_path);
+    write_multi_stripe_orc_timestamp_instant_sarg_file(multi_stripe_file_path, 
0, 1609459200,
+                                                       123456789);
 
     auto reader =
             create_reader_for_path(multi_stripe_file_path, nullptr, nullptr, 
std::nullopt, true);
@@ -5169,10 +5201,10 @@ TEST_F(NewOrcReaderTest, 
AggregatePushdownTimestampInstantMinMaxUsesTimestampTzW
     ASSERT_EQ(aggregate_result.columns[0].max_value.get_type(), 
TYPE_TIMESTAMPTZ);
     
EXPECT_EQ(aggregate_result.columns[0].min_value.get<TYPE_TIMESTAMPTZ>().to_string(
                       state.timezone_obj(), 6),
-              "1970-01-01 08:00:00.123000+08:00");
+              "1970-01-01 08:00:00.123456+08:00");
     
EXPECT_EQ(aggregate_result.columns[0].max_value.get<TYPE_TIMESTAMPTZ>().to_string(
                       state.timezone_obj(), 6),
-              "2021-01-01 08:03:19.123000+08:00");
+              "2021-01-01 08:03:19.123456+08:00");
 }
 
 TEST_F(NewOrcReaderTest, AggregatePushdownCharMinMaxTrimsTrailingSpaces) {
@@ -10371,7 +10403,7 @@ TEST_F(NewOrcReaderTest, ReadPrimitiveTypesWithNulls) {
     EXPECT_EQ(schema[15].type->to_string(*block.get_by_position(15).column, 
NULL_ROW), "NULL");
 }
 
-TEST_F(NewOrcReaderTest, ReadTimestampNanosecondsRoundsToMicroseconds) {
+TEST_F(NewOrcReaderTest, ReadTimestampNanosecondsTruncatesToMicroseconds) {
     const auto file_path = (_test_dir / "timestamp_rounding.orc").string();
     write_timestamp_rounding_orc_file(file_path);
     auto reader = create_reader_for_path(file_path);
@@ -10395,8 +10427,8 @@ TEST_F(NewOrcReaderTest, 
ReadTimestampNanosecondsRoundsToMicroseconds) {
     ASSERT_EQ(rows, TIMESTAMP_ROUNDING_ROW_COUNT);
 
     static constexpr std::array<std::string_view, 
TIMESTAMP_ROUNDING_ROW_COUNT> expected {
-            "1970-01-01 00:00:01.111002", "1970-01-01 00:00:02.345678",
-            "2021-01-01 00:00:00.000000", "1969-12-31 23:59:58.999999"};
+            "1970-01-01 00:00:01.111001", "1970-01-01 00:00:02.345678",
+            "2020-12-31 23:59:59.999999", "1969-12-31 23:59:58.999998"};
     for (size_t column_id = 0; column_id < schema.size(); ++column_id) {
         for (size_t row = 0; row < expected.size(); ++row) {
             
EXPECT_EQ(schema[column_id].type->to_string(*block.get_by_position(column_id).column,
@@ -10439,48 +10471,48 @@ Status decode_orc_timestamp_boundary(int64_t seconds, 
int64_t nanoseconds, bool
     return status;
 }
 
-TEST_F(NewOrcReaderTest, 
PlainTimestampRoundsCarryInCivilTimeAcrossDstTransitions) {
+TEST_F(NewOrcReaderTest, 
PlainTimestampTruncatesSubMicrosecondsAcrossDstTransitions) {
     TimezoneUtils::load_timezones_to_cache();
     cctz::time_zone los_angeles;
     ASSERT_TRUE(TimezoneUtils::find_cctz_time_zone("America/Los_Angeles", 
los_angeles));
 
-    constexpr int64_t ROUNDING_CARRY_NANOS = 999999500;
+    constexpr int64_t SUB_MICROSECOND_NANOS = 999999500;
     std::string decoded_value;
-    ASSERT_TRUE(decode_orc_timestamp_boundary(1636275599, 
ROUNDING_CARRY_NANOS, false, los_angeles,
+    ASSERT_TRUE(decode_orc_timestamp_boundary(1636275599, 
SUB_MICROSECOND_NANOS, false, los_angeles,
                                               &decoded_value)
                         .ok());
-    EXPECT_EQ(decoded_value, "2021-11-07 02:00:00.000000");
+    EXPECT_EQ(decoded_value, "2021-11-07 01:59:59.999999");
 
-    ASSERT_TRUE(decode_orc_timestamp_boundary(1615715999, 
ROUNDING_CARRY_NANOS, false, los_angeles,
+    ASSERT_TRUE(decode_orc_timestamp_boundary(1615715999, 
SUB_MICROSECOND_NANOS, false, los_angeles,
                                               &decoded_value)
                         .ok());
-    EXPECT_EQ(decoded_value, "2021-03-14 02:00:00.000000");
+    EXPECT_EQ(decoded_value, "2021-03-14 01:59:59.999999");
 }
 
-TEST_F(NewOrcReaderTest, TimestampInstantKeepsEpochCarryAcrossDstTransitions) {
+TEST_F(NewOrcReaderTest, 
TimestampInstantTruncatesSubMicrosecondsAcrossDstTransitions) {
     TimezoneUtils::load_timezones_to_cache();
     cctz::time_zone los_angeles;
     ASSERT_TRUE(TimezoneUtils::find_cctz_time_zone("America/Los_Angeles", 
los_angeles));
 
-    constexpr int64_t ROUNDING_CARRY_NANOS = 999999500;
+    constexpr int64_t SUB_MICROSECOND_NANOS = 999999500;
     std::string decoded_value;
-    ASSERT_TRUE(decode_orc_timestamp_boundary(1636275599, 
ROUNDING_CARRY_NANOS, true, los_angeles,
+    ASSERT_TRUE(decode_orc_timestamp_boundary(1636275599, 
SUB_MICROSECOND_NANOS, true, los_angeles,
                                               &decoded_value)
                         .ok());
-    EXPECT_EQ(decoded_value, "2021-11-07 09:00:00.000000+00:00");
+    EXPECT_EQ(decoded_value, "2021-11-07 08:59:59.999999+00:00");
 
-    ASSERT_TRUE(decode_orc_timestamp_boundary(1615715999, 
ROUNDING_CARRY_NANOS, true, los_angeles,
+    ASSERT_TRUE(decode_orc_timestamp_boundary(1615715999, 
SUB_MICROSECOND_NANOS, true, los_angeles,
                                               &decoded_value)
                         .ok());
-    EXPECT_EQ(decoded_value, "2021-03-14 10:00:00.000000+00:00");
+    EXPECT_EQ(decoded_value, "2021-03-14 09:59:59.999999+00:00");
 }
 
-TEST_F(NewOrcReaderTest, PlainTimestampDstCarryMatchesStripeStatistics) {
+TEST_F(NewOrcReaderTest, PlainTimestampTruncationMatchesStripeStatistics) {
     const auto file_path = (_test_dir / 
"timestamp_dst_carry_statistics.orc").string();
     constexpr int64_t BEFORE_DST_ROLLBACK = 1636275599;
-    constexpr int64_t ROUNDING_CARRY_NANOS = 999999500;
+    constexpr int64_t SUB_MICROSECOND_NANOS = 999999500;
     write_two_stripe_constant_timestamp_file(file_path, BEFORE_DST_ROLLBACK, 
BEFORE_DST_ROLLBACK,
-                                             "America/Los_Angeles", 
ROUNDING_CARRY_NANOS);
+                                             "America/Los_Angeles", 
SUB_MICROSECOND_NANOS);
 
     RuntimeState state {TQueryOptions(), TQueryGlobals()};
     state.set_timezone("America/Los_Angeles");
@@ -10500,7 +10532,7 @@ TEST_F(NewOrcReaderTest, 
PlainTimestampDstCarryMatchesStripeStatistics) {
     format::FileAggregateResult aggregate_result;
     ASSERT_TRUE(reader->get_aggregate_result(aggregate_request, 
&aggregate_result).ok());
     ASSERT_EQ(aggregate_result.columns.size(), 1);
-    const auto expected = make_datetime_v2(2021, 11, 7, 2, 0, 0);
+    const auto expected = make_datetime_v2(2021, 11, 7, 1, 59, 59, 999999);
     EXPECT_EQ(aggregate_result.columns[0].min_value.get<TYPE_DATETIMEV2>(), 
expected);
     EXPECT_EQ(aggregate_result.columns[0].max_value.get<TYPE_DATETIMEV2>(), 
expected);
 }
@@ -10522,28 +10554,28 @@ TEST_F(NewOrcReaderTest, 
PlainTimestampAcceptsDorisBoundariesAcrossTimezones) {
                         .ok());
 }
 
-TEST_F(NewOrcReaderTest, TimestampRoundingRejectsUpperDorisBoundary) {
-    // 253402300799 is 9999-12-31 23:59:59 UTC; rounding the fractional part 
carries
-    // into year 10000, which neither Doris timestamp representation can store.
+TEST_F(NewOrcReaderTest, TimestampTruncationAcceptsUpperDorisBoundary) {
+    // 253402300799 is 9999-12-31 23:59:59 UTC. Truncation retains the 
representable
+    // 999999 microsecond value instead of carrying into year 10000.
     constexpr int64_t MAX_DORIS_EPOCH_SECOND = 253402300799LL;
-    constexpr int64_t ROUNDING_CARRY_NANOS = 999999500;
-    EXPECT_FALSE(decode_orc_timestamp_boundary(MAX_DORIS_EPOCH_SECOND, 
ROUNDING_CARRY_NANOS, false)
-                         .ok());
-    EXPECT_FALSE(
-            decode_orc_timestamp_boundary(MAX_DORIS_EPOCH_SECOND, 
ROUNDING_CARRY_NANOS, true).ok());
+    constexpr int64_t SUB_MICROSECOND_NANOS = 999999500;
+    EXPECT_TRUE(decode_orc_timestamp_boundary(MAX_DORIS_EPOCH_SECOND, 
SUB_MICROSECOND_NANOS, false)
+                        .ok());
+    EXPECT_TRUE(decode_orc_timestamp_boundary(MAX_DORIS_EPOCH_SECOND, 
SUB_MICROSECOND_NANOS, true)
+                        .ok());
 }
 
-TEST_F(NewOrcReaderTest, TimestampRoundingRejectsSecondOverflow) {
-    constexpr int64_t ROUNDING_CARRY_NANOS = 999999500;
+TEST_F(NewOrcReaderTest, TimestampTruncationRejectsOutOfRangeSecond) {
+    constexpr int64_t SUB_MICROSECOND_NANOS = 999999500;
     
EXPECT_FALSE(decode_orc_timestamp_boundary(std::numeric_limits<int64_t>::max(),
-                                               ROUNDING_CARRY_NANOS, false)
+                                               SUB_MICROSECOND_NANOS, false)
                          .ok());
     
EXPECT_FALSE(decode_orc_timestamp_boundary(std::numeric_limits<int64_t>::max(),
-                                               ROUNDING_CARRY_NANOS, true)
+                                               SUB_MICROSECOND_NANOS, true)
                          .ok());
 }
 
-TEST_F(NewOrcReaderTest, TimestampSargMatchesRoundedMicroseconds) {
+TEST_F(NewOrcReaderTest, TimestampSargMatchesTruncatedMicroseconds) {
     const auto file_path = (_test_dir / 
"timestamp_rounding_sarg.orc").string();
     write_timestamp_rounding_orc_file(file_path, 1);
     auto reader = create_reader_for_path(file_path);
@@ -10561,7 +10593,7 @@ TEST_F(NewOrcReaderTest, 
TimestampSargMatchesRoundedMicroseconds) {
             
VExprContext::create_shared(std::make_shared<NullableInExpr<TYPE_DATETIMEV2>>(
                     0, remove_nullable(schema[0].type),
                     std::vector<Field> {Field::create_field<TYPE_DATETIMEV2>(
-                            make_datetime_v2(1970, 1, 1, 0, 0, 1, 111002))},
+                            make_datetime_v2(1970, 1, 1, 0, 0, 1, 111001))},
                     "timestamp_col")));
     ASSERT_TRUE(reader->open(request).ok());
 
@@ -10572,14 +10604,14 @@ TEST_F(NewOrcReaderTest, 
TimestampSargMatchesRoundedMicroseconds) {
     EXPECT_FALSE(eof);
     ASSERT_EQ(rows, 1);
     EXPECT_EQ(schema[0].type->to_string(*block.get_by_position(0).column, 0),
-              "1970-01-01 00:00:01.111002");
+              "1970-01-01 00:00:01.111001");
 }
 
 TEST_F(NewOrcReaderTest, TimestampSargMillisecondBoundsNeverPruneMatches) {
     auto scan_rows = [&](std::string_view suffix, TExprOpcode::type 
comparison_op,
                          uint32_t literal_microseconds, bool not_in = false) {
         const auto file_path = (_test_dir / 
fmt::format("timestamp_sarg_{}.orc", suffix)).string();
-        // The first stripe rounds 1.111001900 to 1.111002, while the second 
rounds the same
+        // The first stripe truncates 1.111001900 to 1.111001, while the 
second truncates the same
         // fractional part at second 10. Millisecond statistics must never 
make the SARG stricter
         // than Doris's exact microsecond row comparison.
         write_two_stripe_constant_timestamp_file(file_path, 1, 10, "GMT", 
111001900);
@@ -10620,17 +10652,54 @@ TEST_F(NewOrcReaderTest, 
TimestampSargMillisecondBoundsNeverPruneMatches) {
         return std::pair {result_rows, 
reader->reader_statistics().filtered_row_groups};
     };
 
-    EXPECT_EQ(scan_rows("gt", TExprOpcode::GT, 111001).first, 400);
+    EXPECT_EQ(scan_rows("gt", TExprOpcode::GT, 111001).first, 200);
     EXPECT_EQ(scan_rows("lt", TExprOpcode::LT, 111003).first, 200);
     const auto not_equal = scan_rows("ne", TExprOpcode::NE, 111001);
-    EXPECT_EQ(not_equal.first, 400);
+    EXPECT_EQ(not_equal.first, 200);
     EXPECT_EQ(not_equal.second, 0);
     const auto not_in = scan_rows("not_in", TExprOpcode::INVALID_OPCODE, 
111001, true);
-    EXPECT_EQ(not_in.first, 400);
+    EXPECT_EQ(not_in.first, 200);
     EXPECT_EQ(not_in.second, 0);
 }
 
-TEST_F(NewOrcReaderTest, NegativeTimestampRoundingBypassesUnsafeSarg) {
+TEST_F(NewOrcReaderTest, TimestampSargExactEpochPrunesNegativeStripe) {
+    const auto file_path = (_test_dir / 
"timestamp_sarg_exact_epoch.orc").string();
+    write_two_stripe_constant_timestamp_file(file_path, -2, 0, "GMT", 789);
+    ASSERT_EQ(get_orc_stripe_count(file_path), 2);
+
+    auto reader = create_reader_for_path(file_path);
+    RuntimeState state {TQueryOptions(), TQueryGlobals()};
+    state.set_timezone("+00:00");
+    ASSERT_TRUE(reader->init(&state).ok());
+    std::vector<format::ColumnDefinition> schema;
+    ASSERT_TRUE(reader->get_schema(&schema).ok());
+    ASSERT_EQ(schema.size(), 2);
+
+    auto request = std::make_shared<format::FileScanRequest>();
+    request->predicate_columns = {field_projection(0)};
+    request->conjuncts.push_back(
+            
VExprContext::create_shared(std::make_shared<NullableInExpr<TYPE_DATETIMEV2>>(
+                    0, remove_nullable(schema[0].type),
+                    std::vector<Field> {
+                            
Field::create_field<TYPE_DATETIMEV2>(make_datetime_v2(1970, 1, 1))},
+                    "timestamp_col")));
+    ASSERT_TRUE(reader->open(request).ok());
+
+    bool eof = false;
+    size_t result_rows = 0;
+    while (!eof) {
+        Block block = build_file_block({schema[0]});
+        size_t rows = 0;
+        ASSERT_TRUE(reader->get_block(&block, &rows, &eof).ok());
+        result_rows += rows;
+    }
+    EXPECT_EQ(result_rows, 200);
+    EXPECT_EQ(reader->reader_statistics().filtered_row_groups, 1);
+    EXPECT_EQ(reader->reader_statistics().filtered_row_groups_by_min_max, 1);
+    EXPECT_EQ(reader->reader_statistics().filtered_group_rows, 200);
+}
+
+TEST_F(NewOrcReaderTest, NegativeTimestampTruncationBypassesUnsafeSarg) {
     const auto file_path = (_test_dir / 
"negative_timestamp_rounding_sarg.orc").string();
     write_timestamp_rounding_orc_file(file_path, 1, 3);
     auto reader = create_reader_for_path(file_path);
@@ -10648,7 +10717,7 @@ TEST_F(NewOrcReaderTest, 
NegativeTimestampRoundingBypassesUnsafeSarg) {
             
VExprContext::create_shared(std::make_shared<NullableInExpr<TYPE_DATETIMEV2>>(
                     0, remove_nullable(schema[0].type),
                     std::vector<Field> {Field::create_field<TYPE_DATETIMEV2>(
-                            make_datetime_v2(1969, 12, 31, 23, 59, 58, 
999999))},
+                            make_datetime_v2(1969, 12, 31, 23, 59, 58, 
999998))},
                     "timestamp_col")));
     ASSERT_TRUE(reader->open(request).ok());
 
@@ -10659,7 +10728,7 @@ TEST_F(NewOrcReaderTest, 
NegativeTimestampRoundingBypassesUnsafeSarg) {
     EXPECT_FALSE(eof);
     ASSERT_EQ(rows, 1);
     EXPECT_EQ(schema[0].type->to_string(*block.get_by_position(0).column, 0),
-              "1969-12-31 23:59:58.999999");
+              "1969-12-31 23:59:58.999998");
     EXPECT_EQ(reader->reader_statistics().filtered_row_groups, 0);
 }
 
@@ -10692,7 +10761,7 @@ TEST_F(NewOrcReaderTest, 
NegativeTimestampNotInEpochBypassesUnsafeSarg) {
     EXPECT_FALSE(eof);
     ASSERT_EQ(rows, 1);
     EXPECT_EQ(schema[0].type->to_string(*block.get_by_position(0).column, 0),
-              "1969-12-31 23:59:58.999999");
+              "1969-12-31 23:59:58.999998");
     EXPECT_EQ(reader->reader_statistics().filtered_row_groups, 0);
 }
 
diff --git 
a/regression-test/data/external_table_p0/paimon/paimon_timestamp_types.out 
b/regression-test/data/external_table_p0/paimon/paimon_timestamp_types.out
index ddee5b68511..1645aae4969 100644
--- a/regression-test/data/external_table_p0/paimon/paimon_timestamp_types.out
+++ b/regression-test/data/external_table_p0/paimon/paimon_timestamp_types.out
@@ -5,6 +5,28 @@
 -- !c2 --
 1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456
 
+-- !ts9_eq_orc --
+1
+
+-- !ts9_eq_parquet --
+1
+
+-- !ts9_lt_orc --
+
+-- !ts9_le_orc --
+1
+
+-- !ts9_gt_parquet --
+
+-- !ts9_ge_parquet --
+1
+
+-- !ts9_in_orc --
+1
+
+-- !ts9_ne_parquet --
+1
+
 -- !ltz_ntz_simple2 --
 1      {"2024-01-01 10:12:34.123456":"2024-01-02 10:12:34.123456", "2024-01-03 
10:12:34.123456":"2024-01-04 10:12:34.123456"}  {"2024-01-03 
10:12:34.123456":"2024-01-04 10:12:34.123456", "2024-01-01 
10:12:34.123456":"2024-01-02 10:12:34.123456"}  ["2024-01-01 10:12:34.123456", 
"2024-01-02 10:12:34.123456", "2024-01-03 10:12:34.123456"]      ["2024-01-01 
10:12:34.123456", "2024-01-02 10:12:34.123456", "2024-01-03 10:12:34.123456"]   
   {"crow1":"2024-01-01 10:12:34.123456", "crow2":"2024-01-02 10:12:34.123456"}
 
@@ -46,13 +68,34 @@
 
 -- !ltz_ntz_simple15 --
 2024-01-02T10:12:34.123456
-
 -- !c1 --
-1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123457      
2024-01-02T10:04:05.123457      2024-01-02T10:04:05.123457      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123457      
2024-01-02T02:04:05.123457      2024-01-02T02:04:05.123457
+1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456
 
 -- !c2 --
 1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456
 
+-- !ts9_eq_orc --
+1
+
+-- !ts9_eq_parquet --
+1
+
+-- !ts9_lt_orc --
+
+-- !ts9_le_orc --
+1
+
+-- !ts9_gt_parquet --
+
+-- !ts9_ge_parquet --
+1
+
+-- !ts9_in_orc --
+1
+
+-- !ts9_ne_parquet --
+1
+
 -- !ltz_ntz_simple2 --
 1      {"2024-01-01 10:12:34.123456":"2024-01-02 10:12:34.123456", "2024-01-03 
10:12:34.123456":"2024-01-04 10:12:34.123456"}  {"2024-01-03 
02:12:34.123456":"2024-01-04 02:12:34.123456", "2024-01-01 
02:12:34.123456":"2024-01-02 02:12:34.123456"}  ["2024-01-01 10:12:34.123456", 
"2024-01-02 10:12:34.123456", "2024-01-03 10:12:34.123456"]      ["2024-01-01 
02:12:34.123456", "2024-01-02 02:12:34.123456", "2024-01-03 02:12:34.123456"]   
   {"crow1":"2024-01-01 10:12:34.123456", "crow2":"2024-01-02 02:12:34.123456"}
 
diff --git 
a/regression-test/data/external_table_p0/paimon/test_paimon_minio.out 
b/regression-test/data/external_table_p0/paimon/test_paimon_minio.out
index 78b3edaca1d..37f9f66619c 100644
--- a/regression-test/data/external_table_p0/paimon/test_paimon_minio.out
+++ b/regression-test/data/external_table_p0/paimon/test_paimon_minio.out
@@ -1,25 +1,25 @@
 -- This file is automatically generated. You should know what you did if you 
want to edit this
 -- !no_region1 --
-2024-01-02T10:04:05.100        2024-01-02T02:04:05.123457
+2024-01-02T10:04:05.100        2024-01-02T02:04:05.123456
 
 -- !no_region2 --
-1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123457      
2024-01-02T10:04:05.123457      2024-01-02T10:04:05.123457      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123457      
2024-01-02T02:04:05.123457      2024-01-02T02:04:05.123457
+1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456
 
 -- !region1 --
-2024-01-02T10:04:05.100        2024-01-02T02:04:05.123457
+2024-01-02T10:04:05.100        2024-01-02T02:04:05.123456
 
 -- !region2 --
-1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123457      
2024-01-02T10:04:05.123457      2024-01-02T10:04:05.123457      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123457      
2024-01-02T02:04:05.123457      2024-01-02T02:04:05.123457
+1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456
 
 -- !no_region1 --
-2024-01-02T10:04:05.100        2024-01-02T02:04:05.123457
+2024-01-02T10:04:05.100        2024-01-02T02:04:05.123456
 
 -- !no_region2 --
-1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123457      
2024-01-02T10:04:05.123457      2024-01-02T10:04:05.123457      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123457      
2024-01-02T02:04:05.123457      2024-01-02T02:04:05.123457
+1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456
 
 -- !region1 --
-2024-01-02T10:04:05.100        2024-01-02T02:04:05.123457
+2024-01-02T10:04:05.100        2024-01-02T02:04:05.123456
 
 -- !region2 --
-1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123457      
2024-01-02T10:04:05.123457      2024-01-02T10:04:05.123457      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123457      
2024-01-02T02:04:05.123457      2024-01-02T02:04:05.123457
+1      2024-01-02T10:04:05.100 2024-01-02T10:04:05.120 2024-01-02T10:04:05.123 
2024-01-02T10:04:05.123400      2024-01-02T10:04:05.123450      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T10:04:05.123456      2024-01-02T10:04:05.123456      
2024-01-02T02:04:05.100 2024-01-02T02:04:05.120 2024-01-02T02:04:05.123 
2024-01-02T02:04:05.123400      2024-01-02T02:04:05.123450      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456      
2024-01-02T02:04:05.123456      2024-01-02T02:04:05.123456
 
diff --git 
a/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group4_orc_files.out
 
b/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group4_orc_files.out
index 855cae34668..1f5ea974148 100644
--- 
a/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group4_orc_files.out
+++ 
b/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group4_orc_files.out
@@ -1,6 +1,6 @@
 -- This file is automatically generated. You should know what you did if you 
want to edit this
 -- !test_0 --
-2022-06-10T05:26:22.754        2022-06-09T21:26:22.754
+2022-06-10T05:26:22.753999     2022-06-09T21:26:22.753999
 
 -- !test_1 --
 1      John    Doe
diff --git 
a/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group6_orc_files.out
 
b/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group6_orc_files.out
index dcf3cdb63ef..ea26e348943 100644
--- 
a/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group6_orc_files.out
+++ 
b/regression-test/data/external_table_p0/tvf/orc_tvf/test_hdfs_orc_group6_orc_files.out
@@ -484,7 +484,7 @@ true        127     32767   2147483647      
9223372036854775807     12335.6 1234567.8       jack    rose    titani
 9      yuwgyvkrwntnbpheafsj
 
 -- !test_29 --
-2      0       0       2       banana  4.0
+2      0       0       2       banana  4
 
 -- !test_30 --
 10
@@ -590,7 +590,7 @@ world       world
 1      2006-01-02T07:04:05
 2      2006-01-02T07:04:05.900
 3      2006-01-02T07:04:05.999999
-4      2006-01-02T07:04:06
+4      2006-01-02T07:04:05.999999
 
 -- !test_40 --
 1      [0.999999999]
diff --git 
a/regression-test/suites/external_table_p0/paimon/paimon_timestamp_types.groovy 
b/regression-test/suites/external_table_p0/paimon/paimon_timestamp_types.groovy
index fd0f31d04e1..7adeb3e5677 100644
--- 
a/regression-test/suites/external_table_p0/paimon/paimon_timestamp_types.groovy
+++ 
b/regression-test/suites/external_table_p0/paimon/paimon_timestamp_types.groovy
@@ -99,6 +99,50 @@ suite("paimon_timestamp_types", "p0,external") {
             qt_c1 ts_scale_orc
             qt_c2 ts_scale_parquet
 
+            // TIMESTAMP(7/8/9) values are exposed by Doris at microsecond 
precision. Keep the
+            // predicates below on ts9 so file pruning must retain the row 
whose source value has
+            // nanoseconds beyond the Doris-visible literal.
+            order_qt_ts9_eq_orc """
+                select id from ts_scale_orc
+                where ts9 = '2024-01-02 10:04:05.123456'
+                order by id
+            """
+            order_qt_ts9_eq_parquet """
+                select id from ts_scale_parquet
+                where ts9 = '2024-01-02 10:04:05.123456'
+                order by id
+            """
+            order_qt_ts9_lt_orc """
+                select id from ts_scale_orc
+                where ts9 < '2024-01-02 10:04:05.123456'
+                order by id
+            """
+            order_qt_ts9_le_orc """
+                select id from ts_scale_orc
+                where ts9 <= '2024-01-02 10:04:05.123456'
+                order by id
+            """
+            order_qt_ts9_gt_parquet """
+                select id from ts_scale_parquet
+                where ts9 > '2024-01-02 10:04:05.123456'
+                order by id
+            """
+            order_qt_ts9_ge_parquet """
+                select id from ts_scale_parquet
+                where ts9 >= '2024-01-02 10:04:05.123456'
+                order by id
+            """
+            order_qt_ts9_in_orc """
+                select id from ts_scale_orc
+                where ts9 in ('2024-01-02 10:04:05.123457', '2024-01-02 
10:04:05.123456')
+                order by id
+            """
+            order_qt_ts9_ne_parquet """
+                select id from ts_scale_parquet
+                where ts9 != '2024-01-02 10:04:05.123457'
+                order by id
+            """
+
         }
 
         sql """set force_jni_scanner=true"""


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

Reply via email to