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

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


The following commit(s) were added to refs/heads/branch-4.1 by this push:
     new 041a7e2af4f [fix](arrow) Reject out-of-range Flight timestamps (#68604)
041a7e2af4f is described below

commit 041a7e2af4fb92b73b315f41b2f5a8d8a73fc970
Author: Gabriel <[email protected]>
AuthorDate: Tue Sep 29 21:57:29 2026 +0800

    [fix](arrow) Reject out-of-range Flight timestamps (#68604)
    
    ### What problem does this PR solve?
    
    Flight SQL can publish out-of-range timestamps from legacy INT96 date
    materialization. For example, a non-null zero DATETIME becomes
    `timestamp[us]` with value `-62169984000000000`: Arrow validation
    succeeds, but PyArrow scalar conversion raises `OverflowError`.
    
    Validate timestamps in `ArrowFlightArrowBlockConvertor` before
    publishing each batch. Check the actual timestamp unit and both
    UTC/local calendar bounds for zoned values. Recursively check arrays,
    map keys/values, and structs while skipping values masked by a NULL
    parent. Reject unsupported timestamps with an error identifying the
    field path and block row. Other Arrow consumers and Parquet scan
    compatibility remain unchanged.
    
    Related PR: #68596 adds separate UTF-8 validation. This PR is
    independent of that change; both checks must be retained when
    integrating the shared Flight conversion entry point.
    
    ### Testing
    
    - Added seven BE tests covering zero dates, years 0/10000,
    seconds/milliseconds/microseconds, valid calendar boundaries, pre-epoch
    fractions, timezone offsets, slices/subsequent batches, nested
    timestamps, NULL parents, and other consumers.
    - Before the fix: four new test groups failed because malformed
    timestamps were published; three valid/NULL groups passed.
    - After the fix: all 36 tests in
    `ArrowFlightTimestampTest.*:DataTypeSerDeArrowTest.*` passed under ASAN.
    Recompiled the changed converter and new tests against existing ASAN BE
    libraries, with C++ access control enabled. Test dates are constructed
    through the public unchecked setter so invalid calendar values reach the
    validator.
    - Added a Flight JDBC regression suite and a separate HDFS INT96 suite:
    four existing group4 fixtures must be read successfully with microsecond
    truncation at the upper calendar boundary; the zero-year fixture must
    return a range error. Both suites preserve the configured Flight
    credentials and avoid the TLS-dependent `connect` shortcut. The HDFS
    suite discovers the live FE Flight endpoint through MySQL because
    external clusters can override the configured port. The HDFS suite
    requires `enableHiveTest` and the external test environment.
    - Fixed the nested NULL assertion that caused the P0/Cloud P0 Groovy
    `getAt()` failure by consuming the JDBC array directly.
    - Clang-format 16 passed for all affected C++ files. Groovy 4.0.19
    executed both scripts against mock JDBC connections in 12 checks
    covering TLS on/off, successful assertions, missing expected errors, and
    unrelated errors. These are script-level checks; the live external
    validation below additionally exercises real SQL and HDFS data.
    
    ### Live external regression validation
    
    - Used the CI-built FE/BE binaries for `8d1158d3f5` in an isolated local
    cluster and rebuilt the regression framework from this branch. This
    follow-up changes only test scripts and expected results.
    - Before the update: all four affected suites failed locally,
    reproducing the stale Flight port and zero-year timestamp failures.
    - After the update: all four suites passed, with zero failures and zero
    skips: `test_flight_int96_timestamp_range`,
    `test_remote_doris_all_types_select`, `test_remote_doris_statistics`,
    and `test_remote_doris_table_stats`.
    - The configured Flight port intentionally remained incorrect. The INT96
    suite discovered the running FE endpoint, consumed all four valid HDFS
    fixtures, and asserted the expected error for the invalid fixture.
    - Remote Doris success fixtures now use year 0001 as the minimum
    timestamp, with matching golden results. A separate assertion preserves
    coverage of zero-year rejection through a Remote Doris catalog.
    
    ### Release note
    
    Flight SQL now returns a server-side error for timestamps outside the
    supported 0001–9999 calendar range instead of publishing values that
    fail client conversion. Valid timestamps and NULLs retain their existing
    values.
    
    ### Check List (For Author)
    
    - Test
        - [x] Regression test
        - [x] Unit Test
    - Behavior changed:
    - [x] Yes: reject out-of-range Flight timestamps before publishing a
    batch.
    - Does this need documentation?
        - [x] No.
---
 be/src/format/arrow/arrow_block_convertor.cpp      | 137 +++++++++++++
 .../format/arrow/arrow_flight_timestamp_test.cpp   | 220 +++++++++++++++++++++
 .../test_remote_doris_all_types_select.out         |   4 +-
 .../remote_doris/test_remote_doris_statistics.out  |   2 +-
 .../test_flight_int96_timestamp_range.groovy       |  83 ++++++++
 .../test_flight_timestamp_range.groovy             |  81 ++++++++
 .../test_remote_doris_all_types_select.groovy      |  15 +-
 .../test_remote_doris_statistics.groovy            |   3 +-
 .../test_remote_doris_table_stats.groovy           |   3 +-
 9 files changed, 541 insertions(+), 7 deletions(-)

diff --git a/be/src/format/arrow/arrow_block_convertor.cpp 
b/be/src/format/arrow/arrow_block_convertor.cpp
index b62ca98cde9..6b2ed5a71e8 100644
--- a/be/src/format/arrow/arrow_block_convertor.cpp
+++ b/be/src/format/arrow/arrow_block_convertor.cpp
@@ -17,6 +17,8 @@
 
 #include "format/arrow/arrow_block_convertor.h"
 
+#include <arrow/array/array_nested.h>
+#include <arrow/array/array_primitive.h>
 #include <arrow/array/builder_base.h>
 #include <arrow/array/builder_binary.h>
 #include <arrow/array/builder_decimal.h>
@@ -34,6 +36,7 @@
 #include <cctz/time_zone.h>
 #include <glog/logging.h>
 
+#include <algorithm>
 #include <array>
 #include <cstring>
 #include <ctime>
@@ -63,6 +66,134 @@ namespace doris {
 
 namespace {
 
+class FlightTimestampValidator {
+public:
+    Status init(std::shared_ptr<arrow::Array> array, std::string path,
+                const cctz::time_zone& timezone) {
+        _array = std::move(array);
+        _path = std::move(path);
+        switch (_array->type_id()) {
+        case arrow::Type::TIMESTAMP: {
+            const auto& type = static_cast<const 
arrow::TimestampType&>(*_array->type());
+            int64_t units_per_second = 1;
+            switch (type.unit()) {
+            case arrow::TimeUnit::SECOND:
+                units_per_second = 1;
+                break;
+            case arrow::TimeUnit::MILLI:
+                units_per_second = 1000;
+                break;
+            case arrow::TimeUnit::MICRO:
+                units_per_second = 1000000;
+                break;
+            case arrow::TimeUnit::NANO:
+                units_per_second = 1000000000;
+                break;
+            }
+            int64_t min_seconds = -62135596800LL;
+            int64_t end_seconds = 253402300800LL;
+            if (!type.timezone().empty()) {
+                // The ordinary Arrow writer has already checked the 
schema/timezone binding.
+                const auto local_start = cctz::convert(cctz::civil_second(1, 
1, 1), timezone);
+                const auto local_end = cctz::convert(cctz::civil_second(10000, 
1, 1), timezone);
+                // Python first constructs UTC and then applies the Arrow 
timezone. Both calendar
+                // representations must fit, including offsets at the first 
and last supported day.
+                min_seconds =
+                        std::max<int64_t>(min_seconds, 
local_start.time_since_epoch().count());
+                end_seconds = std::min<int64_t>(end_seconds, 
local_end.time_since_epoch().count());
+            }
+            // Nanosecond bounds do not fit int64_t, even though every encoded 
value does.
+            _min = static_cast<__int128>(min_seconds) * units_per_second;
+            _end = static_cast<__int128>(end_seconds) * units_per_second;
+            break;
+        }
+        case arrow::Type::LIST: {
+            const auto& list = static_cast<const arrow::ListArray&>(*_array);
+            RETURN_IF_ERROR(add_child(list.values(), _path + "[]", timezone));
+            break;
+        }
+        case arrow::Type::MAP: {
+            const auto& map = static_cast<const arrow::MapArray&>(*_array);
+            RETURN_IF_ERROR(add_child(map.keys(), _path + ".key", timezone));
+            RETURN_IF_ERROR(add_child(map.items(), _path + ".value", 
timezone));
+            break;
+        }
+        case arrow::Type::STRUCT: {
+            const auto& structure = static_cast<const 
arrow::StructArray&>(*_array);
+            for (int i = 0; i < structure.num_fields(); ++i) {
+                RETURN_IF_ERROR(add_child(structure.field(i),
+                                          _path + "." + 
_array->type()->field(i)->name(),
+                                          timezone));
+            }
+            break;
+        }
+        default:
+            break;
+        }
+        return Status::OK();
+    }
+
+    Status validate(int64_t start, int64_t end, size_t block_start, int64_t 
parent_row = -1) const {
+        if (!has_timestamps()) {
+            return Status::OK();
+        }
+        for (int64_t row = start; row < end; ++row) {
+            // Children of a NULL struct/list/map are not observable, even if 
their physical
+            // buffers contain default zero dates. Never validate those masked 
values.
+            if (_array->IsNull(row)) {
+                continue;
+            }
+            const int64_t output_row = parent_row < 0 ? row : parent_row;
+            if (_array->type_id() == arrow::Type::TIMESTAMP) {
+                const int64_t value = static_cast<const 
arrow::TimestampArray&>(*_array).Value(row);
+                if (value < _min || value >= _end) {
+                    return Status::InvalidArgument(
+                            "Arrow Flight timestamp in column '{}' at row {} 
is outside the "
+                            "supported 0001-9999 range (type: {})",
+                            _path, block_start + output_row + 1, 
_array->type()->ToString());
+                }
+                continue;
+            }
+            int64_t child_start = row;
+            int64_t child_end = row + 1;
+            if (_array->type_id() == arrow::Type::LIST) {
+                const auto& list = static_cast<const 
arrow::ListArray&>(*_array);
+                child_start = list.value_offset(row);
+                child_end = child_start + list.value_length(row);
+            } else if (_array->type_id() == arrow::Type::MAP) {
+                const auto& map = static_cast<const arrow::MapArray&>(*_array);
+                child_start = map.value_offset(row);
+                child_end = child_start + map.value_length(row);
+            }
+            for (const auto& child : _children) {
+                RETURN_IF_ERROR(child.validate(child_start, child_end, 
block_start, output_row));
+            }
+        }
+        return Status::OK();
+    }
+
+private:
+    bool has_timestamps() const {
+        return _array->type_id() == arrow::Type::TIMESTAMP || 
!_children.empty();
+    }
+
+    Status add_child(std::shared_ptr<arrow::Array> array, std::string path,
+                     const cctz::time_zone& timezone) {
+        FlightTimestampValidator child;
+        RETURN_IF_ERROR(child.init(std::move(array), std::move(path), 
timezone));
+        if (child.has_timestamps()) {
+            _children.push_back(std::move(child));
+        }
+        return Status::OK();
+    }
+
+    std::shared_ptr<arrow::Array> _array;
+    std::string _path;
+    std::vector<FlightTimestampValidator> _children;
+    __int128 _min = 0;
+    __int128 _end = 0;
+};
+
 int hex_value(char c) {
     if (c >= '0' && c <= '9') {
         return c - '0';
@@ -355,6 +486,12 @@ Status 
ArrowFlightArrowBlockConvertor::convert_to_arrow(const Block& block, arro
                                            i + 1, 
batch->schema()->field(i)->name(),
                                            status.ToString());
         }
+        // Arrow accepts the full int64 timestamp domain; ValidateFull cannot 
enforce the calendar
+        // range required by Flight clients. Check the encoded values before 
publishing the batch.
+        FlightTimestampValidator validator;
+        RETURN_IF_ERROR(
+                validator.init(batch->column(i), 
batch->schema()->field(i)->name(), _timezone));
+        RETURN_IF_ERROR(validator.validate(0, batch->num_rows(), start_row));
     }
     *result = std::move(batch);
     return Status::OK();
diff --git a/be/test/format/arrow/arrow_flight_timestamp_test.cpp 
b/be/test/format/arrow/arrow_flight_timestamp_test.cpp
new file mode 100644
index 00000000000..ebcafc04a91
--- /dev/null
+++ b/be/test/format/arrow/arrow_flight_timestamp_test.cpp
@@ -0,0 +1,220 @@
+// 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 <arrow/api.h>
+#include <gtest/gtest.h>
+
+#include "core/column/column_nullable.h"
+#include "core/column/column_vector.h"
+#include "core/data_type/data_type_array.h"
+#include "core/data_type/data_type_date_or_datetime_v2.h"
+#include "core/data_type/data_type_map.h"
+#include "core/data_type/data_type_nullable.h"
+#include "core/data_type/data_type_struct.h"
+#include "format/arrow/arrow_block_convertor.h"
+#include "util/timezone_utils.h"
+
+namespace doris {
+namespace {
+using DateTime = DateV2Value<DateTimeV2ValueType>;
+
+DateTime make_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t 
hour, uint8_t minute,
+                       uint16_t second, uint32_t microsecond) {
+    DateTime value;
+    // Invalid calendar values must reach the converter to exercise its range 
validation.
+    value.unchecked_set_time(year, month, day, hour, minute, second, 
microsecond);
+    return value;
+}
+
+Block timestamp_block(int scale, const std::vector<DateTime>& values) {
+    auto column = ColumnDateTimeV2::create();
+    for (const auto& value : values) {
+        column->insert_value(value);
+    }
+    return Block {{std::move(column), 
std::make_shared<DataTypeDateTimeV2>(scale), "event_time"}};
+}
+
+class ArrowFlightTimestampTest : public testing::Test {
+protected:
+    static void SetUpTestSuite() { TimezoneUtils::load_timezones_to_cache(); }
+};
+
+TEST_F(ArrowFlightTimestampTest, 
RejectsOutOfRangeInEveryUnitWithoutPublishingBatch) {
+    for (int scale : {0, 3, 6}) {
+        for (const auto& value : {DateTime {}, make_datetime(0, 12, 31, 0, 0, 
0, 0),
+                                  make_datetime(10000, 1, 1, 0, 0, 0, 0)}) {
+            SCOPED_TRACE(scale);
+            auto block = timestamp_block(scale, {value});
+            ArrowFlightArrowBlockConvertor flight(block, "UTC", 
cctz::utc_time_zone(), true);
+            ASSERT_TRUE(flight.init().ok());
+            const ArrowBlockConvertor& converter = flight;
+            std::shared_ptr<arrow::RecordBatch> batch;
+            const auto status =
+                    converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch);
+            EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, status.code()) << status;
+            EXPECT_NE(std::string::npos, 
status.to_string().find("event_time"));
+            EXPECT_NE(std::string::npos, status.to_string().find("row 1"));
+            EXPECT_NE(std::string::npos, status.to_string().find("0001-9999"));
+            EXPECT_EQ(nullptr, batch);
+
+            // Other Arrow consumers retain their existing date semantics.
+            DorisArrowBlockConvertor ordinary(block, "UTC", 
cctz::utc_time_zone(), true);
+            ASSERT_TRUE(ordinary.init().ok());
+            ASSERT_TRUE(
+                    ordinary.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch).ok());
+            EXPECT_TRUE(batch->ValidateFull().ok());
+        }
+    }
+}
+
+TEST_F(ArrowFlightTimestampTest, 
PreservesCalendarBoundariesAndPreEpochFractions) {
+    for (int scale : {0, 3, 6}) {
+        const int64_t factor = scale == 0 ? 1 : scale == 3 ? 1000 : 1000000;
+        const uint32_t fraction = scale == 0 ? 0 : scale == 3 ? 999000 : 
999999;
+        auto block = timestamp_block(scale, {make_datetime(1, 1, 1, 0, 0, 0, 
0),
+                                             make_datetime(9999, 12, 31, 23, 
59, 59, fraction),
+                                             make_datetime(1969, 12, 31, 23, 
59, 59, fraction)});
+        ArrowFlightArrowBlockConvertor converter(block, "UTC", 
cctz::utc_time_zone(), true);
+        ASSERT_TRUE(converter.init().ok());
+        std::shared_ptr<arrow::RecordBatch> batch;
+        ASSERT_TRUE(converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch).ok());
+        const auto& values = static_cast<const 
arrow::TimestampArray&>(*batch->column(0));
+        EXPECT_EQ(-62135596800LL * factor, values.Value(0));
+        EXPECT_EQ(253402300800LL * factor - 1, values.Value(1));
+        EXPECT_EQ(-1, values.Value(2));
+    }
+}
+
+TEST_F(ArrowFlightTimestampTest, ChecksSlicesAndSubsequentBatches) {
+    auto block = timestamp_block(6, {make_datetime(2024, 1, 1, 0, 0, 0, 0), 
DateTime {}});
+    ArrowFlightArrowBlockConvertor converter(block, "UTC", 
cctz::utc_time_zone(), true);
+    ASSERT_TRUE(converter.init().ok());
+    std::shared_ptr<arrow::RecordBatch> batch;
+    ASSERT_TRUE(converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch, 0, 1).ok());
+    auto previous = batch;
+    const auto status =
+            converter.convert_to_arrow(block, arrow::default_memory_pool(), 
&batch, 1, 2);
+    EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, status.code());
+    EXPECT_NE(std::string::npos, status.to_string().find("row 2"));
+    EXPECT_EQ(previous, batch);
+}
+
+TEST_F(ArrowFlightTimestampTest, RejectsNestedTimestampValuesAndMapKeys) {
+    auto datetime = make_nullable(std::make_shared<DataTypeDateTimeV2>(6));
+    const auto invalid = Field::create_field<TYPE_DATETIMEV2>(DateTime {});
+    const auto valid = 
Field::create_field<TYPE_DATETIMEV2>(make_datetime(2024, 1, 1, 0, 0, 0, 0));
+    DataTypes types {std::make_shared<DataTypeArray>(datetime),
+                     std::make_shared<DataTypeStruct>(DataTypes {datetime}, 
Strings {"child"}),
+                     std::make_shared<DataTypeMap>(datetime, datetime),
+                     std::make_shared<DataTypeMap>(datetime, datetime),
+                     
std::make_shared<DataTypeArray>(std::make_shared<DataTypeStruct>(
+                             DataTypes {datetime}, Strings {"child"}))};
+    FieldVector fields {
+            Field::create_field<TYPE_ARRAY>(Array {valid, invalid}),
+            Field::create_field<TYPE_STRUCT>(Struct {invalid}),
+            Field::create_field<TYPE_MAP>(Map 
{Field::create_field<TYPE_ARRAY>(Array {valid}),
+                                               
Field::create_field<TYPE_ARRAY>(Array {invalid})}),
+            Field::create_field<TYPE_MAP>(Map 
{Field::create_field<TYPE_ARRAY>(Array {invalid}),
+                                               
Field::create_field<TYPE_ARRAY>(Array {valid})}),
+            Field::create_field<TYPE_ARRAY>(
+                    Array {Field::create_field<TYPE_STRUCT>(Struct 
{invalid})})};
+    for (size_t i = 0; i < types.size(); ++i) {
+        SCOPED_TRACE(types[i]->get_name());
+        auto column = types[i]->create_column();
+        column->insert_default();
+        column->insert(fields[i]);
+        Block block {{std::move(column), types[i], "nested"}};
+        ArrowFlightArrowBlockConvertor converter(block, "UTC", 
cctz::utc_time_zone(), true);
+        ASSERT_TRUE(converter.init().ok());
+        std::shared_ptr<arrow::RecordBatch> batch;
+        const auto status =
+                converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch, 1, 2);
+        EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, status.code()) << status;
+        EXPECT_NE(std::string::npos, status.to_string().find("nested"));
+        EXPECT_NE(std::string::npos, status.to_string().find("row 2"));
+        EXPECT_EQ(nullptr, batch);
+    }
+}
+
+TEST_F(ArrowFlightTimestampTest, ChecksBothUtcAndZonedCalendarBounds) {
+    const std::vector<std::pair<std::string, DateTime>> cases {
+            {"+08:00", make_datetime(1, 1, 1, 0, 0, 0, 0)},
+            {"-08:00", make_datetime(9999, 12, 31, 23, 0, 0, 0)},
+            {"-08:00", make_datetime(0, 12, 31, 23, 0, 0, 0)},
+            {"+08:00", make_datetime(10000, 1, 1, 1, 0, 0, 0)}};
+    for (const auto& [zone, value] : cases) {
+        SCOPED_TRACE(zone);
+        cctz::time_zone timezone;
+        ASSERT_TRUE(TimezoneUtils::find_cctz_time_zone(zone, timezone));
+        auto block = timestamp_block(6, {value});
+        ArrowFlightArrowBlockConvertor converter(block, zone, timezone);
+        ASSERT_TRUE(converter.init().ok());
+        std::shared_ptr<arrow::RecordBatch> batch;
+        const auto status = converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch);
+        EXPECT_EQ(ErrorCode::INVALID_ARGUMENT, status.code()) << status;
+        EXPECT_EQ(nullptr, batch);
+
+        auto valid = timestamp_block(
+                6, {make_datetime(1, 1, 2, 0, 0, 0, 0), make_datetime(9999, 
12, 30, 23, 0, 0, 0)});
+        ASSERT_TRUE(converter.convert_to_arrow(valid, 
arrow::default_memory_pool(), &batch).ok());
+    }
+}
+
+TEST_F(ArrowFlightTimestampTest, NaiveBoundsDoNotDependOnSessionTimezone) {
+    cctz::time_zone timezone;
+    ASSERT_TRUE(TimezoneUtils::find_cctz_time_zone("+08:00", timezone));
+    auto block = timestamp_block(6, {make_datetime(1, 1, 1, 0, 0, 0, 0),
+                                     make_datetime(9999, 12, 31, 23, 59, 59, 
999999)});
+    ArrowFlightArrowBlockConvertor converter(block, "+08:00", timezone, true);
+    ASSERT_TRUE(converter.init().ok());
+    std::shared_ptr<arrow::RecordBatch> batch;
+    ASSERT_TRUE(converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch).ok());
+    const auto& values = static_cast<const 
arrow::TimestampArray&>(*batch->column(0));
+    EXPECT_EQ(-62135596800000000LL, values.Value(0));
+    EXPECT_EQ(253402300799999999LL, values.Value(1));
+}
+
+TEST_F(ArrowFlightTimestampTest, IgnoresTimestampsMaskedByNullParents) {
+    auto datetime = std::make_shared<DataTypeDateTimeV2>(6);
+    const auto invalid = Field::create_field<TYPE_DATETIMEV2>(DateTime {});
+    DataTypes types {
+            datetime, std::make_shared<DataTypeStruct>(DataTypes {datetime}, 
Strings {"child"}),
+            std::make_shared<DataTypeArray>(datetime),
+            std::make_shared<DataTypeMap>(make_nullable(datetime), 
make_nullable(datetime))};
+    FieldVector fields {
+            invalid, Field::create_field<TYPE_STRUCT>(Struct {invalid}),
+            Field::create_field<TYPE_ARRAY>(Array {invalid}),
+            Field::create_field<TYPE_MAP>(Map 
{Field::create_field<TYPE_ARRAY>(Array {invalid}),
+                                               
Field::create_field<TYPE_ARRAY>(Array {invalid})})};
+    for (size_t i = 0; i < types.size(); ++i) {
+        auto data = types[i]->create_column();
+        data->insert(fields[i]);
+        auto nulls = ColumnUInt8::create();
+        nulls->insert_value(1);
+        Block block {{ColumnNullable::create(std::move(data), 
std::move(nulls)),
+                      make_nullable(types[i]), "masked"}};
+        ArrowFlightArrowBlockConvertor converter(block, "UTC", 
cctz::utc_time_zone(), true);
+        ASSERT_TRUE(converter.init().ok());
+        std::shared_ptr<arrow::RecordBatch> batch;
+        const auto status = converter.convert_to_arrow(block, 
arrow::default_memory_pool(), &batch);
+        ASSERT_TRUE(status.ok()) << status;
+        EXPECT_TRUE(batch->column(0)->IsNull(0));
+    }
+}
+
+} // namespace
+} // namespace doris
diff --git 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
index a8823bb027e..70fb18a6da0 100644
--- 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
+++ 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_all_types_select.out
@@ -1,12 +1,12 @@
 -- This file is automatically generated. You should know what you did if you 
want to edit this
 -- !sql --
-2025-05-18T01:00       true    -128    -32768  -2147483648     
-9223372036854775808    -1234567890123456790    -123.456        -123456.789     
-123457 -123456789012346        -1234567890123456789012345678   1970-01-01      
0000-01-01T00:00        A       Hello   Hello, Doris!   ["apple", "banana", 
"orange"]   {"Emily":101, "age":25} {"f1":11, "f2":3.14, "f3":"Emily"}      
{"k1":"v31","k2":300,"k3":[123,456],"k4":[],"k5":{"i1":"iv1"}}
+2025-05-18T01:00       true    -128    -32768  -2147483648     
-9223372036854775808    -1234567890123456790    -123.456        -123456.789     
-123457 -123456789012346        -1234567890123456789012345678   1970-01-01      
0001-01-01T00:00        A       Hello   Hello, Doris!   ["apple", "banana", 
"orange"]   {"Emily":101, "age":25} {"f1":11, "f2":3.14, "f3":"Emily"}      
{"k1":"v31","k2":300,"k3":[123,456],"k4":[],"k5":{"i1":"iv1"}}
 2025-05-18T02:00       \N      \N      \N      \N      \N      \N      \N      
\N      \N      \N      \N      \N      \N      \N      \N      \N      \N      
\N      \N      \N
 2025-05-18T03:00       false   127     32767   2147483647      
9223372036854775807     1234567890123456789     123.456 123456.789      123457  
123456789012346 1234567890123456789012345678    9999-12-31      
9999-12-31T23:59:59                             []      {}      {"f1":11, 
"f2":3.14, "f3":"Emily"}      {}
 2025-05-18T04:00       true    0       0       0       0       0       0.0     
0       0       0       0       2023-10-01      2023-10-01T12:34:56     A       
Hello   Hello, Doris!   ["apple", "banana", "orange"]   {"Emily":101, "age":25} 
{"f1":11, "f2":3.14, "f3":"Emily"}      []
 
 -- !sql --
-2025-05-18T01:00       [1]     [-128]  [-32768]        [-2147483648]   
[-9223372036854775808]  [-1234567890123456790]  [-123.456]      [-123456.789]   
[-123457]       [-123456789012346]      [-1234567890123456789012345678] 
["0000-01-01"]  ["0000-01-01 00:00:00"] ["A"]   ["Hello"]       ["Hello, 
Doris!"]
+2025-05-18T01:00       [1]     [-128]  [-32768]        [-2147483648]   
[-9223372036854775808]  [-1234567890123456790]  [-123.456]      [-123456.789]   
[-123457]       [-123456789012346]      [-1234567890123456789012345678] 
["0000-01-01"]  ["0001-01-01 00:00:00"] ["A"]   ["Hello"]       ["Hello, 
Doris!"]
 2025-05-18T02:00       [null]  [null]  [null]  [null]  [null]  [null]  [null]  
[null]  [null]  [null]  [null]  [null]  [null]  [null]  [null]  [null]
 2025-05-18T03:00       [0]     [127]   [32767] [2147483647]    
[9223372036854775807]   [1234567890123456789]   [123.456]       [123456.789]    
[123457]        [123456789012346]       [1234567890123456789012345678]  
["9999-12-31"]  ["9999-12-31 23:59:59"] [""]    [""]    [""]
 2025-05-18T04:00       [1]     [0]     [0]     [0]     [0]     [0]     [0]     
[0]     [0]     [0]     [0]     ["2023-10-01"]  ["2023-10-01 12:34:56"] ["A"]   
["Hello"]       ["Hello, Doris!"]
diff --git 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
index eecba5cbcd6..aebd716491e 100644
--- 
a/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
+++ 
b/regression-test/data/external_table_p0/remote_doris/test_remote_doris_statistics.out
@@ -4,7 +4,7 @@ c_bigint        4       3       1       -9223372036854775808    
9223372036854775807     32
 c_boolean      4       2       1       0       1       4
 c_char 4       2       1               A       2
 c_date 4       3       1       1970-01-01      9999-12-31      16
-c_datetime     4       3       1       0000-01-01 00:00:00     9999-12-31 
23:59:59     32
+c_datetime     4       3       1       0001-01-01 00:00:00     9999-12-31 
23:59:59     32
 c_decimal18    4       3       1       -123456789012346        123456789012346 
32
 c_decimal32    4       3       1       -1234567890123456789012345678   
1234567890123456789012345678    64
 c_decimal9     4       3       1       -123457 123457  16
diff --git 
a/regression-test/suites/arrow_flight_sql_p0/test_flight_int96_timestamp_range.groovy
 
b/regression-test/suites/arrow_flight_sql_p0/test_flight_int96_timestamp_range.groovy
new file mode 100644
index 00000000000..7cdc5f1bbfe
--- /dev/null
+++ 
b/regression-test/suites/arrow_flight_sql_p0/test_flight_int96_timestamp_range.groovy
@@ -0,0 +1,83 @@
+// 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.
+
+import java.sql.DriverManager
+import java.sql.Types
+import java.time.LocalDateTime
+
+suite("test_flight_int96_timestamp_range", 
"arrow_flight_sql,external,hive,tvf,external_docker") {
+    if 
(!"true".equalsIgnoreCase(context.config.otherConfigs.get("enableHiveTest"))) {
+        return
+    }
+    def host = context.config.otherConfigs.get("externalEnvIp")
+    def port = context.config.otherConfigs.get("hive2HdfsPort")
+    // External clusters can override the configured Flight port; use the 
running FE's endpoint.
+    def frontend = jdbc_sql_return_maparray("SHOW FRONTENDS").find {
+        it.IsMaster.toString().toBoolean() && it.Alive.toString().toBoolean()
+    }
+    assertNotNull(frontend)
+    assertTrue(frontend.ArrowFlightSqlPort.toString().toInteger() > 0)
+    def flightUrl = 
"jdbc:arrow-flight-sql://${frontend.Host}:${frontend.ArrowFlightSqlPort}" +
+            
"/catalog=${context.dbName}?useServerPrepStmts=false&useSSL=false&useEncryption=false"
+    Class.forName("org.apache.arrow.driver.jdbc.ArrowFlightJdbcDriver")
+    def query = { String file ->
+        """SELECT * FROM HDFS(
+           "uri" = 
"hdfs://${host}:${port}/user/doris/tvf_data/test_hdfs_parquet/group4/${file}",
+           "hadoop.username" = "doris", "format" = "parquet") LIMIT 10"""
+    }
+    // Keep the configured Flight identity without the TLS-dependent connect() 
wrapper.
+    DriverManager.getConnection(flightUrl, 
context.config.otherConfigs.get("extArrowFlightSqlUser"),
+            
context.config.otherConfigs.get("extArrowFlightSqlPassword")).withCloseable { 
flight ->
+        flight.createStatement().withCloseable { statement ->
+            // Nanosecond fractions in these fixtures truncate to valid 
DATETIME(6) values.
+            for (def file : 
["part-00000-570d8e52-652d-4892-8bdc-7fa5466ffa69.c000.snappy.parquet",
+                              
"part-00000-b945dfb5-9982-4f86-b903-dabef99caba1.c000.snappy.parquet",
+                              
"part-00000-721700d2-26d7-42a3-a8f9-b6601628ccd4.c000.snappy.parquet",
+                              
"part-00000-afeef968-a917-4d51-a652-e5a4214df453.c000.snappy.parquet"]) {
+                logger.info("Read INT96 Flight fixture: ${file}")
+                statement.executeQuery(query(file)).withCloseable { rows ->
+                    assertTrue(rows.next())
+                    def metadata = rows.getMetaData()
+                    for (int column = 1; column <= metadata.getColumnCount(); 
++column) {
+                        if (metadata.getColumnType(column) == Types.TIMESTAMP) 
{
+                            assertNotNull(rows.getObject(column, 
LocalDateTime.class))
+                        }
+                    }
+                    
assertEquals(LocalDateTime.parse("9999-12-31T23:59:59.999999"),
+                            rows.getObject(metadata.getColumnCount(), 
LocalDateTime.class))
+                    assertFalse(rows.next())
+                }
+            }
+        }
+        // Consume the result on the same discovered endpoint so zero-year 
values must fail over Flight.
+        try {
+            flight.createStatement().withCloseable { statement ->
+                
statement.executeQuery(query("int96_timestamps_nanos_outside_day_range.parquet"))
+                        .withCloseable { rows ->
+                    while (rows.next()) {
+                        for (int column = 1; column <= 
rows.getMetaData().getColumnCount(); ++column) {
+                            rows.getObject(column)
+                        }
+                    }
+                }
+            }
+            assertTrue(false, "Expected an out-of-range Flight timestamp 
error")
+        } catch (Exception error) {
+            assertTrue(error.toString().contains("outside the supported 
0001-9999 range"), error.toString())
+        }
+    }
+}
diff --git 
a/regression-test/suites/arrow_flight_sql_p0/test_flight_timestamp_range.groovy 
b/regression-test/suites/arrow_flight_sql_p0/test_flight_timestamp_range.groovy
new file mode 100644
index 00000000000..c494bd84684
--- /dev/null
+++ 
b/regression-test/suites/arrow_flight_sql_p0/test_flight_timestamp_range.groovy
@@ -0,0 +1,81 @@
+// 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.
+
+import java.time.LocalDateTime
+
+suite("test_flight_timestamp_range", "arrow_flight_sql") {
+    // Reuse the configured Flight connection so TLS settings cannot skip the 
assertions or change credentials.
+    def flight = context.getArrowFlightSqlConnection()
+    def input = "${context.dbName}.flight_timestamp_input"
+    def expectRangeError = { String query ->
+        try {
+            arrow_flight_sql(query)
+            assertTrue(false, "Expected an out-of-range Flight timestamp 
error")
+        } catch (Exception error) {
+            assertTrue(error.toString().contains("outside the supported 
0001-9999 range"), error.toString())
+        }
+    }
+    arrow_flight_sql "DROP TABLE IF EXISTS ${input}"
+    arrow_flight_sql """CREATE TABLE ${input} (id INT, value STRING)
+           DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
+           PROPERTIES ("replication_num" = "1")"""
+    try {
+        arrow_flight_sql """INSERT INTO ${input} VALUES
+               (1, '0001-01-01 00:00:00.000000'),
+               (2, '9999-12-31 23:59:59.999999'),
+               (3, '1969-12-31 23:59:59.999999'),
+               (4, NULL), (5, '0000-12-31 00:00:00.000000')"""
+        // Check that the source value reaches the date conversion instead of 
becoming SQL NULL.
+        assertEquals([['0000-12-31 00:00:00.000000']],
+                arrow_flight_sql("SELECT CAST(CAST(value AS DATETIME(6)) AS 
STRING) FROM ${input} WHERE id = 5"))
+        for (def scale : [0, 3, 6]) {
+            expectRangeError("SELECT CAST(value AS DATETIME(${scale})) AS 
event_time FROM ${input} WHERE id = 5")
+        }
+        expectRangeError("SELECT CAST('0000-12-31 00:00:00' AS DATETIME(6)) AS 
event_time")
+        for (def expression : ["array(CAST(value AS DATETIME(6)))",
+                                "named_struct('child', CAST(value AS 
DATETIME(6)))",
+                                "map('key', CAST(value AS DATETIME(6)))",
+                                "map(CAST(value AS DATETIME(6)), 'value')"]) {
+            expectRangeError("SELECT ${expression} AS nested FROM ${input} 
WHERE id = 5")
+        }
+        expectRangeError("SELECT CAST(value AS DATETIME(6)) AS event_time FROM 
${input} ORDER BY id")
+        flight.createStatement().withCloseable { statement ->
+            statement.executeQuery("SELECT CAST(value AS DATETIME(6)) FROM 
${input} WHERE id <= 4 ORDER BY id")
+                    .withCloseable { rows ->
+                // Typed access preserves proleptic calendar dates and 
microseconds.
+                for (def expected : ["0001-01-01T00:00:00", 
"9999-12-31T23:59:59.999999",
+                                      "1969-12-31T23:59:59.999999"]) {
+                    assertTrue(rows.next())
+                    assertEquals(LocalDateTime.parse(expected), 
rows.getObject(1, LocalDateTime.class))
+                }
+                assertTrue(rows.next())
+                assertNull(rows.getObject(1, LocalDateTime.class))
+                assertFalse(rows.next())
+            }
+            statement.executeQuery("SELECT array(CAST(value AS DATETIME(6))) 
FROM ${input} WHERE id = 4")
+                    .withCloseable { rows ->
+                assertTrue(rows.next())
+                def values = rows.getArray(1).getArray()
+                assertEquals(1, values.length)
+                assertNull(values[0])
+                assertFalse(rows.next())
+            }
+        }
+    } finally {
+        arrow_flight_sql "DROP TABLE IF EXISTS ${input}"
+    }
+}
diff --git 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_all_types_select.groovy
 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_all_types_select.groovy
index 31530d7bfc1..c6919d62b57 100644
--- 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_all_types_select.groovy
+++ 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_all_types_select.groovy
@@ -67,8 +67,9 @@ suite("test_remote_doris_all_types_select", 
"p0,external,doris,external_docker,e
         );
     """
 
+    // Successful Flight reads require timestamps within the supported 
0001-9999 range.
     sql """
-        INSERT INTO 
`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t` 
values('2025-05-18 01:00:00.000', true, -128, -32768, -2147483648, 
-9223372036854775808, -1234567890123456790, -123.456, -123456.789, -123457, 
-123456789012346, -1234567890123456789012345678, '1970-01-01', '0000-01-01 
00:00:00', 'A', 'Hello', 'Hello, Doris!', '["apple", "banana", "orange"]', 
{"Emily":101,"age":25} , {11, 3.14, "Emily"}, '{"k1":"v31", "k2": 300, "k3": 
[123, 456], "k4": [], " [...]
+        INSERT INTO 
`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t` 
values('2025-05-18 01:00:00.000', true, -128, -32768, -2147483648, 
-9223372036854775808, -1234567890123456790, -123.456, -123456.789, -123457, 
-123456789012346, -1234567890123456789012345678, '1970-01-01', '0001-01-01 
00:00:00', 'A', 'Hello', 'Hello, Doris!', '["apple", "banana", "orange"]', 
{"Emily":101,"age":25} , {11, 3.14, "Emily"}, '{"k1":"v31", "k2": 300, "k3": 
[123, 456], "k4": [], " [...]
     """
     sql """
         INSERT INTO 
`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t` 
values('2025-05-18 02:00:00.000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
@@ -108,7 +109,7 @@ suite("test_remote_doris_all_types_select", 
"p0,external,doris,external_docker,e
     """
 
     sql """
-        INSERT INTO 
`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t2` 
values('2025-05-18 01:00:00.000', [true], [-128], [-32768], [-2147483648], 
[-9223372036854775808], [-1234567890123456790], [-123.456], [-123456.789], 
[-123457], [-123456789012346], [-1234567890123456789012345678], ['0000-01-01'], 
['0000-01-01 00:00:00'], ['A'], ['Hello'], ['Hello, Doris!'])
+        INSERT INTO 
`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t2` 
values('2025-05-18 01:00:00.000', [true], [-128], [-32768], [-2147483648], 
[-9223372036854775808], [-1234567890123456790], [-123.456], [-123456.789], 
[-123457], [-123456789012346], [-1234567890123456789012345678], ['0000-01-01'], 
['0001-01-01 00:00:00'], ['A'], ['Hello'], ['Hello, Doris!'])
     """
     sql """
         INSERT INTO 
`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t2` 
values('2025-05-18 02:00:00.000', [NULL], [NULL], [NULL], [NULL], [NULL], 
[NULL], [NULL], [NULL], [NULL], [NULL], [NULL], [NULL], [NULL], [NULL], [NULL], 
[NULL])
@@ -172,6 +173,16 @@ suite("test_remote_doris_all_types_select", 
"p0,external,doris,external_docker,e
         select * from 
`test_remote_doris_all_types_select_catalog`.`test_remote_doris_all_types_select_db`.`test_remote_doris_all_types_select_t3`
 order by id
     """
 
+    // Keep zero-year rejection covered separately from successful all-type 
round trips.
+    sql """INSERT INTO 
test_remote_doris_all_types_select_db.test_remote_doris_all_types_select_t3
+           (id, datetime_0) VALUES ('2025-05-18 02:00:00', '0000-01-01 
00:00:00')"""
+    test {
+        sql """SELECT datetime_0 FROM 
test_remote_doris_all_types_select_catalog
+               
.test_remote_doris_all_types_select_db.test_remote_doris_all_types_select_t3
+               WHERE id = '2025-05-18 02:00:00'"""
+        exception "outside the supported 0001-9999 range"
+    }
+
     sql """ DROP DATABASE IF EXISTS test_remote_doris_all_types_select_db """
     sql """ DROP CATALOG IF EXISTS 
`test_remote_doris_all_types_select_catalog` """
 }
diff --git 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_statistics.groovy
 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_statistics.groovy
index 223c294d811..210946f397d 100644
--- 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_statistics.groovy
+++ 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_statistics.groovy
@@ -82,8 +82,9 @@ suite("test_remote_doris_statistics", 
"p0,external,doris,external_docker,externa
         );
     """
 
+    // Successful Flight reads require timestamps within the supported 
0001-9999 range.
     sql """
-        INSERT INTO 
`test_remote_doris_statistics_db`.`test_remote_doris_statistics_t1` 
values('2025-05-18 01:00:00.000', true, -128, -32768, -2147483648, 
-9223372036854775808, -1234567890123456790, -123.456, -123456.789, -123457, 
-123456789012346, -1234567890123456789012345678, '1970-01-01', '0000-01-01 
00:00:00', 'A', 'Hello', 'Hello, Doris!', '["apple", "banana", "orange"]', 
{"Emily":101,"age":25} , {11, 3.14, "Emily"})
+        INSERT INTO 
`test_remote_doris_statistics_db`.`test_remote_doris_statistics_t1` 
values('2025-05-18 01:00:00.000', true, -128, -32768, -2147483648, 
-9223372036854775808, -1234567890123456790, -123.456, -123456.789, -123457, 
-123456789012346, -1234567890123456789012345678, '1970-01-01', '0001-01-01 
00:00:00', 'A', 'Hello', 'Hello, Doris!', '["apple", "banana", "orange"]', 
{"Emily":101,"age":25} , {11, 3.14, "Emily"})
     """
     sql """
         INSERT INTO 
`test_remote_doris_statistics_db`.`test_remote_doris_statistics_t1` 
values('2025-05-18 02:00:00.000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)
diff --git 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_table_stats.groovy
 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_table_stats.groovy
index e6de44e005a..9486e888a6d 100644
--- 
a/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_table_stats.groovy
+++ 
b/regression-test/suites/external_table_p0/remote_doris/test_remote_doris_table_stats.groovy
@@ -82,8 +82,9 @@ suite("test_remote_doris_table_stats", 
"p0,external,doris,external_docker,extern
         );
     """
 
+    // Successful Flight reads require timestamps within the supported 
0001-9999 range.
     sql """
-        INSERT INTO 
`test_remote_doris_table_stats_db`.`test_remote_doris_table_stats_t1` 
values('2025-05-18 01:00:00.000', true, -128, -32768, -2147483648, 
-9223372036854775808, -1234567890123456790, -123.456, -123456.789, -123457, 
-123456789012346, -1234567890123456789012345678, '1970-01-01', '0000-01-01 
00:00:00', 'A', 'Hello', 'Hello, Doris!', '["apple", "banana", "orange"]', 
{"Emily":101,"age":25} , {11, 3.14, "Emily"})
+        INSERT INTO 
`test_remote_doris_table_stats_db`.`test_remote_doris_table_stats_t1` 
values('2025-05-18 01:00:00.000', true, -128, -32768, -2147483648, 
-9223372036854775808, -1234567890123456790, -123.456, -123456.789, -123457, 
-123456789012346, -1234567890123456789012345678, '1970-01-01', '0001-01-01 
00:00:00', 'A', 'Hello', 'Hello, Doris!', '["apple", "banana", "orange"]', 
{"Emily":101,"age":25} , {11, 3.14, "Emily"})
     """
     sql """
         INSERT INTO 
`test_remote_doris_table_stats_db`.`test_remote_doris_table_stats_t1` 
values('2025-05-18 02:00:00.000', NULL, NULL, NULL, NULL, NULL, NULL, NULL, 
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)


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

Reply via email to