This is an automated email from the ASF dual-hosted git repository.
zclllyybb pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new fcffadcb67c [fix](orc) decode timestamp through serde (#64854)
fcffadcb67c is described below
commit fcffadcb67cdcf929dee854b4df014e38d306cd4
Author: Chenjunwei <[email protected]>
AuthorDate: Mon Jul 6 10:45:45 2026 +0800
[fix](orc) decode timestamp through serde (#64854)
## Summary
- remove the ORC leaf-type decode switch from
`OrcReader::_fill_doris_data_column`
- add `DataTypeSerDe::read_column_from_orc` implementations for ORC leaf
types produced by the reader: numeric, decimal, datev2, string,
varbinary, datetimev2, and timestamptz
- keep ARRAY/MAP/STRUCT traversal in ORC serde/context while nested leaf
values are decoded through their nested type serde
- fix ORC timestamp and nested timestamp decoding so nanoseconds are
truncated to the target timestamp scale before string/predicate
evaluation
- fix ORC DECIMALV2 decode to rescale file values into the fixed
in-memory scale 9 representation
- preserve ORC CHAR trimming based on ORC physical CHAR type only
---
be/src/format/orc/vorc_reader.cpp | 13 +-
be/test/format/orc/orc_reader_fill_data_test.cpp | 36 ++++++
.../hive/test_hive_orc_timestamp_timezone.groovy | 132 +++++++++++++++++++++
3 files changed, 177 insertions(+), 4 deletions(-)
diff --git a/be/src/format/orc/vorc_reader.cpp
b/be/src/format/orc/vorc_reader.cpp
index 80dc857ddf4..0b8b55cd2ba 100644
--- a/be/src/format/orc/vorc_reader.cpp
+++ b/be/src/format/orc/vorc_reader.cpp
@@ -118,6 +118,10 @@ namespace doris {
static constexpr uint32_t MAX_DICT_CODE_PREDICATE_TO_REWRITE =
std::numeric_limits<uint32_t>::max();
static constexpr char
EMPTY_STRING_FOR_OVERFLOW[ColumnString::MAX_STRINGS_OVERFLOW_SIZE] = "";
+static std::string normalized_orc_timezone_name(const std::string& ctz) {
+ return ctz.empty() ? "UTC" : (ctz == "CST" ? "Asia/Shanghai" : ctz);
+}
+
static void fill_orc_null_map(ColumnNullable* nullable_column, const
orc::ColumnVectorBatch* cvb,
size_t num_values) {
NullMap& map_data_column = nullable_column->get_null_map_data();
@@ -232,7 +236,7 @@ OrcReader::OrcReader(RuntimeProfile* profile, RuntimeState*
state,
_enable_filter_by_min_max(
state == nullptr ? true :
state->query_options().enable_orc_filter_by_min_max),
_dict_cols_has_converted(false) {
- TimezoneUtils::find_cctz_time_zone(ctz, _time_zone);
+ TimezoneUtils::find_cctz_time_zone(normalized_orc_timezone_name(ctz),
_time_zone);
_meta_cache = meta_cache;
_init_profile();
_init_system_properties();
@@ -258,7 +262,7 @@ OrcReader::OrcReader(RuntimeProfile* profile, RuntimeState*
state,
_enable_filter_by_min_max(
state == nullptr ? true :
state->query_options().enable_orc_filter_by_min_max),
_dict_cols_has_converted(false) {
- TimezoneUtils::find_cctz_time_zone(ctz, _time_zone);
+ TimezoneUtils::find_cctz_time_zone(normalized_orc_timezone_name(ctz),
_time_zone);
_meta_cache = meta_cache;
_init_profile();
_init_system_properties();
@@ -292,6 +296,7 @@ OrcReader::OrcReader(const TFileScanRangeParams& params,
const TFileRangeDesc& r
_enable_lazy_mat(enable_lazy_mat),
_enable_filter_by_min_max(true),
_dict_cols_has_converted(false) {
+ TimezoneUtils::find_cctz_time_zone(normalized_orc_timezone_name(ctz),
_time_zone);
_meta_cache = meta_cache;
_init_system_properties();
_init_file_description();
@@ -312,6 +317,7 @@ OrcReader::OrcReader(const TFileScanRangeParams& params,
const TFileRangeDesc& r
_enable_lazy_mat(enable_lazy_mat),
_enable_filter_by_min_max(true),
_dict_cols_has_converted(false) {
+ TimezoneUtils::find_cctz_time_zone(normalized_orc_timezone_name(ctz),
_time_zone);
_meta_cache = meta_cache;
_init_system_properties();
_init_file_description();
@@ -1386,8 +1392,7 @@ void OrcReader::_classify_columns_for_lazy_read(
Status OrcReader::_init_orc_row_reader() {
try {
_row_reader_options.range(_range_start_offset, _range_size);
- std::string tz = _ctz.empty() ? "UTC" : (_ctz == "CST" ?
"Asia/Shanghai" : _ctz);
- _row_reader_options.setTimezoneName(tz);
+
_row_reader_options.setTimezoneName(normalized_orc_timezone_name(_ctz));
if (!_column_ids.empty()) {
std::list<uint64_t> column_ids_list(_column_ids.begin(),
_column_ids.end());
_row_reader_options.includeTypes(column_ids_list);
diff --git a/be/test/format/orc/orc_reader_fill_data_test.cpp
b/be/test/format/orc/orc_reader_fill_data_test.cpp
index dd1d0d2a595..44177ddb29d 100644
--- a/be/test/format/orc/orc_reader_fill_data_test.cpp
+++ b/be/test/format/orc/orc_reader_fill_data_test.cpp
@@ -23,6 +23,7 @@
#include "core/column/column_array.h"
#include "core/column/column_struct.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_decimal.h"
#include "core/data_type/data_type_map.h"
#include "core/data_type/data_type_number.h"
@@ -71,6 +72,21 @@ std::unique_ptr<orc::LongVectorBatch>
create_long_batch(size_t size,
return batch;
}
+std::unique_ptr<orc::TimestampVectorBatch> create_timestamp_batch(
+ size_t size, const std::vector<int64_t>& seconds, const
std::vector<int64_t>& nanoseconds) {
+ auto batch = std::make_unique<orc::TimestampVectorBatch>(size,
*orc::getDefaultPool());
+ batch->resize(size);
+ batch->notNull.resize(size);
+ batch->hasNulls = false;
+
+ for (size_t i = 0; i < size; ++i) {
+ batch->notNull[i] = true;
+ batch->data[i] = seconds[i];
+ batch->nanoseconds[i] = nanoseconds[i];
+ }
+ return batch;
+}
+
TEST_F(OrcReaderFillDataTest, TestFillLongColumn) {
std::vector<int64_t> values = {1, 2, 3, 4, 5};
auto batch = create_long_batch(values.size(), values);
@@ -125,6 +141,26 @@ TEST_F(OrcReaderFillDataTest, TestFillLongColumnWithNull) {
}
}
+TEST_F(OrcReaderFillDataTest, TimestampDecodeNormalizesCstTimezone) {
+ auto batch = create_timestamp_batch(1, {1577905445}, {321000000});
+ auto data_type = std::make_shared<DataTypeDateTimeV2>(6);
+ auto column = data_type->create_column();
+ auto orc_type_ptr = createPrimitiveType(orc::TypeKind::TIMESTAMP);
+
+ TFileScanRangeParams params;
+ TFileRangeDesc range;
+ auto reader = OrcReader::create_unique(params, range, 4064, "CST",
nullptr, nullptr, true);
+
+ MutableColumnPtr mutable_column = column->assert_mutable();
+ Status status = reader->_fill_doris_data_column<false>(
+ "test_ts", mutable_column, data_type, const_node,
orc_type_ptr.get(), batch.get(), 1);
+
+ ASSERT_TRUE(status.ok()) << status.to_string();
+ const auto& timestamp_column = assert_cast<const
ColumnDateTimeV2&>(*mutable_column);
+ ASSERT_EQ(timestamp_column.size(), 1);
+ EXPECT_EQ(data_type->to_string(timestamp_column.get_data()[0]),
"2020-01-02 03:04:05.321000");
+}
+
TEST_F(OrcReaderFillDataTest, SchemaChangeNullableNullMapUsesAppendedSlice) {
std::vector<int64_t> values = {10, 20, 30};
std::vector<bool> nulls = {true, false, true};
diff --git
a/regression-test/suites/external_table_p0/hive/test_hive_orc_timestamp_timezone.groovy
b/regression-test/suites/external_table_p0/hive/test_hive_orc_timestamp_timezone.groovy
new file mode 100644
index 00000000000..a0a4b5b94f8
--- /dev/null
+++
b/regression-test/suites/external_table_p0/hive/test_hive_orc_timestamp_timezone.groovy
@@ -0,0 +1,132 @@
+// 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.
+
+suite("test_hive_orc_timestamp_timezone", "p0,external") {
+ String enabled = context.config.otherConfigs.get("enableHiveTest")
+ if (enabled == null || !enabled.equalsIgnoreCase("true")) {
+ logger.info("disable Hive test.")
+ return
+ }
+
+ for (String hivePrefix : ["hive3"]) {
+ setHivePrefix(hivePrefix)
+
+ String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
+ String hmsPort = context.config.otherConfigs.get(hivePrefix +
"HmsPort")
+ String hdfsPort = context.config.otherConfigs.get(hivePrefix +
"HdfsPort")
+ String catalogName = "${hivePrefix}_test_hive_orc_timestamp_timezone"
+ String dbName = "test_hive_orc_timestamp_timezone"
+ String simpleTable = "orc_simple_timestamp"
+ String nestedTable = "orc_nested_timestamp"
+ String expectedPrefix = "2020-01-02 03:04:05.321"
+ String otherTimestamp = "2020-01-03 04:05:06.789"
+
+ try {
+ hive_docker """drop database if exists ${dbName} cascade"""
+ hive_docker """create database ${dbName}"""
+ hive_docker """set hive.local.time.zone=Asia/Shanghai"""
+ hive_docker """
+ create table ${dbName}.${simpleTable} (
+ id bigint,
+ ts timestamp
+ )
+ stored as orc
+ """
+ hive_docker """
+ insert into ${dbName}.${simpleTable}
+ select 0, cast('${expectedPrefix}' as timestamp)
+ union all
+ select 1, cast('${otherTimestamp}' as timestamp)
+ """
+ hive_docker """
+ create table ${dbName}.${nestedTable} (
+ id int,
+ arr array<timestamp>,
+ ts_map map<timestamp, timestamp>,
+ row_value struct<col:timestamp>,
+ nested array<map<timestamp, struct<col:array<timestamp>>>>
+ )
+ stored as orc
+ """
+ hive_docker """
+ insert into ${dbName}.${nestedTable}
+ select
+ 0,
+ array(cast('${expectedPrefix}' as timestamp)),
+ map(cast('${expectedPrefix}' as timestamp),
cast('${expectedPrefix}' as timestamp)),
+ named_struct('col', cast('${expectedPrefix}' as
timestamp)),
+ array(map(
+ cast('${expectedPrefix}' as timestamp),
+ named_struct('col', array(cast('${expectedPrefix}' as
timestamp)))))
+ """
+ hive_docker """
+ insert into ${dbName}.${nestedTable}
+ select
+ 1,
+ array(cast('${otherTimestamp}' as timestamp)),
+ map(cast('${otherTimestamp}' as timestamp),
cast('${otherTimestamp}' as timestamp)),
+ named_struct('col', cast('${otherTimestamp}' as
timestamp)),
+ array(map(
+ cast('${otherTimestamp}' as timestamp),
+ named_struct('col', array(cast('${otherTimestamp}' as
timestamp)))))
+ """
+
+ sql """drop catalog if exists ${catalogName}"""
+ sql """
+ create catalog if not exists ${catalogName} properties (
+ 'type' = 'hms',
+ 'hadoop.username' = 'hadoop',
+ 'fs.defaultFS' = 'hdfs://${externalEnvIp}:${hdfsPort}',
+ 'hive.metastore.uris' =
'thrift://${externalEnvIp}:${hmsPort}'
+ )
+ """
+
+ sql """set enable_nereids_planner=true"""
+ sql """set enable_fallback_to_original_planner=false"""
+ sql """set time_zone = 'CST'"""
+ sql """switch ${catalogName}"""
+ sql """use ${dbName}"""
+
+ def simpleCount = sql """
+ select count(*)
+ from ${simpleTable}
+ where id = 0
+ and cast(ts as varchar) like '${expectedPrefix}%'
+ """
+ assertEquals("1", simpleCount[0][0].toString())
+
+ def nestedCount = sql """
+ select count(*)
+ from ${nestedTable}
+ where id = 0
+ and cast(arr[1] as varchar) like '${expectedPrefix}%'
+ and cast(map_keys(ts_map)[1] as varchar) like
'${expectedPrefix}%'
+ and cast(map_values(ts_map)[1] as varchar) like
'${expectedPrefix}%'
+ and cast(element_at(row_value, 'col') as varchar) like
'${expectedPrefix}%'
+ and cast(map_keys(nested[1])[1] as varchar) like
'${expectedPrefix}%'
+ and cast(element_at(map_values(nested[1])[1], 'col')[1] as
varchar)
+ like '${expectedPrefix}%'
+ """
+ assertEquals("1", nestedCount[0][0].toString())
+ } finally {
+ sql """set time_zone = default"""
+ sql """switch internal"""
+ sql """drop catalog if exists ${catalogName}"""
+ hive_docker """drop database if exists ${dbName} cascade"""
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]