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


##########
be/src/core/data_type/data_type_date_or_datetime_v2.h:
##########
@@ -154,10 +160,56 @@ class DataTypeDateTimeV2 final : public 
DataTypeNumberBase<PrimitiveType::TYPE_D
     UInt32 _scale;
 };
 
+class DataTypeDateTimeV2Nano final
+        : public DataTypeNumberBase<PrimitiveType::TYPE_DATETIMEV2_NANO> {
+public:
+    static constexpr PrimitiveType PType = TYPE_DATETIMEV2_NANO;
+    static constexpr bool is_parametric = true;
+
+    explicit DataTypeDateTimeV2Nano(UInt32 scale = 9) : _scale(scale) {
+        DORIS_CHECK_GE(scale, 7);
+        DORIS_CHECK_LE(scale, 9);
+    }
+
+    PrimitiveType get_primitive_type() const override { return 
TYPE_DATETIMEV2_NANO; }
+    doris::FieldType get_storage_field_type() const override {
+        return doris::FieldType::OLAP_FIELD_TYPE_DATETIMEV2_NANO;
+    }
+    void to_protobuf(PTypeDesc* ptype, PTypeNode* node, PScalarType* 
scalar_type) const override {
+        scalar_type->set_scale(_scale);
+    }
+
+    const std::string get_family_name() const override { return 
"DateTimeV2Nano"; }

Review Comment:
   [P1] Register runtime functions for the new family
   
   Giving scale-7..9 values a distinct `DateTimeV2Nano` family changes BE 
function lookup keys, but the FE still accepts these values for wildcard 
DATETIMEV2 functions. For example, a stored `dt9` makes `date_format(dt9, 
'%Y')` request `date_formatDateTimeV2NanoString`, while only the legacy-family 
key is registered; `convert_tz` similarly reaches an implementation that 
asserts `ColumnDateTimeV2`. The added cases use foldable literals, so they do 
not exercise this column path. Please register/implement the nano variants (and 
cover stored columns) before exposing this family.



##########
be/src/exprs/function/cast/function_cast_date.cpp:
##########
@@ -30,9 +30,14 @@ WrapperType create_datelike_wrapper(FunctionContext* 
context, const DataTypePtr&
     auto make_datelike_wrapper = [&](const auto& types) -> bool {
         using Types = std::decay_t<decltype(types)>;
         using FromDataType = typename Types::LeftType;
-        if constexpr (CastUtil::IsPureDigitType<FromDataType> || 
IsDatelikeTypes<FromDataType> ||
-                      IsStringType<FromDataType> ||
-                      std::is_same_v<FromDataType, DataTypeTimeStampTz>) {
+        constexpr bool is_nano_source = std::is_same_v<FromDataType, 
DataTypeDateTimeV2Nano>;
+        constexpr bool is_nano_target = std::is_same_v<ToDataType, 
DataTypeDateTimeV2Nano>;
+        constexpr bool is_supported_nano_cast = is_nano_target && 
IsStringType<FromDataType>;

Review Comment:
   [P1] Support non-identity casts involving nano datetimes
   
   This accepts only string-to-nano, while the legacy branch rejects every nano 
endpoint. Consequently stored-column casts fail not only across the scale-6/7 
representation boundary in both directions, but also within the nano family: 
`DATETIMEV2(9)` to `DATETIMEV2(8)` is non-identity because equality includes 
scale and then reaches the unsupported wrapper. FE can insert the cross-family 
failure implicitly for comparisons, joins, or unions. Please implement all 
required scale conversions and add non-foldable column tests.



##########
be/src/core/data_type_serde/data_type_datetimev2_nano_serde.cpp:
##########
@@ -0,0 +1,430 @@
+// 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.
+
+#include "core/data_type_serde/data_type_datetimev2_nano_serde.h"
+
+#include <arrow/array.h>
+#include <arrow/builder.h>
+#include <cctz/time_zone.h>
+
+#include <cctype>
+#include <limits>
+#include <orc/Vector.hh>
+#include <string>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type_serde/arrow_validation.h"
+#include "core/data_type_serde/decoded_column_view.h"
+#include "core/value/vdatetime_value.h"
+#include "exprs/function/cast/cast_to_datetimev2_impl.hpp"
+#include "util/mysql_row_buffer.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+constexpr int64_t NANOS_PER_MILLISECOND = 1000000;
+constexpr int64_t NANOS_PER_MICROSECOND = 1000;
+
+bool checked_scale_to_nanos(int64_t value, int64_t multiplier, int64_t* 
result) {
+    return !__builtin_mul_overflow(value, multiplier, result);
+}
+
+Status utc_epoch_nanos_to_local_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    DateV2Value<DateTimeV2ValueType> local_value;
+    local_value.from_unixtime(source_value.epoch_seconds(), timezone);
+    local_value.set_microsecond(source_value.microsecond());
+    DateTimeV2NanoValue target_value;
+    if (!target_value.from_datetime(local_value, 
source_value.nanosecond_remainder())) {
+        return Status::DataQualityError("Timestamp {} is outside DATETIMEV2 
nanosecond range",
+                                        source);
+    }
+    *result = target_value.epoch_nanos();
+    return Status::OK();
+}
+
+Status local_epoch_nanos_to_utc_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    auto local_value = source_value.to_datetime();
+    int64_t seconds = 0;
+    local_value.unix_timestamp(&seconds, timezone);
+    const __int128 epoch_nanos =
+            static_cast<__int128>(seconds) * 
DateTimeV2NanoValue::NANOS_PER_SECOND +
+            source_value.nanosecond();
+    if (epoch_nanos < std::numeric_limits<int64_t>::min() ||
+        epoch_nanos > std::numeric_limits<int64_t>::max()) {
+        return Status::DataQualityError("DATETIMEV2 value {} is outside epoch 
nanosecond range",
+                                        source_value.to_string(9));
+    }
+    *result = static_cast<int64_t>(epoch_nanos);
+    return Status::OK();
+}
+
+} // namespace
+
+Status parse_datetimev2_nano(StringRef str, int scale, int64_t* epoch_nanos,
+                             const cctz::time_zone* local_time_zone) {
+    DORIS_CHECK_GE(scale, 7);
+    DORIS_CHECK_LE(scale, 9);
+
+    std::string input(str.data, str.size);
+    size_t fraction_begin = std::string::npos;
+    size_t fraction_end = std::string::npos;
+    const size_t dot = input.rfind('.');
+    if (dot != std::string::npos && dot + 1 < input.size() &&
+        std::isdigit(static_cast<unsigned char>(input[dot + 1]))) {
+        fraction_begin = dot + 1;
+        fraction_end = fraction_begin;
+        while (fraction_end < input.size() &&
+               std::isdigit(static_cast<unsigned char>(input[fraction_end]))) {
+            ++fraction_end;
+        }
+    }
+
+    uint32_t nanos = 0;
+    size_t fraction_length = 0;
+    if (fraction_begin != std::string::npos) {
+        fraction_length = fraction_end - fraction_begin;
+        const size_t copied_digits = std::min<size_t>(fraction_length, 9);
+        for (size_t i = 0; i < copied_digits; ++i) {
+            nanos = nanos * 10 + static_cast<uint32_t>(input[fraction_begin + 
i] - '0');
+        }
+        for (size_t i = copied_digits; i < 9; ++i) {
+            nanos *= 10;
+        }
+    }
+
+    const auto quantum = static_cast<uint32_t>(int_exp10(9 - scale));
+    nanos = nanos / quantum * quantum;
+    if (fraction_length > static_cast<size_t>(scale) && input[fraction_begin + 
scale] >= '5') {
+        nanos += quantum;
+    }
+
+    std::string base = input;
+    if (fraction_begin != std::string::npos) {
+        base.erase(dot, fraction_end - dot);
+    }
+    const StringRef base_ref(base.data(), base.size());
+    DateV2Value<DateTimeV2ValueType> datetime;
+    CastParameters params {.status = Status::OK(), .is_strict = true};
+    CastToDatetimeV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
+            base_ref, datetime, local_time_zone, 0, params);
+    if (!params.status.ok()) {
+        return params.status;
+    }
+
+    if (nanos == DateTimeV2NanoValue::NANOS_PER_SECOND) {
+        if (!datetime.date_add_interval<TimeUnit::SECOND>(
+                    TimeInterval {TimeUnit::SECOND, 1, false})) {
+            return Status::InvalidArgument("DATETIMEV2 value overflows while 
rounding '{}'", input);
+        }
+        nanos = 0;
+    }
+    datetime.set_microsecond(nanos / NANOS_PER_MICROSECOND);
+    DateTimeV2NanoValue value;
+    if (!value.from_datetime(datetime, static_cast<uint16_t>(nanos % 
NANOS_PER_MICROSECOND))) {
+        return Status::InvalidArgument(
+                "DATETIMEV2({}) value '{}' is outside [{}, {}]", scale, input,
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::min()).to_string(scale),
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::max()).to_string(scale));
+    }
+    *epoch_nanos = value.epoch_nanos();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_batch(const ColumnString& 
strings,
+                                                      ColumnNullable& result,
+                                                      const FormatOptions& 
options) const {
+    auto& data = 
assert_cast<ColumnDateTimeV2Nano&>(result.get_nested_column()).get_data();
+    auto& null_map = result.get_null_map_column().get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        int64_t value = 0;
+        const auto status =
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone);
+        null_map[i] = !status.ok();
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode_batch(
+        const ColumnString& strings, IColumn& result, const FormatOptions& 
options,
+        const NullMap::value_type* null_map) const {
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(result).get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        if (null_map != nullptr && null_map[i]) {
+            continue;
+        }
+        int64_t value = 0;
+        RETURN_IF_ERROR(
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone));
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string(StringRef& str, IColumn& 
column,
+                                                const FormatOptions& options) 
const {
+    int64_t value = 0;
+    RETURN_IF_ERROR(parse_datetimev2_nano(str, _scale, &value, 
options.timezone));
+    
assert_cast<ColumnDateTimeV2Nano&>(column).insert_value(DateTimeV2NanoValue(value));
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode(StringRef& str, 
IColumn& column,
+                                                            const 
FormatOptions& options) const {
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_column_to_json(const IColumn& 
column,
+                                                             int64_t 
start_idx, int64_t end_idx,
+                                                             BufferWritable& 
bw,
+                                                             FormatOptions& 
options) const {
+    SERIALIZE_COLUMN_TO_JSON();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_one_cell_to_json(const IColumn& 
column,
+                                                               int64_t 
row_num, BufferWritable& bw,
+                                                               FormatOptions& 
options) const {
+    auto [column_ptr, index] = check_column_const_set_readability(column, 
row_num);
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    const auto value =
+            assert_cast<const ColumnDateTimeV2Nano&, 
TypeCheckOnRelease::DISABLE>(*column_ptr)
+                    .get_element(index);
+    const std::string result = value.to_string(_scale);
+    bw.write(result.data(), result.size());
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_column_from_json_vector(
+        IColumn& column, std::vector<Slice>& slices, uint64_t* 
num_deserialized,
+        const FormatOptions& options) const {
+    DESERIALIZE_COLUMN_FROM_JSON_VECTOR();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_one_cell_from_json(
+        IColumn& column, Slice& slice, const FormatOptions& options) const {
+    if (_nesting_level > 1) {
+        slice.trim_quote();
+    }
+    StringRef str(slice.data, slice.size);
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_arrow(const IColumn& 
column,
+                                                          const NullMap* 
null_map,
+                                                          arrow::ArrayBuilder* 
array_builder,
+                                                          int64_t start, 
int64_t end,
+                                                          const 
cctz::time_zone& ctz) const {
+    const auto& data = assert_cast<const 
ColumnDateTimeV2Nano&>(column).get_data();
+    auto& builder = assert_cast<arrow::TimestampBuilder&>(*array_builder);
+    const auto timestamp_type = 
std::static_pointer_cast<arrow::TimestampType>(builder.type());
+    const auto& timezone = timestamp_type->timezone();
+    for (int64_t i = start; i < end; ++i) {
+        if (null_map != nullptr && (*null_map)[i]) {
+            RETURN_IF_ERROR(checkArrowStatus(builder.AppendNull(), column, 
builder));
+            continue;
+        }
+        int64_t value = data[i].epoch_nanos();
+        if (!timezone.empty()) {
+            RETURN_IF_ERROR(local_epoch_nanos_to_utc_epoch_nanos(value, ctz, 
&value));
+        }
+        switch (timestamp_type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            value /= DateTimeV2NanoValue::NANOS_PER_SECOND;

Review Comment:
   [P1] Floor pre-epoch values when downscaling Arrow timestamps
   
   C++ signed division truncates toward zero here, so `-1ns` is emitted as 
Arrow second `0` instead of `-1`; the MILLI and MICRO branches have the same 
problem. Doris's own `epoch_seconds()` and Parquet nanos-to-micros conversion 
decrement a negative quotient when a remainder is discarded. Please apply that 
rule to every coarser Arrow unit and cover negative non-aligned values.



##########
be/src/storage/tablet/tablet_schema.cpp:
##########
@@ -478,6 +482,9 @@ void TabletColumn::init_from_pb(const ColumnPB& column) {
     if (column.has_frac()) {
         _frac = column.frac();
     }
+    if (_type == FieldType::OLAP_FIELD_TYPE_DATETIMEV2 && _frac > 6) {
+        _type = FieldType::OLAP_FIELD_TYPE_DATETIMEV2_NANO;

Review Comment:
   [P1] Extend delete predicates for the new storage type
   
   After this rewrite, a scale-7..9 column reaches `DeleteHandler` as 
`OLAP_FIELD_TYPE_DATETIMEV2_NANO`, but condition validation handles only the 
legacy datetime field type and rejects the new enum as unknown. The IN/NOT IN 
replay converter likewise has no `TYPE_DATETIMEV2_NANO` case. As a result, 
DELETE predicates on these columns fail even though scan predicates work. 
Please add validation/parsing for both comparison and list predicates and cover 
an actual DELETE.



##########
be/src/core/data_type_serde/data_type_datetimev2_nano_serde.cpp:
##########
@@ -0,0 +1,430 @@
+// 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.
+
+#include "core/data_type_serde/data_type_datetimev2_nano_serde.h"
+
+#include <arrow/array.h>
+#include <arrow/builder.h>
+#include <cctz/time_zone.h>
+
+#include <cctype>
+#include <limits>
+#include <orc/Vector.hh>
+#include <string>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type_serde/arrow_validation.h"
+#include "core/data_type_serde/decoded_column_view.h"
+#include "core/value/vdatetime_value.h"
+#include "exprs/function/cast/cast_to_datetimev2_impl.hpp"
+#include "util/mysql_row_buffer.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+constexpr int64_t NANOS_PER_MILLISECOND = 1000000;
+constexpr int64_t NANOS_PER_MICROSECOND = 1000;
+
+bool checked_scale_to_nanos(int64_t value, int64_t multiplier, int64_t* 
result) {
+    return !__builtin_mul_overflow(value, multiplier, result);
+}
+
+Status utc_epoch_nanos_to_local_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    DateV2Value<DateTimeV2ValueType> local_value;
+    local_value.from_unixtime(source_value.epoch_seconds(), timezone);
+    local_value.set_microsecond(source_value.microsecond());
+    DateTimeV2NanoValue target_value;
+    if (!target_value.from_datetime(local_value, 
source_value.nanosecond_remainder())) {
+        return Status::DataQualityError("Timestamp {} is outside DATETIMEV2 
nanosecond range",
+                                        source);
+    }
+    *result = target_value.epoch_nanos();
+    return Status::OK();
+}
+
+Status local_epoch_nanos_to_utc_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    auto local_value = source_value.to_datetime();
+    int64_t seconds = 0;
+    local_value.unix_timestamp(&seconds, timezone);
+    const __int128 epoch_nanos =
+            static_cast<__int128>(seconds) * 
DateTimeV2NanoValue::NANOS_PER_SECOND +
+            source_value.nanosecond();
+    if (epoch_nanos < std::numeric_limits<int64_t>::min() ||
+        epoch_nanos > std::numeric_limits<int64_t>::max()) {
+        return Status::DataQualityError("DATETIMEV2 value {} is outside epoch 
nanosecond range",
+                                        source_value.to_string(9));
+    }
+    *result = static_cast<int64_t>(epoch_nanos);
+    return Status::OK();
+}
+
+} // namespace
+
+Status parse_datetimev2_nano(StringRef str, int scale, int64_t* epoch_nanos,
+                             const cctz::time_zone* local_time_zone) {
+    DORIS_CHECK_GE(scale, 7);
+    DORIS_CHECK_LE(scale, 9);
+
+    std::string input(str.data, str.size);
+    size_t fraction_begin = std::string::npos;
+    size_t fraction_end = std::string::npos;
+    const size_t dot = input.rfind('.');
+    if (dot != std::string::npos && dot + 1 < input.size() &&
+        std::isdigit(static_cast<unsigned char>(input[dot + 1]))) {
+        fraction_begin = dot + 1;
+        fraction_end = fraction_begin;
+        while (fraction_end < input.size() &&
+               std::isdigit(static_cast<unsigned char>(input[fraction_end]))) {
+            ++fraction_end;
+        }
+    }
+
+    uint32_t nanos = 0;
+    size_t fraction_length = 0;
+    if (fraction_begin != std::string::npos) {
+        fraction_length = fraction_end - fraction_begin;
+        const size_t copied_digits = std::min<size_t>(fraction_length, 9);
+        for (size_t i = 0; i < copied_digits; ++i) {
+            nanos = nanos * 10 + static_cast<uint32_t>(input[fraction_begin + 
i] - '0');
+        }
+        for (size_t i = copied_digits; i < 9; ++i) {
+            nanos *= 10;
+        }
+    }
+
+    const auto quantum = static_cast<uint32_t>(int_exp10(9 - scale));
+    nanos = nanos / quantum * quantum;
+    if (fraction_length > static_cast<size_t>(scale) && input[fraction_begin + 
scale] >= '5') {
+        nanos += quantum;
+    }
+
+    std::string base = input;
+    if (fraction_begin != std::string::npos) {
+        base.erase(dot, fraction_end - dot);
+    }
+    const StringRef base_ref(base.data(), base.size());
+    DateV2Value<DateTimeV2ValueType> datetime;
+    CastParameters params {.status = Status::OK(), .is_strict = true};
+    CastToDatetimeV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
+            base_ref, datetime, local_time_zone, 0, params);
+    if (!params.status.ok()) {
+        return params.status;
+    }
+
+    if (nanos == DateTimeV2NanoValue::NANOS_PER_SECOND) {
+        if (!datetime.date_add_interval<TimeUnit::SECOND>(
+                    TimeInterval {TimeUnit::SECOND, 1, false})) {
+            return Status::InvalidArgument("DATETIMEV2 value overflows while 
rounding '{}'", input);
+        }
+        nanos = 0;
+    }
+    datetime.set_microsecond(nanos / NANOS_PER_MICROSECOND);
+    DateTimeV2NanoValue value;
+    if (!value.from_datetime(datetime, static_cast<uint16_t>(nanos % 
NANOS_PER_MICROSECOND))) {
+        return Status::InvalidArgument(
+                "DATETIMEV2({}) value '{}' is outside [{}, {}]", scale, input,
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::min()).to_string(scale),
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::max()).to_string(scale));
+    }
+    *epoch_nanos = value.epoch_nanos();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_batch(const ColumnString& 
strings,
+                                                      ColumnNullable& result,
+                                                      const FormatOptions& 
options) const {
+    auto& data = 
assert_cast<ColumnDateTimeV2Nano&>(result.get_nested_column()).get_data();
+    auto& null_map = result.get_null_map_column().get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        int64_t value = 0;
+        const auto status =
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone);
+        null_map[i] = !status.ok();
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode_batch(
+        const ColumnString& strings, IColumn& result, const FormatOptions& 
options,
+        const NullMap::value_type* null_map) const {
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(result).get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        if (null_map != nullptr && null_map[i]) {
+            continue;
+        }
+        int64_t value = 0;
+        RETURN_IF_ERROR(
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone));
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string(StringRef& str, IColumn& 
column,
+                                                const FormatOptions& options) 
const {
+    int64_t value = 0;
+    RETURN_IF_ERROR(parse_datetimev2_nano(str, _scale, &value, 
options.timezone));
+    
assert_cast<ColumnDateTimeV2Nano&>(column).insert_value(DateTimeV2NanoValue(value));
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode(StringRef& str, 
IColumn& column,
+                                                            const 
FormatOptions& options) const {
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_column_to_json(const IColumn& 
column,
+                                                             int64_t 
start_idx, int64_t end_idx,
+                                                             BufferWritable& 
bw,
+                                                             FormatOptions& 
options) const {
+    SERIALIZE_COLUMN_TO_JSON();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_one_cell_to_json(const IColumn& 
column,
+                                                               int64_t 
row_num, BufferWritable& bw,
+                                                               FormatOptions& 
options) const {
+    auto [column_ptr, index] = check_column_const_set_readability(column, 
row_num);
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    const auto value =
+            assert_cast<const ColumnDateTimeV2Nano&, 
TypeCheckOnRelease::DISABLE>(*column_ptr)
+                    .get_element(index);
+    const std::string result = value.to_string(_scale);
+    bw.write(result.data(), result.size());
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_column_from_json_vector(
+        IColumn& column, std::vector<Slice>& slices, uint64_t* 
num_deserialized,
+        const FormatOptions& options) const {
+    DESERIALIZE_COLUMN_FROM_JSON_VECTOR();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_one_cell_from_json(
+        IColumn& column, Slice& slice, const FormatOptions& options) const {
+    if (_nesting_level > 1) {
+        slice.trim_quote();
+    }
+    StringRef str(slice.data, slice.size);
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_arrow(const IColumn& 
column,
+                                                          const NullMap* 
null_map,
+                                                          arrow::ArrayBuilder* 
array_builder,
+                                                          int64_t start, 
int64_t end,
+                                                          const 
cctz::time_zone& ctz) const {
+    const auto& data = assert_cast<const 
ColumnDateTimeV2Nano&>(column).get_data();
+    auto& builder = assert_cast<arrow::TimestampBuilder&>(*array_builder);
+    const auto timestamp_type = 
std::static_pointer_cast<arrow::TimestampType>(builder.type());
+    const auto& timezone = timestamp_type->timezone();
+    for (int64_t i = start; i < end; ++i) {
+        if (null_map != nullptr && (*null_map)[i]) {
+            RETURN_IF_ERROR(checkArrowStatus(builder.AppendNull(), column, 
builder));
+            continue;
+        }
+        int64_t value = data[i].epoch_nanos();
+        if (!timezone.empty()) {
+            RETURN_IF_ERROR(local_epoch_nanos_to_utc_epoch_nanos(value, ctz, 
&value));
+        }
+        switch (timestamp_type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            value /= DateTimeV2NanoValue::NANOS_PER_SECOND;
+            break;
+        case arrow::TimeUnit::MILLI:
+            value /= NANOS_PER_MILLISECOND;
+            break;
+        case arrow::TimeUnit::MICRO:
+            value /= NANOS_PER_MICROSECOND;
+            break;
+        case arrow::TimeUnit::NANO:
+            break;
+        }
+        RETURN_IF_ERROR(checkArrowStatus(builder.Append(value), column, 
builder));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::read_column_from_arrow(IColumn& column,
+                                                           const arrow::Array* 
arrow_array,
+                                                           int64_t start, 
int64_t end,
+                                                           const 
cctz::time_zone& ctz) const {
+    if (arrow_array->type_id() != arrow::Type::TIMESTAMP) {
+        return Status::InvalidArgument("Expected Arrow timestamp, got {}", 
arrow_array->type_id());
+    }
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
+    const auto& array = assert_cast<const 
arrow::TimestampArray&>(*arrow_array);
+    const auto type = 
std::static_pointer_cast<arrow::TimestampType>(array.type());
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(column).get_data();
+    for (int64_t i = start; i < end; ++i) {
+        int64_t value = array.Value(i);
+        int64_t nanos = 0;
+        switch (type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            if (!checked_scale_to_nanos(value, 
DateTimeV2NanoValue::NANOS_PER_SECOND, &nanos)) {
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::MILLI:
+            if (!checked_scale_to_nanos(value, NANOS_PER_MILLISECOND, &nanos)) 
{
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::MICRO:
+            if (!checked_scale_to_nanos(value, NANOS_PER_MICROSECOND, &nanos)) 
{
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::NANO:
+            nanos = value;
+            break;
+        }
+        if (!type->timezone().empty()) {
+            RETURN_IF_ERROR(utc_epoch_nanos_to_local_epoch_nanos(nanos, ctz, 
&nanos));
+        }
+        data.push_back(DateTimeV2NanoValue(nanos));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::read_column_from_decoded_values(
+        IColumn& column, const DecodedColumnView& view) const {
+    if (view.value_kind != DecodedValueKind::INT64) {
+        return decoded_column_view_handle_conversion_failure(
+                column, view,
+                Status::NotSupported("DATETIMEV2 nano decoded reader expects 
INT64 source"));
+    }
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(column).get_data();
+    const auto* values = reinterpret_cast<const int64_t*>(view.values);
+    static const auto utc = cctz::utc_time_zone();
+    const auto& timezone = view.timezone == nullptr ? utc : *view.timezone;
+    for (int64_t row = 0; row < view.row_count; ++row) {
+        if (decoded_column_view_row_is_null(view, row)) {
+            data.push_back(DateTimeV2NanoValue(0));
+            continue;
+        }
+        int64_t nanos = 0;
+        int64_t multiplier = 1;
+        switch (view.time_unit) {
+        case DecodedTimeUnit::MILLIS:
+            multiplier = NANOS_PER_MILLISECOND;
+            break;
+        case DecodedTimeUnit::MICROS:
+            multiplier = NANOS_PER_MICROSECOND;
+            break;
+        case DecodedTimeUnit::NANOS:
+            break;
+        case DecodedTimeUnit::UNKNOWN:
+            return decoded_column_view_handle_conversion_failure(
+                    column, view,
+                    Status::NotSupported("DATETIMEV2 nano decoded reader 
requires a time unit"));
+        }
+        if (!checked_scale_to_nanos(values[row], multiplier, &nanos)) {
+            return decoded_column_view_handle_conversion_failure(
+                    column, view,
+                    Status::DataQualityError("Timestamp {} overflows 
nanoseconds", values[row]));
+        }
+        if (view.timestamp_is_adjusted_to_utc) {
+            RETURN_IF_ERROR(utc_epoch_nanos_to_local_epoch_nanos(nanos, 
timezone, &nanos));
+        }
+        data.push_back(DateTimeV2NanoValue(nanos));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_mysql_binary(
+        const IColumn& column, MysqlRowBinaryBuffer& row_buffer, int64_t 
row_idx, bool col_const,
+        const FormatOptions& options) const {
+    const auto& data = assert_cast<const 
ColumnDateTimeV2Nano&>(column).get_data();
+    const auto index = index_check_const(row_idx, col_const);
+    const auto value = DateTimeV2NanoValue(data[index]).to_string(_scale);
+    if (row_buffer.push_string(value.data(), value.size()) != 0) {

Review Comment:
   [P1] Keep MySQL binary metadata and value encoding consistent
   
   This writes a length-encoded 29-byte ASCII string, but FE still advertises 
the result as `MYSQL_TYPE_DATETIME`. Prepared-statement clients decode that 
type using the temporal 0/4/7/11-byte layout (which the legacy SerDe emits), so 
the leading `29` is not a valid DATETIME length and the row can be rejected or 
corrupted. Since the standard temporal layout cannot carry nanoseconds, please 
advertise a string-compatible type for this path or use another 
protocol-compatible representation.



##########
fe/fe-core/src/test/java/org/apache/doris/http/ForwardToMasterTest.java:
##########
@@ -50,13 +50,14 @@ public void testAddBeDropBe() throws Exception {
                 JSONObject object = (JSONObject) JSONValue.parse(respStr);
 
                 JSONObject data = (JSONObject) object.get("data");
-                JSONArray columnNames = (JSONArray) data.get("columnNames");
-                JSONArray rows = (JSONArray) data.get("rows");
+                JSONArray columnNames = (JSONArray) ((JSONObject) 
data.get("columnNames"))

Review Comment:
   [P1] Keep the forwarded node response as JSON arrays
   
   `NodeInfo` still serializes `columnNames` as `List<String>` and `rows` as 
`List<List<String>>`, so JSON-simple returns a `JSONArray` for each field. 
Casting either value to `JSONObject` throws before these assertions, and 
flattening `rows` also contradicts the endpoint shape. Please restore direct 
array extraction and row-by-row traversal (or remove this unrelated test 
change).



##########
be/src/core/data_type_serde/data_type_datetimev2_nano_serde.cpp:
##########
@@ -0,0 +1,430 @@
+// 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.
+
+#include "core/data_type_serde/data_type_datetimev2_nano_serde.h"
+
+#include <arrow/array.h>
+#include <arrow/builder.h>
+#include <cctz/time_zone.h>
+
+#include <cctype>
+#include <limits>
+#include <orc/Vector.hh>
+#include <string>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type_serde/arrow_validation.h"
+#include "core/data_type_serde/decoded_column_view.h"
+#include "core/value/vdatetime_value.h"
+#include "exprs/function/cast/cast_to_datetimev2_impl.hpp"
+#include "util/mysql_row_buffer.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+constexpr int64_t NANOS_PER_MILLISECOND = 1000000;
+constexpr int64_t NANOS_PER_MICROSECOND = 1000;
+
+bool checked_scale_to_nanos(int64_t value, int64_t multiplier, int64_t* 
result) {
+    return !__builtin_mul_overflow(value, multiplier, result);
+}
+
+Status utc_epoch_nanos_to_local_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    DateV2Value<DateTimeV2ValueType> local_value;
+    local_value.from_unixtime(source_value.epoch_seconds(), timezone);
+    local_value.set_microsecond(source_value.microsecond());
+    DateTimeV2NanoValue target_value;
+    if (!target_value.from_datetime(local_value, 
source_value.nanosecond_remainder())) {
+        return Status::DataQualityError("Timestamp {} is outside DATETIMEV2 
nanosecond range",
+                                        source);
+    }
+    *result = target_value.epoch_nanos();
+    return Status::OK();
+}
+
+Status local_epoch_nanos_to_utc_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    auto local_value = source_value.to_datetime();
+    int64_t seconds = 0;
+    local_value.unix_timestamp(&seconds, timezone);
+    const __int128 epoch_nanos =
+            static_cast<__int128>(seconds) * 
DateTimeV2NanoValue::NANOS_PER_SECOND +
+            source_value.nanosecond();
+    if (epoch_nanos < std::numeric_limits<int64_t>::min() ||
+        epoch_nanos > std::numeric_limits<int64_t>::max()) {
+        return Status::DataQualityError("DATETIMEV2 value {} is outside epoch 
nanosecond range",
+                                        source_value.to_string(9));
+    }
+    *result = static_cast<int64_t>(epoch_nanos);
+    return Status::OK();
+}
+
+} // namespace
+
+Status parse_datetimev2_nano(StringRef str, int scale, int64_t* epoch_nanos,
+                             const cctz::time_zone* local_time_zone) {
+    DORIS_CHECK_GE(scale, 7);
+    DORIS_CHECK_LE(scale, 9);
+
+    std::string input(str.data, str.size);
+    size_t fraction_begin = std::string::npos;
+    size_t fraction_end = std::string::npos;
+    const size_t dot = input.rfind('.');
+    if (dot != std::string::npos && dot + 1 < input.size() &&
+        std::isdigit(static_cast<unsigned char>(input[dot + 1]))) {
+        fraction_begin = dot + 1;
+        fraction_end = fraction_begin;
+        while (fraction_end < input.size() &&
+               std::isdigit(static_cast<unsigned char>(input[fraction_end]))) {
+            ++fraction_end;
+        }
+    }
+
+    uint32_t nanos = 0;
+    size_t fraction_length = 0;
+    if (fraction_begin != std::string::npos) {
+        fraction_length = fraction_end - fraction_begin;
+        const size_t copied_digits = std::min<size_t>(fraction_length, 9);
+        for (size_t i = 0; i < copied_digits; ++i) {
+            nanos = nanos * 10 + static_cast<uint32_t>(input[fraction_begin + 
i] - '0');
+        }
+        for (size_t i = copied_digits; i < 9; ++i) {
+            nanos *= 10;
+        }
+    }
+
+    const auto quantum = static_cast<uint32_t>(int_exp10(9 - scale));
+    nanos = nanos / quantum * quantum;
+    if (fraction_length > static_cast<size_t>(scale) && input[fraction_begin + 
scale] >= '5') {
+        nanos += quantum;
+    }
+
+    std::string base = input;
+    if (fraction_begin != std::string::npos) {
+        base.erase(dot, fraction_end - dot);
+    }
+    const StringRef base_ref(base.data(), base.size());
+    DateV2Value<DateTimeV2ValueType> datetime;
+    CastParameters params {.status = Status::OK(), .is_strict = true};
+    CastToDatetimeV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
+            base_ref, datetime, local_time_zone, 0, params);
+    if (!params.status.ok()) {
+        return params.status;
+    }
+
+    if (nanos == DateTimeV2NanoValue::NANOS_PER_SECOND) {
+        if (!datetime.date_add_interval<TimeUnit::SECOND>(
+                    TimeInterval {TimeUnit::SECOND, 1, false})) {
+            return Status::InvalidArgument("DATETIMEV2 value overflows while 
rounding '{}'", input);
+        }
+        nanos = 0;
+    }
+    datetime.set_microsecond(nanos / NANOS_PER_MICROSECOND);
+    DateTimeV2NanoValue value;
+    if (!value.from_datetime(datetime, static_cast<uint16_t>(nanos % 
NANOS_PER_MICROSECOND))) {
+        return Status::InvalidArgument(
+                "DATETIMEV2({}) value '{}' is outside [{}, {}]", scale, input,
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::min()).to_string(scale),
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::max()).to_string(scale));
+    }
+    *epoch_nanos = value.epoch_nanos();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_batch(const ColumnString& 
strings,
+                                                      ColumnNullable& result,
+                                                      const FormatOptions& 
options) const {
+    auto& data = 
assert_cast<ColumnDateTimeV2Nano&>(result.get_nested_column()).get_data();
+    auto& null_map = result.get_null_map_column().get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        int64_t value = 0;
+        const auto status =
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone);
+        null_map[i] = !status.ok();
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode_batch(
+        const ColumnString& strings, IColumn& result, const FormatOptions& 
options,
+        const NullMap::value_type* null_map) const {
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(result).get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        if (null_map != nullptr && null_map[i]) {
+            continue;
+        }
+        int64_t value = 0;
+        RETURN_IF_ERROR(
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone));
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string(StringRef& str, IColumn& 
column,
+                                                const FormatOptions& options) 
const {
+    int64_t value = 0;
+    RETURN_IF_ERROR(parse_datetimev2_nano(str, _scale, &value, 
options.timezone));
+    
assert_cast<ColumnDateTimeV2Nano&>(column).insert_value(DateTimeV2NanoValue(value));
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode(StringRef& str, 
IColumn& column,
+                                                            const 
FormatOptions& options) const {
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_column_to_json(const IColumn& 
column,
+                                                             int64_t 
start_idx, int64_t end_idx,
+                                                             BufferWritable& 
bw,
+                                                             FormatOptions& 
options) const {
+    SERIALIZE_COLUMN_TO_JSON();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_one_cell_to_json(const IColumn& 
column,
+                                                               int64_t 
row_num, BufferWritable& bw,
+                                                               FormatOptions& 
options) const {
+    auto [column_ptr, index] = check_column_const_set_readability(column, 
row_num);
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    const auto value =
+            assert_cast<const ColumnDateTimeV2Nano&, 
TypeCheckOnRelease::DISABLE>(*column_ptr)
+                    .get_element(index);
+    const std::string result = value.to_string(_scale);
+    bw.write(result.data(), result.size());
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_column_from_json_vector(
+        IColumn& column, std::vector<Slice>& slices, uint64_t* 
num_deserialized,
+        const FormatOptions& options) const {
+    DESERIALIZE_COLUMN_FROM_JSON_VECTOR();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_one_cell_from_json(
+        IColumn& column, Slice& slice, const FormatOptions& options) const {
+    if (_nesting_level > 1) {
+        slice.trim_quote();
+    }
+    StringRef str(slice.data, slice.size);
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_arrow(const IColumn& 
column,

Review Comment:
   [P1] Register the nano primitive in Arrow schema conversion
   
   Normal Arrow/Parquet output cannot reach this new SerDe: 
`convert_to_arrow_type` handles only legacy `TYPE_DATETIMEV2`, so a scale-7..9 
expression fails schema construction with `Unknown primitive 
type(DateTimeV2(9))`. This also affects Arrow Flight/Python paths and nested 
values. Please map the new primitive to an Arrow nanosecond timestamp and add 
an end-to-end output test rather than only a manually constructed builder test.



##########
be/src/core/data_type/define_primitive_type.h:
##########
@@ -73,7 +73,9 @@ enum PrimitiveType : PrimitiveNative {
     TYPE_UINT64,                         /* 39, used as offset */
     TYPE_FIXED_LENGTH_OBJECT,            /* 40, represent fixed-length object 
on BE */
     TYPE_VARBINARY,                      /* 41, varbinary */
-    TYPE_TIMESTAMPTZ                     /* 42, timestamptz */
+    TYPE_TIMESTAMPTZ,                    /* 42, timestamptz */
+    // Internal physical type for DATETIMEV2(7..9). The SQL/Thrift type 
remains DATETIMEV2.
+    TYPE_DATETIMEV2_NANO /* 43, signed Int64 epoch nanoseconds */

Review Comment:
   [P1] Extend the JNI bridge for the new primitive
   
   Scale-7..9 columns now carry `TYPE_DATETIMEV2_NANO`, but `JniDataBridge` 
recognizes only legacy `TYPE_DATETIMEV2` in schema mapping, metadata, and 
column transfer. JNI-backed external scans and Java UDF/UDAF/UDTF input/output 
therefore hit unsupported branches (also for nested values). Because the raw 
representation changed, this needs an explicit C++/Java wire contract rather 
than an alias; please implement both directions and add a scale-9 round trip.



##########
be/src/core/data_type_serde/data_type_datetimev2_nano_serde.cpp:
##########
@@ -0,0 +1,430 @@
+// 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.
+
+#include "core/data_type_serde/data_type_datetimev2_nano_serde.h"
+
+#include <arrow/array.h>
+#include <arrow/builder.h>
+#include <cctz/time_zone.h>
+
+#include <cctype>
+#include <limits>
+#include <orc/Vector.hh>
+#include <string>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type_serde/arrow_validation.h"
+#include "core/data_type_serde/decoded_column_view.h"
+#include "core/value/vdatetime_value.h"
+#include "exprs/function/cast/cast_to_datetimev2_impl.hpp"
+#include "util/mysql_row_buffer.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+constexpr int64_t NANOS_PER_MILLISECOND = 1000000;
+constexpr int64_t NANOS_PER_MICROSECOND = 1000;
+
+bool checked_scale_to_nanos(int64_t value, int64_t multiplier, int64_t* 
result) {
+    return !__builtin_mul_overflow(value, multiplier, result);
+}
+
+Status utc_epoch_nanos_to_local_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    DateV2Value<DateTimeV2ValueType> local_value;
+    local_value.from_unixtime(source_value.epoch_seconds(), timezone);
+    local_value.set_microsecond(source_value.microsecond());
+    DateTimeV2NanoValue target_value;
+    if (!target_value.from_datetime(local_value, 
source_value.nanosecond_remainder())) {
+        return Status::DataQualityError("Timestamp {} is outside DATETIMEV2 
nanosecond range",
+                                        source);
+    }
+    *result = target_value.epoch_nanos();
+    return Status::OK();
+}
+
+Status local_epoch_nanos_to_utc_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    auto local_value = source_value.to_datetime();
+    int64_t seconds = 0;
+    local_value.unix_timestamp(&seconds, timezone);
+    const __int128 epoch_nanos =
+            static_cast<__int128>(seconds) * 
DateTimeV2NanoValue::NANOS_PER_SECOND +
+            source_value.nanosecond();
+    if (epoch_nanos < std::numeric_limits<int64_t>::min() ||
+        epoch_nanos > std::numeric_limits<int64_t>::max()) {
+        return Status::DataQualityError("DATETIMEV2 value {} is outside epoch 
nanosecond range",
+                                        source_value.to_string(9));
+    }
+    *result = static_cast<int64_t>(epoch_nanos);
+    return Status::OK();
+}
+
+} // namespace
+
+Status parse_datetimev2_nano(StringRef str, int scale, int64_t* epoch_nanos,
+                             const cctz::time_zone* local_time_zone) {
+    DORIS_CHECK_GE(scale, 7);
+    DORIS_CHECK_LE(scale, 9);
+
+    std::string input(str.data, str.size);
+    size_t fraction_begin = std::string::npos;
+    size_t fraction_end = std::string::npos;
+    const size_t dot = input.rfind('.');
+    if (dot != std::string::npos && dot + 1 < input.size() &&
+        std::isdigit(static_cast<unsigned char>(input[dot + 1]))) {
+        fraction_begin = dot + 1;
+        fraction_end = fraction_begin;
+        while (fraction_end < input.size() &&
+               std::isdigit(static_cast<unsigned char>(input[fraction_end]))) {
+            ++fraction_end;
+        }
+    }
+
+    uint32_t nanos = 0;
+    size_t fraction_length = 0;
+    if (fraction_begin != std::string::npos) {
+        fraction_length = fraction_end - fraction_begin;
+        const size_t copied_digits = std::min<size_t>(fraction_length, 9);
+        for (size_t i = 0; i < copied_digits; ++i) {
+            nanos = nanos * 10 + static_cast<uint32_t>(input[fraction_begin + 
i] - '0');
+        }
+        for (size_t i = copied_digits; i < 9; ++i) {
+            nanos *= 10;
+        }
+    }
+
+    const auto quantum = static_cast<uint32_t>(int_exp10(9 - scale));
+    nanos = nanos / quantum * quantum;
+    if (fraction_length > static_cast<size_t>(scale) && input[fraction_begin + 
scale] >= '5') {
+        nanos += quantum;
+    }
+
+    std::string base = input;
+    if (fraction_begin != std::string::npos) {
+        base.erase(dot, fraction_end - dot);
+    }
+    const StringRef base_ref(base.data(), base.size());
+    DateV2Value<DateTimeV2ValueType> datetime;
+    CastParameters params {.status = Status::OK(), .is_strict = true};
+    CastToDatetimeV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
+            base_ref, datetime, local_time_zone, 0, params);
+    if (!params.status.ok()) {
+        return params.status;
+    }
+
+    if (nanos == DateTimeV2NanoValue::NANOS_PER_SECOND) {
+        if (!datetime.date_add_interval<TimeUnit::SECOND>(
+                    TimeInterval {TimeUnit::SECOND, 1, false})) {
+            return Status::InvalidArgument("DATETIMEV2 value overflows while 
rounding '{}'", input);
+        }
+        nanos = 0;
+    }
+    datetime.set_microsecond(nanos / NANOS_PER_MICROSECOND);
+    DateTimeV2NanoValue value;
+    if (!value.from_datetime(datetime, static_cast<uint16_t>(nanos % 
NANOS_PER_MICROSECOND))) {
+        return Status::InvalidArgument(
+                "DATETIMEV2({}) value '{}' is outside [{}, {}]", scale, input,
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::min()).to_string(scale),
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::max()).to_string(scale));
+    }
+    *epoch_nanos = value.epoch_nanos();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_batch(const ColumnString& 
strings,
+                                                      ColumnNullable& result,
+                                                      const FormatOptions& 
options) const {
+    auto& data = 
assert_cast<ColumnDateTimeV2Nano&>(result.get_nested_column()).get_data();
+    auto& null_map = result.get_null_map_column().get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        int64_t value = 0;
+        const auto status =
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone);
+        null_map[i] = !status.ok();
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode_batch(
+        const ColumnString& strings, IColumn& result, const FormatOptions& 
options,
+        const NullMap::value_type* null_map) const {
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(result).get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        if (null_map != nullptr && null_map[i]) {
+            continue;
+        }
+        int64_t value = 0;
+        RETURN_IF_ERROR(
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone));
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string(StringRef& str, IColumn& 
column,
+                                                const FormatOptions& options) 
const {
+    int64_t value = 0;
+    RETURN_IF_ERROR(parse_datetimev2_nano(str, _scale, &value, 
options.timezone));
+    
assert_cast<ColumnDateTimeV2Nano&>(column).insert_value(DateTimeV2NanoValue(value));
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode(StringRef& str, 
IColumn& column,
+                                                            const 
FormatOptions& options) const {
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_column_to_json(const IColumn& 
column,
+                                                             int64_t 
start_idx, int64_t end_idx,
+                                                             BufferWritable& 
bw,
+                                                             FormatOptions& 
options) const {
+    SERIALIZE_COLUMN_TO_JSON();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_one_cell_to_json(const IColumn& 
column,
+                                                               int64_t 
row_num, BufferWritable& bw,
+                                                               FormatOptions& 
options) const {
+    auto [column_ptr, index] = check_column_const_set_readability(column, 
row_num);
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    const auto value =
+            assert_cast<const ColumnDateTimeV2Nano&, 
TypeCheckOnRelease::DISABLE>(*column_ptr)
+                    .get_element(index);
+    const std::string result = value.to_string(_scale);
+    bw.write(result.data(), result.size());
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_column_from_json_vector(
+        IColumn& column, std::vector<Slice>& slices, uint64_t* 
num_deserialized,
+        const FormatOptions& options) const {
+    DESERIALIZE_COLUMN_FROM_JSON_VECTOR();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_one_cell_from_json(
+        IColumn& column, Slice& slice, const FormatOptions& options) const {
+    if (_nesting_level > 1) {
+        slice.trim_quote();
+    }
+    StringRef str(slice.data, slice.size);
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_arrow(const IColumn& 
column,
+                                                          const NullMap* 
null_map,
+                                                          arrow::ArrayBuilder* 
array_builder,
+                                                          int64_t start, 
int64_t end,
+                                                          const 
cctz::time_zone& ctz) const {
+    const auto& data = assert_cast<const 
ColumnDateTimeV2Nano&>(column).get_data();
+    auto& builder = assert_cast<arrow::TimestampBuilder&>(*array_builder);
+    const auto timestamp_type = 
std::static_pointer_cast<arrow::TimestampType>(builder.type());
+    const auto& timezone = timestamp_type->timezone();
+    for (int64_t i = start; i < end; ++i) {
+        if (null_map != nullptr && (*null_map)[i]) {
+            RETURN_IF_ERROR(checkArrowStatus(builder.AppendNull(), column, 
builder));
+            continue;
+        }
+        int64_t value = data[i].epoch_nanos();
+        if (!timezone.empty()) {
+            RETURN_IF_ERROR(local_epoch_nanos_to_utc_epoch_nanos(value, ctz, 
&value));
+        }
+        switch (timestamp_type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            value /= DateTimeV2NanoValue::NANOS_PER_SECOND;
+            break;
+        case arrow::TimeUnit::MILLI:
+            value /= NANOS_PER_MILLISECOND;
+            break;
+        case arrow::TimeUnit::MICRO:
+            value /= NANOS_PER_MICROSECOND;
+            break;
+        case arrow::TimeUnit::NANO:
+            break;
+        }
+        RETURN_IF_ERROR(checkArrowStatus(builder.Append(value), column, 
builder));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::read_column_from_arrow(IColumn& column,
+                                                           const arrow::Array* 
arrow_array,
+                                                           int64_t start, 
int64_t end,
+                                                           const 
cctz::time_zone& ctz) const {
+    if (arrow_array->type_id() != arrow::Type::TIMESTAMP) {
+        return Status::InvalidArgument("Expected Arrow timestamp, got {}", 
arrow_array->type_id());
+    }
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
+    const auto& array = assert_cast<const 
arrow::TimestampArray&>(*arrow_array);
+    const auto type = 
std::static_pointer_cast<arrow::TimestampType>(array.type());
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(column).get_data();
+    for (int64_t i = start; i < end; ++i) {
+        int64_t value = array.Value(i);
+        int64_t nanos = 0;
+        switch (type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            if (!checked_scale_to_nanos(value, 
DateTimeV2NanoValue::NANOS_PER_SECOND, &nanos)) {
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::MILLI:
+            if (!checked_scale_to_nanos(value, NANOS_PER_MILLISECOND, &nanos)) 
{
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::MICRO:
+            if (!checked_scale_to_nanos(value, NANOS_PER_MICROSECOND, &nanos)) 
{
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::NANO:
+            nanos = value;
+            break;
+        }
+        if (!type->timezone().empty()) {
+            RETURN_IF_ERROR(utc_epoch_nanos_to_local_epoch_nanos(nanos, ctz, 
&nanos));
+        }
+        data.push_back(DateTimeV2NanoValue(nanos));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::read_column_from_decoded_values(
+        IColumn& column, const DecodedColumnView& view) const {
+    if (view.value_kind != DecodedValueKind::INT64) {
+        return decoded_column_view_handle_conversion_failure(
+                column, view,
+                Status::NotSupported("DATETIMEV2 nano decoded reader expects 
INT64 source"));
+    }
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(column).get_data();
+    const auto* values = reinterpret_cast<const int64_t*>(view.values);
+    static const auto utc = cctz::utc_time_zone();
+    const auto& timezone = view.timezone == nullptr ? utc : *view.timezone;
+    for (int64_t row = 0; row < view.row_count; ++row) {
+        if (decoded_column_view_row_is_null(view, row)) {
+            data.push_back(DateTimeV2NanoValue(0));
+            continue;
+        }
+        int64_t nanos = 0;
+        int64_t multiplier = 1;
+        switch (view.time_unit) {
+        case DecodedTimeUnit::MILLIS:
+            multiplier = NANOS_PER_MILLISECOND;
+            break;
+        case DecodedTimeUnit::MICROS:
+            multiplier = NANOS_PER_MICROSECOND;
+            break;
+        case DecodedTimeUnit::NANOS:
+            break;
+        case DecodedTimeUnit::UNKNOWN:
+            return decoded_column_view_handle_conversion_failure(
+                    column, view,
+                    Status::NotSupported("DATETIMEV2 nano decoded reader 
requires a time unit"));
+        }
+        if (!checked_scale_to_nanos(values[row], multiplier, &nanos)) {
+            return decoded_column_view_handle_conversion_failure(
+                    column, view,
+                    Status::DataQualityError("Timestamp {} overflows 
nanoseconds", values[row]));
+        }
+        if (view.timestamp_is_adjusted_to_utc) {
+            RETURN_IF_ERROR(utc_epoch_nanos_to_local_epoch_nanos(nanos, 
timezone, &nanos));
+        }
+        data.push_back(DateTimeV2NanoValue(nanos));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_mysql_binary(
+        const IColumn& column, MysqlRowBinaryBuffer& row_buffer, int64_t 
row_idx, bool col_const,
+        const FormatOptions& options) const {
+    const auto& data = assert_cast<const 
ColumnDateTimeV2Nano&>(column).get_data();
+    const auto index = index_check_const(row_idx, col_const);
+    const auto value = DateTimeV2NanoValue(data[index]).to_string(_scale);
+    if (row_buffer.push_string(value.data(), value.size()) != 0) {
+        return Status::InternalError("pack MySQL DATETIMEV2 nano buffer 
failed");
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_orc(const std::string& 
timezone,

Review Comment:
   [P1] Register the nano primitive in ORC schema construction
   
   The production ORC transformer builds its schema before this method is 
called, and `_build_orc_type` recognizes only legacy `TYPE_DATETIMEV2`. A 
scale-7..9 value therefore fails with `Unsupported type DateTimeV2(9) to build 
orc type` (also recursively in arrays/maps/structs), even though the direct 
SerDe unit test passes a prebuilt timestamp batch. Please add the new primitive 
to ORC schema dispatch and cover end-to-end output.



##########
be/src/core/data_type/data_type_date_time.h:
##########
@@ -111,6 +111,8 @@ template <typename DataType>
 constexpr bool IsDateTimeV2Type = false;
 template <>
 inline constexpr bool IsDateTimeV2Type<DataTypeDateTimeV2> = true;
+template <>
+inline constexpr bool IsDateTimeV2Type<DataTypeDateTimeV2Nano> = true;

Review Comment:
   [P1] Do not reinterpret epoch nanoseconds as packed datetime
   
   This specialization sends the new type through the existing numeric-cast 
branches, which `reinterpret_cast` each raw value to the legacy packed 
`DateTimeV2Value` layout before extracting date/time digits. The nano type 
instead stores signed epoch nanoseconds, so stored-column casts to 
BIGINT/FLOAT/DOUBLE silently return unrelated values (even epoch zero is not 
packed 1970-01-01). Please add an explicit civil-time conversion for the nano 
representation and cover non-folded numeric casts.



##########
be/src/exprs/create_predicate_function.h:
##########
@@ -92,6 +92,7 @@ class PredicateFunctionCreator {
     M(TYPE_DATETIME)          \
     M(TYPE_DATEV2)            \
     M(TYPE_DATETIMEV2)        \
+    M(TYPE_DATETIMEV2_NANO)   \

Review Comment:
   [P1] Add nano transport for global runtime filters
   
   This enables local IN/min/max objects for `TYPE_DATETIMEV2_NANO`, but 
distributed publication is incomplete. The producer instantiates 
`get_convertor<DateTimeV2NanoValue>()`, which has no protobuf converter, and 
the remote IN/min/max assignment switches also omit the new primitive. Thus an 
equal-scale distributed join can fail even without any cast. Please add 
matching raw-nanosecond serialization/deserialization and cover broadcast and 
shuffle joins below the IN-to-bloom threshold.



##########
be/src/core/data_type_serde/data_type_datetimev2_nano_serde.cpp:
##########
@@ -0,0 +1,430 @@
+// 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.
+
+#include "core/data_type_serde/data_type_datetimev2_nano_serde.h"
+
+#include <arrow/array.h>
+#include <arrow/builder.h>
+#include <cctz/time_zone.h>
+
+#include <cctype>
+#include <limits>
+#include <orc/Vector.hh>
+#include <string>
+
+#include "common/config.h"
+#include "core/assert_cast.h"
+#include "core/column/column_nullable.h"
+#include "core/column/column_string.h"
+#include "core/data_type_serde/arrow_validation.h"
+#include "core/data_type_serde/decoded_column_view.h"
+#include "core/value/vdatetime_value.h"
+#include "exprs/function/cast/cast_to_datetimev2_impl.hpp"
+#include "util/mysql_row_buffer.h"
+#include "util/unaligned.h"
+
+namespace doris {
+namespace {
+
+constexpr int64_t NANOS_PER_MILLISECOND = 1000000;
+constexpr int64_t NANOS_PER_MICROSECOND = 1000;
+
+bool checked_scale_to_nanos(int64_t value, int64_t multiplier, int64_t* 
result) {
+    return !__builtin_mul_overflow(value, multiplier, result);
+}
+
+Status utc_epoch_nanos_to_local_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    DateV2Value<DateTimeV2ValueType> local_value;
+    local_value.from_unixtime(source_value.epoch_seconds(), timezone);
+    local_value.set_microsecond(source_value.microsecond());
+    DateTimeV2NanoValue target_value;
+    if (!target_value.from_datetime(local_value, 
source_value.nanosecond_remainder())) {
+        return Status::DataQualityError("Timestamp {} is outside DATETIMEV2 
nanosecond range",
+                                        source);
+    }
+    *result = target_value.epoch_nanos();
+    return Status::OK();
+}
+
+Status local_epoch_nanos_to_utc_epoch_nanos(int64_t source, const 
cctz::time_zone& timezone,
+                                            int64_t* result) {
+    const DateTimeV2NanoValue source_value(source);
+    auto local_value = source_value.to_datetime();
+    int64_t seconds = 0;
+    local_value.unix_timestamp(&seconds, timezone);
+    const __int128 epoch_nanos =
+            static_cast<__int128>(seconds) * 
DateTimeV2NanoValue::NANOS_PER_SECOND +
+            source_value.nanosecond();
+    if (epoch_nanos < std::numeric_limits<int64_t>::min() ||
+        epoch_nanos > std::numeric_limits<int64_t>::max()) {
+        return Status::DataQualityError("DATETIMEV2 value {} is outside epoch 
nanosecond range",
+                                        source_value.to_string(9));
+    }
+    *result = static_cast<int64_t>(epoch_nanos);
+    return Status::OK();
+}
+
+} // namespace
+
+Status parse_datetimev2_nano(StringRef str, int scale, int64_t* epoch_nanos,
+                             const cctz::time_zone* local_time_zone) {
+    DORIS_CHECK_GE(scale, 7);
+    DORIS_CHECK_LE(scale, 9);
+
+    std::string input(str.data, str.size);
+    size_t fraction_begin = std::string::npos;
+    size_t fraction_end = std::string::npos;
+    const size_t dot = input.rfind('.');
+    if (dot != std::string::npos && dot + 1 < input.size() &&
+        std::isdigit(static_cast<unsigned char>(input[dot + 1]))) {
+        fraction_begin = dot + 1;
+        fraction_end = fraction_begin;
+        while (fraction_end < input.size() &&
+               std::isdigit(static_cast<unsigned char>(input[fraction_end]))) {
+            ++fraction_end;
+        }
+    }
+
+    uint32_t nanos = 0;
+    size_t fraction_length = 0;
+    if (fraction_begin != std::string::npos) {
+        fraction_length = fraction_end - fraction_begin;
+        const size_t copied_digits = std::min<size_t>(fraction_length, 9);
+        for (size_t i = 0; i < copied_digits; ++i) {
+            nanos = nanos * 10 + static_cast<uint32_t>(input[fraction_begin + 
i] - '0');
+        }
+        for (size_t i = copied_digits; i < 9; ++i) {
+            nanos *= 10;
+        }
+    }
+
+    const auto quantum = static_cast<uint32_t>(int_exp10(9 - scale));
+    nanos = nanos / quantum * quantum;
+    if (fraction_length > static_cast<size_t>(scale) && input[fraction_begin + 
scale] >= '5') {
+        nanos += quantum;
+    }
+
+    std::string base = input;
+    if (fraction_begin != std::string::npos) {
+        base.erase(dot, fraction_end - dot);
+    }
+    const StringRef base_ref(base.data(), base.size());
+    DateV2Value<DateTimeV2ValueType> datetime;
+    CastParameters params {.status = Status::OK(), .is_strict = true};
+    CastToDatetimeV2::from_string_strict_mode<DatelikeParseMode::STRICT>(
+            base_ref, datetime, local_time_zone, 0, params);
+    if (!params.status.ok()) {
+        return params.status;
+    }
+
+    if (nanos == DateTimeV2NanoValue::NANOS_PER_SECOND) {
+        if (!datetime.date_add_interval<TimeUnit::SECOND>(
+                    TimeInterval {TimeUnit::SECOND, 1, false})) {
+            return Status::InvalidArgument("DATETIMEV2 value overflows while 
rounding '{}'", input);
+        }
+        nanos = 0;
+    }
+    datetime.set_microsecond(nanos / NANOS_PER_MICROSECOND);
+    DateTimeV2NanoValue value;
+    if (!value.from_datetime(datetime, static_cast<uint16_t>(nanos % 
NANOS_PER_MICROSECOND))) {
+        return Status::InvalidArgument(
+                "DATETIMEV2({}) value '{}' is outside [{}, {}]", scale, input,
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::min()).to_string(scale),
+                
DateTimeV2NanoValue(std::numeric_limits<int64_t>::max()).to_string(scale));
+    }
+    *epoch_nanos = value.epoch_nanos();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_batch(const ColumnString& 
strings,
+                                                      ColumnNullable& result,
+                                                      const FormatOptions& 
options) const {
+    auto& data = 
assert_cast<ColumnDateTimeV2Nano&>(result.get_nested_column()).get_data();
+    auto& null_map = result.get_null_map_column().get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        int64_t value = 0;
+        const auto status =
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone);
+        null_map[i] = !status.ok();
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode_batch(
+        const ColumnString& strings, IColumn& result, const FormatOptions& 
options,
+        const NullMap::value_type* null_map) const {
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(result).get_data();
+    result.resize(strings.size());
+    for (size_t i = 0; i < strings.size(); ++i) {
+        if (null_map != nullptr && null_map[i]) {
+            continue;
+        }
+        int64_t value = 0;
+        RETURN_IF_ERROR(
+                parse_datetimev2_nano(strings.get_data_at(i), _scale, &value, 
options.timezone));
+        data[i] = DateTimeV2NanoValue(value);
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string(StringRef& str, IColumn& 
column,
+                                                const FormatOptions& options) 
const {
+    int64_t value = 0;
+    RETURN_IF_ERROR(parse_datetimev2_nano(str, _scale, &value, 
options.timezone));
+    
assert_cast<ColumnDateTimeV2Nano&>(column).insert_value(DateTimeV2NanoValue(value));
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::from_string_strict_mode(StringRef& str, 
IColumn& column,
+                                                            const 
FormatOptions& options) const {
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_column_to_json(const IColumn& 
column,
+                                                             int64_t 
start_idx, int64_t end_idx,
+                                                             BufferWritable& 
bw,
+                                                             FormatOptions& 
options) const {
+    SERIALIZE_COLUMN_TO_JSON();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::serialize_one_cell_to_json(const IColumn& 
column,
+                                                               int64_t 
row_num, BufferWritable& bw,
+                                                               FormatOptions& 
options) const {
+    auto [column_ptr, index] = check_column_const_set_readability(column, 
row_num);
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    const auto value =
+            assert_cast<const ColumnDateTimeV2Nano&, 
TypeCheckOnRelease::DISABLE>(*column_ptr)
+                    .get_element(index);
+    const std::string result = value.to_string(_scale);
+    bw.write(result.data(), result.size());
+    if (_nesting_level > 1) {
+        bw.write('"');
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_column_from_json_vector(
+        IColumn& column, std::vector<Slice>& slices, uint64_t* 
num_deserialized,
+        const FormatOptions& options) const {
+    DESERIALIZE_COLUMN_FROM_JSON_VECTOR();
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::deserialize_one_cell_from_json(
+        IColumn& column, Slice& slice, const FormatOptions& options) const {
+    if (_nesting_level > 1) {
+        slice.trim_quote();
+    }
+    StringRef str(slice.data, slice.size);
+    return from_string(str, column, options);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::write_column_to_arrow(const IColumn& 
column,
+                                                          const NullMap* 
null_map,
+                                                          arrow::ArrayBuilder* 
array_builder,
+                                                          int64_t start, 
int64_t end,
+                                                          const 
cctz::time_zone& ctz) const {
+    const auto& data = assert_cast<const 
ColumnDateTimeV2Nano&>(column).get_data();
+    auto& builder = assert_cast<arrow::TimestampBuilder&>(*array_builder);
+    const auto timestamp_type = 
std::static_pointer_cast<arrow::TimestampType>(builder.type());
+    const auto& timezone = timestamp_type->timezone();
+    for (int64_t i = start; i < end; ++i) {
+        if (null_map != nullptr && (*null_map)[i]) {
+            RETURN_IF_ERROR(checkArrowStatus(builder.AppendNull(), column, 
builder));
+            continue;
+        }
+        int64_t value = data[i].epoch_nanos();
+        if (!timezone.empty()) {
+            RETURN_IF_ERROR(local_epoch_nanos_to_utc_epoch_nanos(value, ctz, 
&value));
+        }
+        switch (timestamp_type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            value /= DateTimeV2NanoValue::NANOS_PER_SECOND;
+            break;
+        case arrow::TimeUnit::MILLI:
+            value /= NANOS_PER_MILLISECOND;
+            break;
+        case arrow::TimeUnit::MICRO:
+            value /= NANOS_PER_MICROSECOND;
+            break;
+        case arrow::TimeUnit::NANO:
+            break;
+        }
+        RETURN_IF_ERROR(checkArrowStatus(builder.Append(value), column, 
builder));
+    }
+    return Status::OK();
+}
+
+Status DataTypeDateTimeV2NanoSerDe::read_column_from_arrow(IColumn& column,
+                                                           const arrow::Array* 
arrow_array,
+                                                           int64_t start, 
int64_t end,
+                                                           const 
cctz::time_zone& ctz) const {
+    if (arrow_array->type_id() != arrow::Type::TIMESTAMP) {
+        return Status::InvalidArgument("Expected Arrow timestamp, got {}", 
arrow_array->type_id());
+    }
+    if (config::enable_arrow_input_validation) {
+        check_arrow_no_offset(*arrow_array);
+    }
+    const auto& array = assert_cast<const 
arrow::TimestampArray&>(*arrow_array);
+    const auto type = 
std::static_pointer_cast<arrow::TimestampType>(array.type());
+    auto& data = assert_cast<ColumnDateTimeV2Nano&>(column).get_data();
+    for (int64_t i = start; i < end; ++i) {
+        int64_t value = array.Value(i);
+        int64_t nanos = 0;
+        switch (type->unit()) {
+        case arrow::TimeUnit::SECOND:
+            if (!checked_scale_to_nanos(value, 
DateTimeV2NanoValue::NANOS_PER_SECOND, &nanos)) {
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::MILLI:
+            if (!checked_scale_to_nanos(value, NANOS_PER_MILLISECOND, &nanos)) 
{
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::MICRO:
+            if (!checked_scale_to_nanos(value, NANOS_PER_MICROSECOND, &nanos)) 
{
+                return Status::DataQualityError("Arrow timestamp {} overflows 
nanoseconds", value);
+            }
+            break;
+        case arrow::TimeUnit::NANO:
+            nanos = value;
+            break;
+        }
+        if (!type->timezone().empty()) {
+            RETURN_IF_ERROR(utc_epoch_nanos_to_local_epoch_nanos(nanos, ctz, 
&nanos));
+        }
+        data.push_back(DateTimeV2NanoValue(nanos));

Review Comment:
   [P1] Normalize imported values to the declared scale
   
   Both external readers append raw nanoseconds without consulting `_scale`. A 
NANO value ending in `.123456789` can therefore retain all nine digits in a 
`DATETIMEV2(7)` column, even though it prints as `.1234567`; equality, hashing, 
and ordering still see the hidden digits. String ingestion of the same value 
rounds to `.1234568`, so ingestion paths disagree. Please round/carry to the 
destination scale before insertion and cover scale-7/8 external values.



##########
be/src/core/data_type_serde/data_type_serde.cpp:
##########
@@ -847,7 +884,20 @@ Status 
DataTypeDateTimeV2SerDe::read_column_from_orc(IColumn& column,
     if (orc_decode_row_count(view.rows, view.selected_rows) == 0) {
         return Status::OK();
     }
-    return decode_timestamp_orc_values(column, view, *view.timezone);
+    return decode_datetimev2_orc_values(column, view, *view.timezone);
+}
+
+Status DataTypeDateTimeV2NanoSerDe::read_column_from_orc(IColumn& column,

Review Comment:
   [P1] Route nanosecond file timestamps to the nano reader
   
   The production schema mappers still infer legacy `DATETIMEV2(6)` for Parquet 
`TIMESTAMP(NANOS)` and ORC `TIMESTAMP`. `_decode_column` consequently selects 
the legacy SerDe (the ORC path divides nanoseconds by 1000), so this new nano 
dispatch is bypassed and sub-microsecond input is lost. Please preserve 
nanosecond precision in both reader generations and add an end-to-end file scan 
instead of only direct SerDe tests.



##########
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/literal/DateLiteral.java:
##########
@@ -254,14 +254,13 @@ static Result<String, AnalysisException> normalize(String 
s) {
             sb.append(":00");
         }
 
-        // parse MicroSecond
-        // Keep up to 7 digits at most, 7th digit is use for overflow.
+        // Parse fractional seconds. Java's NANO_OF_SECOND supports up to 9 
digits.
         int j = i;
         if (partNumber == 6 && i < s.length() && s.charAt(i) == '.') {
             sb.append(s.charAt(i));
             i += 1;
             while (i < s.length() && Character.isDigit(s.charAt(i))) {
-                if (i - j <= 7) {
+                if (i - j <= DateTimeV2Type.MAX_SCALE) {

Review Comment:
   [P1] Preserve the guard digit for scale-9 rounding
   
   This now keeps only nine fractional digits, but scale-9 rounding needs the 
tenth digit. For example, FE folding truncates `.9999999995` to `.999999999`, 
while the BE parser checks the next digit and carries it to the following 
second. The old microsecond code intentionally kept a seventh guard digit; 
please retain the equivalent tenth digit (or reject excess precision 
consistently) and add folded/unfolded boundary cases.



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