You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by hu...@apache.org on 2025/10/31 04:46:45 UTC
(doris) 02/13: [Feature](catalog) Prune nested columns for hive/iceberg parquet and orc reader.
This is an automated email from the ASF dual-hosted git repository.
huajianlan pushed a commit to branch nested_column_prune
in repository https://gitbox.apache.org/repos/asf/doris.git
commit ae92dbce84100235b21538c8acf89564687dfe8d
Author: kakachen <ch...@selectdb.com>
AuthorDate: Thu Oct 23 09:54:32 2025 +0800
[Feature](catalog) Prune nested columns for hive/iceberg parquet and orc reader.
---
.gitmodules | 2 +-
be/src/runtime/descriptors.h | 6 +
be/src/vec/exec/format/orc/vorc_reader.cpp | 436 ++++++--
be/src/vec/exec/format/orc/vorc_reader.h | 24 +-
be/src/vec/exec/format/parquet/schema_desc.cpp | 87 +-
be/src/vec/exec/format/parquet/schema_desc.h | 30 +-
.../exec/format/parquet/vparquet_column_reader.cpp | 264 ++++-
.../exec/format/parquet/vparquet_column_reader.h | 105 +-
.../exec/format/parquet/vparquet_group_reader.cpp | 40 +-
.../exec/format/parquet/vparquet_group_reader.h | 8 +-
be/src/vec/exec/format/parquet/vparquet_reader.cpp | 21 +-
be/src/vec/exec/format/parquet/vparquet_reader.h | 5 +-
.../table/hive/hive_orc_nested_column_utils.cpp | 148 +++
.../table/hive/hive_orc_nested_column_utils.h | 43 +
.../hive/hive_parquet_nested_column_utils.cpp | 172 +++
.../table/hive/hive_parquet_nested_column_utils.h | 41 +
be/src/vec/exec/format/table/hive_reader.cpp | 453 +++++++-
be/src/vec/exec/format/table/hive_reader.h | 14 +
.../iceberg/iceberg_orc_nested_column_utils.cpp | 149 +++
.../iceberg/iceberg_orc_nested_column_utils.h | 46 +
.../iceberg_parquet_nested_column_utils.cpp | 173 +++
.../iceberg/iceberg_parquet_nested_column_utils.h | 42 +
be/src/vec/exec/format/table/iceberg_reader.cpp | 253 ++++-
be/src/vec/exec/format/table/iceberg_reader.h | 7 +
.../vec/exec/format/table/table_format_reader.cpp | 21 +-
be/src/vec/exec/format/table/table_format_reader.h | 36 +
be/src/vec/exec/scan/file_scanner.cpp | 6 +-
be/src/vec/exec/scan/file_scanner.h | 2 +-
...4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc | Bin 0 -> 2277 bytes
...4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc | Bin 0 -> 2334 bytes
...2c4c8-e0bd-4db6-b550-6323977446a4.metadata.json | 145 +++
...66011-3129-4bfb-9b4b-c1510c566d73.metadata.json | 176 +++
.../6a876742-85c2-4f84-ae24-292482fa9288-m0.avro | Bin 0 -> 8175 bytes
...212-1-6a876742-85c2-4f84-ae24-292482fa9288.avro | Bin 0 -> 4264 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4068 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4020 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4043 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4082 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4067 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4047 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4077 bytes
...aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet | Bin 0 -> 4147 bytes
...38ed9-31af-46c8-9274-506c7f2709c9.metadata.json | 145 +++
...4732d-f823-4900-8185-3eb3cb23e4a9.metadata.json | 176 +++
.../51fb6058-7fc2-4791-96a9-f3111933728a-m0.avro | Bin 0 -> 8783 bytes
...552-1-51fb6058-7fc2-4791-96a9-f3111933728a.avro | Bin 0 -> 4264 bytes
...a144c37-16a4-47c6-96db-0007175b5c90-0-00001.orc | Bin 0 -> 7181 bytes
...20949-0948-4f33-bcf4-5b6bcb0cd400.metadata.json | 548 +++++++++
...ddc20-6aa8-4864-9d15-546dd5a6bae4.metadata.json | 579 ++++++++++
.../d9aed717-61ef-422b-b044-a16ca31f9e86-m0.avro | 1 +
...228-1-d9aed717-61ef-422b-b044-a16ca31f9e86.avro | Bin 0 -> 4261 bytes
...35f-d581-40e4-8d56-a929aded99e4-0-00001.parquet | Bin 0 -> 21586 bytes
...ae7dc-be1a-410b-ae74-7cf04ad10765.metadata.json | 548 +++++++++
...56265-e89e-4c9f-a0fb-7f3075d4e8f9.metadata.json | 579 ++++++++++
.../bee22dff-a1d3-420d-bfba-c1b4193ab0df-m0.avro | 5 +
...002-1-bee22dff-a1d3-420d-bfba-c1b4193ab0df.avro | Bin 0 -> 4262 bytes
.../test_data/nested_user_profiles_orc/_SUCCESS | 0
...f23-05d1-4043-a533-b155ef52b720-c000.snappy.orc | Bin 0 -> 8396 bytes
.../nested_user_profiles_parquet/_SUCCESS | 0
...1a03-4efc-ab51-557e9369a1f9-c000.snappy.parquet | Bin 0 -> 21157 bytes
.../exec/format/parquet/parquet_thrift_test.cpp | 12 +-
.../hive/hive_reader_create_column_ids_test.cpp | 1179 ++++++++++++++++++++
.../exec/format/table/hive/hive_reader_test.cpp | 743 ++++++++++++
.../iceberg_reader_create_column_ids_test.cpp | 1177 +++++++++++++++++++
.../format/table/iceberg/iceberg_reader_test.cpp | 739 ++++++++++++
.../org/apache/doris/datasource/FileScanNode.java | 1 -
.../trees/plans/logical/LogicalFileScan.java | 19 +-
.../trees/plans/logical/LogicalHudiScan.java | 2 +-
68 files changed, 9229 insertions(+), 179 deletions(-)
diff --git a/.gitmodules b/.gitmodules
index 54c1a8a3636..63e4b14f70d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -20,7 +20,7 @@
[submodule "contrib/apache-orc"]
path = contrib/apache-orc
url = https://github.com/apache/doris-thirdparty.git
- branch = orc
+ branch = cq_nested_column_prune_external_table
[submodule "doris-faiss"]
path = contrib/faiss
url = https://github.com/apache/doris-thirdparty.git
diff --git a/be/src/runtime/descriptors.h b/be/src/runtime/descriptors.h
index c66ccd80b1d..b34e54622b5 100644
--- a/be/src/runtime/descriptors.h
+++ b/be/src/runtime/descriptors.h
@@ -100,6 +100,10 @@ public:
return virtual_column_expr;
}
+ void set_is_predicate(bool is_predicate) { _is_predicate = is_predicate; }
+
+ bool is_predicate() const { return _is_predicate; }
+
private:
friend class DescriptorTbl;
friend class TupleDescriptor;
@@ -141,6 +145,8 @@ private:
std::shared_ptr<doris::TExpr> virtual_column_expr = nullptr;
+ bool _is_predicate = false;
+
SlotDescriptor(const TSlotDescriptor& tdesc);
SlotDescriptor(const PSlotDescriptor& pdesc);
MOCK_DEFINE(SlotDescriptor();)
diff --git a/be/src/vec/exec/format/orc/vorc_reader.cpp b/be/src/vec/exec/format/orc/vorc_reader.cpp
index bd996f2189b..cfbad191772 100644
--- a/be/src/vec/exec/format/orc/vorc_reader.cpp
+++ b/be/src/vec/exec/format/orc/vorc_reader.cpp
@@ -26,6 +26,7 @@
#include <algorithm>
#include <cctype>
+#include <list>
#include "vec/exprs/vdirect_in_predicate.h"
#include "vec/exprs/vexpr.h"
@@ -355,8 +356,11 @@ Status OrcReader::init_reader(
const RowDescriptor* row_descriptor,
const VExprContextSPtrs* not_single_slot_filter_conjuncts,
const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts,
- std::shared_ptr<TableSchemaChangeHelper::Node> table_info_node_ptr) {
+ std::shared_ptr<TableSchemaChangeHelper::Node> table_info_node_ptr,
+ const std::set<uint64_t>& column_ids, const std::set<uint64_t>& filter_column_ids) {
_table_column_names = column_names;
+ _column_ids = column_ids;
+ _filter_column_ids = filter_column_ids;
_colname_to_value_range = colname_to_value_range;
_lazy_read_ctx.conjuncts = conjuncts;
_is_acid = is_acid;
@@ -401,6 +405,32 @@ Status OrcReader::get_parsed_schema(std::vector<std::string>* col_names,
Status OrcReader::_init_read_columns() {
SCOPED_RAW_TIMER(&_statistics.init_column_time);
const auto& root_type = _reader->getType();
+
+ // Save file root type for later use in type conversion
+ _file_root_type = &root_type;
+
+ // Build column ID to file type mapping for all columns in file
+ _column_id_to_file_type.clear();
+ std::function<void(const orc::Type*, int)> build_column_id_map = [&](const orc::Type* type,
+ int depth) {
+ if (type == nullptr) return;
+ uint64_t col_id = type->getColumnId();
+ _column_id_to_file_type[col_id] = type;
+
+ std::string indent(depth * 2, ' ');
+ VLOG_DEBUG << indent << "[OrcReader] Mapping column_id=" << col_id
+ << ", kind=" << static_cast<int>(type->getKind())
+ << ", subtype_count=" << type->getSubtypeCount();
+
+ for (uint64_t i = 0; i < type->getSubtypeCount(); ++i) {
+ build_column_id_map(type->getSubtype(i), depth + 1);
+ }
+ };
+
+ VLOG_DEBUG << "[OrcReader] Building column ID to file type mapping...";
+ build_column_id_map(&root_type, 0);
+ VLOG_DEBUG << "[OrcReader] Total mapped columns: " << _column_id_to_file_type.size();
+
if (_is_acid) {
for (uint64_t i = 0; i < root_type.getSubtypeCount(); ++i) {
if (root_type.getSubtype(i)->getKind() == orc::TypeKind::STRUCT) {
@@ -650,7 +680,7 @@ std::pair<bool, orc::Literal> OrcReader::_make_orc_literal(const VSlotRef* slot_
auto* slot = _tuple_descriptor->slots()[slot_ref->column_id()];
auto slot_type = slot->type();
auto primitive_type = slot_type->get_primitive_type();
- auto src_type = OrcReader::convert_to_doris_type(orc_type)->get_primitive_type();
+ auto src_type = convert_to_doris_type(orc_type)->get_primitive_type();
// should not down predicate for string type change from other type
if (src_type != primitive_type && !is_string_type(src_type) && is_string_type(primitive_type)) {
LOG(WARNING) << "Unsupported Push Down Schema Changed Column " << primitive_type << " to "
@@ -1158,7 +1188,12 @@ Status OrcReader::set_fill_columns(
try {
_row_reader_options.range(_range_start_offset, _range_size);
_row_reader_options.setTimezoneName(_ctz == "CST" ? "Asia/Shanghai" : _ctz);
- _row_reader_options.include(_read_file_cols);
+ if (!_column_ids.empty()) {
+ std::list<uint64_t> column_ids_list(_column_ids.begin(), _column_ids.end());
+ _row_reader_options.includeTypes(column_ids_list);
+ } else {
+ _row_reader_options.include(_read_file_cols);
+ }
_row_reader_options.setEnableLazyDecoding(true);
//orc reader should not use the tiny stripe optimization when reading by row id.
@@ -1246,6 +1281,57 @@ Status OrcReader::set_fill_columns(
}
}
+ // Helper function to recursively print ORC type structure
+ std::function<void(const orc::Type*, int, const std::string&)> print_orc_type_tree =
+ [&](const orc::Type* type, int indent, const std::string& name) {
+ if (type) {
+ std::string indent_str(indent * 2, ' ');
+ VLOG_DEBUG << indent_str << "- " << name
+ << " (type_id=" << type->getColumnId()
+ << ", kind=" << static_cast<int>(type->getKind()) << ")";
+ }
+
+ if (type->getKind() == orc::TypeKind::STRUCT) {
+ for (uint64_t i = 0; i < type->getSubtypeCount(); ++i) {
+ std::string field_name = type->getFieldName(i);
+ print_orc_type_tree(type->getSubtype(i), indent + 1, field_name);
+ }
+ } else if (type->getKind() == orc::TypeKind::LIST) {
+ print_orc_type_tree(type->getSubtype(0), indent + 1, "element");
+ } else if (type->getKind() == orc::TypeKind::MAP) {
+ print_orc_type_tree(type->getSubtype(0), indent + 1, "key");
+ print_orc_type_tree(type->getSubtype(1), indent + 1, "value");
+ } else {
+ // Primitive type, no children
+ }
+ };
+
+ // VLOG_DEBUG << "\n[OrcReader] ========== ORC Type Structure ==========";
+ // print_orc_type_tree(&selected_type, 0, "root");
+ // VLOG_DEBUG << "[OrcReader] ==========================================\n";
+
+ _type_map.clear();
+ if (_is_acid) {
+ for (uint64_t i = 0; i < selected_type.getSubtypeCount(); ++i) {
+ if (selected_type.getSubtype(i)->getKind() == orc::TypeKind::STRUCT) {
+ auto row_orc_type = selected_type.getSubtype(i);
+ for (uint64_t j = 0; j < row_orc_type->getSubtypeCount(); j++) {
+ std::string field_name =
+ TransactionalHive::ROW + "." + row_orc_type->getFieldName(j);
+ _type_map.emplace(field_name, row_orc_type->getSubtype(j));
+ }
+ } else {
+ std::string field_name = selected_type.getFieldName(i);
+ _type_map.emplace(field_name, selected_type.getSubtype(i));
+ }
+ }
+ } else {
+ for (int i = 0; i < selected_type.getSubtypeCount(); ++i) {
+ std::string field_name = selected_type.getFieldName(i);
+ _type_map.emplace(field_name, selected_type.getSubtype(i));
+ }
+ }
+
_remaining_rows = _row_reader->getNumberOfRows();
} catch (std::exception& e) {
@@ -1343,6 +1429,11 @@ Status OrcReader::_fill_missing_columns(
block->erase(result_column_id);
}
}
+
+ // // no default column, fill with null
+ // auto mutable_column = block->get_by_name(kv.first).column->assume_mutable();
+ // auto* nullable_column = static_cast<vectorized::ColumnNullable*>(mutable_column.get());
+ // nullable_column->insert_many_defaults(rows);
}
return Status::OK();
}
@@ -1391,6 +1482,14 @@ void OrcReader::_init_file_description() {
}
DataTypePtr OrcReader::convert_to_doris_type(const orc::Type* orc_type) {
+ // Critical: check for nullptr BEFORE accessing any methods
+ if (orc_type == nullptr) {
+ LOG(WARNING) << "[OrcReader] ERROR: convert_to_doris_type called with nullptr orc_type! "
+ "Falling back to STRING";
+ // Return a safe default type instead of crashing
+ return DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_STRING, true);
+ }
+
switch (orc_type->getKind()) {
case orc::TypeKind::BOOLEAN:
return DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_BOOLEAN, true);
@@ -1437,6 +1536,74 @@ DataTypePtr OrcReader::convert_to_doris_type(const orc::Type* orc_type) {
std::make_shared<DataTypeArray>(convert_to_doris_type(orc_type->getSubtype(0))));
}
case orc::TypeKind::MAP: {
+ // Handle incomplete MAP type due to column pruning
+ // If MAP doesn't have both key and value subtypes, try to find the complete type from file
+ if (orc_type->getSubtypeCount() < 2 || orc_type->getSubtype(0) == nullptr ||
+ orc_type->getSubtype(1) == nullptr) {
+ // Try to find the complete type from _column_id_to_file_type
+ uint64_t column_id = orc_type->getColumnId();
+ VLOG(1) << "[OrcReader] Detected incomplete MAP type: column_id=" << column_id
+ << ", subtype_count=" << orc_type->getSubtypeCount() << ", subtype(0)="
+ << (orc_type->getSubtypeCount() > 0 && orc_type->getSubtype(0) != nullptr
+ ? "not null"
+ : "null")
+ << ", subtype(1)="
+ << (orc_type->getSubtypeCount() > 1 && orc_type->getSubtype(1) != nullptr
+ ? "not null"
+ : "null");
+
+ auto it = _column_id_to_file_type.find(column_id);
+ if (it != _column_id_to_file_type.end() && it->second != nullptr) {
+ const orc::Type* complete_type = it->second;
+ VLOG(1) << "[OrcReader] Found complete type in mapping: column_id=" << column_id
+ << ", complete_type_kind=" << static_cast<int>(complete_type->getKind())
+ << ", complete_subtype_count=" << complete_type->getSubtypeCount();
+
+ // Print subtypes information
+ for (uint64_t i = 0; i < complete_type->getSubtypeCount(); ++i) {
+ const orc::Type* subtype = complete_type->getSubtype(i);
+ VLOG_DEBUG << "[OrcReader] complete_type->subtype[" << i << "]: "
+ << (subtype != nullptr
+ ? ("kind=" +
+ std::to_string(static_cast<int>(subtype->getKind())) +
+ ", column_id=" +
+ std::to_string(subtype->getColumnId()))
+ : "nullptr");
+ }
+
+ if (complete_type->getKind() == orc::TypeKind::MAP &&
+ complete_type->getSubtypeCount() == 2) {
+ VLOG(1) << "[OrcReader] Using complete MAP type from file schema for column_id="
+ << column_id;
+
+ // Get subtypes with extra validation
+ const orc::Type* key_type = complete_type->getSubtype(0);
+ const orc::Type* value_type = complete_type->getSubtype(1);
+
+ VLOG_DEBUG << "[OrcReader] About to convert key_type: "
+ << (key_type != nullptr ? "not null" : "NULL");
+ VLOG_DEBUG << "[OrcReader] About to convert value_type: "
+ << (value_type != nullptr ? "not null" : "NULL");
+
+ // Use the complete type from file - with null checks
+ DataTypePtr key_doris_type = convert_to_doris_type(key_type);
+ VLOG_DEBUG << "[OrcReader] Successfully converted key_type";
+
+ DataTypePtr value_doris_type = convert_to_doris_type(value_type);
+ VLOG_DEBUG << "[OrcReader] Successfully converted value_type";
+
+ return make_nullable(
+ std::make_shared<DataTypeMap>(key_doris_type, value_doris_type));
+ } else {
+ LOG(WARNING) << "[OrcReader] Warning: Complete type is not a valid MAP or has "
+ "wrong subtype count";
+ }
+ } else {
+ LOG(WARNING) << "[OrcReader] Warning: Could not find complete type in mapping for "
+ "column_id="
+ << column_id << ", mapping_size=" << _column_id_to_file_type.size();
+ }
+ }
return make_nullable(
std::make_shared<DataTypeMap>(convert_to_doris_type(orc_type->getSubtype(0)),
convert_to_doris_type(orc_type->getSubtype(1))));
@@ -1682,6 +1849,7 @@ Status OrcReader::_fill_doris_data_column(const std::string& col_name,
const orc::Type* orc_column_type,
const orc::ColumnVectorBatch* cvb, size_t num_values) {
auto logical_type = data_type->get_primitive_type();
+
switch (logical_type) {
#define DISPATCH(FlatType, CppType, OrcColumnType) \
case FlatType: \
@@ -1752,21 +1920,99 @@ Status OrcReader::_fill_doris_data_column(const std::string& col_name,
const DataTypePtr& doris_value_type =
reinterpret_cast<const DataTypeMap*>(remove_nullable(data_type).get())
->get_value_type();
+
+ // Get ORC key and value types with null checks
const orc::Type* orc_key_type = orc_column_type->getSubtype(0);
const orc::Type* orc_value_type = orc_column_type->getSubtype(1);
+
+ VLOG(1) << "[OrcReader] MAP column '" << col_name
+ << "': orc_key_type=" << (orc_key_type != nullptr ? "not null" : "NULL")
+ << ", orc_value_type=" << (orc_value_type != nullptr ? "not null" : "NULL")
+ << ", element_size=" << element_size;
+
+ // Handle incomplete MAP type - if key or value type is nullptr, try to recover from mapping
+ bool key_is_missing = (orc_key_type == nullptr);
+ bool value_is_missing = (orc_value_type == nullptr);
+
+ if (key_is_missing || value_is_missing) {
+ VLOG(1) << "[OrcReader] Detected incomplete MAP subtypes for column '" << col_name
+ << "', attempting to recover from mapping...";
+
+ uint64_t column_id = orc_column_type->getColumnId();
+ auto it = _column_id_to_file_type.find(column_id);
+ if (it != _column_id_to_file_type.end() && it->second != nullptr) {
+ const orc::Type* complete_map_type = it->second;
+ if (complete_map_type->getKind() == orc::TypeKind::MAP &&
+ complete_map_type->getSubtypeCount() == 2) {
+ if (key_is_missing) {
+ orc_key_type = complete_map_type->getSubtype(0);
+ if (orc_key_type != nullptr) {
+ // key_is_missing = false;
+ VLOG(1) << "[OrcReader] Recovered key type from mapping for column '"
+ << col_name << "'";
+ }
+ }
+ if (value_is_missing) {
+ orc_value_type = complete_map_type->getSubtype(1);
+ if (orc_value_type != nullptr) {
+ // value_is_missing = false;
+ VLOG(1) << "[OrcReader] Recovered value type from mapping for column '"
+ << col_name << "'";
+ }
+ }
+ }
+ }
+ }
+
ColumnPtr& doris_key_column = doris_map.get_keys_ptr();
ColumnPtr& doris_value_column = doris_map.get_values_ptr();
std::string key_col_name = col_name + ".key";
std::string value_col_name = col_name + ".value";
- RETURN_IF_ERROR(_orc_column_to_doris_column<false>(
- key_col_name, doris_key_column, doris_key_type, root_node->get_key_node(),
+ // Handle key column: if still missing, fill with default values
+ if (key_is_missing) {
+ LOG(INFO) << "[OrcReader] Key type is missing for MAP column '" << col_name
+ << "', filling with default values (element_size=" << element_size << ")";
+
+ // Fill key column with default values (nulls or empty values)
+ auto mutable_key_column = doris_key_column->assume_mutable();
+ if (mutable_key_column->is_nullable()) {
+ auto* nullable_column = static_cast<ColumnNullable*>(mutable_key_column.get());
+ nullable_column->insert_many_defaults(element_size);
+ } else {
+ mutable_key_column->insert_many_defaults(element_size);
+ }
+ } else {
+ // Normal processing: convert ORC column to Doris column
+ auto status = _orc_column_to_doris_column<false>(
+ key_col_name, doris_key_column, doris_key_type, root_node->get_key_node(),
+ orc_key_type, orc_map->keys.get(), element_size);
+ if (!status.ok()) {
+ return status;
+ }
+ }
+
+ // Handle value column: if still missing, fill with default values
+ if (value_is_missing) {
+ LOG(INFO) << "[OrcReader] Value type is missing for MAP column '" << col_name
+ << "', filling with default values (element_size=" << element_size << ")";
- orc_key_type, orc_map->keys.get(), element_size));
- RETURN_IF_ERROR(_orc_column_to_doris_column<false>(
- value_col_name, doris_value_column, doris_value_type, root_node->get_value_node(),
- orc_value_type, orc_map->elements.get(), element_size));
- return doris_map.deduplicate_keys();
+ // Fill value column with default values (nulls or empty values)
+ auto mutable_value_column = doris_value_column->assume_mutable();
+ if (mutable_value_column->is_nullable()) {
+ auto* nullable_column = static_cast<ColumnNullable*>(mutable_value_column.get());
+ nullable_column->insert_many_defaults(element_size);
+ } else {
+ mutable_value_column->insert_many_defaults(element_size);
+ }
+ return Status::OK();
+ } else {
+ // Normal processing: convert ORC column to Doris column
+ return _orc_column_to_doris_column<false>(value_col_name, doris_value_column,
+ doris_value_type, root_node->get_value_node(),
+ orc_value_type, orc_map->elements.get(),
+ element_size);
+ }
}
case PrimitiveType::TYPE_STRUCT: {
if (orc_column_type->getKind() != orc::TypeKind::STRUCT) {
@@ -1781,6 +2027,14 @@ Status OrcReader::_fill_doris_data_column(const std::string& col_name,
const auto* doris_struct_type =
assert_cast<const DataTypeStruct*>(remove_nullable(data_type).get());
+ // Build ORC field name to index map for faster lookup
+ std::unordered_map<std::string, int> orc_field_name_to_idx;
+ for (int j = 0; j < orc_column_type->getSubtypeCount(); ++j) {
+ std::string field_name = orc_column_type->getFieldName(j);
+ std::transform(field_name.begin(), field_name.end(), field_name.begin(), ::tolower);
+ orc_field_name_to_idx[field_name] = j;
+ }
+
for (int i = 0; i < doris_struct.tuple_size(); ++i) {
const auto& table_column_name = doris_struct_type->get_name_by_position(i);
if (!root_node->children_column_exists(table_column_name)) {
@@ -1788,10 +2042,23 @@ Status OrcReader::_fill_doris_data_column(const std::string& col_name,
continue;
}
const auto& file_column_name = root_node->children_file_column_name(table_column_name);
- for (int j = 0; j < orc_column_type->getSubtypeCount(); ++j) {
- if (boost::iequals(orc_column_type->getFieldName(j), file_column_name)) {
- read_fields[i] = j;
- }
+ std::string file_column_name_lower = file_column_name;
+ std::transform(file_column_name_lower.begin(), file_column_name_lower.end(),
+ file_column_name_lower.begin(), ::tolower);
+
+ auto it = orc_field_name_to_idx.find(file_column_name_lower);
+ if (it != orc_field_name_to_idx.end()) {
+ read_fields[i] = it->second;
+ VLOG_DEBUG << "[OrcReader] Found field mapping: doris_field[" << i
+ << "] -> orc_field[" << it->second
+ << "], table_column: " << table_column_name
+ << ", file_column: " << file_column_name_lower;
+ } else {
+ missing_fields.insert(i);
+ VLOG_DEBUG << "[OrcReader] Missing field: doris_field[" << i
+ << "], table_column: " << table_column_name
+ << ", file_column: " << file_column_name_lower
+ << " (not found in ORC file)";
}
}
@@ -1808,6 +2075,7 @@ Status OrcReader::_fill_doris_data_column(const std::string& col_name,
for (auto read_field : read_fields) {
orc::ColumnVectorBatch* orc_field = orc_struct->fields[read_field.second];
+
const orc::Type* orc_type = orc_column_type->getSubtype(read_field.second);
std::string field_name =
col_name + "." + orc_column_type->getFieldName(read_field.second);
@@ -1832,67 +2100,81 @@ Status OrcReader::_orc_column_to_doris_column(
const std::string& col_name, ColumnPtr& doris_column, const DataTypePtr& data_type,
std::shared_ptr<TableSchemaChangeHelper::Node> root_node, const orc::Type* orc_column_type,
const orc::ColumnVectorBatch* cvb, size_t num_values) {
- auto src_type = convert_to_doris_type(orc_column_type);
- bool is_dict_filter_col = false;
- for (const std::pair<std::string, int>& dict_col : _dict_filter_cols) {
- if (col_name == dict_col.first) {
- src_type = DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_INT, true);
- is_dict_filter_col = true;
- break;
- }
- }
- // If the column can be dictionary filtered, there will be two types.
- // It may be plain or a dictionary, because the same field in different stripes may have different types.
- // Here we use the $dict_ prefix to represent the dictionary type converter.
- auto converter_key = !is_dict_filter_col ? col_name : fmt::format("$dict_{}", col_name);
-
- if (!_converters.contains(converter_key)) {
- std::unique_ptr<converter::ColumnTypeConverter> converter =
- converter::ColumnTypeConverter::get_converter(src_type, data_type,
- converter::FileFormat::ORC);
- if (!converter->support()) {
- return Status::InternalError(
- "The column type of '{}' has changed and is not supported: ", col_name,
- converter->get_error_msg());
- }
- // reuse the cached converter
- _converters[converter_key] = std::move(converter);
- }
- converter::ColumnTypeConverter* converter = _converters[converter_key].get();
- ColumnPtr resolved_column = converter->get_column(src_type, doris_column, data_type);
- const DataTypePtr& resolved_type = converter->get_type();
-
+ DataTypePtr resolved_type;
+ ColumnPtr resolved_column;
+ converter::ColumnTypeConverter* converter_ptr = nullptr;
MutableColumnPtr data_column;
- if (resolved_column->is_nullable()) {
- SCOPED_RAW_TIMER(&_statistics.decode_null_map_time);
- auto* nullable_column =
- reinterpret_cast<ColumnNullable*>(resolved_column->assume_mutable().get());
- data_column = nullable_column->get_nested_column_ptr();
- NullMap& map_data_column = nullable_column->get_null_map_data();
- auto origin_size = map_data_column.size();
- map_data_column.resize(origin_size + num_values);
- if (cvb->hasNulls) {
- const auto* cvb_nulls = cvb->notNull.data();
- for (int i = 0; i < num_values; ++i) {
- map_data_column[origin_size + i] = !cvb_nulls[i];
+ if (orc_column_type != nullptr) {
+ auto src_type = convert_to_doris_type(orc_column_type);
+ bool is_dict_filter_col = false;
+ for (const std::pair<std::string, int>& dict_col : _dict_filter_cols) {
+ if (col_name == dict_col.first) {
+ src_type =
+ DataTypeFactory::instance().create_data_type(PrimitiveType::TYPE_INT, true);
+ is_dict_filter_col = true;
+ break;
+ }
+ }
+ // If the column can be dictionary filtered, there will be two types.
+ // It may be plain or a dictionary, because the same field in different stripes may have different types.
+ // Here we use the $dict_ prefix to represent the dictionary type converter.
+ auto converter_key = !is_dict_filter_col ? col_name : fmt::format("$dict_{}", col_name);
+
+ if (!_converters.contains(converter_key)) {
+ std::unique_ptr<converter::ColumnTypeConverter> converter =
+ converter::ColumnTypeConverter::get_converter(src_type, data_type,
+ converter::FileFormat::ORC);
+ if (!converter->support()) {
+ return Status::InternalError(
+ "The column type of '{}' has changed and is not supported: ", col_name,
+ converter->get_error_msg());
+ }
+ // reuse the cached converter
+ _converters[converter_key] = std::move(converter);
+ }
+ converter_ptr = _converters[converter_key].get();
+ resolved_column = converter_ptr->get_column(src_type, doris_column, data_type);
+ resolved_type = converter_ptr->get_type();
+
+ if (resolved_column->is_nullable()) {
+ SCOPED_RAW_TIMER(&_statistics.decode_null_map_time);
+ auto* nullable_column =
+ reinterpret_cast<ColumnNullable*>(resolved_column->assume_mutable().get());
+ data_column = nullable_column->get_nested_column_ptr();
+
+ NullMap& map_data_column = nullable_column->get_null_map_data();
+ auto origin_size = map_data_column.size();
+ map_data_column.resize(origin_size + num_values);
+ if (cvb->hasNulls) {
+ const auto* cvb_nulls = cvb->notNull.data();
+ for (int i = 0; i < num_values; ++i) {
+ map_data_column[origin_size + i] = !cvb_nulls[i];
+ }
+ } else {
+ memset(map_data_column.data() + origin_size, 0, num_values);
}
} else {
- memset(map_data_column.data() + origin_size, 0, num_values);
+ if (cvb->hasNulls) {
+ return Status::InternalError("Not nullable column {} has null values in orc file",
+ col_name);
+ }
+ data_column = resolved_column->assume_mutable();
}
+
+ RETURN_IF_ERROR((_fill_doris_data_column<is_filter>(
+ col_name, data_column, remove_nullable(resolved_type), root_node, orc_column_type,
+ cvb, num_values)));
+ // if (converter_ptr != nullptr) {
+ // resolve schema change
+ auto converted_column = doris_column->assume_mutable();
+ return converter_ptr->convert(resolved_column, converted_column);
+ // }
} else {
- if (cvb->hasNulls) {
- return Status::InternalError("Not nullable column {} has null values in orc file",
- col_name);
- }
- data_column = resolved_column->assume_mutable();
+ reinterpret_cast<ColumnNullable*>(doris_column->assume_mutable().get())
+ ->insert_many_defaults(num_values);
}
- RETURN_IF_ERROR(_fill_doris_data_column<is_filter>(col_name, data_column,
- remove_nullable(resolved_type), root_node,
- orc_column_type, cvb, num_values));
- // resolve schema change
- auto converted_column = doris_column->assume_mutable();
- return converter->convert(resolved_column, converted_column);
+ return Status::OK();
}
std::string OrcReader::get_field_name_lower_case(const orc::Type* orc_type, int pos) {
@@ -1993,10 +2275,10 @@ Status OrcReader::_get_next_block_impl(Block* block, size_t* read_rows, bool* eo
if (orc_col_idx == _colname_to_idx.end()) {
return Status::InternalError("Wrong read column '{}' in orc file", col_name);
}
- RETURN_IF_ERROR(_orc_column_to_doris_column<true>(
+ RETURN_IF_ERROR((_orc_column_to_doris_column<true>(
col_name, column_ptr, column_type,
_table_info_node_ptr->get_children_node(col_name), _type_map[file_column_name],
- batch_vec[orc_col_idx->second], _batch->numElements));
+ batch_vec[orc_col_idx->second], _batch->numElements)));
}
RETURN_IF_ERROR(_fill_partition_columns(block, _batch->numElements,
@@ -2051,6 +2333,12 @@ Status OrcReader::_get_next_block_impl(Block* block, size_t* read_rows, bool* eo
}
}
+ //std::cout << "[OrcReader] _column_ids: ";
+ //for (const auto& cid : _column_ids) {
+ // std::cout << cid << " ";
+ //}
+ //std::cout << std::endl;
+
if (!_dict_cols_has_converted && !_dict_filter_cols.empty()) {
for (auto& dict_filter_cols : _dict_filter_cols) {
MutableColumnPtr dict_col_ptr = ColumnInt32::create();
@@ -2084,10 +2372,10 @@ Status OrcReader::_get_next_block_impl(Block* block, size_t* read_rows, bool* eo
if (orc_col_idx == _colname_to_idx.end()) {
return Status::InternalError("Wrong read column '{}' in orc file", col_name);
}
- RETURN_IF_ERROR(_orc_column_to_doris_column(
+ RETURN_IF_ERROR((_orc_column_to_doris_column<false>(
col_name, column_ptr, column_type,
_table_info_node_ptr->get_children_node(col_name), _type_map[file_column_name],
- batch_vec[orc_col_idx->second], _batch->numElements));
+ batch_vec[orc_col_idx->second], _batch->numElements)));
}
RETURN_IF_ERROR(_fill_partition_columns(block, _batch->numElements,
@@ -2250,10 +2538,10 @@ Status OrcReader::filter(orc::ColumnVectorBatch& data, uint16_t* sel, uint16_t s
if (orc_col_idx == _colname_to_idx.end()) {
return Status::InternalError("Wrong read column '{}' in orc file", table_col_name);
}
- RETURN_IF_ERROR(_orc_column_to_doris_column(
+ RETURN_IF_ERROR((_orc_column_to_doris_column<false>(
table_col_name, column_ptr, column_type,
_table_info_node_ptr->get_children_node(table_col_name),
- _type_map[file_column_name], batch_vec[orc_col_idx->second], data.numElements));
+ _type_map[file_column_name], batch_vec[orc_col_idx->second], data.numElements)));
}
RETURN_IF_ERROR(
_fill_partition_columns(block, size, _lazy_read_ctx.predicate_partition_columns));
diff --git a/be/src/vec/exec/format/orc/vorc_reader.h b/be/src/vec/exec/format/orc/vorc_reader.h
index 358ba1d2468..341e40a9ce2 100644
--- a/be/src/vec/exec/format/orc/vorc_reader.h
+++ b/be/src/vec/exec/format/orc/vorc_reader.h
@@ -112,6 +112,12 @@ struct LazyReadContext {
std::unordered_map<std::string, VExprContextSPtr> predicate_missing_columns;
// lazy read missing columns or all missing columns
std::unordered_map<std::string, VExprContextSPtr> missing_columns;
+
+ std::vector<std::string> partial_predicate_columns;
+
+ // Record the number of rows filled in filter phase for lazy materialization
+ // This is used to check if a column was already processed in filter phase
+ size_t filter_phase_rows = 0;
};
class OrcReader : public GenericReader {
@@ -158,7 +164,9 @@ public:
const VExprContextSPtrs* not_single_slot_filter_conjuncts,
const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts,
std::shared_ptr<TableSchemaChangeHelper::Node> table_info_node_ptr =
- TableSchemaChangeHelper::ConstNode::get_instance());
+ TableSchemaChangeHelper::ConstNode::get_instance(),
+ const std::set<uint64_t>& column_ids = {},
+ const std::set<uint64_t>& filter_column_ids = {});
Status set_fill_columns(
const std::unordered_map<std::string, std::tuple<std::string, const SlotDescriptor*>>&
@@ -195,7 +203,8 @@ public:
std::unordered_map<std::string, orc::StringDictionary*>& column_name_to_dict_map,
bool* is_stripe_filtered);
- static DataTypePtr convert_to_doris_type(const orc::Type* orc_type);
+ DataTypePtr convert_to_doris_type(const orc::Type* orc_type);
+
static std::string get_field_name_lower_case(const orc::Type* orc_type, int pos);
void set_row_id_column_iterator(
@@ -630,7 +639,8 @@ private:
size_t _batch_size;
int64_t _range_start_offset;
int64_t _range_size;
- const std::string& _ctz;
+ // const std::string& _ctz;
+ std::string _ctz;
int32_t _offset_days = 0;
cctz::time_zone _time_zone;
@@ -653,6 +663,12 @@ private:
// file column name to orc type
std::unordered_map<std::string, const orc::Type*> _type_map;
+ // Optimized type conversion support
+ // column ID to file original type mapping for handling partial column selection
+ std::unordered_map<uint64_t, const orc::Type*> _column_id_to_file_type;
+ // Keep reference to file root type for complete type information
+ const orc::Type* _file_root_type = nullptr;
+
std::unique_ptr<ORCFileInputStream> _file_input_stream;
Statistics _statistics;
OrcProfile _orc_profile;
@@ -673,6 +689,8 @@ private:
std::vector<DecimalScaleParams> _decimal_scale_params;
size_t _decimal_scale_params_index;
+ std::set<uint64_t> _column_ids;
+ std::set<uint64_t> _filter_column_ids;
const std::unordered_map<std::string, ColumnValueRangeType>* _colname_to_value_range = nullptr;
bool _is_acid = false;
std::unique_ptr<IColumn::Filter> _filter;
diff --git a/be/src/vec/exec/format/parquet/schema_desc.cpp b/be/src/vec/exec/format/parquet/schema_desc.cpp
index 82e6e0c9d61..c022f1e3cfe 100644
--- a/be/src/vec/exec/format/parquet/schema_desc.cpp
+++ b/be/src/vec/exec/format/parquet/schema_desc.cpp
@@ -176,6 +176,7 @@ Status FieldDescriptor::parse_node_field(const std::vector<tparquet::SchemaEleme
parse_physical_field(t_schema, false, child);
node_field->name = t_schema.name;
+ node_field->lower_case_name = to_lower(t_schema.name);
node_field->data_type = std::make_shared<DataTypeArray>(make_nullable(child->data_type));
_next_schema_pos = curr_pos + 1;
node_field->field_id = t_schema.__isset.field_id ? t_schema.field_id : -1;
@@ -193,8 +194,10 @@ Status FieldDescriptor::parse_node_field(const std::vector<tparquet::SchemaEleme
void FieldDescriptor::parse_physical_field(const tparquet::SchemaElement& physical_schema,
bool is_nullable, FieldSchema* physical_field) {
physical_field->name = physical_schema.name;
+ physical_field->lower_case_name = to_lower(physical_field->name);
physical_field->parquet_schema = physical_schema;
physical_field->physical_type = physical_schema.type;
+ physical_field->column_id = UNASSIGNED_COLUMN_ID; // Initialize column_id
_physical_fields.push_back(physical_field);
physical_field->physical_column_index = cast_set<int>(_physical_fields.size() - 1);
auto type = get_doris_type(physical_schema, is_nullable);
@@ -393,6 +396,8 @@ Status FieldDescriptor::parse_group_field(const std::vector<tparquet::SchemaElem
RETURN_IF_ERROR(parse_struct_field(t_schemas, curr_pos, struct_field));
group_field->name = group_schema.name;
+ group_field->lower_case_name = to_lower(group_field->name);
+ group_field->column_id = UNASSIGNED_COLUMN_ID; // Initialize column_id
group_field->data_type =
std::make_shared<DataTypeArray>(make_nullable(struct_field->data_type));
group_field->field_id = group_schema.__isset.field_id ? group_schema.field_id : -1;
@@ -461,6 +466,8 @@ Status FieldDescriptor::parse_list_field(const std::vector<tparquet::SchemaEleme
}
list_field->name = first_level.name;
+ list_field->lower_case_name = to_lower(first_level.name);
+ list_field->column_id = UNASSIGNED_COLUMN_ID; // Initialize column_id
list_field->data_type =
std::make_shared<DataTypeArray>(make_nullable(list_field->children[0].data_type));
if (is_optional) {
@@ -519,21 +526,24 @@ Status FieldDescriptor::parse_map_field(const std::vector<tparquet::SchemaElemen
map_field->repetition_level++;
map_field->definition_level++;
- map_field->children.resize(1);
+ // Directly create key and value children instead of intermediate key_value node
+ map_field->children.resize(2);
// map is a repeated node, we should set the `repeated_parent_def_level` of its children as `definition_level`
set_child_node_level(map_field, map_field->definition_level);
- auto map_kv_field = &map_field->children[0];
- // produce MAP<STRUCT<KEY, VALUE>>
- RETURN_IF_ERROR(parse_struct_field(t_schemas, curr_pos + 1, map_kv_field));
+
+ auto key_field = &map_field->children[0];
+ auto value_field = &map_field->children[1];
+
+ // Parse key and value fields directly from the key_value group's children
+ _next_schema_pos = curr_pos + 2; // Skip key_value group, go directly to key
+ RETURN_IF_ERROR(parse_node_field(t_schemas, _next_schema_pos, key_field));
+ RETURN_IF_ERROR(parse_node_field(t_schemas, _next_schema_pos, value_field));
map_field->name = map_schema.name;
- map_field->data_type = std::make_shared<DataTypeMap>(
- make_nullable(assert_cast<const DataTypeStruct*>(
- remove_nullable(map_kv_field->data_type).get())
- ->get_element(0)),
- make_nullable(assert_cast<const DataTypeStruct*>(
- remove_nullable(map_kv_field->data_type).get())
- ->get_element(1)));
+ map_field->lower_case_name = to_lower(map_field->name);
+ map_field->column_id = UNASSIGNED_COLUMN_ID; // Initialize column_id
+ map_field->data_type = std::make_shared<DataTypeMap>(make_nullable(key_field->data_type),
+ make_nullable(value_field->data_type));
if (is_optional) {
map_field->data_type = make_nullable(map_field->data_type);
}
@@ -558,6 +568,8 @@ Status FieldDescriptor::parse_struct_field(const std::vector<tparquet::SchemaEle
RETURN_IF_ERROR(parse_node_field(t_schemas, _next_schema_pos, &struct_field->children[i]));
}
struct_field->name = struct_schema.name;
+ struct_field->lower_case_name = to_lower(struct_field->name);
+ struct_field->column_id = UNASSIGNED_COLUMN_ID; // Initialize column_id
struct_field->field_id = struct_schema.__isset.field_id ? struct_schema.field_id : -1;
DataTypes res_data_types;
@@ -610,6 +622,59 @@ std::string FieldDescriptor::debug_string() const {
ss << "]";
return ss.str();
}
+
+void FieldDescriptor::assign_ids() {
+ uint64_t next_id = 1;
+ for (auto& field : _fields) {
+ field.assign_ids(next_id);
+ }
+}
+
+const FieldSchema* FieldDescriptor::find_column_by_id(uint64_t column_id) const {
+ for (const auto& field : _fields) {
+ if (auto result = field.find_column_by_id(column_id)) {
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+void FieldSchema::assign_ids(uint64_t& next_id) {
+ column_id = next_id++;
+
+ for (auto& child : children) {
+ child.assign_ids(next_id);
+ }
+
+ max_column_id = next_id - 1;
+}
+
+const FieldSchema* FieldSchema::find_column_by_id(uint64_t target_id) const {
+ if (column_id == target_id) {
+ return this;
+ }
+
+ for (const auto& child : children) {
+ if (auto result = child.find_column_by_id(target_id)) {
+ return result;
+ }
+ }
+
+ return nullptr;
+}
+
+uint64_t FieldSchema::get_column_id() const {
+ return column_id;
+}
+
+void FieldSchema::set_column_id(uint64_t id) {
+ column_id = id;
+}
+
+uint64_t FieldSchema::get_max_column_id() const {
+ return max_column_id;
+}
+
#include "common/compile_check_end.h"
} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/parquet/schema_desc.h b/be/src/vec/exec/format/parquet/schema_desc.h
index 09a52a4251a..3ba38071406 100644
--- a/be/src/vec/exec/format/parquet/schema_desc.h
+++ b/be/src/vec/exec/format/parquet/schema_desc.h
@@ -36,8 +36,13 @@
namespace doris::vectorized {
#include "common/compile_check_begin.h"
+
+// Constant for unassigned column IDs
+constexpr uint64_t UNASSIGNED_COLUMN_ID = UINT64_MAX;
+
struct FieldSchema {
std::string name;
+ std::string lower_case_name; // for hms column name case insensitive match
// the referenced parquet schema element
tparquet::SchemaElement parquet_schema;
@@ -56,12 +61,22 @@ struct FieldSchema {
//For UInt8 -> Int16,UInt16 -> Int32,UInt32 -> Int64,UInt64 -> Int128.
bool is_type_compatibility = false;
- FieldSchema() : data_type(std::make_shared<DataTypeNothing>()) {}
+ FieldSchema()
+ : data_type(std::make_shared<DataTypeNothing>()), column_id(UNASSIGNED_COLUMN_ID) {}
~FieldSchema() = default;
FieldSchema(const FieldSchema& fieldSchema) = default;
std::string debug_string() const;
int32_t field_id = -1;
+ uint64_t column_id = UNASSIGNED_COLUMN_ID;
+ uint64_t max_column_id = 0; // Maximum column ID for this field and its children
+
+ // Column ID assignment and lookup methods
+ void assign_ids(uint64_t& next_id);
+ const FieldSchema* find_column_by_id(uint64_t target_id) const;
+ uint64_t get_column_id() const;
+ void set_column_id(uint64_t id);
+ uint64_t get_max_column_id() const;
};
class FieldDescriptor {
@@ -133,6 +148,19 @@ public:
int32_t size() const { return cast_set<int32_t>(_fields.size()); }
const std::vector<FieldSchema>& get_fields_schema() const { return _fields; }
+
+ /**
+ * Assign stable column IDs to schema fields.
+ *
+ * This uses an ORC-compatible encoding so that the results of
+ * create_column_ids() are consistent across formats. IDs start from 1
+ * and are assigned in a pre-order traversal (parent before children).
+ * After calling this, each FieldSchema will have column_id and
+ * max_column_id populated.
+ */
+ void assign_ids();
+
+ const FieldSchema* find_column_by_id(uint64_t column_id) const;
};
#include "common/compile_check_end.h"
diff --git a/be/src/vec/exec/format/parquet/vparquet_column_reader.cpp b/be/src/vec/exec/format/parquet/vparquet_column_reader.cpp
index 74fe4c3601b..a7455455198 100644
--- a/be/src/vec/exec/format/parquet/vparquet_column_reader.cpp
+++ b/be/src/vec/exec/format/parquet/vparquet_column_reader.cpp
@@ -108,45 +108,120 @@ Status ParquetColumnReader::create(io::FileReaderSPtr file, FieldSchema* field,
const std::vector<RowRange>& row_ranges,
const cctz::time_zone* ctz, io::IOContext* io_ctx,
std::unique_ptr<ParquetColumnReader>& reader,
- size_t max_buf_size, const tparquet::OffsetIndex* offset_index) {
+ size_t max_buf_size, const tparquet::OffsetIndex* offset_index,
+ const std::set<uint64_t>& column_ids,
+ const std::set<uint64_t>& filter_column_ids) {
+ // if (!column_ids.empty()) {
+ // uint64_t field_column_id = field->get_column_id();
+ // VLOG_DEBUG << "[ParquetReader] Checking column_id: " << field_column_id
+ // << ", column_ids.size(): " << column_ids.size();
+ // if (column_ids.find(field_column_id) == column_ids.end()) {
+ // VLOG_DEBUG << "[ParquetReader] Setting skip_reading=true for column_id: "
+ // << field_column_id;
+ // auto skip_reader = std::make_unique<SkipReadingReader>(row_ranges, ctz, io_ctx, field);
+ // skip_reader->_filter_column_ids = filter_column_ids;
+ // reader = std::move(skip_reader);
+ // return Status::OK();
+ // } else {
+ // VLOG_DEBUG << "[ParquetReader] Column_id " << field_column_id
+ // << " found in column_ids, skip_reading=false";
+ // }
+ // }
+
if (field->data_type->get_primitive_type() == TYPE_ARRAY) {
std::unique_ptr<ParquetColumnReader> element_reader;
RETURN_IF_ERROR(create(file, &field->children[0], row_group, row_ranges, ctz, io_ctx,
- element_reader, max_buf_size));
+ element_reader, max_buf_size, nullptr, column_ids,
+ filter_column_ids));
element_reader->set_nested_column();
auto array_reader = ArrayColumnReader::create_unique(row_ranges, ctz, io_ctx);
RETURN_IF_ERROR(array_reader->init(std::move(element_reader), field));
+ array_reader->_filter_column_ids = filter_column_ids;
reader.reset(array_reader.release());
} else if (field->data_type->get_primitive_type() == TYPE_MAP) {
std::unique_ptr<ParquetColumnReader> key_reader;
std::unique_ptr<ParquetColumnReader> value_reader;
- RETURN_IF_ERROR(create(file, &field->children[0].children[0], row_group, row_ranges, ctz,
- io_ctx, key_reader, max_buf_size));
- RETURN_IF_ERROR(create(file, &field->children[0].children[1], row_group, row_ranges, ctz,
- io_ctx, value_reader, max_buf_size));
- key_reader->set_nested_column();
- value_reader->set_nested_column();
+
+ if (column_ids.empty() ||
+ column_ids.find(field->children[0].get_column_id()) != column_ids.end()) {
+ // Create key reader
+ RETURN_IF_ERROR(create(file, &field->children[0], row_group, row_ranges, ctz, io_ctx,
+ key_reader, max_buf_size, nullptr, column_ids,
+ filter_column_ids));
+ key_reader->set_nested_column();
+ } else {
+ auto skip_reader = std::make_unique<SkipReadingReader>(row_ranges, ctz, io_ctx,
+ &field->children[0]);
+ key_reader = std::move(skip_reader);
+ }
+
+ if (column_ids.empty() ||
+ column_ids.find(field->children[1].get_column_id()) != column_ids.end()) {
+ // Create value reader
+ RETURN_IF_ERROR(create(file, &field->children[1], row_group, row_ranges, ctz, io_ctx,
+ value_reader, max_buf_size, nullptr, column_ids,
+ filter_column_ids));
+ value_reader->set_nested_column();
+ } else {
+ auto skip_reader = std::make_unique<SkipReadingReader>(row_ranges, ctz, io_ctx,
+ &field->children[0]);
+ value_reader = std::move(skip_reader);
+ }
+
auto map_reader = MapColumnReader::create_unique(row_ranges, ctz, io_ctx);
RETURN_IF_ERROR(map_reader->init(std::move(key_reader), std::move(value_reader), field));
+ map_reader->_filter_column_ids = filter_column_ids;
reader.reset(map_reader.release());
} else if (field->data_type->get_primitive_type() == TYPE_STRUCT) {
std::unordered_map<std::string, std::unique_ptr<ParquetColumnReader>> child_readers;
child_readers.reserve(field->children.size());
+ int non_skip_reader_idx = -1;
for (int i = 0; i < field->children.size(); ++i) {
+ auto& child = field->children[i]; // 去掉 const,保证 &child 类型为 FieldSchema*
+ std::unique_ptr<ParquetColumnReader> child_reader;
+ if (column_ids.empty() || column_ids.find(child.get_column_id()) != column_ids.end()) {
+ RETURN_IF_ERROR(create(file, &child, row_group, row_ranges, ctz, io_ctx,
+ child_reader, max_buf_size, nullptr, column_ids,
+ filter_column_ids));
+ child_reader->set_nested_column();
+ child_readers[child.name] = std::move(child_reader);
+ // 记录第一个非 SkippingReader
+ if (non_skip_reader_idx == -1) {
+ non_skip_reader_idx = i;
+ }
+ } else {
+ // 不在 column_ids 的 child,创建 SkippingReader
+ auto skip_reader =
+ std::make_unique<SkipReadingReader>(row_ranges, ctz, io_ctx, &child);
+ skip_reader->_filter_column_ids = filter_column_ids;
+ child_readers[child.name] = std::move(skip_reader);
+ }
+ }
+ // 如果所有都是 SkipReadingReader,则强制第一个 child 调用 create
+ if (non_skip_reader_idx == -1) {
+ // for (int i = 0; i < field->children.size(); ++i) {
+ // 只处理 SkipReadingReader(即刚才创建的)
+ // if (dynamic_cast<SkipReadingReader*>(child_readers[field->children[i].name].get()) != nullptr) {
std::unique_ptr<ParquetColumnReader> child_reader;
- RETURN_IF_ERROR(create(file, &field->children[i], row_group, row_ranges, ctz, io_ctx,
- child_reader, max_buf_size));
+ RETURN_IF_ERROR(create(file, &field->children[0], row_group, row_ranges, ctz, io_ctx,
+ child_reader, max_buf_size, nullptr, column_ids,
+ filter_column_ids));
child_reader->set_nested_column();
- child_readers[field->children[i].name] = std::move(child_reader);
+ child_readers[field->children[0].name] = std::move(child_reader);
+ // break;
+ // }
+ // }
}
auto struct_reader = StructColumnReader::create_unique(row_ranges, ctz, io_ctx);
RETURN_IF_ERROR(struct_reader->init(std::move(child_readers), field));
+ struct_reader->_filter_column_ids = filter_column_ids;
reader.reset(struct_reader.release());
} else {
const tparquet::ColumnChunk& chunk = row_group.columns[field->physical_column_index];
auto scalar_reader =
ScalarColumnReader::create_unique(row_ranges, chunk, offset_index, ctz, io_ctx);
RETURN_IF_ERROR(scalar_reader->init(file, field, max_buf_size));
+ scalar_reader->_filter_column_ids = filter_column_ids;
reader.reset(scalar_reader.release());
}
return Status::OK();
@@ -584,7 +659,8 @@ Status ScalarColumnReader::_try_load_dict_page(bool* loaded, bool* has_dict) {
Status ScalarColumnReader::read_column_data(
ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node, FilterMap& filter_map,
- size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter) {
+ size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter,
+ int64_t real_column_size) {
if (_converter == nullptr) {
_converter = parquet::PhysicalToLogicalConverter::get_converter(
_field_schema, _field_schema->data_type, type, _ctz, is_dict_filter);
@@ -640,7 +716,7 @@ Status ScalarColumnReader::read_column_data(
}
if (batch_size >= remaining_num_values &&
filter_map.can_filter_all(remaining_num_values, _filter_map_index)) {
- // We can skip the whole page if the remaining values is filtered by predicate columns
+ // We can skip the whole page if the remaining values are filtered by predicate columns
_filter_map_index += remaining_num_values;
_current_row_index += _chunk_reader->remaining_num_values();
RETURN_IF_ERROR(_chunk_reader->skip_page());
@@ -701,7 +777,8 @@ Status ArrayColumnReader::init(std::unique_ptr<ParquetColumnReader> element_read
Status ArrayColumnReader::read_column_data(
ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node, FilterMap& filter_map,
- size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter) {
+ size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter,
+ int64_t real_column_size) {
MutableColumnPtr data_column;
NullMap* null_map_ptr = nullptr;
if (doris_column->is_nullable()) {
@@ -753,7 +830,8 @@ Status MapColumnReader::init(std::unique_ptr<ParquetColumnReader> key_reader,
Status MapColumnReader::read_column_data(
ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node, FilterMap& filter_map,
- size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter) {
+ size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter,
+ int64_t real_column_size) {
MutableColumnPtr data_column;
NullMap* null_map_ptr = nullptr;
if (doris_column->is_nullable()) {
@@ -785,6 +863,7 @@ Status MapColumnReader::read_column_data(
size_t value_rows = 0;
bool key_eof = false;
bool value_eof = false;
+ int64_t orig_col_column_size = key_column->size();
RETURN_IF_ERROR(_key_reader->read_column_data(key_column, key_type, root_node->get_key_node(),
filter_map, batch_size, &key_rows, &key_eof,
@@ -794,11 +873,12 @@ Status MapColumnReader::read_column_data(
size_t loop_rows = 0;
RETURN_IF_ERROR(_value_reader->read_column_data(
value_column, value_type, root_node->get_value_node(), filter_map,
- key_rows - value_rows, &loop_rows, &value_eof, is_dict_filter));
+ key_rows - value_rows, &loop_rows, &value_eof, is_dict_filter,
+ key_column->size() - orig_col_column_size));
value_rows += loop_rows;
}
DCHECK_EQ(key_rows, value_rows);
- DCHECK_EQ(key_eof, value_eof);
+ // DCHECK_EQ(key_eof, value_eof);
*read_rows = key_rows;
*eof = key_eof;
@@ -826,7 +906,8 @@ Status StructColumnReader::init(
Status StructColumnReader::read_column_data(
ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node, FilterMap& filter_map,
- size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter) {
+ size_t batch_size, size_t* read_rows, bool* eof, bool is_dict_filter,
+ int64_t real_column_size) {
MutableColumnPtr data_column;
NullMap* null_map_ptr = nullptr;
if (doris_column->is_nullable()) {
@@ -850,7 +931,9 @@ Status StructColumnReader::read_column_data(
const auto* doris_struct_type = assert_cast<const DataTypeStruct*>(remove_nullable(type).get());
int64_t not_missing_column_id = -1;
+ size_t not_missing_orig_column_size = 0;
std::vector<size_t> missing_column_idxs {};
+ std::vector<size_t> skip_reading_column_idxs {};
_read_column_names.clear();
@@ -860,21 +943,48 @@ Status StructColumnReader::read_column_data(
auto& doris_name = doris_struct_type->get_element_name(i);
if (!root_node->children_column_exists(doris_name)) {
missing_column_idxs.push_back(i);
+ VLOG_DEBUG << "[ParquetReader] Missing column in schema: column_idx[" << i
+ << "], doris_name: " << doris_name << " (column not exists in root node)";
continue;
}
auto file_name = root_node->children_file_column_name(doris_name);
+ // if (_child_readers.find(file_name) == _child_readers.end()) {
+ // missing_column_idxs.push_back(i);
+ // continue;
+ // }
+
+ // Check if this is a SkipReadingReader - we should skip it when choosing reference column
+ // because SkipReadingReader doesn't know the actual data size in nested context
+ bool is_skip_reader =
+ dynamic_cast<SkipReadingReader*>(_child_readers[file_name].get()) != nullptr;
+
+ if (is_skip_reader) {
+ // Store SkipReadingReader columns to fill them later based on reference column size
+ skip_reading_column_idxs.push_back(i);
+ continue;
+ }
+ // Only add non-SkipReadingReader columns to _read_column_names
+ // This ensures get_rep_level() and get_def_level() return valid levels
_read_column_names.emplace_back(file_name);
size_t field_rows = 0;
bool field_eof = false;
if (not_missing_column_id == -1) {
not_missing_column_id = i;
+ not_missing_orig_column_size = doris_field->size();
RETURN_IF_ERROR(_child_readers[file_name]->read_column_data(
doris_field, doris_type, root_node->get_children_node(doris_name), filter_map,
batch_size, &field_rows, &field_eof, is_dict_filter));
*read_rows = field_rows;
*eof = field_eof;
+ // Debug: print the first non-missing child column read info
+ //std::cout << "[ParquetReader] struct '" << _field_schema->name << "' first non-missing child='"
+ // << file_name << "' doris_name='" << doris_name << "' field_rows=" << field_rows
+ // << " field_eof=" << field_eof << " doris_field_size=" << doris_field->size()
+ // << " child_rep_levels=" << _child_readers[file_name]->get_rep_level().size()
+ // << " child_def_levels=" << _child_readers[file_name]->get_def_level().size()
+ // << std::endl;
/*
* Considering the issue in the `_read_nested_column` function where data may span across pages, leading
* to missing definition and repetition levels, when filling the null_map of the struct later, it is
@@ -891,6 +1001,12 @@ Status StructColumnReader::read_column_data(
doris_field, doris_type, root_node->get_children_node(doris_name),
filter_map, *read_rows - field_rows, &loop_rows, &field_eof,
is_dict_filter));
+ // Debug: print each loop iteration for non-first child columns
+ //std::cout << "[ParquetReader] struct '" << _field_schema->name << "' non-first child='"
+ // << file_name << "' doris_name='" << doris_name << "' loop_rows=" << loop_rows
+ // << " field_rows=" << field_rows << " target_rows=" << *read_rows
+ // << " field_eof=" << field_eof << " doris_field_size=" << doris_field->size()
+ // << std::endl;
field_rows += loop_rows;
}
DCHECK_EQ(*read_rows, field_rows);
@@ -898,10 +1014,90 @@ Status StructColumnReader::read_column_data(
}
}
+ // After scanning children, print summary of missing/skip info
+ //std::cout << "[ParquetReader] struct '" << _field_schema->name << "' summary: not_missing_column_id="
+ // << not_missing_column_id << " missing_columns_count=" << missing_column_idxs.size()
+ // << " skip_reading_count=" << skip_reading_column_idxs.size() << std::endl;
+
+ int64_t missing_column_sz = -1;
+
if (not_missing_column_id == -1) {
- // TODO: support read struct which columns are all missing
- return Status::Corruption("Not support read struct '{}' which columns are all missing",
- _field_schema->name);
+ // All queried columns are missing in the file (e.g., all added after schema change)
+ // We need to pick a column from _field_schema that exists in the file to get RL/DL
+ //std::cout << "[ParquetReader] All queried columns missing for struct '" << _field_schema->name
+ // << "', searching for reference column in _field_schema" << std::endl;
+
+ // Find a column from _field_schema children that exists in the file for RL/DL reference
+ std::string reference_file_column_name;
+ std::unique_ptr<ParquetColumnReader>* reference_reader = nullptr;
+
+ for (const auto& child : _field_schema->children) {
+ auto it = _child_readers.find(child.name);
+ if (it != _child_readers.end()) {
+ // Skip SkipReadingReader as they don't have valid RL/DL
+ bool is_skip_reader = dynamic_cast<SkipReadingReader*>(it->second.get()) != nullptr;
+ if (!is_skip_reader) {
+ reference_file_column_name = child.name;
+ reference_reader = &(it->second);
+ //std::cout << "[ParquetReader] Found reference column: " << reference_file_column_name
+ // << " for struct '" << _field_schema->name << "'" << std::endl;
+ break;
+ }
+ }
+ }
+
+ if (reference_reader != nullptr) {
+ // Read the reference column to get correct RL/DL information
+ // We need to find the FieldSchema for the reference column from _field_schema children
+ FieldSchema* ref_field_schema = nullptr;
+ for (auto& child : _field_schema->children) {
+ if (child.name == reference_file_column_name) {
+ ref_field_schema = &child;
+ break;
+ }
+ }
+
+ if (ref_field_schema == nullptr) {
+ return Status::InternalError(
+ "Cannot find field schema for reference column '{}' in struct '{}'",
+ reference_file_column_name, _field_schema->name);
+ }
+
+ // Create a temporary column to hold the data (we'll use its size for missing_column_sz)
+ ColumnPtr temp_column = ref_field_schema->data_type->create_column();
+ auto temp_type = ref_field_schema->data_type;
+
+ size_t field_rows = 0;
+ bool field_eof = false;
+
+ // Use root_node to get the correct child node for the reference column
+ // reference_file_column_name is the file column name, use get_children_node_by_file_column_name
+ auto ref_child_node =
+ root_node->get_children_node_by_file_column_name(reference_file_column_name);
+ not_missing_orig_column_size = temp_column->size();
+
+ RETURN_IF_ERROR((*reference_reader)
+ ->read_column_data(temp_column, temp_type, ref_child_node,
+ filter_map, batch_size, &field_rows,
+ &field_eof, is_dict_filter));
+
+ *read_rows = field_rows;
+ *eof = field_eof;
+
+ // Store this reference column name for get_rep_level/get_def_level to use
+ _read_column_names.emplace_back(reference_file_column_name);
+
+ //std::cout << "[ParquetReader] Read " << field_rows << " rows from reference column '"
+ // << reference_file_column_name << "', temp_column.size=" << temp_column->size()
+ // << " for struct '" << _field_schema->name << "'" << std::endl;
+
+ missing_column_sz = temp_column->size() - not_missing_orig_column_size;
+ } else {
+ return Status::Corruption(
+ "Cannot read struct '{}': all queried columns are missing and no reference "
+ "column found in file",
+ _field_schema->name);
+ }
}
// This missing_column_sz is not *read_rows. Because read_rows returns the number of rows.
@@ -912,8 +1108,27 @@ Status StructColumnReader::read_column_data(
// [{4,null},{5,null}]
// When you first read subcolumn a, you read 5 data items and the value of *read_rows is 2.
// You should insert 5 records into subcolumn b instead of 2.
- auto missing_column_sz = doris_struct.get_column(not_missing_column_id).size();
- // fill missing column with null or default value
+ if (missing_column_sz == -1) {
+ missing_column_sz = doris_struct.get_column(not_missing_column_id).size() -
+ not_missing_orig_column_size;
+ }
+
+ // Fill SkipReadingReader columns with the correct amount of data based on reference column
+ // Let SkipReadingReader handle the data filling through its read_column_data method
+ for (auto idx : skip_reading_column_idxs) {
+ auto& doris_field = doris_struct.get_column_ptr(idx);
+ auto& doris_type = const_cast<DataTypePtr&>(doris_struct_type->get_element(idx));
+ auto& doris_name = const_cast<String&>(doris_struct_type->get_element_name(idx));
+ auto file_name = root_node->children_file_column_name(doris_name);
+
+ size_t field_rows = 0;
+ bool field_eof = false;
+ RETURN_IF_ERROR(_child_readers[file_name]->read_column_data(
+ doris_field, doris_type, root_node->get_children_node(doris_name), filter_map,
+ missing_column_sz, &field_rows, &field_eof, is_dict_filter, missing_column_sz));
+ }
+
+ // Fill truly missing columns (not in root_node) with null or default value
for (auto idx : missing_column_idxs) {
auto& doris_field = doris_struct.get_column_ptr(idx);
auto& doris_type = doris_struct_type->get_element(idx);
@@ -924,10 +1139,11 @@ Status StructColumnReader::read_column_data(
}
if (null_map_ptr != nullptr) {
+ //std::cout << "[ParquetReader] struct '" << _field_schema->name << "' before fill_struct_null_map: rep_levels="
+ // << this->get_rep_level().size() << " def_levels=" << this->get_def_level().size() << std::endl;
fill_struct_null_map(_field_schema, *null_map_ptr, this->get_rep_level(),
this->get_def_level());
}
-
return Status::OK();
}
#include "common/compile_check_end.h"
diff --git a/be/src/vec/exec/format/parquet/vparquet_column_reader.h b/be/src/vec/exec/format/parquet/vparquet_column_reader.h
index f0d02b0d3e4..7b37f2e0e6c 100644
--- a/be/src/vec/exec/format/parquet/vparquet_column_reader.h
+++ b/be/src/vec/exec/format/parquet/vparquet_column_reader.h
@@ -123,7 +123,8 @@ public:
virtual Status read_column_data(ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node,
FilterMap& filter_map, size_t batch_size, size_t* read_rows,
- bool* eof, bool is_dict_filter) = 0;
+ bool* eof, bool is_dict_filter,
+ int64_t real_column_size = -1) = 0;
virtual Status read_dict_values_to_column(MutableColumnPtr& doris_column, bool* has_dict) {
return Status::NotSupported("read_dict_values_to_column is not supported");
@@ -138,7 +139,9 @@ public:
const tparquet::RowGroup& row_group,
const std::vector<RowRange>& row_ranges, const cctz::time_zone* ctz,
io::IOContext* io_ctx, std::unique_ptr<ParquetColumnReader>& reader,
- size_t max_buf_size, const tparquet::OffsetIndex* offset_index = nullptr);
+ size_t max_buf_size, const tparquet::OffsetIndex* offset_index = nullptr,
+ const std::set<uint64_t>& column_ids = {},
+ const std::set<uint64_t>& filter_column_ids = {});
void set_nested_column() { _nested_column = true; }
virtual const std::vector<level_t>& get_rep_level() const = 0;
virtual const std::vector<level_t>& get_def_level() const = 0;
@@ -147,6 +150,8 @@ public:
virtual void reset_filter_map_index() = 0;
+ FieldSchema* get_field_schema() const { return _field_schema; }
+
protected:
void _generate_read_ranges(int64_t start_index, int64_t end_index,
std::list<RowRange>& read_ranges);
@@ -162,6 +167,7 @@ protected:
int64_t _decode_null_map_time = 0;
size_t _filter_map_index = 0;
+ std::set<uint64_t> _filter_column_ids;
};
class ScalarColumnReader : public ParquetColumnReader {
@@ -179,7 +185,7 @@ public:
Status read_column_data(ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node,
FilterMap& filter_map, size_t batch_size, size_t* read_rows, bool* eof,
- bool is_dict_filter) override;
+ bool is_dict_filter, int64_t real_column_size = -1) override;
Status read_dict_values_to_column(MutableColumnPtr& doris_column, bool* has_dict) override;
MutableColumnPtr convert_dict_column_to_string_column(const ColumnInt32* dict_column) override;
const std::vector<level_t>& get_rep_level() const override { return _rep_levels; }
@@ -226,7 +232,7 @@ public:
Status read_column_data(ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node,
FilterMap& filter_map, size_t batch_size, size_t* read_rows, bool* eof,
- bool is_dict_filter) override;
+ bool is_dict_filter, int64_t real_column_size = -1) override;
const std::vector<level_t>& get_rep_level() const override {
return _element_reader->get_rep_level();
}
@@ -255,7 +261,7 @@ public:
Status read_column_data(ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node,
FilterMap& filter_map, size_t batch_size, size_t* read_rows, bool* eof,
- bool is_dict_filter) override;
+ bool is_dict_filter, int64_t real_column_size = -1) override;
const std::vector<level_t>& get_rep_level() const override {
return _key_reader->get_rep_level();
@@ -297,7 +303,7 @@ public:
Status read_column_data(ColumnPtr& doris_column, const DataTypePtr& type,
const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node,
FilterMap& filter_map, size_t batch_size, size_t* read_rows, bool* eof,
- bool is_dict_filter) override;
+ bool is_dict_filter, int64_t real_column_size = -1) override;
const std::vector<level_t>& get_rep_level() const override {
if (!_read_column_names.empty()) {
@@ -346,6 +352,93 @@ private:
std::vector<std::string> _read_column_names;
//Need to use vector instead of set,see `get_rep_level()` for the reason.
};
+
+// A special reader that skips actual reading but provides empty data with correct structure
+// This is used when a column is not needed but its structure is required (e.g., for map keys)
+class SkipReadingReader : public ParquetColumnReader {
+public:
+ SkipReadingReader(const std::vector<RowRange>& row_ranges, const cctz::time_zone* ctz,
+ io::IOContext* io_ctx, FieldSchema* field_schema)
+ : ParquetColumnReader(row_ranges, ctz, io_ctx) {
+ _field_schema = field_schema; // Use inherited member from base class
+ VLOG_DEBUG << "[ParquetReader] Created SkipReadingReader for field: "
+ << _field_schema->name;
+ }
+
+ Status read_column_data(ColumnPtr& doris_column, const DataTypePtr& type,
+ const std::shared_ptr<TableSchemaChangeHelper::Node>& root_node,
+ FilterMap& filter_map, size_t batch_size, size_t* read_rows, bool* eof,
+ bool is_dict_filter, int64_t real_column_size = -1) override {
+ VLOG_DEBUG << "[ParquetReader] SkipReadingReader::read_column_data for field: "
+ << _field_schema->name << ", batch_size: " << batch_size;
+ DCHECK(real_column_size >= 0); // real_column_size for filtered column size.
+
+ // Simulate reading without actually reading data
+ // Fill with default/null values based on column type
+ MutableColumnPtr data_column = doris_column->assume_mutable();
+
+ if (real_column_size > 0) {
+ if (doris_column->is_nullable()) {
+ auto* nullable_column = static_cast<vectorized::ColumnNullable*>(data_column.get());
+ nullable_column->insert_many_defaults(real_column_size);
+ } else {
+ // For non-nullable columns, insert appropriate default values
+ for (size_t i = 0; i < real_column_size; ++i) {
+ data_column->insert_default();
+ }
+ }
+ }
+
+ *read_rows = batch_size; // Indicate we "read" batch_size rows
+ *eof = false; // We can always provide more empty data
+
+ VLOG_DEBUG << "[ParquetReader] SkipReadingReader generated " << batch_size
+ << " default values for field: " << _field_schema->name;
+
+ return Status::OK();
+ }
+
+ static std::unique_ptr<SkipReadingReader> create_unique(const std::vector<RowRange>& row_ranges,
+ cctz::time_zone* ctz,
+ io::IOContext* io_ctx,
+ FieldSchema* field_schema) {
+ return std::make_unique<SkipReadingReader>(row_ranges, ctz, io_ctx, field_schema);
+ }
+
+ // These methods should not be called for SkipReadingReader
+ // If they are called, it indicates a logic error in the code
+ const std::vector<level_t>& get_rep_level() const override {
+ LOG(FATAL) << "get_rep_level() should not be called on SkipReadingReader for field: "
+ << _field_schema->name
+ << ". This indicates the SkipReadingReader was incorrectly used as a reference "
+ "column.";
+ // static std::vector<level_t> empty_levels;
+ // return empty_levels;
+ __builtin_unreachable();
+ }
+
+ const std::vector<level_t>& get_def_level() const override {
+ LOG(FATAL) << "get_def_level() should not be called on SkipReadingReader for field: "
+ << _field_schema->name
+ << ". This indicates the SkipReadingReader was incorrectly used as a reference "
+ "column.";
+ // static std::vector<level_t> empty_levels;
+ // return empty_levels;
+ __builtin_unreachable();
+ }
+
+ // Implement required pure virtual methods from base class
+ Statistics statistics() override {
+ return Statistics(); // Return empty statistics
+ }
+
+ void close() override {
+ // Nothing to close for skip reading
+ }
+
+ void reset_filter_map_index() override { _filter_map_index = 0; }
+};
+
#include "common/compile_check_end.h"
}; // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/parquet/vparquet_group_reader.cpp b/be/src/vec/exec/format/parquet/vparquet_group_reader.cpp
index 7d1f9d27430..19ddb8d1b99 100644
--- a/be/src/vec/exec/format/parquet/vparquet_group_reader.cpp
+++ b/be/src/vec/exec/format/parquet/vparquet_group_reader.cpp
@@ -25,6 +25,7 @@
#include <algorithm>
#include <boost/iterator/iterator_facade.hpp>
+#include <iostream>
#include <ostream>
#include "common/config.h"
@@ -82,7 +83,9 @@ RowGroupReader::RowGroupReader(io::FileReaderSPtr file_reader,
const int32_t row_group_id, const tparquet::RowGroup& row_group,
const cctz::time_zone* ctz, io::IOContext* io_ctx,
const PositionDeleteContext& position_delete_ctx,
- const LazyReadContext& lazy_read_ctx, RuntimeState* state)
+ const LazyReadContext& lazy_read_ctx, RuntimeState* state,
+ const std::set<uint64_t>& column_ids,
+ const std::set<uint64_t>& filter_column_ids)
: _file_reader(file_reader),
_read_table_columns(read_columns),
_row_group_id(row_group_id),
@@ -93,7 +96,15 @@ RowGroupReader::RowGroupReader(io::FileReaderSPtr file_reader,
_position_delete_ctx(position_delete_ctx),
_lazy_read_ctx(lazy_read_ctx),
_state(state),
- _obj_pool(new ObjectPool()) {}
+ _obj_pool(new ObjectPool()),
+ _column_ids(column_ids),
+ _filter_column_ids(filter_column_ids) {
+ //std::cout << "[RowGroupReader] _column_ids: ";
+ //for (const auto& col_id : _column_ids) {
+ // std::cout << col_id << " ";
+ //}
+ //std::cout << std::endl;
+}
RowGroupReader::~RowGroupReader() {
_column_readers.clear();
@@ -130,9 +141,9 @@ Status RowGroupReader::init(
const tparquet::OffsetIndex* offset_index =
col_offsets.find(physical_index) != col_offsets.end() ? &col_offsets[physical_index]
: nullptr;
- RETURN_IF_ERROR(ParquetColumnReader::create(_file_reader, field, _row_group_meta,
- _read_ranges, _ctz, _io_ctx, reader,
- max_buf_size, offset_index));
+ RETURN_IF_ERROR(ParquetColumnReader::create(
+ _file_reader, field, _row_group_meta, _read_ranges, _ctz, _io_ctx, reader,
+ max_buf_size, offset_index, _column_ids, _filter_column_ids));
if (reader == nullptr) {
VLOG_DEBUG << "Init row group(" << _row_group_id << ") reader failed";
return Status::Corruption("Init row group reader failed");
@@ -320,8 +331,8 @@ Status RowGroupReader::next_batch(Block* block, size_t batch_size, size_t* read_
return _do_lazy_read(block, batch_size, read_rows, batch_eof);
} else {
FilterMap filter_map;
- RETURN_IF_ERROR(_read_column_data(block, _lazy_read_ctx.all_read_columns, batch_size,
- read_rows, batch_eof, filter_map));
+ RETURN_IF_ERROR((_read_column_data(block, _lazy_read_ctx.all_read_columns, batch_size,
+ read_rows, batch_eof, filter_map)));
RETURN_IF_ERROR(
_fill_partition_columns(block, *read_rows, _lazy_read_ctx.partition_columns));
RETURN_IF_ERROR(_fill_missing_columns(block, *read_rows, _lazy_read_ctx.missing_columns));
@@ -427,9 +438,19 @@ Status RowGroupReader::_read_column_data(Block* block,
RETURN_IF_ERROR(_column_readers[read_col_name]->read_column_data(
column_ptr, column_type, _table_info_node_ptr->get_children_node(read_col_name),
filter_map, batch_size - col_read_rows, &loop_rows, &col_eof, is_dict_filter));
+ // Debug: print loop_rows for this iteration
+ //std::cout << "[RowGroupReader] column '" << read_col_name << "' loop_rows=" << loop_rows
+ // << " col_read_rows_so_far=" << col_read_rows << std::endl;
col_read_rows += loop_rows;
}
+ // Debug: print rows read for this column
+ //std::cout << "[RowGroupReader] column '" << read_col_name << "' read_rows=" << col_read_rows
+ // << std::endl;
if (batch_read_rows > 0 && batch_read_rows != col_read_rows) {
+ LOG(WARNING) << "[RowGroupReader] Mismatched read rows among parquet columns. "
+ "previous_batch_read_rows="
+ << batch_read_rows << ", current_column='" << read_col_name
+ << "', current_col_read_rows=" << col_read_rows;
return Status::Corruption("Can't read the same number of rows among parquet columns");
}
batch_read_rows = col_read_rows;
@@ -720,6 +741,10 @@ Status RowGroupReader::_fill_missing_columns(
block->erase(result_column_id);
}
}
+
+ // auto mutable_column = block->get_by_name(kv.first).column->assume_mutable();
+ // auto* nullable_column = assert_cast<vectorized::ColumnNullable*>(mutable_column.get());
+ // nullable_column->insert_many_defaults(rows);
}
return Status::OK();
}
@@ -1110,6 +1135,7 @@ ParquetColumnReader::Statistics RowGroupReader::statistics() {
}
return st;
}
+
#include "common/compile_check_end.h"
} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/parquet/vparquet_group_reader.h b/be/src/vec/exec/format/parquet/vparquet_group_reader.h
index 86b7cdf3f0e..5853cd6827d 100644
--- a/be/src/vec/exec/format/parquet/vparquet_group_reader.h
+++ b/be/src/vec/exec/format/parquet/vparquet_group_reader.h
@@ -150,7 +150,9 @@ public:
const int32_t row_group_id, const tparquet::RowGroup& row_group,
const cctz::time_zone* ctz, io::IOContext* io_ctx,
const PositionDeleteContext& position_delete_ctx,
- const LazyReadContext& lazy_read_ctx, RuntimeState* state);
+ const LazyReadContext& lazy_read_ctx, RuntimeState* state,
+ const std::set<uint64_t>& column_ids,
+ const std::set<uint64_t>& filter_column_ids);
~RowGroupReader();
Status init(const FieldDescriptor& schema, std::vector<RowRange>& row_ranges,
@@ -188,9 +190,11 @@ private:
void _merge_read_ranges(std::vector<RowRange>& row_ranges);
Status _read_empty_batch(size_t batch_size, size_t* read_rows, bool* batch_eof,
bool* modify_row_ids);
+
Status _read_column_data(Block* block, const std::vector<std::string>& columns,
size_t batch_size, size_t* read_rows, bool* batch_eof,
FilterMap& filter_map);
+
Status _do_lazy_read(Block* block, size_t batch_size, size_t* read_rows, bool* batch_eof);
Status _rebuild_filter_map(FilterMap& filter_map,
DorisUniqueBufferPtr<uint8_t>& filter_map_data,
@@ -249,6 +253,8 @@ private:
std::vector<std::pair<std::string, int>> _dict_filter_cols;
RuntimeState* _state = nullptr;
std::shared_ptr<ObjectPool> _obj_pool;
+ const std::set<uint64_t>& _column_ids;
+ const std::set<uint64_t>& _filter_column_ids;
bool _is_row_group_filtered = false;
RowGroupIndex _current_row_group_idx {0, 0, 0};
diff --git a/be/src/vec/exec/format/parquet/vparquet_reader.cpp b/be/src/vec/exec/format/parquet/vparquet_reader.cpp
index 5accadf77f7..e47a0eddaac 100644
--- a/be/src/vec/exec/format/parquet/vparquet_reader.cpp
+++ b/be/src/vec/exec/format/parquet/vparquet_reader.cpp
@@ -319,7 +319,8 @@ Status ParquetReader::init_reader(
const std::unordered_map<std::string, int>* colname_to_slot_id,
const VExprContextSPtrs* not_single_slot_filter_conjuncts,
const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts,
- std::shared_ptr<TableSchemaChangeHelper::Node> table_info_node_ptr, bool filter_groups) {
+ std::shared_ptr<TableSchemaChangeHelper::Node> table_info_node_ptr, bool filter_groups,
+ const std::set<uint64_t>& column_ids, const std::set<uint64_t>& filter_column_ids) {
_tuple_descriptor = tuple_descriptor;
_row_descriptor = row_descriptor;
_colname_to_slot_id = colname_to_slot_id;
@@ -328,6 +329,8 @@ Status ParquetReader::init_reader(
_colname_to_value_range = colname_to_value_range;
_table_info_node_ptr = table_info_node_ptr;
_filter_groups = filter_groups;
+ _column_ids = column_ids;
+ _filter_column_ids = filter_column_ids;
RETURN_IF_ERROR(_open_file());
_t_metadata = &(_file_metadata->to_thrift());
@@ -706,12 +709,12 @@ Status ParquetReader::_next_row_group_reader() {
_profile, _file_reader, io_ranges, merged_read_slice_size)
: _file_reader;
}
- _current_group_reader.reset(
- new RowGroupReader(_io_ctx ? std::make_shared<io::TracingFileReader>(
- group_file_reader, _io_ctx->file_reader_stats)
- : group_file_reader,
- _read_table_columns, row_group_index.row_group_id, row_group, _ctz,
- _io_ctx, position_delete_ctx, _lazy_read_ctx, _state));
+ _current_group_reader.reset(new RowGroupReader(
+ _io_ctx ? std::make_shared<io::TracingFileReader>(group_file_reader,
+ _io_ctx->file_reader_stats)
+ : group_file_reader,
+ _read_table_columns, row_group_index.row_group_id, row_group, _ctz, _io_ctx,
+ position_delete_ctx, _lazy_read_ctx, _state, _column_ids, _filter_column_ids));
_row_group_eof = false;
_current_group_reader->set_current_row_group_idx(row_group_index);
@@ -796,8 +799,8 @@ std::vector<io::PrefetchRange> ParquetReader::_generate_random_access_ranges(
if (field->data_type->get_primitive_type() == TYPE_ARRAY) {
scalar_range(&field->children[0], row_group);
} else if (field->data_type->get_primitive_type() == TYPE_MAP) {
- scalar_range(&field->children[0].children[0], row_group);
- scalar_range(&field->children[0].children[1], row_group);
+ scalar_range(&field->children[0], row_group);
+ scalar_range(&field->children[1], row_group);
} else if (field->data_type->get_primitive_type() == TYPE_STRUCT) {
for (int i = 0; i < field->children.size(); ++i) {
scalar_range(&field->children[i], row_group);
diff --git a/be/src/vec/exec/format/parquet/vparquet_reader.h b/be/src/vec/exec/format/parquet/vparquet_reader.h
index fecc433dba6..689c45a37d8 100644
--- a/be/src/vec/exec/format/parquet/vparquet_reader.h
+++ b/be/src/vec/exec/format/parquet/vparquet_reader.h
@@ -123,7 +123,8 @@ public:
const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts,
std::shared_ptr<TableSchemaChangeHelper::Node> table_info_node_ptr =
TableSchemaChangeHelper::ConstNode::get_instance(),
- bool filter_groups = true);
+ bool filter_groups = true, const std::set<uint64_t>& column_ids = {},
+ const std::set<uint64_t>& filter_column_ids = {});
Status get_next_block(Block* block, size_t* read_rows, bool* eof) override;
@@ -316,6 +317,8 @@ private:
-1};
bool _filter_groups;
+ std::set<uint64_t> _column_ids;
+ std::set<uint64_t> _filter_column_ids;
// for page index filter. slot id => expr
std::map<int, std::vector<std::unique_ptr<ColumnPredicate>>> _push_down_simple_predicates;
diff --git a/be/src/vec/exec/format/table/hive/hive_orc_nested_column_utils.cpp b/be/src/vec/exec/format/table/hive/hive_orc_nested_column_utils.cpp
new file mode 100644
index 00000000000..6e9fa381fc7
--- /dev/null
+++ b/be/src/vec/exec/format/table/hive/hive_orc_nested_column_utils.cpp
@@ -0,0 +1,148 @@
+// 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 "vec/exec/format/table/hive/hive_orc_nested_column_utils.h"
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "common/logging.h"
+#include "orc/Type.hh"
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+void HiveOrcNestedColumnUtils::extract_nested_column_ids(
+ const orc::Type& type, const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids) {
+ std::unordered_map<std::string, std::vector<std::vector<std::string>>>
+ child_paths_by_table_col_name;
+ for (const auto& path : paths) {
+ if (!path.empty()) {
+ std::string first_table_col_name = path[0];
+ std::vector<std::string> remaining;
+ if (path.size() > 1) {
+ remaining.assign(path.begin() + 1, path.end());
+ }
+ child_paths_by_table_col_name[first_table_col_name].push_back(std::move(remaining));
+ }
+ }
+
+ // Track whether any child column was added to determine if parent should be included
+ bool has_child_columns = false;
+
+ // For MAP type, normalize wildcard "*" to explicit KEYS/VALUES access
+ // Wildcard in MAP context means accessing both map keys and values
+ // Normalization logic:
+ // path: ["map_col", "*"] → ["map_col", "VALUES"] + ["map_col", "KEYS"]
+ // path: ["map_col", "*", "field"] → ["map_col", "VALUES", "field"] + ["map_col", "KEYS"]
+ // KEYS are always needed for correct RL/DL computation when accessing MAP via wildcard
+ if (type.getKind() == orc::TypeKind::MAP) {
+ auto wildcard_it = child_paths_by_table_col_name.find("*");
+ if (wildcard_it != child_paths_by_table_col_name.end()) {
+ auto& wildcard_paths = wildcard_it->second;
+
+ // All wildcard paths go to VALUES
+ auto& values_paths = child_paths_by_table_col_name["VALUES"];
+ values_paths.insert(values_paths.end(), wildcard_paths.begin(), wildcard_paths.end());
+
+ // Always add KEYS for wildcard access (needed for RL/DL computation)
+ auto& keys_paths = child_paths_by_table_col_name["KEYS"];
+ // Add an empty path to request full KEYS
+ std::vector<std::string> empty_path;
+ keys_paths.push_back(empty_path);
+
+ // Remove wildcard entry as it's been expanded
+ child_paths_by_table_col_name.erase(wildcard_it);
+ }
+ }
+
+ for (uint64_t i = 0; i < type.getSubtypeCount(); ++i) {
+ const orc::Type* child = type.getSubtype(i);
+
+ std::string child_field_name;
+ switch (type.getKind()) {
+ case orc::TypeKind::STRUCT:
+ child_field_name = to_lower(type.getFieldName(i));
+ break;
+ case orc::TypeKind::LIST:
+ child_field_name = "*";
+ break;
+ case orc::TypeKind::MAP:
+ // After wildcard normalization above, all MAP accesses are explicit KEYS/VALUES
+ // Simply assign the appropriate field name based on which child we're processing
+ if (i == 0) {
+ child_field_name = "KEYS";
+ } else if (i == 1) {
+ child_field_name = "VALUES";
+ }
+ break;
+ default:
+ child_field_name = "";
+ break;
+ }
+
+ if (child_field_name.empty()) {
+ continue;
+ }
+
+ const auto child_paths_it = child_paths_by_table_col_name.find(child_field_name);
+
+ if (child_paths_it != child_paths_by_table_col_name.end()) {
+ const auto& child_paths = child_paths_it->second;
+
+ // Check if any child path is empty (meaning full child needed)
+ bool needs_full_child =
+ std::any_of(child_paths.begin(), child_paths.end(),
+ [](const std::vector<std::string>& path) { return path.empty(); });
+
+ if (needs_full_child) {
+ uint64_t start_id = child->getColumnId();
+ uint64_t max_column_id = child->getMaximumColumnId();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ column_ids.insert(id);
+ }
+ has_child_columns = true;
+ } else {
+ // Store current size to check if recursive call added any columns
+ size_t before_size = column_ids.size();
+
+ // Recursively extract from child
+ extract_nested_column_ids(*child, child_paths, column_ids);
+
+ // Check if recursive call added any columns
+ if (column_ids.size() > before_size) {
+ has_child_columns = true;
+ };
+ }
+ }
+ }
+
+ // If any child columns were added, also add the parent column ID
+ // This ensures parent struct/container nodes are included when their children are needed
+ if (has_child_columns) {
+ // Set automatically handles deduplication, so no need to check if it already exists
+ column_ids.insert(type.getColumnId());
+ }
+}
+
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/hive/hive_orc_nested_column_utils.h b/be/src/vec/exec/format/table/hive/hive_orc_nested_column_utils.h
new file mode 100644
index 00000000000..1e1968c09b9
--- /dev/null
+++ b/be/src/vec/exec/format/table/hive/hive_orc_nested_column_utils.h
@@ -0,0 +1,43 @@
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace orc {
+class Type;
+} // namespace orc
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+class HiveOrcNestedColumnUtils {
+public:
+ static void extract_nested_column_ids(const orc::Type& type,
+ const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids);
+};
+
+#include "common/compile_check_end.h"
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/hive/hive_parquet_nested_column_utils.cpp b/be/src/vec/exec/format/table/hive/hive_parquet_nested_column_utils.cpp
new file mode 100644
index 00000000000..a0052ecfbfc
--- /dev/null
+++ b/be/src/vec/exec/format/table/hive/hive_parquet_nested_column_utils.cpp
@@ -0,0 +1,172 @@
+// 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 "vec/exec/format/table/hive/hive_parquet_nested_column_utils.h"
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "vec/exec/format/parquet/schema_desc.h"
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+void HiveParquetNestedColumnUtils::extract_nested_column_ids(
+ const FieldSchema& field_schema, const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids) {
+ // Group paths by first field_id
+ std::unordered_map<std::string, std::vector<std::vector<std::string>>>
+ child_paths_by_table_col_name;
+
+ for (const auto& path : paths) {
+ if (!path.empty()) {
+ std::string first_table_col_name = path[0];
+ std::vector<std::string> remaining;
+ if (path.size() > 1) {
+ remaining.assign(path.begin() + 1, path.end());
+ }
+ child_paths_by_table_col_name[first_table_col_name].push_back(std::move(remaining));
+ }
+ }
+
+ // Track whether any child column was added to determine if parent should be included
+ bool has_child_columns = false;
+
+ // For MAP type, normalize wildcard "*" to explicit KEYS/VALUES access
+ // Wildcard in MAP context means accessing both map keys and values
+ // Normalization logic:
+ // path: ["map_col", "*"] → ["map_col", "VALUES"] + ["map_col", "KEYS"]
+ // path: ["map_col", "*", "field"] → ["map_col", "VALUES", "field"] + ["map_col", "KEYS"]
+ // KEYS are always needed for correct RL/DL computation when accessing MAP via wildcard
+ if (field_schema.data_type->get_primitive_type() == PrimitiveType::TYPE_MAP) {
+ auto wildcard_it = child_paths_by_table_col_name.find("*");
+ if (wildcard_it != child_paths_by_table_col_name.end()) {
+ auto& wildcard_paths = wildcard_it->second;
+
+ // All wildcard paths go to VALUES
+ auto& values_paths = child_paths_by_table_col_name["VALUES"];
+ values_paths.insert(values_paths.end(), wildcard_paths.begin(), wildcard_paths.end());
+
+ // Always add KEYS for wildcard access (needed for RL/DL computation)
+ auto& keys_paths = child_paths_by_table_col_name["KEYS"];
+ // Add an empty path to request full KEYS
+ std::vector<std::string> empty_path;
+ keys_paths.push_back(empty_path);
+
+ // Remove wildcard entry as it's been expanded
+ child_paths_by_table_col_name.erase(wildcard_it);
+ }
+ }
+
+ // Efficiently traverse children
+ for (uint64_t i = 0; i < field_schema.children.size(); ++i) {
+ const auto& child = field_schema.children[i];
+ std::string child_field_name;
+
+ bool is_list = field_schema.data_type->get_primitive_type() == PrimitiveType::TYPE_ARRAY;
+ bool is_map = field_schema.data_type->get_primitive_type() == PrimitiveType::TYPE_MAP;
+
+ if (is_list) {
+ child_field_name = "*";
+ } else if (is_map) {
+ // After wildcard normalization above, all MAP accesses are explicit KEYS/VALUES
+ // Simply assign the appropriate field name based on which child we're processing
+ if (i == 0) {
+ child_field_name = "KEYS";
+ } else if (i == 1) {
+ child_field_name = "VALUES";
+ }
+
+ // Special handling for Parquet MAP structure:
+ // When accessing only VALUES, we still need KEY structure for levels
+ // Check if we're at key child (i==0) and only VALUES is requested (no KEYS)
+ if (i == 0) {
+ bool has_keys_access = child_paths_by_table_col_name.find("KEYS") !=
+ child_paths_by_table_col_name.end();
+ bool has_values_access = child_paths_by_table_col_name.find("VALUES") !=
+ child_paths_by_table_col_name.end();
+
+ // If only VALUES is accessed (not KEYS), still include key structure for RL/DL
+ if (!has_keys_access && has_values_access) {
+ // For map_values() queries, we need key's structure for correct RL/DL parsing.
+ // If key is a nested type (e.g., STRUCT), RL/DL info is stored at leaf columns.
+ // Add all column IDs from key's start to max (all leaves + intermediate nodes).
+ uint64_t key_start_id = child.get_column_id();
+ uint64_t key_max_id = child.get_max_column_id();
+ for (uint64_t id = key_start_id; id <= key_max_id; ++id) {
+ column_ids.insert(id);
+ }
+ has_child_columns = true;
+ continue; // Skip further processing of key child
+ }
+ }
+
+ } else {
+ child_field_name = child.lower_case_name;
+ }
+
+ if (child_field_name.empty()) {
+ continue;
+ }
+
+ auto child_paths_it = child_paths_by_table_col_name.find(child_field_name);
+ if (child_paths_it != child_paths_by_table_col_name.end()) {
+ const auto& child_paths = child_paths_it->second;
+
+ // Check if any child path is empty (meaning full child needed)
+ bool needs_full_child =
+ std::any_of(child_paths.begin(), child_paths.end(),
+ [](const std::vector<std::string>& path) { return path.empty(); });
+
+ if (needs_full_child) {
+ // Add all column IDs from current child node to max_column_id
+ // This efficiently handles all nested/complex cases in one loop
+ uint64_t start_id = child.get_column_id();
+ uint64_t max_column_id = child.get_max_column_id();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ column_ids.insert(id);
+ }
+ has_child_columns = true;
+ } else {
+ // Store current size to check if recursive call added any columns
+ size_t before_size = column_ids.size();
+
+ // Recursively extract from child
+ extract_nested_column_ids(child, child_paths, column_ids);
+
+ // Check if recursive call added any columns
+ if (column_ids.size() > before_size) {
+ has_child_columns = true;
+ }
+ }
+ }
+ }
+
+ // If any child columns were added, also add the parent column ID
+ // This ensures parent struct/container nodes are included when their children are needed
+ if (has_child_columns) {
+ // Set automatically handles deduplication, so no need to check if it already exists
+ column_ids.insert(field_schema.get_column_id());
+ }
+}
+
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/hive/hive_parquet_nested_column_utils.h b/be/src/vec/exec/format/table/hive/hive_parquet_nested_column_utils.h
new file mode 100644
index 00000000000..6b57a0a3a38
--- /dev/null
+++ b/be/src/vec/exec/format/table/hive/hive_parquet_nested_column_utils.h
@@ -0,0 +1,41 @@
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+struct FieldSchema;
+
+class HiveParquetNestedColumnUtils {
+public:
+ static void extract_nested_column_ids(const FieldSchema& field_schema,
+ const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids);
+};
+
+#include "common/compile_check_end.h"
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/hive_reader.cpp b/be/src/vec/exec/format/table/hive_reader.cpp
index a454118b244..96ca2dff2d0 100644
--- a/be/src/vec/exec/format/table/hive_reader.cpp
+++ b/be/src/vec/exec/format/table/hive_reader.cpp
@@ -21,6 +21,8 @@
#include "common/status.h"
#include "runtime/runtime_state.h"
+#include "vec/exec/format/table/hive/hive_orc_nested_column_utils.h"
+#include "vec/exec/format/table/hive/hive_parquet_nested_column_utils.h"
namespace doris::vectorized {
#include "common/compile_check_begin.h"
@@ -77,10 +79,223 @@ Status HiveOrcReader::init_reader(
}
}
+ auto column_id_result = ColumnIdResult();
+ if (_state->query_options().hive_orc_use_column_names && !is_hive_col_name) {
+ column_id_result = _create_column_ids(orc_type_ptr, tuple_descriptor);
+ } else {
+ column_id_result =
+ _create_column_ids_by_top_level_col_index(orc_type_ptr, tuple_descriptor);
+ }
+
+ // const auto& file_col_names = column_id_result.column_names;
+ const auto& column_ids = column_id_result.column_ids;
+ const auto& filter_column_ids = column_id_result.filter_column_ids;
+
return orc_reader->init_reader(&read_table_col_names, table_col_name_to_value_range, conjuncts,
false, tuple_descriptor, row_descriptor,
not_single_slot_filter_conjuncts, slot_id_to_filter_conjuncts,
- table_info_node_ptr);
+ table_info_node_ptr, column_ids, filter_column_ids);
+}
+
+ColumnIdResult HiveOrcReader::_create_column_ids(const orc::Type* orc_type,
+ const TupleDescriptor* tuple_descriptor) {
+ if (!orc_type) {
+ return ColumnIdResult();
+ }
+
+ // map top-level table column name (lower-cased) -> orc::Type*
+ std::unordered_map<std::string, const orc::Type*> table_col_name_to_orc_type_map;
+ for (uint64_t i = 0; i < orc_type->getSubtypeCount(); ++i) {
+ auto orc_sub_type = orc_type->getSubtype(i);
+ if (!orc_sub_type) continue;
+
+ std::string table_col_name = to_lower(orc_type->getFieldName(i));
+ table_col_name_to_orc_type_map[table_col_name] = orc_sub_type;
+ }
+
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ // helper to process name access paths for a given top-level orc field
+ auto process_access_paths = [](const orc::Type* orc_field,
+ const std::vector<TColumnAccessPath>& access_paths,
+ std::set<uint64_t>& out_ids) {
+ if (!orc_field) return;
+ if (access_paths.empty()) return;
+
+ std::vector<std::vector<std::string>> paths;
+ bool has_top_level_only = false;
+ for (const auto& access_path : access_paths) {
+ std::vector<std::string> path;
+ if (access_path.type == TAccessPathType::DATA) {
+ path = access_path.data_access_path.path;
+ } else if (access_path.type == TAccessPathType::META) {
+ path = access_path.meta_access_path.path;
+ } else {
+ continue;
+ }
+ DCHECK(path.size() >= 1);
+ std::vector<std::string> remaining_path;
+ if (path.size() > 1) {
+ remaining_path.assign(path.begin() + 1, path.end());
+ } else {
+ // only top-level column name => means whole field
+ remaining_path = std::vector<std::string>();
+ }
+ if (remaining_path.empty()) {
+ has_top_level_only = true;
+ }
+ paths.push_back(std::move(remaining_path));
+ }
+
+ if (has_top_level_only) {
+ uint64_t start_id = orc_field->getColumnId();
+ uint64_t max_column_id = orc_field->getMaximumColumnId();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ out_ids.insert(id);
+ }
+ } else if (!paths.empty()) {
+ out_ids.insert(orc_field->getColumnId());
+ HiveOrcNestedColumnUtils::extract_nested_column_ids(*orc_field, paths, out_ids);
+ }
+ };
+
+ for (const auto* slot : tuple_descriptor->slots()) {
+ auto it = table_col_name_to_orc_type_map.find(slot->col_name());
+ if (it == table_col_name_to_orc_type_map.end()) {
+ // Column not found in file (e.g., partition column, added column)
+ continue;
+ }
+ const orc::Type* orc_field = it->second;
+
+ const auto& all_access_paths = slot->all_access_paths();
+
+ // primitive (non-nested) types: direct mapping by name
+ if ((slot->col_type() != TYPE_STRUCT && slot->col_type() != TYPE_ARRAY &&
+ slot->col_type() != TYPE_MAP)) {
+ column_ids.insert(orc_field->getColumnId());
+ if (slot->is_predicate()) {
+ filter_column_ids.insert(orc_field->getColumnId());
+ }
+ continue;
+ }
+
+ // complex types:
+
+ // collect and process all_access_paths -> column_ids
+ if (!all_access_paths.empty()) {
+ process_access_paths(orc_field, all_access_paths, column_ids);
+ }
+
+ // collect and process predicate_access_paths -> filter_column_ids
+ const auto& predicate_access_paths = slot->predicate_access_paths();
+ if (!predicate_access_paths.empty()) {
+ process_access_paths(orc_field, predicate_access_paths, filter_column_ids);
+ }
+ }
+
+ return ColumnIdResult(std::move(column_ids), std::move(filter_column_ids));
+}
+
+ColumnIdResult HiveOrcReader::_create_column_ids_by_top_level_col_index(
+ const orc::Type* orc_type, const TupleDescriptor* tuple_descriptor) {
+ std::shared_ptr<TableSchemaChangeHelper::Node> schema_node = nullptr;
+
+ if (!orc_type) {
+ return ColumnIdResult();
+ }
+
+ // map top-level table column index -> orc::Type*
+ std::unordered_map<uint64_t, const orc::Type*> table_col_pos_to_orc_type_map;
+ for (uint64_t i = 0; i < orc_type->getSubtypeCount(); ++i) {
+ auto orc_sub_type = orc_type->getSubtype(i);
+ if (!orc_sub_type) continue;
+
+ table_col_pos_to_orc_type_map[i] = orc_sub_type;
+ }
+
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ // helper to process name access paths for a given top-level orc field
+ auto process_access_paths = [](const orc::Type* orc_field,
+ const std::vector<TColumnAccessPath>& access_paths,
+ std::set<uint64_t>& out_ids) {
+ if (!orc_field) return;
+ if (access_paths.empty()) return;
+
+ std::vector<std::vector<std::string>> paths;
+ bool has_top_level_only = false;
+ for (const auto& access_path : access_paths) {
+ std::vector<std::string> path;
+ if (access_path.type == TAccessPathType::DATA) {
+ path = access_path.data_access_path.path;
+ } else if (access_path.type == TAccessPathType::META) {
+ path = access_path.meta_access_path.path;
+ } else {
+ continue;
+ }
+ DCHECK(path.size() >= 1);
+ std::vector<std::string> remaining_path;
+ if (path.size() > 1) {
+ remaining_path.assign(path.begin() + 1, path.end());
+ } else {
+ // only top-level column name => means whole field
+ remaining_path = std::vector<std::string>();
+ }
+ if (remaining_path.empty()) {
+ has_top_level_only = true;
+ }
+ paths.push_back(std::move(remaining_path));
+ }
+
+ if (has_top_level_only) {
+ uint64_t start_id = orc_field->getColumnId();
+ uint64_t max_column_id = orc_field->getMaximumColumnId();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ out_ids.insert(id);
+ }
+ } else if (!paths.empty()) {
+ out_ids.insert(orc_field->getColumnId());
+ HiveOrcNestedColumnUtils::extract_nested_column_ids(*orc_field, paths, out_ids);
+ }
+ };
+
+ for (const auto* slot : tuple_descriptor->slots()) {
+ auto it = table_col_pos_to_orc_type_map.find(slot->col_pos());
+ if (it == table_col_pos_to_orc_type_map.end()) {
+ // Column not found in file (e.g., partition column, added column)
+ continue;
+ }
+ const orc::Type* orc_field = it->second;
+
+ const auto& all_access_paths = slot->all_access_paths();
+
+ // primitive (non-nested) types: direct mapping by pos
+ if ((slot->col_type() != TYPE_STRUCT && slot->col_type() != TYPE_ARRAY &&
+ slot->col_type() != TYPE_MAP)) {
+ column_ids.insert(orc_field->getColumnId());
+ if (slot->is_predicate()) {
+ filter_column_ids.insert(orc_field->getColumnId());
+ }
+ continue;
+ }
+
+ // complex types
+
+ // collect and process all_access_paths -> column_ids
+ if (!all_access_paths.empty()) {
+ process_access_paths(orc_field, all_access_paths, column_ids);
+ }
+
+ // collect and process predicate_access_paths -> filter_column_ids
+ const auto& predicate_access_paths = slot->predicate_access_paths();
+ if (!predicate_access_paths.empty()) {
+ process_access_paths(orc_field, predicate_access_paths, filter_column_ids);
+ }
+ }
+
+ return ColumnIdResult(std::move(column_ids), std::move(filter_column_ids));
}
Status HiveParquetReader::init_reader(
@@ -145,10 +360,238 @@ Status HiveParquetReader::init_reader(
}
}
- return parquet_reader->init_reader(read_table_col_names, table_col_name_to_value_range,
- conjuncts, tuple_descriptor, row_descriptor,
- colname_to_slot_id, not_single_slot_filter_conjuncts,
- slot_id_to_filter_conjuncts, table_info_node_ptr);
+ auto column_id_result = ColumnIdResult();
+ if (_state->query_options().hive_parquet_use_column_names) {
+ column_id_result = _create_column_ids(field_desc, tuple_descriptor);
+ } else {
+ column_id_result = _create_column_ids_by_top_level_col_index(field_desc, tuple_descriptor);
+ }
+
+ const auto& column_ids = column_id_result.column_ids;
+ const auto& filter_column_ids = column_id_result.filter_column_ids;
+
+ RETURN_IF_ERROR(init_row_filters());
+
+ return parquet_reader->init_reader(
+ read_table_col_names, table_col_name_to_value_range, conjuncts, tuple_descriptor,
+ row_descriptor, colname_to_slot_id, not_single_slot_filter_conjuncts,
+ slot_id_to_filter_conjuncts, table_info_node_ptr, true, column_ids, filter_column_ids);
+}
+
+ColumnIdResult HiveParquetReader::_create_column_ids(const FieldDescriptor* field_desc,
+ const TupleDescriptor* tuple_descriptor) {
+ if (!field_desc) {
+ return ColumnIdResult();
+ }
+
+ // First, assign column IDs to the field descriptor
+ auto* mutable_field_desc = const_cast<FieldDescriptor*>(field_desc);
+ mutable_field_desc->assign_ids();
+
+ std::unordered_map<std::string, const FieldSchema*> table_col_name_to_field_schema_map;
+ for (int i = 0; i < field_desc->size(); ++i) {
+ auto field_schema = field_desc->get_column(i);
+ if (!field_schema) continue;
+
+ table_col_name_to_field_schema_map[field_schema->lower_case_name] = field_schema;
+ }
+
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ // helper to process name access paths for a given top-level parquet field
+ auto process_access_paths = [](const FieldSchema* parquet_field,
+ const std::vector<TColumnAccessPath>& access_paths,
+ std::set<uint64_t>& out_ids) {
+ if (!parquet_field) return;
+ if (access_paths.empty()) return;
+
+ std::vector<std::vector<std::string>> paths;
+ bool has_top_level_only = false;
+ for (const auto& access_path : access_paths) {
+ std::vector<std::string> path;
+ if (access_path.type == TAccessPathType::DATA) {
+ path = access_path.data_access_path.path;
+ } else if (access_path.type == TAccessPathType::META) {
+ path = access_path.meta_access_path.path;
+ } else {
+ continue;
+ }
+ DCHECK(path.size() >= 1);
+ std::vector<std::string> remaining_path;
+ if (path.size() > 1) {
+ remaining_path.assign(path.begin() + 1, path.end());
+ } else {
+ // only top-level column name => means whole field
+ remaining_path = std::vector<std::string>();
+ }
+ if (remaining_path.empty()) {
+ has_top_level_only = true;
+ }
+ paths.push_back(std::move(remaining_path));
+ }
+
+ if (has_top_level_only) {
+ uint64_t start_id = parquet_field->get_column_id();
+ uint64_t max_column_id = parquet_field->get_max_column_id();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ out_ids.insert(id);
+ }
+ } else if (!paths.empty()) {
+ out_ids.insert(parquet_field->get_column_id());
+ HiveParquetNestedColumnUtils::extract_nested_column_ids(*parquet_field, paths, out_ids);
+ }
+ };
+
+ for (const auto* slot : tuple_descriptor->slots()) {
+ // if (slot->col_name().starts_with(BeConsts::GLOBAL_ROWID_COL)) {
+ // continue;
+ // }
+
+ // Find the field schema for this slot (may not exist for partition columns, etc.)
+ auto it = table_col_name_to_field_schema_map.find(slot->col_name());
+ if (it == table_col_name_to_field_schema_map.end()) {
+ // Column not found in file (e.g., partition column, added column)
+ continue;
+ }
+ auto field_schema = it->second;
+
+ const auto& all_access_paths = slot->all_access_paths();
+
+ // primitive (non-nested) types: direct mapping by name
+ if ((slot->col_type() != TYPE_STRUCT && slot->col_type() != TYPE_ARRAY &&
+ slot->col_type() != TYPE_MAP)) {
+ column_ids.insert(field_schema->column_id);
+
+ if (slot->is_predicate()) {
+ filter_column_ids.insert(field_schema->column_id);
+ }
+ continue;
+ }
+
+ // complex types:
+
+ // collect and process all_access_paths -> column_ids
+ if (!all_access_paths.empty()) {
+ process_access_paths(field_schema, all_access_paths, column_ids);
+ }
+
+ // collect and process predicate_access_paths -> filter_column_ids
+ const auto& predicate_access_paths = slot->predicate_access_paths();
+ if (!predicate_access_paths.empty()) {
+ process_access_paths(field_schema, predicate_access_paths, filter_column_ids);
+ }
+ }
+
+ return ColumnIdResult(std::move(column_ids), std::move(filter_column_ids));
+}
+
+ColumnIdResult HiveParquetReader::_create_column_ids_by_top_level_col_index(
+ const FieldDescriptor* field_desc, const TupleDescriptor* tuple_descriptor) {
+ std::shared_ptr<TableSchemaChangeHelper::Node> schema_node = nullptr;
+
+ if (!field_desc) {
+ return ColumnIdResult();
+ }
+
+ // First, assign column IDs to the field descriptor
+ auto* mutable_field_desc = const_cast<FieldDescriptor*>(field_desc);
+ mutable_field_desc->assign_ids();
+
+ std::unordered_map<uint64_t, const FieldSchema*> table_col_pos_to_field_schema_map;
+ for (int i = 0; i < field_desc->size(); ++i) {
+ auto field_schema = field_desc->get_column(i);
+ if (!field_schema) continue;
+
+ table_col_pos_to_field_schema_map[i] = field_schema;
+ }
+
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ // helper to process name access paths for a given top-level parquet field
+ auto process_access_paths = [](const FieldSchema* parquet_field,
+ const std::vector<TColumnAccessPath>& access_paths,
+ std::set<uint64_t>& out_ids) {
+ if (!parquet_field) return;
+ if (access_paths.empty()) return;
+
+ std::vector<std::vector<std::string>> paths;
+ bool has_top_level_only = false;
+ for (const auto& access_path : access_paths) {
+ std::vector<std::string> path;
+ if (access_path.type == TAccessPathType::DATA) {
+ path = access_path.data_access_path.path;
+ } else if (access_path.type == TAccessPathType::META) {
+ path = access_path.meta_access_path.path;
+ } else {
+ continue;
+ }
+ DCHECK(path.size() >= 1);
+ std::vector<std::string> remaining_path;
+ if (path.size() > 1) {
+ remaining_path.assign(path.begin() + 1, path.end());
+ } else {
+ // only top-level column name => means whole field
+ remaining_path = std::vector<std::string>();
+ }
+ if (remaining_path.empty()) {
+ has_top_level_only = true;
+ }
+ paths.push_back(std::move(remaining_path));
+ }
+
+ if (has_top_level_only) {
+ uint64_t start_id = parquet_field->get_column_id();
+ uint64_t max_column_id = parquet_field->get_max_column_id();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ out_ids.insert(id);
+ }
+ } else if (!paths.empty()) {
+ out_ids.insert(parquet_field->get_column_id());
+ HiveParquetNestedColumnUtils::extract_nested_column_ids(*parquet_field, paths, out_ids);
+ }
+ };
+
+ for (const auto* slot : tuple_descriptor->slots()) {
+ // if (slot->col_name().starts_with(BeConsts::GLOBAL_ROWID_COL)) {
+ // continue;
+ // }
+
+ // Find the field schema for this slot (may not exist for partition columns, etc.)
+ auto it = table_col_pos_to_field_schema_map.find(slot->col_pos());
+ if (it == table_col_pos_to_field_schema_map.end()) {
+ // Column not found in file (e.g., partition column, added column)
+ continue;
+ }
+ auto field_schema = it->second;
+
+ const auto& all_access_paths = slot->all_access_paths();
+
+ // primitive (non-nested) types: direct mapping by position
+ if ((slot->col_type() != TYPE_STRUCT && slot->col_type() != TYPE_ARRAY &&
+ slot->col_type() != TYPE_MAP)) {
+ column_ids.insert(field_schema->column_id);
+
+ if (slot->is_predicate()) {
+ filter_column_ids.insert(field_schema->column_id);
+ }
+ continue;
+ }
+
+ // collect and process all_access_paths -> column_ids
+ if (!all_access_paths.empty()) {
+ process_access_paths(field_schema, all_access_paths, column_ids);
+ }
+
+ // collect and process predicate_access_paths -> filter_column_ids
+ const auto& predicate_access_paths = slot->predicate_access_paths();
+ if (!predicate_access_paths.empty()) {
+ process_access_paths(field_schema, predicate_access_paths, filter_column_ids);
+ }
+ }
+
+ return ColumnIdResult(std::move(column_ids), std::move(filter_column_ids));
}
#include "common/compile_check_end.h"
diff --git a/be/src/vec/exec/format/table/hive_reader.h b/be/src/vec/exec/format/table/hive_reader.h
index 6acd344e417..b9592c43243 100644
--- a/be/src/vec/exec/format/table/hive_reader.h
+++ b/be/src/vec/exec/format/table/hive_reader.h
@@ -66,6 +66,13 @@ public:
const RowDescriptor* row_descriptor,
const VExprContextSPtrs* not_single_slot_filter_conjuncts,
const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts);
+
+private:
+ static ColumnIdResult _create_column_ids(const orc::Type* orc_type,
+ const TupleDescriptor* tuple_descriptor);
+
+ static ColumnIdResult _create_column_ids_by_top_level_col_index(
+ const orc::Type* orc_type, const TupleDescriptor* tuple_descriptor);
};
class HiveParquetReader final : public HiveReader {
@@ -88,6 +95,13 @@ public:
const std::unordered_map<std::string, int>* colname_to_slot_id,
const VExprContextSPtrs* not_single_slot_filter_conjuncts,
const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts);
+
+private:
+ static ColumnIdResult _create_column_ids(const FieldDescriptor* field_desc,
+ const TupleDescriptor* tuple_descriptor);
+
+ static ColumnIdResult _create_column_ids_by_top_level_col_index(
+ const FieldDescriptor* field_desc, const TupleDescriptor* tuple_descriptor);
};
#include "common/compile_check_end.h"
} // namespace doris::vectorized
\ No newline at end of file
diff --git a/be/src/vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.cpp b/be/src/vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.cpp
new file mode 100644
index 00000000000..5dbbbfb2af2
--- /dev/null
+++ b/be/src/vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.cpp
@@ -0,0 +1,149 @@
+// 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 "vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.h"
+
+#include <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "orc/Type.hh"
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+void IcebergOrcNestedColumnUtils::extract_nested_column_ids(
+ const orc::Type& type, const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids) {
+ // Group paths by first field_id
+ std::unordered_map<std::string, std::vector<std::vector<std::string>>> child_paths_by_field_id;
+
+ for (const auto& path : paths) {
+ if (!path.empty()) {
+ std::string first_field_id = path[0];
+ std::vector<std::string> remaining;
+ if (path.size() > 1) {
+ remaining.assign(path.begin() + 1, path.end());
+ }
+ child_paths_by_field_id[first_field_id].push_back(std::move(remaining));
+ }
+ }
+
+ // For MAP type, normalize wildcard "*" to explicit KEYS/VALUES access
+ // Wildcard in MAP context means accessing both map keys and values
+ // Normalization logic:
+ // path: ["map_col", "*"] → ["map_col", "VALUES"] + ["map_col", "KEYS"]
+ // path: ["map_col", "*", "field"] → ["map_col", "VALUES", "field"] + ["map_col", "KEYS"]
+ // KEYS are always needed for correct RL/DL computation when accessing MAP via wildcard
+ if (type.getKind() == orc::TypeKind::MAP) {
+ auto wildcard_it = child_paths_by_field_id.find("*");
+ if (wildcard_it != child_paths_by_field_id.end()) {
+ auto& wildcard_paths = wildcard_it->second;
+
+ // All wildcard paths go to VALUES
+ auto& values_paths = child_paths_by_field_id["VALUES"];
+ values_paths.insert(values_paths.end(), wildcard_paths.begin(), wildcard_paths.end());
+
+ // Always add KEYS for wildcard access (needed for RL/DL computation)
+ auto& keys_paths = child_paths_by_field_id["KEYS"];
+ // Add an empty path to request full KEYS
+ std::vector<std::string> empty_path;
+ keys_paths.push_back(empty_path);
+
+ // Remove wildcard entry as it's been expanded
+ child_paths_by_field_id.erase(wildcard_it);
+ }
+ }
+
+ bool has_child_columns = false;
+ // Efficiently traverse children
+ for (uint64_t i = 0; i < type.getSubtypeCount(); ++i) {
+ const orc::Type* child = type.getSubtype(i);
+
+ std::string child_field_id;
+ switch (type.getKind()) {
+ case orc::TypeKind::STRUCT:
+ if (!child->hasAttributeKey(ICEBERG_ORC_ATTRIBUTE)) {
+ continue;
+ }
+ child_field_id = child->getAttributeValue(ICEBERG_ORC_ATTRIBUTE);
+ break;
+ case orc::TypeKind::LIST:
+ child_field_id = "*";
+ break;
+ case orc::TypeKind::MAP:
+ // After wildcard normalization above, all MAP accesses are explicit KEYS/VALUES
+ // Simply assign the appropriate field name based on which child we're processing
+ if (i == 0) {
+ child_field_id = "KEYS";
+ } else if (i == 1) {
+ child_field_id = "VALUES";
+ }
+ break;
+ default:
+ child_field_id = "";
+ break;
+ }
+
+ if (child_field_id.empty() || child_field_id == "-1") {
+ continue;
+ }
+
+ auto child_paths_it = child_paths_by_field_id.find(child_field_id);
+ if (child_paths_it != child_paths_by_field_id.end()) {
+ const auto& child_paths = child_paths_it->second;
+
+ // Check if any child path is empty (meaning full child needed)
+ bool needs_full_child =
+ std::any_of(child_paths.begin(), child_paths.end(),
+ [](const std::vector<std::string>& path) { return path.empty(); });
+
+ if (needs_full_child) {
+ uint64_t start_id = child->getColumnId();
+ uint64_t max_column_id = child->getMaximumColumnId();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ column_ids.insert(id);
+ }
+ has_child_columns = true;
+ } else {
+ // Store current size to check if recursive call added any columns
+ size_t before_size = column_ids.size();
+
+ // Recursively extract from child
+ extract_nested_column_ids(*child, child_paths, column_ids);
+
+ // Check if recursive call added any columns
+ if (column_ids.size() > before_size) {
+ has_child_columns = true;
+ }
+ }
+ }
+ }
+
+ // If any child columns were added, also add the parent column ID
+ // This ensures parent struct/container nodes are included when their children are needed
+ if (has_child_columns) {
+ // Set automatically handles deduplication, so no need to check if it already exists
+ column_ids.insert(type.getColumnId());
+ }
+}
+
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.h b/be/src/vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.h
new file mode 100644
index 00000000000..576ec598970
--- /dev/null
+++ b/be/src/vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.h
@@ -0,0 +1,46 @@
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace orc {
+class Type;
+} // namespace orc
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+class IcebergOrcNestedColumnUtils {
+public:
+ static void extract_nested_column_ids(const orc::Type& type,
+ const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids);
+
+private:
+ static constexpr const char* ICEBERG_ORC_ATTRIBUTE = "iceberg.id";
+};
+
+#include "common/compile_check_end.h"
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.cpp b/be/src/vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.cpp
new file mode 100644
index 00000000000..cb2b2e6b8c7
--- /dev/null
+++ b/be/src/vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.cpp
@@ -0,0 +1,173 @@
+// 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 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 "vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.h"
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "vec/exec/format/parquet/schema_desc.h"
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+void IcebergParquetNestedColumnUtils::extract_nested_column_ids(
+ const FieldSchema& field_schema, const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids) {
+ // Group paths by first field_id
+ std::unordered_map<std::string, std::vector<std::vector<std::string>>> child_paths_by_field_id;
+
+ for (const auto& path : paths) {
+ if (!path.empty()) {
+ std::string first_field_id = path[0];
+ std::vector<std::string> remaining;
+ if (path.size() > 1) {
+ remaining.assign(path.begin() + 1, path.end());
+ }
+ child_paths_by_field_id[first_field_id].push_back(std::move(remaining));
+ }
+ }
+
+ // Track whether any child column was added to determine if parent should be included
+ bool has_child_columns = false;
+
+ // For MAP type, normalize wildcard "*" to explicit KEYS/VALUES access
+ // Wildcard in MAP context means accessing both map keys and values
+ // Normalization logic:
+ // path: ["map_col", "*"] → ["map_col", "VALUES"] + ["map_col", "KEYS"]
+ // path: ["map_col", "*", "field"] → ["map_col", "VALUES", "field"] + ["map_col", "KEYS"]
+ // KEYS are always needed for correct RL/DL computation when accessing MAP via wildcard
+ if (field_schema.data_type->get_primitive_type() == PrimitiveType::TYPE_MAP) {
+ auto wildcard_it = child_paths_by_field_id.find("*");
+ if (wildcard_it != child_paths_by_field_id.end()) {
+ auto& wildcard_paths = wildcard_it->second;
+
+ // All wildcard paths go to VALUES
+ auto& values_paths = child_paths_by_field_id["VALUES"];
+ values_paths.insert(values_paths.end(), wildcard_paths.begin(), wildcard_paths.end());
+
+ // Always add KEYS for wildcard access (needed for RL/DL computation)
+ auto& keys_paths = child_paths_by_field_id["KEYS"];
+ // Add an empty path to request full KEYS
+ std::vector<std::string> empty_path;
+ keys_paths.push_back(empty_path);
+
+ // Remove wildcard entry as it's been expanded
+ child_paths_by_field_id.erase(wildcard_it);
+ }
+ }
+
+ // Efficiently traverse children
+ for (uint64_t i = 0; i < field_schema.children.size(); ++i) {
+ const auto& child = field_schema.children[i];
+
+ std::string child_field_id;
+
+ bool is_list = field_schema.data_type->get_primitive_type() == PrimitiveType::TYPE_ARRAY;
+ bool is_map = field_schema.data_type->get_primitive_type() == PrimitiveType::TYPE_MAP;
+
+ if (is_list) {
+ child_field_id = "*";
+ } else if (is_map) {
+ // After wildcard normalization above, all MAP accesses are explicit KEYS/VALUES
+ // Simply assign the appropriate field name based on which child we're processing
+ if (i == 0) {
+ child_field_id = "KEYS";
+ } else if (i == 1) {
+ child_field_id = "VALUES";
+ }
+
+ // Special handling for Parquet MAP structure:
+ // When accessing only VALUES, we still need KEY structure for levels
+ // Check if we're at key child (i==0) and only VALUES is requested (no KEYS)
+ if (i == 0) {
+ bool has_keys_access =
+ child_paths_by_field_id.find("KEYS") != child_paths_by_field_id.end();
+ bool has_values_access =
+ child_paths_by_field_id.find("VALUES") != child_paths_by_field_id.end();
+
+ // If only VALUES is accessed (not KEYS), still include key structure for RL/DL
+ if (!has_keys_access && has_values_access) {
+ // For map_values() queries, we need key's structure for correct RL/DL parsing.
+ // If key is a nested type (e.g., STRUCT), RL/DL info is stored at leaf columns.
+ // Add all column IDs from key's start to max (all leaves + intermediate nodes).
+ uint64_t key_start_id = child.get_column_id();
+ uint64_t key_max_id = child.get_max_column_id();
+ for (uint64_t id = key_start_id; id <= key_max_id; ++id) {
+ column_ids.insert(id);
+ }
+ has_child_columns = true;
+ continue; // Skip further processing of key child
+ }
+ }
+
+ } else {
+ child_field_id = std::to_string(child.field_id);
+ }
+
+ if (child_field_id.empty() || child_field_id == "-1") {
+ continue;
+ }
+
+ auto child_paths_it = child_paths_by_field_id.find(child_field_id);
+ if (child_paths_it != child_paths_by_field_id.end()) {
+ const auto& child_paths = child_paths_it->second;
+
+ // Check if any child path is empty (meaning full child needed)
+ bool needs_full_child =
+ std::any_of(child_paths.begin(), child_paths.end(),
+ [](const std::vector<std::string>& path) { return path.empty(); });
+
+ if (needs_full_child) {
+ // Add all column IDs from current child node to max_column_id
+ // This efficiently handles all nested/complex cases in one loop
+ uint64_t start_id = child.get_column_id();
+ uint64_t max_column_id = child.get_max_column_id();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ column_ids.insert(id);
+ }
+ has_child_columns = true;
+ } else {
+ // Store current size to check if recursive call added any columns
+ size_t before_size = column_ids.size();
+
+ // Recursively extract from child
+ extract_nested_column_ids(child, child_paths, column_ids);
+
+ // Check if recursive call added any columns
+ if (column_ids.size() > before_size) {
+ has_child_columns = true;
+ }
+ }
+ }
+ }
+
+ // If any child columns were added, also add the parent column ID
+ // This ensures parent struct/container nodes are included when their children are needed
+ if (has_child_columns) {
+ // Set automatically handles deduplication, so no need to check if it already exists
+ column_ids.insert(field_schema.get_column_id());
+ }
+}
+
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.h b/be/src/vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.h
new file mode 100644
index 00000000000..58237d8182b
--- /dev/null
+++ b/be/src/vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.h
@@ -0,0 +1,42 @@
+// 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.
+
+#pragma once
+
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "vec/exec/format/table/table_format_reader.h"
+
+namespace doris::vectorized {
+#include "common/compile_check_begin.h"
+
+class FieldDescriptor;
+struct FieldSchema;
+
+class IcebergParquetNestedColumnUtils {
+public:
+ static void extract_nested_column_ids(const FieldSchema& field_schema,
+ const std::vector<std::vector<std::string>>& paths,
+ std::set<uint64_t>& column_ids);
+};
+
+#include "common/compile_check_end.h"
+} // namespace doris::vectorized
diff --git a/be/src/vec/exec/format/table/iceberg_reader.cpp b/be/src/vec/exec/format/table/iceberg_reader.cpp
index db6e44c3d4b..95aa41a43e6 100644
--- a/be/src/vec/exec/format/table/iceberg_reader.cpp
+++ b/be/src/vec/exec/format/table/iceberg_reader.cpp
@@ -17,6 +17,7 @@
#include "iceberg_reader.h"
+#include <gen_cpp/Descriptors_types.h>
#include <gen_cpp/Metrics_types.h>
#include <gen_cpp/PlanNodes_types.h>
#include <gen_cpp/parquet_types.h>
@@ -52,6 +53,8 @@
#include "vec/exec/format/orc/vorc_reader.h"
#include "vec/exec/format/parquet/schema_desc.h"
#include "vec/exec/format/parquet/vparquet_column_chunk_reader.h"
+#include "vec/exec/format/table/iceberg/iceberg_orc_nested_column_utils.h"
+#include "vec/exec/format/table/iceberg/iceberg_parquet_nested_column_utils.h"
#include "vec/exec/format/table/table_format_reader.h"
namespace cctz {
@@ -445,11 +448,134 @@ Status IcebergParquetReader::init_reader(
}
_all_required_col_names = file_col_names;
+
+ auto column_id_result = _create_column_ids(field_desc, tuple_descriptor);
+ // const auto& file_col_names = column_id_result.column_names;
+ auto& column_ids = column_id_result.column_ids;
+ const auto& filter_column_ids = column_id_result.filter_column_ids;
+
RETURN_IF_ERROR(init_row_filters());
- return parquet_reader->init_reader(_all_required_col_names, colname_to_value_range, conjuncts,
- tuple_descriptor, row_descriptor, colname_to_slot_id,
- not_single_slot_filter_conjuncts,
- slot_id_to_filter_conjuncts, table_info_node_ptr);
+ for (int i = 0; i < field_desc->size(); ++i) {
+ auto field_schema = field_desc->get_column(i);
+ std::string col_name = field_schema->name;
+ if (std::find(_expand_col_names.begin(), _expand_col_names.end(), col_name) !=
+ _expand_col_names.end()) {
+ column_ids.insert(field_schema->get_column_id());
+ }
+ }
+ return parquet_reader->init_reader(
+ _all_required_col_names, colname_to_value_range, conjuncts, tuple_descriptor,
+ row_descriptor, colname_to_slot_id, not_single_slot_filter_conjuncts,
+ slot_id_to_filter_conjuncts, table_info_node_ptr, true, column_ids, filter_column_ids);
+}
+
+ColumnIdResult IcebergParquetReader::_create_column_ids(const FieldDescriptor* field_desc,
+ const TupleDescriptor* tuple_descriptor) {
+ if (!field_desc) {
+ return ColumnIdResult();
+ }
+
+ // First, assign column IDs to the field descriptor
+ auto* mutable_field_desc = const_cast<FieldDescriptor*>(field_desc);
+ mutable_field_desc->assign_ids();
+
+ // Create a mapping from iceberg field_id to FieldSchema for quick lookup
+ std::unordered_map<int, const FieldSchema*> iceberg_id_to_field_schema_map;
+
+ for (int i = 0; i < field_desc->size(); ++i) {
+ auto field_schema = field_desc->get_column(i);
+ if (!field_schema) continue;
+
+ int iceberg_id = field_schema->field_id;
+ iceberg_id_to_field_schema_map[iceberg_id] = field_schema;
+ }
+
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ // helper to process name access paths for a given top-level parquet field
+ auto process_access_paths = [](const FieldSchema* parquet_field,
+ const std::vector<TColumnAccessPath>& access_paths,
+ std::set<uint64_t>& out_ids) {
+ if (!parquet_field) return;
+ if (access_paths.empty()) return;
+
+ std::vector<std::vector<std::string>> paths;
+ bool has_top_level_only = false;
+ for (const auto& access_path : access_paths) {
+ std::vector<std::string> path;
+ if (access_path.type == TAccessPathType::DATA) {
+ path = access_path.data_access_path.path;
+ } else if (access_path.type == TAccessPathType::META) {
+ path = access_path.meta_access_path.path;
+ } else {
+ continue;
+ }
+ DCHECK(path.size() >= 1);
+ std::vector<std::string> remaining_path;
+ if (path.size() > 1) {
+ remaining_path.assign(path.begin() + 1, path.end());
+ } else {
+ // only top-level column name => means whole field
+ remaining_path = std::vector<std::string>();
+ }
+ if (remaining_path.empty()) {
+ has_top_level_only = true;
+ }
+ paths.push_back(std::move(remaining_path));
+ }
+
+ if (has_top_level_only) {
+ uint64_t start_id = parquet_field->get_column_id();
+ uint64_t max_column_id = parquet_field->get_max_column_id();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ out_ids.insert(id);
+ }
+ } else if (!paths.empty()) {
+ out_ids.insert(parquet_field->get_column_id());
+ IcebergParquetNestedColumnUtils::extract_nested_column_ids(*parquet_field, paths,
+ out_ids);
+ }
+ };
+
+ for (const auto* slot : tuple_descriptor->slots()) {
+ // if (slot->col_name().starts_with(BeConsts::GLOBAL_ROWID_COL)) {
+ // continue;
+ // }
+ auto it = iceberg_id_to_field_schema_map.find(slot->col_unique_id());
+ if (it == iceberg_id_to_field_schema_map.end()) {
+ // Column not found in file (e.g., partition column, added column)
+ continue;
+ }
+ auto field_schema = it->second;
+
+ const auto& all_access_paths = slot->all_access_paths();
+
+ // primitive (non-nested) types: direct mapping by name
+ if ((slot->col_type() != TYPE_STRUCT && slot->col_type() != TYPE_ARRAY &&
+ slot->col_type() != TYPE_MAP)) {
+ column_ids.insert(field_schema->column_id);
+
+ if (slot->is_predicate()) {
+ filter_column_ids.insert(field_schema->column_id);
+ }
+ continue;
+ }
+
+ // complex types:
+
+ // collect and process all_access_paths -> column_ids
+ if (!all_access_paths.empty()) {
+ process_access_paths(field_schema, all_access_paths, column_ids);
+ }
+
+ // collect and process predicate_access_paths -> filter_column_ids
+ const auto& predicate_access_paths = slot->predicate_access_paths();
+ if (!predicate_access_paths.empty()) {
+ process_access_paths(field_schema, predicate_access_paths, filter_column_ids);
+ }
+ }
+ return ColumnIdResult(std::move(column_ids), std::move(filter_column_ids));
}
Status IcebergParquetReader ::_read_position_delete_file(const TFileRangeDesc* delete_range,
@@ -527,11 +653,128 @@ Status IcebergOrcReader::init_reader(
}
}
+ auto column_id_result = _create_column_ids(orc_type_ptr, tuple_descriptor);
+ auto& column_ids = column_id_result.column_ids;
+ const auto& filter_column_ids = column_id_result.filter_column_ids;
+
RETURN_IF_ERROR(init_row_filters());
+ for (uint64_t i = 0; i < orc_type_ptr->getSubtypeCount(); ++i) {
+ const orc::Type* sub_type = orc_type_ptr->getSubtype(i);
+ std::string col_name = orc_type_ptr->getFieldName(i);
+ if (std::find(_expand_col_names.begin(), _expand_col_names.end(), col_name) !=
+ _expand_col_names.end()) {
+ column_ids.insert(sub_type->getColumnId());
+ }
+ }
return orc_reader->init_reader(&_all_required_col_names, colname_to_value_range, conjuncts,
false, tuple_descriptor, row_descriptor,
not_single_slot_filter_conjuncts, slot_id_to_filter_conjuncts,
- table_info_node_ptr);
+ table_info_node_ptr, column_ids, filter_column_ids);
+}
+
+ColumnIdResult IcebergOrcReader::_create_column_ids(const orc::Type* orc_type,
+ const TupleDescriptor* tuple_descriptor) {
+ std::shared_ptr<TableSchemaChangeHelper::Node> schema_node = nullptr;
+
+ if (!orc_type) {
+ return ColumnIdResult();
+ }
+
+ // map top-level table column name (lower-cased) -> orc::Type*
+ std::unordered_map<int, const orc::Type*> iceberg_id_to_orc_type_map;
+ for (uint64_t i = 0; i < orc_type->getSubtypeCount(); ++i) {
+ auto orc_sub_type = orc_type->getSubtype(i);
+ if (!orc_sub_type) continue;
+
+ if (!orc_sub_type->hasAttributeKey(ICEBERG_ORC_ATTRIBUTE)) {
+ continue;
+ }
+ int iceberg_id = std::stoi(orc_sub_type->getAttributeValue(ICEBERG_ORC_ATTRIBUTE));
+ iceberg_id_to_orc_type_map[iceberg_id] = orc_sub_type;
+ }
+
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ // helper to process name access paths for a given top-level orc field
+ auto process_access_paths = [](const orc::Type* orc_field,
+ const std::vector<TColumnAccessPath>& access_paths,
+ std::set<uint64_t>& out_ids) {
+ if (!orc_field) return;
+ if (access_paths.empty()) return;
+
+ std::vector<std::vector<std::string>> paths;
+ bool has_top_level_only = false;
+ for (const auto& access_path : access_paths) {
+ std::vector<std::string> path;
+ if (access_path.type == TAccessPathType::DATA) {
+ path = access_path.data_access_path.path;
+ } else if (access_path.type == TAccessPathType::META) {
+ path = access_path.meta_access_path.path;
+ } else {
+ continue;
+ }
+ DCHECK(path.size() >= 1);
+ std::vector<std::string> remaining_path;
+ if (path.size() > 1) {
+ remaining_path.assign(path.begin() + 1, path.end());
+ } else {
+ // only top-level column name => means whole field
+ remaining_path = std::vector<std::string>();
+ }
+ if (remaining_path.empty()) {
+ has_top_level_only = true;
+ }
+ paths.push_back(std::move(remaining_path));
+ }
+
+ if (has_top_level_only) {
+ uint64_t start_id = orc_field->getColumnId();
+ uint64_t max_column_id = orc_field->getMaximumColumnId();
+ for (uint64_t id = start_id; id <= max_column_id; ++id) {
+ out_ids.insert(id);
+ }
+ } else if (!paths.empty()) {
+ out_ids.insert(orc_field->getColumnId());
+ IcebergOrcNestedColumnUtils::extract_nested_column_ids(*orc_field, paths, out_ids);
+ }
+ };
+
+ for (const auto* slot : tuple_descriptor->slots()) {
+ auto it = iceberg_id_to_orc_type_map.find(slot->col_unique_id());
+ if (it == iceberg_id_to_orc_type_map.end()) {
+ // Column not found in file (e.g., partition column, added column)
+ continue;
+ }
+ const orc::Type* orc_field = it->second;
+
+ const auto& all_access_paths = slot->all_access_paths();
+
+ // primitive (non-nested) types: direct mapping by name
+ if ((slot->col_type() != TYPE_STRUCT && slot->col_type() != TYPE_ARRAY &&
+ slot->col_type() != TYPE_MAP)) {
+ column_ids.insert(orc_field->getColumnId());
+ if (slot->is_predicate()) {
+ filter_column_ids.insert(orc_field->getColumnId());
+ }
+ continue;
+ }
+
+ // nested types:
+
+ // collect and process all_access_paths -> column_ids
+ if (!all_access_paths.empty()) {
+ process_access_paths(orc_field, all_access_paths, column_ids);
+ }
+
+ // collect and process predicate_access_paths -> filter_column_ids
+ const auto& predicate_access_paths = slot->predicate_access_paths();
+ if (!predicate_access_paths.empty()) {
+ process_access_paths(orc_field, predicate_access_paths, filter_column_ids);
+ }
+ }
+
+ return ColumnIdResult(std::move(column_ids), std::move(filter_column_ids));
}
Status IcebergOrcReader::_read_position_delete_file(const TFileRangeDesc* delete_range,
diff --git a/be/src/vec/exec/format/table/iceberg_reader.h b/be/src/vec/exec/format/table/iceberg_reader.h
index 6b0e3fe4b3e..d123261714e 100644
--- a/be/src/vec/exec/format/table/iceberg_reader.h
+++ b/be/src/vec/exec/format/table/iceberg_reader.h
@@ -187,6 +187,9 @@ protected:
}
private:
+ static ColumnIdResult _create_column_ids(const FieldDescriptor* field_desc,
+ const TupleDescriptor* tuple_descriptor);
+
Status _read_position_delete_file(const TFileRangeDesc* delete_range,
DeleteFile* position_delete) final;
};
@@ -226,6 +229,10 @@ protected:
_meta_cache);
}
+private:
+ static ColumnIdResult _create_column_ids(const orc::Type* orc_type,
+ const TupleDescriptor* tuple_descriptor);
+
private:
static const std::string ICEBERG_ORC_ATTRIBUTE;
};
diff --git a/be/src/vec/exec/format/table/table_format_reader.cpp b/be/src/vec/exec/format/table/table_format_reader.cpp
index b3b7e1df336..2c614777a89 100644
--- a/be/src/vec/exec/format/table/table_format_reader.cpp
+++ b/be/src/vec/exec/format/table/table_format_reader.cpp
@@ -76,8 +76,7 @@ Status TableSchemaChangeHelper::BuildTableInfoUtil::by_parquet_name(
if (file_field.data_type->get_primitive_type() != TYPE_MAP) [[unlikely]] {
return SCHEMA_ERROR;
}
- MOCK_REMOVE(DCHECK(file_field.children.size() == 1));
- MOCK_REMOVE(DCHECK(file_field.children[0].children.size() == 2));
+ MOCK_REMOVE(DCHECK(file_field.children.size() == 2));
std::shared_ptr<TableSchemaChangeHelper::Node> key_node = nullptr;
{
@@ -85,8 +84,7 @@ Status TableSchemaChangeHelper::BuildTableInfoUtil::by_parquet_name(
assert_cast<const DataTypeMap*>(remove_nullable(table_data_type).get())
->get_key_type());
- RETURN_IF_ERROR(
- by_parquet_name(key_type, file_field.children[0].children[0], key_node));
+ RETURN_IF_ERROR(by_parquet_name(key_type, file_field.children[0], key_node));
}
std::shared_ptr<TableSchemaChangeHelper::Node> value_node = nullptr;
@@ -95,8 +93,7 @@ Status TableSchemaChangeHelper::BuildTableInfoUtil::by_parquet_name(
assert_cast<const DataTypeMap*>(remove_nullable(table_data_type).get())
->get_value_type());
- RETURN_IF_ERROR(
- by_parquet_name(value_type, file_field.children[0].children[1], value_node));
+ RETURN_IF_ERROR(by_parquet_name(value_type, file_field.children[1], value_node));
}
node = std::make_shared<TableSchemaChangeHelper::MapNode>(key_node, value_node);
break;
@@ -430,19 +427,17 @@ Status TableSchemaChangeHelper::BuildTableInfoUtil::by_parquet_field_id(
MOCK_REMOVE(DCHECK(table_schema.nestedField.map_field.key_field.field_ptr != nullptr));
MOCK_REMOVE(DCHECK(table_schema.nestedField.map_field.value_field.field_ptr != nullptr));
- MOCK_REMOVE(DCHECK(parquet_field.children.size() == 1));
- MOCK_REMOVE(DCHECK(parquet_field.children[0].children.size() == 2));
+ MOCK_REMOVE(DCHECK(parquet_field.children.size() == 2));
std::shared_ptr<TableSchemaChangeHelper::Node> key_node = nullptr;
std::shared_ptr<TableSchemaChangeHelper::Node> value_node = nullptr;
RETURN_IF_ERROR(by_parquet_field_id(*table_schema.nestedField.map_field.key_field.field_ptr,
- parquet_field.children[0].children[0], key_node,
- exist_field_id));
+ parquet_field.children[0], key_node, exist_field_id));
- RETURN_IF_ERROR(by_parquet_field_id(
- *table_schema.nestedField.map_field.value_field.field_ptr,
- parquet_field.children[0].children[1], value_node, exist_field_id));
+ RETURN_IF_ERROR(
+ by_parquet_field_id(*table_schema.nestedField.map_field.value_field.field_ptr,
+ parquet_field.children[1], value_node, exist_field_id));
node = std::make_shared<TableSchemaChangeHelper::MapNode>(key_node, value_node);
break;
diff --git a/be/src/vec/exec/format/table/table_format_reader.h b/be/src/vec/exec/format/table/table_format_reader.h
index 1f2a41af930..00442dc45f9 100644
--- a/be/src/vec/exec/format/table/table_format_reader.h
+++ b/be/src/vec/exec/format/table/table_format_reader.h
@@ -133,6 +133,13 @@ public:
throw std::logic_error("get_children_node should not be called on base TableInfoNode");
};
+ virtual std::shared_ptr<Node> get_children_node_by_file_column_name(
+ std::string file_column_name) const {
+ throw std::logic_error(
+ "get_children_node_by_file_column_name should not be called on base "
+ "TableInfoNode");
+ };
+
virtual std::string children_file_column_name(std::string table_column_name) const {
throw std::logic_error(
"children_file_column_name should not be called on base TableInfoNode");
@@ -184,6 +191,19 @@ public:
return children.at(table_column_name).node;
}
+ std::shared_ptr<Node> get_children_node_by_file_column_name(
+ std::string file_column_name) const override {
+ // Search for the child by file column name
+ for (const auto& [table_name, child] : children) {
+ if (child.exists && child.column_name == file_column_name) {
+ return child.node;
+ }
+ }
+ // Not found - throw or return nullptr
+ throw std::runtime_error("File column name '" + file_column_name +
+ "' not found in struct children");
+ }
+
std::string children_file_column_name(std::string table_column_name) const override {
DCHECK(children.contains(table_column_name));
DCHECK(children_column_exists(table_column_name));
@@ -238,6 +258,11 @@ public:
return get_instance();
};
+ std::shared_ptr<Node> get_children_node_by_file_column_name(
+ std::string file_column_name) const override {
+ return get_instance();
+ };
+
std::string children_file_column_name(std::string table_column_name) const override {
return table_column_name;
}
@@ -388,5 +413,16 @@ public:
};
};
+struct ColumnIdResult {
+ std::set<uint64_t> column_ids;
+ std::set<uint64_t> filter_column_ids;
+
+ ColumnIdResult() = default; // Add default constructor
+
+ ColumnIdResult(std::set<uint64_t> column_ids_, std::set<uint64_t> filter_column_ids_)
+ : column_ids(std::move(column_ids_)),
+ filter_column_ids(std::move(filter_column_ids_)) {}
+};
+
#include "common/compile_check_end.h"
} // namespace doris::vectorized
diff --git a/be/src/vec/exec/scan/file_scanner.cpp b/be/src/vec/exec/scan/file_scanner.cpp
index c0b440fa6f3..1ca195c319e 100644
--- a/be/src/vec/exec/scan/file_scanner.cpp
+++ b/be/src/vec/exec/scan/file_scanner.cpp
@@ -335,7 +335,7 @@ Status FileScanner::_process_runtime_filters_partition_prune(bool& can_filter_al
return Status::OK();
}
-Status FileScanner::_process_conjuncts_for_dict_filter() {
+Status FileScanner::_process_conjuncts() {
_slot_id_to_filter_conjuncts.clear();
_not_single_slot_filter_conjuncts.clear();
for (auto& conjunct : _push_down_conjuncts) {
@@ -373,7 +373,7 @@ Status FileScanner::_process_late_arrival_conjuncts() {
for (size_t i = 0; i != _conjuncts.size(); ++i) {
RETURN_IF_ERROR(_conjuncts[i]->clone(_state, _push_down_conjuncts[i]));
}
- RETURN_IF_ERROR(_process_conjuncts_for_dict_filter());
+ RETURN_IF_ERROR(_process_conjuncts());
_discard_conjuncts();
}
if (_applied_rf_num == _total_rf_num) {
@@ -386,6 +386,8 @@ void FileScanner::_get_slot_ids(VExpr* expr, std::vector<int>* slot_ids) {
for (auto& child_expr : expr->children()) {
if (child_expr->is_slot_ref()) {
VSlotRef* slot_ref = reinterpret_cast<VSlotRef*>(child_expr.get());
+ SlotDescriptor* slot_desc = _state->desc_tbl().get_slot_descriptor(slot_ref->slot_id());
+ slot_desc->set_is_predicate(true);
slot_ids->emplace_back(slot_ref->slot_id());
} else {
_get_slot_ids(child_expr.get(), slot_ids);
diff --git a/be/src/vec/exec/scan/file_scanner.h b/be/src/vec/exec/scan/file_scanner.h
index 1a44d06c77b..8c4682ca89f 100644
--- a/be/src/vec/exec/scan/file_scanner.h
+++ b/be/src/vec/exec/scan/file_scanner.h
@@ -252,7 +252,7 @@ private:
void _init_runtime_filter_partition_prune_ctxs();
void _init_runtime_filter_partition_prune_block();
Status _process_runtime_filters_partition_prune(bool& is_partition_pruned);
- Status _process_conjuncts_for_dict_filter();
+ Status _process_conjuncts();
Status _process_late_arrival_conjuncts();
void _get_slot_ids(VExpr* expr, std::vector<int>* slot_ids);
Status _generate_truncate_columns(bool need_to_get_parsed_schema);
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/00000-0-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/00000-0-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc
new file mode 100644
index 00000000000..832aaef64a0
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/00000-0-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/00001-1-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/00001-1-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc
new file mode 100644
index 00000000000..36f589e393e
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/00001-1-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/00000-aa52c4c8-e0bd-4db6-b550-6323977446a4.metadata.json b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/00000-aa52c4c8-e0bd-4db6-b550-6323977446a4.metadata.json
new file mode 100644
index 00000000000..97208990039
--- /dev/null
+++ b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/00000-aa52c4c8-e0bd-4db6-b550-6323977446a4.metadata.json
@@ -0,0 +1,145 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "daf54708-3539-4de4-a130-322cd4c3fdc4",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg_orc",
+ "last-sequence-number" : 0,
+ "last-updated-ms" : 1758865887090,
+ "last-column-id" : 18,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 4,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 7,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 8,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 9,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 10,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 11,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 5,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 14,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 15,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 6,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 16,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "orc",
+ "write.parquet.compression-codec" : "zlib"
+ },
+ "current-snapshot-id" : -1,
+ "refs" : { },
+ "snapshots" : [ ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ ],
+ "metadata-log" : [ ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/00001-88a66011-3129-4bfb-9b4b-c1510c566d73.metadata.json b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/00001-88a66011-3129-4bfb-9b4b-c1510c566d73.metadata.json
new file mode 100644
index 00000000000..12ec38bbdcd
--- /dev/null
+++ b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/00001-88a66011-3129-4bfb-9b4b-c1510c566d73.metadata.json
@@ -0,0 +1,176 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "daf54708-3539-4de4-a130-322cd4c3fdc4",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg_orc",
+ "last-sequence-number" : 1,
+ "last-updated-ms" : 1758865954986,
+ "last-column-id" : 18,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 4,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 7,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 8,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 9,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 10,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 11,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 5,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 14,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 15,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 6,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 16,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "orc",
+ "write.parquet.compression-codec" : "zlib"
+ },
+ "current-snapshot-id" : 2197982135324349212,
+ "refs" : {
+ "main" : {
+ "snapshot-id" : 2197982135324349212,
+ "type" : "branch"
+ }
+ },
+ "snapshots" : [ {
+ "sequence-number" : 1,
+ "snapshot-id" : 2197982135324349212,
+ "timestamp-ms" : 1758865954986,
+ "summary" : {
+ "operation" : "append",
+ "spark.app.id" : "application_1738810850199_0674",
+ "added-data-files" : "2",
+ "added-records" : "10",
+ "added-files-size" : "4611",
+ "changed-partition-count" : "1",
+ "total-records" : "10",
+ "total-files-size" : "4611",
+ "total-data-files" : "2",
+ "total-delete-files" : "0",
+ "total-position-deletes" : "0",
+ "total-equality-deletes" : "0"
+ },
+ "manifest-list" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg_orc/metadata/snap-2197982135324349212-1-6a876742-85c2-4f84-ae24-292482fa9288.avro",
+ "schema-id" : 0
+ } ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ {
+ "timestamp-ms" : 1758865954986,
+ "snapshot-id" : 2197982135324349212
+ } ],
+ "metadata-log" : [ {
+ "timestamp-ms" : 1758865887090,
+ "metadata-file" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg_orc/metadata/00000-aa52c4c8-e0bd-4db6-b550-6323977446a4.metadata.json"
+ } ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/6a876742-85c2-4f84-ae24-292482fa9288-m0.avro b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/6a876742-85c2-4f84-ae24-292482fa9288-m0.avro
new file mode 100644
index 00000000000..1887220ebf0
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/6a876742-85c2-4f84-ae24-292482fa9288-m0.avro differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/snap-2197982135324349212-1-6a876742-85c2-4f84-ae24-292482fa9288.avro b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/snap-2197982135324349212-1-6a876742-85c2-4f84-ae24-292482fa9288.avro
new file mode 100644
index 00000000000..7218d1326d3
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_orc/metadata/snap-2197982135324349212-1-6a876742-85c2-4f84-ae24-292482fa9288.avro differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00000-0-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00000-0-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..160d000fed6
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00000-0-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00001-1-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00001-1-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..3ec61d58469
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00001-1-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00002-2-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00002-2-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..b0f56644ae5
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00002-2-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00003-3-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00003-3-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..dee411d7ef7
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00003-3-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00004-4-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00004-4-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..4264e841218
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00004-4-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00005-5-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00005-5-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..0264972b11d
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00005-5-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00006-6-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00006-6-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..3db75d8c01a
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00006-6-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00007-7-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00007-7-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet
new file mode 100644
index 00000000000..4838bb7d6e7
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/00007-7-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/00000-30938ed9-31af-46c8-9274-506c7f2709c9.metadata.json b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/00000-30938ed9-31af-46c8-9274-506c7f2709c9.metadata.json
new file mode 100644
index 00000000000..e6fc794e81f
--- /dev/null
+++ b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/00000-30938ed9-31af-46c8-9274-506c7f2709c9.metadata.json
@@ -0,0 +1,145 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "baf92432-1443-47fd-971a-0a9df2432e4a",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg",
+ "last-sequence-number" : 0,
+ "last-updated-ms" : 1758188914000,
+ "last-column-id" : 18,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 4,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 7,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 8,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 9,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 10,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 11,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 5,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 14,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 15,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 6,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 16,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "parquet",
+ "write.parquet.compression-codec" : "zstd"
+ },
+ "current-snapshot-id" : -1,
+ "refs" : { },
+ "snapshots" : [ ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ ],
+ "metadata-log" : [ ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/00001-b1b4732d-f823-4900-8185-3eb3cb23e4a9.metadata.json b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/00001-b1b4732d-f823-4900-8185-3eb3cb23e4a9.metadata.json
new file mode 100644
index 00000000000..d52c580cbf2
--- /dev/null
+++ b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/00001-b1b4732d-f823-4900-8185-3eb3cb23e4a9.metadata.json
@@ -0,0 +1,176 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "baf92432-1443-47fd-971a-0a9df2432e4a",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg",
+ "last-sequence-number" : 1,
+ "last-updated-ms" : 1758188959726,
+ "last-column-id" : 18,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 4,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 7,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 8,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 9,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 10,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 11,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 5,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 14,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 15,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 6,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 16,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "parquet",
+ "write.parquet.compression-codec" : "zstd"
+ },
+ "current-snapshot-id" : 1689809712605583552,
+ "refs" : {
+ "main" : {
+ "snapshot-id" : 1689809712605583552,
+ "type" : "branch"
+ }
+ },
+ "snapshots" : [ {
+ "sequence-number" : 1,
+ "snapshot-id" : 1689809712605583552,
+ "timestamp-ms" : 1758188959726,
+ "summary" : {
+ "operation" : "append",
+ "spark.app.id" : "application_1738810850199_0668",
+ "added-data-files" : "8",
+ "added-records" : "10",
+ "added-files-size" : "32551",
+ "changed-partition-count" : "1",
+ "total-records" : "10",
+ "total-files-size" : "32551",
+ "total-data-files" : "8",
+ "total-delete-files" : "0",
+ "total-position-deletes" : "0",
+ "total-equality-deletes" : "0"
+ },
+ "manifest-list" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg/metadata/snap-1689809712605583552-1-51fb6058-7fc2-4791-96a9-f3111933728a.avro",
+ "schema-id" : 0
+ } ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ {
+ "timestamp-ms" : 1758188959726,
+ "snapshot-id" : 1689809712605583552
+ } ],
+ "metadata-log" : [ {
+ "timestamp-ms" : 1758188914000,
+ "metadata-file" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/complex_user_profiles_iceberg/metadata/00000-30938ed9-31af-46c8-9274-506c7f2709c9.metadata.json"
+ } ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/51fb6058-7fc2-4791-96a9-f3111933728a-m0.avro b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/51fb6058-7fc2-4791-96a9-f3111933728a-m0.avro
new file mode 100644
index 00000000000..201c7908bc4
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/51fb6058-7fc2-4791-96a9-f3111933728a-m0.avro differ
diff --git a/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/snap-1689809712605583552-1-51fb6058-7fc2-4791-96a9-f3111933728a.avro b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/snap-1689809712605583552-1-51fb6058-7fc2-4791-96a9-f3111933728a.avro
new file mode 100644
index 00000000000..1f6548d7ba8
Binary files /dev/null and b/be/test/exec/test_data/complex_user_profiles_iceberg_parquet/metadata/snap-1689809712605583552-1-51fb6058-7fc2-4791-96a9-f3111933728a.avro differ
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_orc/data/00000-8-5a144c37-16a4-47c6-96db-0007175b5c90-0-00001.orc b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/data/00000-8-5a144c37-16a4-47c6-96db-0007175b5c90-0-00001.orc
new file mode 100644
index 00000000000..2f63195ff34
Binary files /dev/null and b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/data/00000-8-5a144c37-16a4-47c6-96db-0007175b5c90-0-00001.orc differ
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/00000-daa20949-0948-4f33-bcf4-5b6bcb0cd400.metadata.json b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/00000-daa20949-0948-4f33-bcf4-5b6bcb0cd400.metadata.json
new file mode 100644
index 00000000000..174f7a92c79
--- /dev/null
+++ b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/00000-daa20949-0948-4f33-bcf4-5b6bcb0cd400.metadata.json
@@ -0,0 +1,548 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "a93bcb6d-568f-4fc2-8235-dc8e218c1beb",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_orc",
+ "last-sequence-number" : 0,
+ "last-updated-ms" : 1760884376565,
+ "last-column-id" : 92,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 9,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 14,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 15,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 16,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 10,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 19,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 20,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 11,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 21,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 22,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 23,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ }, {
+ "id" : 4,
+ "name" : "tags",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 24,
+ "element" : "string",
+ "element-required" : false
+ }
+ }, {
+ "id" : 5,
+ "name" : "friends",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 25,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 26,
+ "name" : "user_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 27,
+ "name" : "nickname",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 28,
+ "name" : "friendship_level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 6,
+ "name" : "recent_activity",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 29,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 30,
+ "name" : "action",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 31,
+ "name" : "details",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 32,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 33,
+ "name" : "key",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 34,
+ "name" : "value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 7,
+ "name" : "attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 35,
+ "key" : "string",
+ "value-id" : 36,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 8,
+ "name" : "complex_attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 37,
+ "key" : "string",
+ "value-id" : 38,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 39,
+ "name" : "metadata",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 43,
+ "name" : "version",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 44,
+ "name" : "created_time",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 45,
+ "name" : "last_updated",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 46,
+ "name" : "quality_score",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ }, {
+ "id" : 40,
+ "name" : "historical_scores",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 47,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 48,
+ "name" : "period",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 49,
+ "name" : "score",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 50,
+ "name" : "components",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 52,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 53,
+ "name" : "component_name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 54,
+ "name" : "weight",
+ "required" : false,
+ "type" : "float"
+ }, {
+ "id" : 55,
+ "name" : "sub_scores",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 56,
+ "key" : "string",
+ "value-id" : 57,
+ "value" : "double",
+ "value-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 51,
+ "name" : "trends",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 58,
+ "name" : "direction",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 59,
+ "name" : "magnitude",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 60,
+ "name" : "confidence_interval",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 61,
+ "name" : "lower",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 62,
+ "name" : "upper",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 41,
+ "name" : "hierarchical_data",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 63,
+ "key" : "string",
+ "value-id" : 64,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 65,
+ "name" : "category",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 66,
+ "name" : "sub_items",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 68,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 69,
+ "name" : "item_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 70,
+ "name" : "properties",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 72,
+ "key" : "string",
+ "value-id" : 73,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 71,
+ "name" : "metrics",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 74,
+ "name" : "value",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 75,
+ "name" : "unit",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 67,
+ "name" : "summary",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 76,
+ "name" : "total_count",
+ "required" : false,
+ "type" : "int"
+ }, {
+ "id" : 77,
+ "name" : "average_value",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ }, {
+ "id" : 42,
+ "name" : "validation_rules",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 78,
+ "name" : "required",
+ "required" : false,
+ "type" : "boolean"
+ }, {
+ "id" : 79,
+ "name" : "constraints",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 81,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 82,
+ "name" : "rule_type",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 83,
+ "name" : "parameters",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 85,
+ "key" : "string",
+ "value-id" : 86,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 84,
+ "name" : "error_message",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 80,
+ "name" : "complex_constraint",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 87,
+ "name" : "logic_operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 88,
+ "name" : "operands",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 89,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 90,
+ "name" : "field_path",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 91,
+ "name" : "operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 92,
+ "name" : "comparison_value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "orc",
+ "write.parquet.compression-codec" : "zstd"
+ },
+ "current-snapshot-id" : -1,
+ "refs" : { },
+ "snapshots" : [ ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ ],
+ "metadata-log" : [ ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/00001-5c6ddc20-6aa8-4864-9d15-546dd5a6bae4.metadata.json b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/00001-5c6ddc20-6aa8-4864-9d15-546dd5a6bae4.metadata.json
new file mode 100644
index 00000000000..3fc692fea56
--- /dev/null
+++ b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/00001-5c6ddc20-6aa8-4864-9d15-546dd5a6bae4.metadata.json
@@ -0,0 +1,579 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "a93bcb6d-568f-4fc2-8235-dc8e218c1beb",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_orc",
+ "last-sequence-number" : 1,
+ "last-updated-ms" : 1760884424603,
+ "last-column-id" : 92,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 9,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 14,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 15,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 16,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 10,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 19,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 20,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 11,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 21,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 22,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 23,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ }, {
+ "id" : 4,
+ "name" : "tags",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 24,
+ "element" : "string",
+ "element-required" : false
+ }
+ }, {
+ "id" : 5,
+ "name" : "friends",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 25,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 26,
+ "name" : "user_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 27,
+ "name" : "nickname",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 28,
+ "name" : "friendship_level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 6,
+ "name" : "recent_activity",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 29,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 30,
+ "name" : "action",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 31,
+ "name" : "details",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 32,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 33,
+ "name" : "key",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 34,
+ "name" : "value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 7,
+ "name" : "attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 35,
+ "key" : "string",
+ "value-id" : 36,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 8,
+ "name" : "complex_attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 37,
+ "key" : "string",
+ "value-id" : 38,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 39,
+ "name" : "metadata",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 43,
+ "name" : "version",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 44,
+ "name" : "created_time",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 45,
+ "name" : "last_updated",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 46,
+ "name" : "quality_score",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ }, {
+ "id" : 40,
+ "name" : "historical_scores",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 47,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 48,
+ "name" : "period",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 49,
+ "name" : "score",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 50,
+ "name" : "components",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 52,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 53,
+ "name" : "component_name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 54,
+ "name" : "weight",
+ "required" : false,
+ "type" : "float"
+ }, {
+ "id" : 55,
+ "name" : "sub_scores",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 56,
+ "key" : "string",
+ "value-id" : 57,
+ "value" : "double",
+ "value-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 51,
+ "name" : "trends",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 58,
+ "name" : "direction",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 59,
+ "name" : "magnitude",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 60,
+ "name" : "confidence_interval",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 61,
+ "name" : "lower",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 62,
+ "name" : "upper",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 41,
+ "name" : "hierarchical_data",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 63,
+ "key" : "string",
+ "value-id" : 64,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 65,
+ "name" : "category",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 66,
+ "name" : "sub_items",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 68,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 69,
+ "name" : "item_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 70,
+ "name" : "properties",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 72,
+ "key" : "string",
+ "value-id" : 73,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 71,
+ "name" : "metrics",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 74,
+ "name" : "value",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 75,
+ "name" : "unit",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 67,
+ "name" : "summary",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 76,
+ "name" : "total_count",
+ "required" : false,
+ "type" : "int"
+ }, {
+ "id" : 77,
+ "name" : "average_value",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ }, {
+ "id" : 42,
+ "name" : "validation_rules",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 78,
+ "name" : "required",
+ "required" : false,
+ "type" : "boolean"
+ }, {
+ "id" : 79,
+ "name" : "constraints",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 81,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 82,
+ "name" : "rule_type",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 83,
+ "name" : "parameters",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 85,
+ "key" : "string",
+ "value-id" : 86,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 84,
+ "name" : "error_message",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 80,
+ "name" : "complex_constraint",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 87,
+ "name" : "logic_operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 88,
+ "name" : "operands",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 89,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 90,
+ "name" : "field_path",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 91,
+ "name" : "operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 92,
+ "name" : "comparison_value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "orc",
+ "write.parquet.compression-codec" : "zstd"
+ },
+ "current-snapshot-id" : 514179801967160228,
+ "refs" : {
+ "main" : {
+ "snapshot-id" : 514179801967160228,
+ "type" : "branch"
+ }
+ },
+ "snapshots" : [ {
+ "sequence-number" : 1,
+ "snapshot-id" : 514179801967160228,
+ "timestamp-ms" : 1760884424603,
+ "summary" : {
+ "operation" : "append",
+ "spark.app.id" : "application_1738810850199_0681",
+ "added-data-files" : "1",
+ "added-records" : "1",
+ "added-files-size" : "7181",
+ "changed-partition-count" : "1",
+ "total-records" : "1",
+ "total-files-size" : "7181",
+ "total-data-files" : "1",
+ "total-delete-files" : "0",
+ "total-position-deletes" : "0",
+ "total-equality-deletes" : "0"
+ },
+ "manifest-list" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_orc/metadata/snap-514179801967160228-1-d9aed717-61ef-422b-b044-a16ca31f9e86.avro",
+ "schema-id" : 0
+ } ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ {
+ "timestamp-ms" : 1760884424603,
+ "snapshot-id" : 514179801967160228
+ } ],
+ "metadata-log" : [ {
+ "timestamp-ms" : 1760884376565,
+ "metadata-file" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_orc/metadata/00000-daa20949-0948-4f33-bcf4-5b6bcb0cd400.metadata.json"
+ } ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/d9aed717-61ef-422b-b044-a16ca31f9e86-m0.avro b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/d9aed717-61ef-422b-b044-a16ca31f9e86-m0.avro
new file mode 100644
index 00000000000..d8b578a8f86
--- /dev/null
+++ b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/d9aed717-61ef-422b-b044-a16ca31f9e86-m0.avro
@@ -0,0 +1 @@
+Objschema�]{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"id","required":false,"type":"long"},{"id":2,"name":"name","required":false,"type":"string"},{"id":3,"name":"profile","required":false,"type":{"type":"struct","fields":[{"id":9,"name":"address","required":false,"type":{"type":"struct","fields":[{"id":12,"name":"street","required":false,"type":"string"},{"id":13,"name":"city","required":false,"type":"string"},{"id":14,"name":"coordinates","required":false,"type":{"type":"struc [...]
format-version2"partition-spec-id0iceberg.schema�+{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":3,"name":"sequence_number","required":false,"type":"long"},{"id":4,"name":"file_sequence_number","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":134,"name":"content","required":true,"type":"int","doc":"Contents of the file: 0=data, 1=position delet [...]
partition-spec[]contentdata v�7�l���r���P�Kq=��l��XY~,�ð,�5;3�*baia��6_�kێͮ"!!!�co��{�B=��l��}8�s�8̇�����m7 `��bg�4{dx��N7�̕N��j`�9Q�cmz��A�[�]�s��{��r.6[A���7�n���3}��Vo�����7\3�S6�5�j�V}�5�Vq+ިeX=i٥�;y~�o/��H�I�"-я��s� T�I��GA�A�J�*�,��1��'�8&�����}�:�\��.箨���������"X��d[,OD��\����ܐw7 ��p���C�#J?&����H�"���/(�M�%�^��!�^����[��H{G�}ʾ'��>�~@\dNp��8łӬp?gXr�s<�*k�s���gEqHA *@9�@����\�a���ul��������봻9����ѵ���.�a0�r����P��6��Q��T*ò-;+��am�i���ݜ2�iW�親,�=<rbnr�_��O s2�v�7�
�l���r�
\ No newline at end of file
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/snap-514179801967160228-1-d9aed717-61ef-422b-b044-a16ca31f9e86.avro b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/snap-514179801967160228-1-d9aed717-61ef-422b-b044-a16ca31f9e86.avro
new file mode 100644
index 00000000000..7621edf3b7f
Binary files /dev/null and b/be/test/exec/test_data/nested_user_profiles_iceberg_orc/metadata/snap-514179801967160228-1-d9aed717-61ef-422b-b044-a16ca31f9e86.avro differ
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/data/00000-9-a7e0135f-d581-40e4-8d56-a929aded99e4-0-00001.parquet b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/data/00000-9-a7e0135f-d581-40e4-8d56-a929aded99e4-0-00001.parquet
new file mode 100644
index 00000000000..014ad377122
Binary files /dev/null and b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/data/00000-9-a7e0135f-d581-40e4-8d56-a929aded99e4-0-00001.parquet differ
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/00000-ed5ae7dc-be1a-410b-ae74-7cf04ad10765.metadata.json b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/00000-ed5ae7dc-be1a-410b-ae74-7cf04ad10765.metadata.json
new file mode 100644
index 00000000000..8bf743851ff
--- /dev/null
+++ b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/00000-ed5ae7dc-be1a-410b-ae74-7cf04ad10765.metadata.json
@@ -0,0 +1,548 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "d3b72ae1-4880-4c3e-9111-ca1af3cc9c7c",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_parquet",
+ "last-sequence-number" : 0,
+ "last-updated-ms" : 1760884395135,
+ "last-column-id" : 92,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 9,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 14,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 15,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 16,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 10,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 19,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 20,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 11,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 21,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 22,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 23,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ }, {
+ "id" : 4,
+ "name" : "tags",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 24,
+ "element" : "string",
+ "element-required" : false
+ }
+ }, {
+ "id" : 5,
+ "name" : "friends",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 25,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 26,
+ "name" : "user_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 27,
+ "name" : "nickname",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 28,
+ "name" : "friendship_level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 6,
+ "name" : "recent_activity",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 29,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 30,
+ "name" : "action",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 31,
+ "name" : "details",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 32,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 33,
+ "name" : "key",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 34,
+ "name" : "value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 7,
+ "name" : "attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 35,
+ "key" : "string",
+ "value-id" : 36,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 8,
+ "name" : "complex_attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 37,
+ "key" : "string",
+ "value-id" : 38,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 39,
+ "name" : "metadata",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 43,
+ "name" : "version",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 44,
+ "name" : "created_time",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 45,
+ "name" : "last_updated",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 46,
+ "name" : "quality_score",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ }, {
+ "id" : 40,
+ "name" : "historical_scores",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 47,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 48,
+ "name" : "period",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 49,
+ "name" : "score",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 50,
+ "name" : "components",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 52,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 53,
+ "name" : "component_name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 54,
+ "name" : "weight",
+ "required" : false,
+ "type" : "float"
+ }, {
+ "id" : 55,
+ "name" : "sub_scores",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 56,
+ "key" : "string",
+ "value-id" : 57,
+ "value" : "double",
+ "value-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 51,
+ "name" : "trends",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 58,
+ "name" : "direction",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 59,
+ "name" : "magnitude",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 60,
+ "name" : "confidence_interval",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 61,
+ "name" : "lower",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 62,
+ "name" : "upper",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 41,
+ "name" : "hierarchical_data",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 63,
+ "key" : "string",
+ "value-id" : 64,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 65,
+ "name" : "category",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 66,
+ "name" : "sub_items",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 68,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 69,
+ "name" : "item_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 70,
+ "name" : "properties",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 72,
+ "key" : "string",
+ "value-id" : 73,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 71,
+ "name" : "metrics",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 74,
+ "name" : "value",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 75,
+ "name" : "unit",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 67,
+ "name" : "summary",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 76,
+ "name" : "total_count",
+ "required" : false,
+ "type" : "int"
+ }, {
+ "id" : 77,
+ "name" : "average_value",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ }, {
+ "id" : 42,
+ "name" : "validation_rules",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 78,
+ "name" : "required",
+ "required" : false,
+ "type" : "boolean"
+ }, {
+ "id" : 79,
+ "name" : "constraints",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 81,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 82,
+ "name" : "rule_type",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 83,
+ "name" : "parameters",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 85,
+ "key" : "string",
+ "value-id" : 86,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 84,
+ "name" : "error_message",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 80,
+ "name" : "complex_constraint",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 87,
+ "name" : "logic_operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 88,
+ "name" : "operands",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 89,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 90,
+ "name" : "field_path",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 91,
+ "name" : "operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 92,
+ "name" : "comparison_value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "parquet",
+ "write.parquet.compression-codec" : "zstd"
+ },
+ "current-snapshot-id" : -1,
+ "refs" : { },
+ "snapshots" : [ ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ ],
+ "metadata-log" : [ ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/00001-a1156265-e89e-4c9f-a0fb-7f3075d4e8f9.metadata.json b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/00001-a1156265-e89e-4c9f-a0fb-7f3075d4e8f9.metadata.json
new file mode 100644
index 00000000000..de90e000742
--- /dev/null
+++ b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/00001-a1156265-e89e-4c9f-a0fb-7f3075d4e8f9.metadata.json
@@ -0,0 +1,579 @@
+{
+ "format-version" : 2,
+ "table-uuid" : "d3b72ae1-4880-4c3e-9111-ca1af3cc9c7c",
+ "location" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_parquet",
+ "last-sequence-number" : 1,
+ "last-updated-ms" : 1760884432817,
+ "last-column-id" : 92,
+ "current-schema-id" : 0,
+ "schemas" : [ {
+ "type" : "struct",
+ "schema-id" : 0,
+ "fields" : [ {
+ "id" : 1,
+ "name" : "id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 2,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 3,
+ "name" : "profile",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 9,
+ "name" : "address",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 12,
+ "name" : "street",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 13,
+ "name" : "city",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 14,
+ "name" : "coordinates",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 15,
+ "name" : "lat",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 16,
+ "name" : "lng",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 10,
+ "name" : "contact",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 17,
+ "name" : "email",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 18,
+ "name" : "phone",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 19,
+ "name" : "country_code",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 20,
+ "name" : "number",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ }
+ }, {
+ "id" : 11,
+ "name" : "hobbies",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 21,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 22,
+ "name" : "name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 23,
+ "name" : "level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ }, {
+ "id" : 4,
+ "name" : "tags",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 24,
+ "element" : "string",
+ "element-required" : false
+ }
+ }, {
+ "id" : 5,
+ "name" : "friends",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 25,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 26,
+ "name" : "user_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 27,
+ "name" : "nickname",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 28,
+ "name" : "friendship_level",
+ "required" : false,
+ "type" : "int"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 6,
+ "name" : "recent_activity",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 29,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 30,
+ "name" : "action",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 31,
+ "name" : "details",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 32,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 33,
+ "name" : "key",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 34,
+ "name" : "value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 7,
+ "name" : "attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 35,
+ "key" : "string",
+ "value-id" : 36,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 8,
+ "name" : "complex_attributes",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 37,
+ "key" : "string",
+ "value-id" : 38,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 39,
+ "name" : "metadata",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 43,
+ "name" : "version",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 44,
+ "name" : "created_time",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 45,
+ "name" : "last_updated",
+ "required" : false,
+ "type" : "timestamptz"
+ }, {
+ "id" : 46,
+ "name" : "quality_score",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ }, {
+ "id" : 40,
+ "name" : "historical_scores",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 47,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 48,
+ "name" : "period",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 49,
+ "name" : "score",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 50,
+ "name" : "components",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 52,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 53,
+ "name" : "component_name",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 54,
+ "name" : "weight",
+ "required" : false,
+ "type" : "float"
+ }, {
+ "id" : 55,
+ "name" : "sub_scores",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 56,
+ "key" : "string",
+ "value-id" : 57,
+ "value" : "double",
+ "value-required" : false
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 51,
+ "name" : "trends",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 58,
+ "name" : "direction",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 59,
+ "name" : "magnitude",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 60,
+ "name" : "confidence_interval",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 61,
+ "name" : "lower",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 62,
+ "name" : "upper",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 41,
+ "name" : "hierarchical_data",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 63,
+ "key" : "string",
+ "value-id" : 64,
+ "value" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 65,
+ "name" : "category",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 66,
+ "name" : "sub_items",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 68,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 69,
+ "name" : "item_id",
+ "required" : false,
+ "type" : "long"
+ }, {
+ "id" : 70,
+ "name" : "properties",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 72,
+ "key" : "string",
+ "value-id" : 73,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 71,
+ "name" : "metrics",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 74,
+ "name" : "value",
+ "required" : false,
+ "type" : "double"
+ }, {
+ "id" : 75,
+ "name" : "unit",
+ "required" : false,
+ "type" : "string"
+ } ]
+ }
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 67,
+ "name" : "summary",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 76,
+ "name" : "total_count",
+ "required" : false,
+ "type" : "int"
+ }, {
+ "id" : 77,
+ "name" : "average_value",
+ "required" : false,
+ "type" : "double"
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ }, {
+ "id" : 42,
+ "name" : "validation_rules",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 78,
+ "name" : "required",
+ "required" : false,
+ "type" : "boolean"
+ }, {
+ "id" : 79,
+ "name" : "constraints",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 81,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 82,
+ "name" : "rule_type",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 83,
+ "name" : "parameters",
+ "required" : false,
+ "type" : {
+ "type" : "map",
+ "key-id" : 85,
+ "key" : "string",
+ "value-id" : 86,
+ "value" : "string",
+ "value-required" : false
+ }
+ }, {
+ "id" : 84,
+ "name" : "error_message",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ }, {
+ "id" : 80,
+ "name" : "complex_constraint",
+ "required" : false,
+ "type" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 87,
+ "name" : "logic_operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 88,
+ "name" : "operands",
+ "required" : false,
+ "type" : {
+ "type" : "list",
+ "element-id" : 89,
+ "element" : {
+ "type" : "struct",
+ "fields" : [ {
+ "id" : 90,
+ "name" : "field_path",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 91,
+ "name" : "operator",
+ "required" : false,
+ "type" : "string"
+ }, {
+ "id" : 92,
+ "name" : "comparison_value",
+ "required" : false,
+ "type" : "string"
+ } ]
+ },
+ "element-required" : false
+ }
+ } ]
+ }
+ } ]
+ }
+ } ]
+ },
+ "value-required" : false
+ }
+ } ]
+ } ],
+ "default-spec-id" : 0,
+ "partition-specs" : [ {
+ "spec-id" : 0,
+ "fields" : [ ]
+ } ],
+ "last-partition-id" : 999,
+ "default-sort-order-id" : 0,
+ "sort-orders" : [ {
+ "order-id" : 0,
+ "fields" : [ ]
+ } ],
+ "properties" : {
+ "owner" : "hive",
+ "write.format.default" : "parquet",
+ "write.parquet.compression-codec" : "zstd"
+ },
+ "current-snapshot-id" : 6622777947381868002,
+ "refs" : {
+ "main" : {
+ "snapshot-id" : 6622777947381868002,
+ "type" : "branch"
+ }
+ },
+ "snapshots" : [ {
+ "sequence-number" : 1,
+ "snapshot-id" : 6622777947381868002,
+ "timestamp-ms" : 1760884432817,
+ "summary" : {
+ "operation" : "append",
+ "spark.app.id" : "application_1738810850199_0681",
+ "added-data-files" : "1",
+ "added-records" : "1",
+ "added-files-size" : "21586",
+ "changed-partition-count" : "1",
+ "total-records" : "1",
+ "total-files-size" : "21586",
+ "total-data-files" : "1",
+ "total-delete-files" : "0",
+ "total-position-deletes" : "0",
+ "total-equality-deletes" : "0"
+ },
+ "manifest-list" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_parquet/metadata/snap-6622777947381868002-1-bee22dff-a1d3-420d-bfba-c1b4193ab0df.avro",
+ "schema-id" : 0
+ } ],
+ "statistics" : [ ],
+ "partition-statistics" : [ ],
+ "snapshot-log" : [ {
+ "timestamp-ms" : 1760884432817,
+ "snapshot-id" : 6622777947381868002
+ } ],
+ "metadata-log" : [ {
+ "timestamp-ms" : 1760884395135,
+ "metadata-file" : "hdfs://hdfs-cluster/user/hive/warehouse/cqtest.db/nested_user_profiles_iceberg_parquet/metadata/00000-ed5ae7dc-be1a-410b-ae74-7cf04ad10765.metadata.json"
+ } ]
+}
\ No newline at end of file
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/bee22dff-a1d3-420d-bfba-c1b4193ab0df-m0.avro b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/bee22dff-a1d3-420d-bfba-c1b4193ab0df-m0.avro
new file mode 100644
index 00000000000..6071b684120
--- /dev/null
+++ b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/bee22dff-a1d3-420d-bfba-c1b4193ab0df-m0.avro
@@ -0,0 +1,5 @@
+Objschema�]{"type":"struct","schema-id":0,"fields":[{"id":1,"name":"id","required":false,"type":"long"},{"id":2,"name":"name","required":false,"type":"string"},{"id":3,"name":"profile","required":false,"type":{"type":"struct","fields":[{"id":9,"name":"address","required":false,"type":{"type":"struct","fields":[{"id":12,"name":"street","required":false,"type":"string"},{"id":13,"name":"city","required":false,"type":"string"},{"id":14,"name":"coordinates","required":false,"type":{"type":"struc [...]
format-version2"partition-spec-id0iceberg.schema�+{"type":"struct","schema-id":0,"fields":[{"id":0,"name":"status","required":true,"type":"int"},{"id":1,"name":"snapshot_id","required":false,"type":"long"},{"id":3,"name":"sequence_number","required":false,"type":"long"},{"id":4,"name":"file_sequence_number","required":false,"type":"long"},{"id":2,"name":"data_file","required":true,"type":{"type":"struct","fields":[{"id":134,"name":"content","required":true,"type":"int","doc":"Contents of the file: 0=data, 1=position delet [...]
partition-spec[]contentdata ��b���}zd�ɤb����
+Œ�kaǟ���Lr���!AB�^ri��H�g��D*�?bC�K�br���rM%� �A�C)����!H)����M�:��(����{����wx����~����{�����߾�F xIe�T�:�X4V�fW�,�+ -��XZ���`q���v@-���Լ3�7�Z�bh�|��4�n�T��U����%Gb\T.iRh,R�H,$�%-,��HTT�qE��x�U$љN gg���Z��@��D:�ri�1\�䄜����K#]R;�G�Xaܘl'�qʚ��픑62F�X�
+��C�4_��m�Qm�-���`���:>� �`�M�na���m� ��a�]l�ak�|������lGn& �%�i�p�(�h�&�7EӔ��)CYZ����.�eS��Ԣ6u��H��m �C�D�B�#m# �v�v����������߁7����0� Q��8LB�`���4d �P 0�Є����EXG�@�!l"l!��;�{��GlShH`,�q��泌�a��;��㓵rUHԴ�+�
+�� 3J�*����MmMX�Y��ȗ�H�ٓ[�DŽ���x�ʝ�,�??�ν�
+:�Ԛ&k���(N�)��]��H$"J!)d�>_�N�� 7x�3�������E�uR�����s������O�q~�_�����b���}zd�ɤb���
\ No newline at end of file
diff --git a/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/snap-6622777947381868002-1-bee22dff-a1d3-420d-bfba-c1b4193ab0df.avro b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/snap-6622777947381868002-1-bee22dff-a1d3-420d-bfba-c1b4193ab0df.avro
new file mode 100644
index 00000000000..a3eee4e6a11
Binary files /dev/null and b/be/test/exec/test_data/nested_user_profiles_iceberg_parquet/metadata/snap-6622777947381868002-1-bee22dff-a1d3-420d-bfba-c1b4193ab0df.avro differ
diff --git a/be/test/exec/test_data/nested_user_profiles_orc/_SUCCESS b/be/test/exec/test_data/nested_user_profiles_orc/_SUCCESS
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/be/test/exec/test_data/nested_user_profiles_orc/part-00000-62614f23-05d1-4043-a533-b155ef52b720-c000.snappy.orc b/be/test/exec/test_data/nested_user_profiles_orc/part-00000-62614f23-05d1-4043-a533-b155ef52b720-c000.snappy.orc
new file mode 100644
index 00000000000..8a0f8bc17b4
Binary files /dev/null and b/be/test/exec/test_data/nested_user_profiles_orc/part-00000-62614f23-05d1-4043-a533-b155ef52b720-c000.snappy.orc differ
diff --git a/be/test/exec/test_data/nested_user_profiles_parquet/_SUCCESS b/be/test/exec/test_data/nested_user_profiles_parquet/_SUCCESS
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/be/test/exec/test_data/nested_user_profiles_parquet/part-00000-64a7a390-1a03-4efc-ab51-557e9369a1f9-c000.snappy.parquet b/be/test/exec/test_data/nested_user_profiles_parquet/part-00000-64a7a390-1a03-4efc-ab51-557e9369a1f9-c000.snappy.parquet
new file mode 100644
index 00000000000..73ae0061c21
Binary files /dev/null and b/be/test/exec/test_data/nested_user_profiles_parquet/part-00000-64a7a390-1a03-4efc-ab51-557e9369a1f9-c000.snappy.parquet differ
diff --git a/be/test/vec/exec/format/parquet/parquet_thrift_test.cpp b/be/test/vec/exec/format/parquet/parquet_thrift_test.cpp
index 797ab9fc7db..edd131a2410 100644
--- a/be/test/vec/exec/format/parquet/parquet_thrift_test.cpp
+++ b/be/test/vec/exec/format/parquet/parquet_thrift_test.cpp
@@ -140,20 +140,18 @@ TEST_F(ParquetThriftReaderTest, complex_nested_file) {
ASSERT_EQ(schemaDescriptor.get_column_index("hobby"), 2);
auto hobby = schemaDescriptor.get_column("hobby");
- // should be parsed as ARRAY<MAP<STRUCT<STRING,STRING>>>
- ASSERT_TRUE(hobby->children.size() == 1 && hobby->children[0].children.size() == 1 &&
- hobby->children[0].children[0].children.size() == 2);
+ // should be parsed as ARRAY<MAP<KEY,VALUE>>
+ ASSERT_TRUE(hobby->children.size() == 1 && hobby->children[0].children.size() == 2);
ASSERT_TRUE(hobby->data_type->get_primitive_type() == TYPE_ARRAY &&
- hobby->children[0].data_type->get_primitive_type() == TYPE_MAP &&
- hobby->children[0].children[0].data_type->get_primitive_type() == TYPE_STRUCT);
+ hobby->children[0].data_type->get_primitive_type() == TYPE_MAP);
// hobby(opt) --- bag(rep) --- array_element(opt) --- map(rep)
// \------- key(req)
// \------- value(opt)
// R=0,D=1 R=1,D=2 R=1,D=3 R=2,D=4
// \------ R=2,D=4
// \------ R=2,D=5
- auto h_key = hobby->children[0].children[0].children[0];
- auto h_value = hobby->children[0].children[0].children[1];
+ auto h_key = hobby->children[0];
+ auto h_value = hobby->children[1];
ASSERT_TRUE(h_key.repetition_level == 2 && h_key.definition_level == 4);
ASSERT_TRUE(h_value.repetition_level == 2 && h_value.definition_level == 5);
diff --git a/be/test/vec/exec/format/table/hive/hive_reader_create_column_ids_test.cpp b/be/test/vec/exec/format/table/hive/hive_reader_create_column_ids_test.cpp
new file mode 100644
index 00000000000..7efca721fcd
--- /dev/null
+++ b/be/test/vec/exec/format/table/hive/hive_reader_create_column_ids_test.cpp
@@ -0,0 +1,1179 @@
+// 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 <cctz/time_zone.h>
+#include <gen_cpp/Descriptors_types.h>
+#include <gen_cpp/PaloInternalService_types.h>
+#include <gen_cpp/PlanNodes_types.h>
+#include <gen_cpp/Types_types.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "common/object_pool.h"
+#include "exec/olap_common.h"
+#include "io/fs/file_meta_cache.h"
+#include "io/fs/file_reader_writer_fwd.h"
+#include "io/fs/file_system.h"
+#include "io/fs/local_file_system.h"
+#include "runtime/descriptors.h"
+#include "runtime/runtime_state.h"
+#include "util/timezone_utils.h"
+#include "vec/columns/column.h"
+#include "vec/columns/column_array.h"
+#include "vec/columns/column_nullable.h"
+#include "vec/columns/column_struct.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_factory.hpp"
+#include "vec/data_types/data_type_nullable.h"
+#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_string.h"
+#include "vec/data_types/data_type_struct.h"
+#include "vec/exec/format/parquet/vparquet_reader.h"
+#include "vec/exec/format/table/hive_reader.h"
+
+namespace doris::vectorized {
+
+// Define the column access path configuration structure
+struct ColumnAccessPathConfig {
+ std::string column_name;
+ std::vector<std::vector<std::string>> all_column_paths; // For all_column_access_paths
+ std::vector<std::vector<std::string>> predicate_paths; // For predicate_column_access_paths
+ bool use_name_paths = true; // true=use field names, false=use indices
+};
+
+// ORC column IDs are assigned in a tree-increment order, root is 0 and children increment sequentially.
+// 0: struct (table/root)
+// 1: id (bigint)
+// 2: name (string)
+// 3: profile (struct)
+// 4: address (struct)
+// 5: street (string)
+// 6: city (string)
+// 7: coordinates (struct)
+// 8: lat (double)
+// 9: lng (double)
+// 10: contact (struct)
+// 11: email (string)
+// 12: phone (struct)
+// 13: country_code (string)
+// 14: number (string)
+// 15: hobbies (array)
+// 16: element (struct)
+// 17: name (string)
+// 18: level (int)
+// 19: tags (array)
+// 20: element (string)
+// 21: friends (array)
+// 22: element (struct)
+// 23: user_id (bigint)
+// 24: nickname (string)
+// 25: friendship_level (int)
+// 26: recent_activity (array)
+// 27: element (struct)
+// 28: action (string)
+// 29: details (array)
+// 30: element (struct)
+// 31: key (string)
+// 32: value (string)
+// 33: attributes (map)
+// 34: key (string)
+// 35: value (string)
+// 36: complex_attributes (map)
+// 37: key (string)
+// 38: value (struct)
+// 39: metadata (struct)
+// 40: version (string)
+// 41: created_time (timestamp)
+// 42: last_updated (timestamp)
+// 43: quality_score (double)
+// 44: historical_scores (array)
+// 45: element (struct)
+// 46: period (string)
+// 47: score (double)
+// 48: components (array)
+// 49: element (struct)
+// 50: component_name (string)
+// 51: weight (float)
+// 52: sub_scores (map)
+// 53: key (string)
+// 54: value (double)
+// 55: trends (struct)
+// 56: direction (string)
+// 57: magnitude (double)
+// 58: confidence_interval (struct)
+// 59: lower (double)
+// 60: upper (double)
+// 61: hierarchical_data (map)
+// 62: key (string)
+// 63: value (struct)
+// 64: category (string)
+// 65: sub_items (array)
+// 66: element (struct)
+// 67: item_id (bigint)
+// 68: properties (map)
+// 69: key (string)
+// 70: value (string)
+// 71: metrics (struct)
+// 72: value (double)
+// 73: unit (string)
+// 74: summary (struct)
+// 75: total_count (int)
+// 76: average_value (double)
+// 77: validation_rules (struct)
+// 78: required (boolean)
+// 79: constraints (array)
+// 80: element (struct)
+// 81: rule_type (string)
+// 82: parameters (map)
+// 83: key (string)
+// 84: value (string)
+// 85: error_message (string)
+// 86: complex_constraint (struct)
+// 87: logic_operator (string)
+// 88: operands (array)
+// 89: element (struct)
+// 90: field_path (string)
+// 91: operator (string)
+// 92: comparison_value (string)
+
+class HiveReaderCreateColumnIdsTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ cache = std::make_unique<doris::FileMetaCache>(1024);
+
+ // Setup timezone
+ doris::TimezoneUtils::find_cctz_time_zone(doris::TimezoneUtils::default_time_zone,
+ timezone_obj);
+ }
+
+ void TearDown() override { cache.reset(); }
+
+ // Helper function to create tuple descriptor
+ const TupleDescriptor* create_tuple_descriptor(
+ DescriptorTbl** desc_tbl, ObjectPool& obj_pool, TDescriptorTable& t_desc_table,
+ TTableDescriptor& t_table_desc, const std::vector<std::string>& column_names,
+ const std::vector<int>& column_positions,
+ const std::vector<TPrimitiveType::type>& column_types,
+ const std::vector<ColumnAccessPathConfig>& access_configs = {}) {
+ // Create table descriptor with complex schema
+ auto create_table_desc = [this, &access_configs](
+ TDescriptorTable& t_desc_table,
+ TTableDescriptor& t_table_desc,
+ const std::vector<std::string>& table_column_names,
+ const std::vector<int>& table_column_positions,
+ const std::vector<TPrimitiveType::type>& types) {
+ t_table_desc.__set_id(0);
+ t_table_desc.__set_tableType(TTableType::HIVE_TABLE);
+ t_table_desc.__set_numCols(0);
+ t_table_desc.__set_numClusteringCols(0);
+ t_desc_table.tableDescriptors.push_back(t_table_desc);
+ t_desc_table.__isset.tableDescriptors = true;
+ for (int i = 0; i < table_column_names.size(); i++) {
+ TSlotDescriptor tslot_desc;
+ TTypeDesc type;
+ if (table_column_names[i] == "id") {
+ // id: bigint
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(TPrimitiveType::BIGINT);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "name") {
+ // name: string
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(TPrimitiveType::STRING);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "tags") {
+ // tags: array<string>
+ TTypeNode array_node;
+ array_node.__set_type(TTypeNodeType::ARRAY);
+ array_node.__set_contains_nulls({true});
+ type.types.push_back(array_node);
+ TTypeNode element_node;
+ element_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType element_scalar;
+ element_scalar.__set_type(TPrimitiveType::STRING);
+ element_node.__set_scalar_type(element_scalar);
+ type.types.push_back(element_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "friends") {
+ // friends: array<struct<user_id:bigint, nickname:string, friendship_level:int>>
+ TTypeNode array_node;
+ array_node.__set_type(TTypeNodeType::ARRAY);
+ array_node.__set_contains_nulls({true});
+ type.types.push_back(array_node);
+ TTypeNode element_struct_node;
+ element_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> element_fields;
+ TStructField user_id_field;
+ user_id_field.__set_name("user_id");
+ user_id_field.__set_contains_null(true);
+ element_fields.push_back(user_id_field);
+ TStructField nickname_field;
+ nickname_field.__set_name("nickname");
+ nickname_field.__set_contains_null(true);
+ element_fields.push_back(nickname_field);
+ TStructField friendship_level_field;
+ friendship_level_field.__set_name("friendship_level");
+ friendship_level_field.__set_contains_null(true);
+ element_fields.push_back(friendship_level_field);
+ element_struct_node.__set_struct_fields(element_fields);
+ type.types.push_back(element_struct_node);
+ // user_id: bigint
+ TTypeNode user_id_node;
+ user_id_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType user_id_scalar;
+ user_id_scalar.__set_type(TPrimitiveType::BIGINT);
+ user_id_node.__set_scalar_type(user_id_scalar);
+ type.types.push_back(user_id_node);
+ // nickname: string
+ TTypeNode nickname_node;
+ nickname_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType nickname_scalar;
+ nickname_scalar.__set_type(TPrimitiveType::STRING);
+ nickname_node.__set_scalar_type(nickname_scalar);
+ type.types.push_back(nickname_node);
+ // friendship_level: int
+ TTypeNode friendship_level_node;
+ friendship_level_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType friendship_level_scalar;
+ friendship_level_scalar.__set_type(TPrimitiveType::INT);
+ friendship_level_node.__set_scalar_type(friendship_level_scalar);
+ type.types.push_back(friendship_level_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "recent_activity") {
+ // recent_activity: array<struct<action:string, details:array<struct<key:string, value:string>>>>
+ TTypeNode array_node;
+ array_node.__set_type(TTypeNodeType::ARRAY);
+ array_node.__set_contains_nulls({true});
+ type.types.push_back(array_node);
+ TTypeNode element_struct_node;
+ element_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> element_fields;
+ TStructField action_field;
+ action_field.__set_name("action");
+ action_field.__set_contains_null(true);
+ element_fields.push_back(action_field);
+ TStructField details_field;
+ details_field.__set_name("details");
+ details_field.__set_contains_null(true);
+ element_fields.push_back(details_field);
+ element_struct_node.__set_struct_fields(element_fields);
+ type.types.push_back(element_struct_node);
+ // action: string
+ TTypeNode action_node;
+ action_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType action_scalar;
+ action_scalar.__set_type(TPrimitiveType::STRING);
+ action_node.__set_scalar_type(action_scalar);
+ type.types.push_back(action_node);
+ // details: array<struct<key:string, value:string>>
+ TTypeNode details_array_node;
+ details_array_node.__set_type(TTypeNodeType::ARRAY);
+ details_array_node.__set_contains_nulls({true});
+ type.types.push_back(details_array_node);
+ TTypeNode details_element_struct_node;
+ details_element_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> details_element_fields;
+ TStructField key_field;
+ key_field.__set_name("key");
+ key_field.__set_contains_null(true);
+ details_element_fields.push_back(key_field);
+ TStructField value_field;
+ value_field.__set_name("value");
+ value_field.__set_contains_null(true);
+ details_element_fields.push_back(value_field);
+ details_element_struct_node.__set_struct_fields(details_element_fields);
+ type.types.push_back(details_element_struct_node);
+ // key: string
+ TTypeNode key_node;
+ key_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType key_scalar;
+ key_scalar.__set_type(TPrimitiveType::STRING);
+ key_node.__set_scalar_type(key_scalar);
+ type.types.push_back(key_node);
+ // value: string
+ TTypeNode value_node;
+ value_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType value_scalar;
+ value_scalar.__set_type(TPrimitiveType::STRING);
+ value_node.__set_scalar_type(value_scalar);
+ type.types.push_back(value_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "attributes") {
+ // attributes: map<string, string>
+ TTypeNode map_node;
+ map_node.__set_type(TTypeNodeType::MAP);
+ map_node.__set_contains_nulls({true, true});
+ type.types.push_back(map_node);
+ // key: string
+ TTypeNode key_node;
+ key_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType key_scalar;
+ key_scalar.__set_type(TPrimitiveType::STRING);
+ key_node.__set_scalar_type(key_scalar);
+ type.types.push_back(key_node);
+ // value: string
+ TTypeNode value_node;
+ value_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType value_scalar;
+ value_scalar.__set_type(TPrimitiveType::STRING);
+ value_node.__set_scalar_type(value_scalar);
+ type.types.push_back(value_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "complex_attributes") {
+ // complex_attributes: map<string, struct<metadata:struct<version:string, created_time:timestamp, last_updated:timestamp>>>
+ TTypeNode map_node;
+ map_node.__set_type(TTypeNodeType::MAP);
+ map_node.__set_contains_nulls({true, true});
+ type.types.push_back(map_node);
+ // key: string
+ TTypeNode key_node;
+ key_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType key_scalar;
+ key_scalar.__set_type(TPrimitiveType::STRING);
+ key_node.__set_scalar_type(key_scalar);
+ type.types.push_back(key_node);
+ // value: struct<metadata:struct<version:string, created_time:timestamp, last_updated:timestamp>>
+ TTypeNode value_struct_node;
+ value_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> value_fields;
+ TStructField metadata_field;
+ metadata_field.__set_name("metadata");
+ metadata_field.__set_contains_null(true);
+ value_fields.push_back(metadata_field);
+ value_struct_node.__set_struct_fields(value_fields);
+ type.types.push_back(value_struct_node);
+ // metadata: struct<version:string, created_time:timestamp, last_updated:timestamp>
+ TTypeNode metadata_struct_node;
+ metadata_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> metadata_fields;
+ TStructField version_field;
+ version_field.__set_name("version");
+ version_field.__set_contains_null(true);
+ metadata_fields.push_back(version_field);
+ TStructField created_time_field;
+ created_time_field.__set_name("created_time");
+ created_time_field.__set_contains_null(true);
+ metadata_fields.push_back(created_time_field);
+ TStructField last_updated_field;
+ last_updated_field.__set_name("last_updated");
+ last_updated_field.__set_contains_null(true);
+ metadata_fields.push_back(last_updated_field);
+ metadata_struct_node.__set_struct_fields(metadata_fields);
+ type.types.push_back(metadata_struct_node);
+ // version: string
+ TTypeNode version_node;
+ version_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType version_scalar;
+ version_scalar.__set_type(TPrimitiveType::STRING);
+ version_node.__set_scalar_type(version_scalar);
+ type.types.push_back(version_node);
+ // created_time: timestamp
+ TTypeNode created_time_node;
+ created_time_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType created_time_scalar;
+ created_time_scalar.__set_type(TPrimitiveType::DATETIME);
+ created_time_node.__set_scalar_type(created_time_scalar);
+ type.types.push_back(created_time_node);
+ // last_updated: timestamp
+ TTypeNode last_updated_node;
+ last_updated_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType last_updated_scalar;
+ last_updated_scalar.__set_type(TPrimitiveType::DATETIME);
+ last_updated_node.__set_scalar_type(last_updated_scalar);
+ type.types.push_back(last_updated_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "profile") {
+ // STRUCT/ARRAY nodes need contains_nulls, SCALAR nodes do not
+ TTypeNode struct_node;
+ struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> struct_fields;
+ TStructField address_field;
+ address_field.__set_name("address");
+ address_field.__set_contains_null(true);
+ struct_fields.push_back(address_field);
+ TStructField contact_field;
+ contact_field.__set_name("contact");
+ contact_field.__set_contains_null(true);
+ struct_fields.push_back(contact_field);
+ TStructField hobbies_field;
+ hobbies_field.__set_name("hobbies");
+ hobbies_field.__set_contains_null(true);
+ struct_fields.push_back(hobbies_field);
+ struct_node.__set_struct_fields(struct_fields);
+ type.types.push_back(struct_node);
+ TTypeNode address_node;
+ address_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> address_fields;
+ TStructField street_field;
+ street_field.__set_name("street");
+ street_field.__set_contains_null(true);
+ address_fields.push_back(street_field);
+ TStructField city_field;
+ city_field.__set_name("city");
+ city_field.__set_contains_null(true);
+ address_fields.push_back(city_field);
+ TStructField coordinates_field;
+ coordinates_field.__set_name("coordinates");
+ coordinates_field.__set_contains_null(true);
+ address_fields.push_back(coordinates_field);
+ address_node.__set_struct_fields(address_fields);
+ type.types.push_back(address_node);
+ TTypeNode street_node;
+ street_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType street_scalar;
+ street_scalar.__set_type(TPrimitiveType::STRING);
+ street_node.__set_scalar_type(street_scalar);
+ type.types.push_back(street_node);
+ TTypeNode city_node;
+ city_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType city_scalar;
+ city_scalar.__set_type(TPrimitiveType::STRING);
+ city_node.__set_scalar_type(city_scalar);
+ type.types.push_back(city_node);
+ TTypeNode coordinates_node;
+ coordinates_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> coordinates_fields;
+ TStructField lat_field;
+ lat_field.__set_name("lat");
+ lat_field.__set_contains_null(true);
+ coordinates_fields.push_back(lat_field);
+ TStructField lng_field;
+ lng_field.__set_name("lng");
+ lng_field.__set_contains_null(true);
+ coordinates_fields.push_back(lng_field);
+ coordinates_node.__set_struct_fields(coordinates_fields);
+ type.types.push_back(coordinates_node);
+ TTypeNode lat_node;
+ lat_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType lat_scalar;
+ lat_scalar.__set_type(TPrimitiveType::DOUBLE);
+ lat_node.__set_scalar_type(lat_scalar);
+ type.types.push_back(lat_node);
+ TTypeNode lng_node;
+ lng_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType lng_scalar;
+ lng_scalar.__set_type(TPrimitiveType::DOUBLE);
+ lng_node.__set_scalar_type(lng_scalar);
+ type.types.push_back(lng_node);
+ TTypeNode contact_node;
+ contact_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> contact_fields;
+ TStructField email_field;
+ email_field.__set_name("email");
+ email_field.__set_contains_null(true);
+ contact_fields.push_back(email_field);
+ TStructField phone_field;
+ phone_field.__set_name("phone");
+ phone_field.__set_contains_null(true);
+ contact_fields.push_back(phone_field);
+ contact_node.__set_struct_fields(contact_fields);
+ type.types.push_back(contact_node);
+ TTypeNode email_node;
+ email_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType email_scalar;
+ email_scalar.__set_type(TPrimitiveType::STRING);
+ email_node.__set_scalar_type(email_scalar);
+ type.types.push_back(email_node);
+ TTypeNode phone_node;
+ phone_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> phone_fields;
+ TStructField country_code_field;
+ country_code_field.__set_name("country_code");
+ country_code_field.__set_contains_null(true);
+ phone_fields.push_back(country_code_field);
+ TStructField number_field;
+ number_field.__set_name("number");
+ number_field.__set_contains_null(true);
+ phone_fields.push_back(number_field);
+ phone_node.__set_struct_fields(phone_fields);
+ type.types.push_back(phone_node);
+ TTypeNode country_code_node;
+ country_code_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType country_code_scalar;
+ country_code_scalar.__set_type(TPrimitiveType::STRING);
+ country_code_node.__set_scalar_type(country_code_scalar);
+ type.types.push_back(country_code_node);
+ TTypeNode number_node;
+ number_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType number_scalar;
+ number_scalar.__set_type(TPrimitiveType::STRING);
+ number_node.__set_scalar_type(number_scalar);
+ type.types.push_back(number_node);
+ TTypeNode hobbies_node;
+ hobbies_node.__set_type(TTypeNodeType::ARRAY);
+ hobbies_node.__set_contains_nulls({true});
+ type.types.push_back(hobbies_node);
+ TTypeNode hobby_element_node;
+ hobby_element_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> hobby_element_fields;
+ TStructField hobby_name_field;
+ hobby_name_field.__set_name("name");
+ hobby_name_field.__set_contains_null(true);
+ hobby_element_fields.push_back(hobby_name_field);
+ TStructField hobby_level_field;
+ hobby_level_field.__set_name("level");
+ hobby_level_field.__set_contains_null(true);
+ hobby_element_fields.push_back(hobby_level_field);
+ hobby_element_node.__set_struct_fields(hobby_element_fields);
+ type.types.push_back(hobby_element_node);
+ TTypeNode hobby_name_node;
+ hobby_name_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType hobby_name_scalar;
+ hobby_name_scalar.__set_type(TPrimitiveType::STRING);
+ hobby_name_node.__set_scalar_type(hobby_name_scalar);
+ type.types.push_back(hobby_name_node);
+ TTypeNode hobby_level_node;
+ hobby_level_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType hobby_level_scalar;
+ hobby_level_scalar.__set_type(TPrimitiveType::INT);
+ hobby_level_node.__set_scalar_type(hobby_level_scalar);
+ type.types.push_back(hobby_level_node);
+ tslot_desc.__set_slotType(type);
+ } else {
+ // Regular types
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(types[i]);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ }
+ tslot_desc.__set_id(i);
+ tslot_desc.__set_parent(0);
+ tslot_desc.__set_colName(table_column_names[i]);
+ tslot_desc.__set_columnPos(table_column_positions[i]);
+ tslot_desc.__set_byteOffset(0);
+ tslot_desc.__set_nullIndicatorByte(0);
+ tslot_desc.__set_nullIndicatorBit(-1);
+ tslot_desc.__set_slotIdx(0);
+ tslot_desc.__set_isMaterialized(true);
+
+ // Use configuration to set column_access_paths
+ for (const auto& config : access_configs) {
+ set_column_access_paths(tslot_desc, config);
+ }
+
+ t_desc_table.slotDescriptors.push_back(tslot_desc);
+ }
+ t_desc_table.__isset.slotDescriptors = true;
+ TTupleDescriptor t_tuple_desc;
+ t_tuple_desc.__set_id(0);
+ t_tuple_desc.__set_byteSize(16);
+ t_tuple_desc.__set_numNullBytes(0);
+ t_tuple_desc.__set_tableId(0);
+ t_tuple_desc.__isset.tableId = true;
+ t_desc_table.tupleDescriptors.push_back(t_tuple_desc);
+ };
+
+ create_table_desc(t_desc_table, t_table_desc, column_names, column_positions, column_types);
+ EXPECT_TRUE(DescriptorTbl::create(&obj_pool, t_desc_table, desc_tbl).ok());
+ return (*desc_tbl)->get_tuple_descriptor(0);
+ }
+
+ // Helper function: Set column access paths
+ void set_column_access_paths(TSlotDescriptor& tslot_desc,
+ const ColumnAccessPathConfig& config) {
+ if (config.column_name != tslot_desc.colName) {
+ return; // Not the target column, skip
+ }
+
+ // Set all_column_access_paths
+ if (!config.all_column_paths.empty()) {
+ std::vector<TColumnAccessPath> access_paths;
+ for (const auto& path_vector : config.all_column_paths) {
+ TColumnAccessPath access_path;
+ access_path.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path;
+ data_path.__set_path(path_vector);
+ access_path.__set_data_access_path(data_path);
+ access_paths.push_back(access_path);
+ }
+ tslot_desc.__set_all_access_paths(access_paths);
+ }
+
+ // Set predicate_column_access_paths
+ if (!config.predicate_paths.empty()) {
+ std::vector<TColumnAccessPath> access_paths;
+ for (const auto& path_vector : config.predicate_paths) {
+ TColumnAccessPath access_path;
+ access_path.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path;
+ data_path.__set_path(path_vector);
+ access_path.__set_data_access_path(data_path);
+ access_paths.push_back(access_path);
+ }
+ tslot_desc.__set_predicate_access_paths(access_paths);
+ }
+ }
+
+ std::unique_ptr<doris::FileMetaCache> cache;
+ cctz::time_zone timezone_obj;
+
+ // Helper function: Create and setup ParquetReader
+ std::tuple<std::unique_ptr<HiveParquetReader>, const FieldDescriptor*> create_parquet_reader(
+ const std::string& test_file) {
+ auto local_fs = io::global_local_filesystem();
+ io::FileReaderSPtr file_reader;
+ auto st = local_fs->open_file(test_file, &file_reader);
+ if (!st.ok()) {
+ return {nullptr, nullptr};
+ }
+
+ RuntimeState runtime_state((TQueryGlobals()));
+ TFileScanRangeParams scan_params;
+ scan_params.format_type = TFileFormatType::FORMAT_PARQUET;
+ TFileRangeDesc scan_range;
+ scan_range.start_offset = 0;
+ scan_range.size = file_reader->size();
+ scan_range.path = test_file;
+ RuntimeProfile profile("test_profile");
+
+ cctz::time_zone ctz;
+ TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, ctz);
+ auto generic_reader =
+ ParquetReader::create_unique(&profile, scan_params, scan_range, 1024, &ctz, nullptr,
+ &runtime_state, cache.get());
+ if (!generic_reader) {
+ return {nullptr, nullptr};
+ }
+
+ auto parquet_reader = static_cast<ParquetReader*>(generic_reader.get());
+ parquet_reader->set_file_reader(file_reader);
+
+ const FieldDescriptor* field_desc = nullptr;
+ st = parquet_reader->get_file_metadata_schema(&field_desc);
+ if (!st.ok() || !field_desc) {
+ return {nullptr, nullptr};
+ }
+
+ auto hive_reader = std::make_unique<HiveParquetReader>(
+ std::move(generic_reader), &profile, &runtime_state, scan_params, scan_range,
+ nullptr, nullptr, cache.get());
+
+ return {std::move(hive_reader), field_desc};
+ }
+
+ // Helper function: Create and setup OrcReader
+ std::tuple<std::unique_ptr<HiveOrcReader>, const orc::Type*> create_orc_reader(
+ const std::string& test_file) {
+ // Open the Hive Orc test file
+ auto local_fs = io::global_local_filesystem();
+ io::FileReaderSPtr file_reader;
+ auto st = local_fs->open_file(test_file, &file_reader);
+ if (!st.ok()) {
+ return {nullptr, nullptr};
+ }
+
+ // Setup runtime state
+ RuntimeState runtime_state((TQueryGlobals()));
+
+ // Setup scan parameters
+ TFileScanRangeParams scan_params;
+ scan_params.format_type = TFileFormatType::FORMAT_ORC;
+
+ TFileRangeDesc scan_range;
+ scan_range.start_offset = 0;
+ scan_range.size = file_reader->size(); // Read entire file
+ scan_range.path = test_file;
+
+ // Create mock profile
+ RuntimeProfile profile("test_profile");
+
+ // Create OrcReader as the underlying file format reader
+ cctz::time_zone ctz;
+ TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, ctz);
+
+ auto generic_reader =
+ OrcReader::create_unique(&profile, &runtime_state, scan_params, scan_range, 1024,
+ "CST", nullptr, cache.get());
+ if (!generic_reader) {
+ return {nullptr, nullptr};
+ }
+
+ auto orc_reader = static_cast<OrcReader*>(generic_reader.get());
+ // Get FieldDescriptor from Orc file
+ const orc::Type* orc_type_ptr = nullptr;
+ st = orc_reader->get_file_type(&orc_type_ptr);
+ if (!st.ok() || !orc_type_ptr) {
+ return {nullptr, nullptr};
+ }
+
+ // Create HiveOrcReader
+ auto hive_reader = std::make_unique<HiveOrcReader>(std::move(generic_reader), &profile,
+ &runtime_state, scan_params, scan_range,
+ nullptr, nullptr, cache.get());
+
+ return {std::move(hive_reader), orc_type_ptr};
+ }
+
+ // Helper function: Run Parquet test with different column ID extraction methods
+ void run_parquet_test_with_method(const std::vector<ColumnAccessPathConfig>& access_configs,
+ const std::set<uint64_t>& expected_column_ids,
+ const std::set<uint64_t>& expected_filter_column_ids,
+ bool use_top_level_method = false,
+ bool should_skip_assertion = false) {
+ std::string test_file =
+ "./be/test/exec/test_data/nested_user_profiles_parquet/"
+ "part-00000-64a7a390-1a03-4efc-ab51-557e9369a1f9-c000.snappy.parquet";
+
+ auto [hive_reader, field_desc] = create_parquet_reader(test_file);
+ if (!hive_reader || !field_desc) {
+ GTEST_SKIP() << "Test file not found or failed to create reader: " << test_file;
+ return;
+ }
+
+ // Create tuple descriptor based on full schema
+ DescriptorTbl* desc_tbl;
+ ObjectPool obj_pool;
+ TDescriptorTable t_desc_table;
+ TTableDescriptor t_table_desc;
+
+ // Define all columns according to the schema
+ std::vector<std::string> table_column_names = {
+ "name", "profile", "tags", "friends", "recent_activity",
+ "attributes", "complex_attributes"};
+ std::vector<int> table_column_positions = {1, 2, 3, 4, 5, 6, 7};
+ std::vector<TPrimitiveType::type> table_column_types = {
+ // TPrimitiveType::BIGINT, // id
+ TPrimitiveType::STRING, // name
+ TPrimitiveType::STRUCT, // profile
+ TPrimitiveType::ARRAY, // tags
+ TPrimitiveType::ARRAY, // friends
+ TPrimitiveType::ARRAY, // recent_activity
+ TPrimitiveType::MAP, // attributes
+ TPrimitiveType::MAP // complex_attributes
+ };
+
+ const TupleDescriptor* tuple_descriptor = create_tuple_descriptor(
+ &desc_tbl, obj_pool, t_desc_table, t_table_desc, table_column_names,
+ table_column_positions, table_column_types, access_configs);
+
+ // Execute test based on method choice
+ ColumnIdResult actual_result;
+ if (use_top_level_method) {
+ actual_result = HiveParquetReader::_create_column_ids_by_top_level_col_index(
+ field_desc, tuple_descriptor);
+ } else {
+ actual_result = HiveParquetReader::_create_column_ids(field_desc, tuple_descriptor);
+ }
+
+ if (!should_skip_assertion) {
+ EXPECT_EQ(actual_result.column_ids, expected_column_ids);
+ EXPECT_EQ(actual_result.filter_column_ids, expected_filter_column_ids);
+ }
+ }
+
+ // Helper function: Run ORC test with different column ID extraction methods
+ void run_orc_test_with_method(const std::vector<ColumnAccessPathConfig>& access_configs,
+ const std::set<uint64_t>& expected_column_ids,
+ const std::set<uint64_t>& expected_filter_column_ids,
+ bool use_top_level_method = false,
+ bool should_skip_assertion = false) {
+ std::string test_file =
+ "./be/test/exec/test_data/nested_user_profiles_orc/"
+ "part-00000-62614f23-05d1-4043-a533-b155ef52b720-c000.snappy.orc";
+
+ auto [hive_reader, orc_type] = create_orc_reader(test_file);
+ if (!hive_reader || !orc_type) {
+ GTEST_SKIP() << "Test file not found or failed to create reader: " << test_file;
+ return;
+ }
+
+ // Create tuple descriptor based on full schema
+ DescriptorTbl* desc_tbl;
+ ObjectPool obj_pool;
+ TDescriptorTable t_desc_table;
+ TTableDescriptor t_table_desc;
+
+ // Define all columns according to the schema
+ std::vector<std::string> table_column_names = {
+ "name", "profile", "tags", "friends", "recent_activity",
+ "attributes", "complex_attributes"};
+ std::vector<int> table_column_positions = {1, 2, 3, 4, 5, 6, 7};
+ std::vector<TPrimitiveType::type> table_column_types = {
+ // TPrimitiveType::BIGINT, // id
+ TPrimitiveType::STRING, // name
+ TPrimitiveType::STRUCT, // profile
+ TPrimitiveType::ARRAY, // tags
+ TPrimitiveType::ARRAY, // friends
+ TPrimitiveType::ARRAY, // recent_activity
+ TPrimitiveType::MAP, // attributes
+ TPrimitiveType::MAP // complex_attributes
+ };
+
+ const TupleDescriptor* tuple_descriptor = create_tuple_descriptor(
+ &desc_tbl, obj_pool, t_desc_table, t_table_desc, table_column_names,
+ table_column_positions, table_column_types, access_configs);
+
+ // Execute test based on method choice
+ ColumnIdResult actual_result;
+ if (use_top_level_method) {
+ actual_result = HiveOrcReader::_create_column_ids_by_top_level_col_index(
+ orc_type, tuple_descriptor);
+ } else {
+ actual_result = HiveOrcReader::_create_column_ids(orc_type, tuple_descriptor);
+ }
+
+ if (!should_skip_assertion) {
+ EXPECT_EQ(actual_result.column_ids, expected_column_ids);
+ EXPECT_EQ(actual_result.filter_column_ids, expected_filter_column_ids);
+ }
+ }
+};
+
+TEST_F(HiveReaderCreateColumnIdsTest, test_create_column_ids_1) {
+ // Properties:
+
+ // Configure access paths for profile column
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"profile", "address", "coordinates", "lat"},
+ {"profile", "address", "coordinates", "lng"},
+ {"profile", "contact", "email"},
+ {"profile", "hobbies", "*", "level"}};
+ access_config.predicate_paths = {{"profile", "address", "coordinates", "lat"},
+ {"profile", "contact", "email"}};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 3, 4, 7, 8, 9, 10, 11, 15, 16, 18};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {3, 4, 7, 8, 10, 11};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+}
+
+TEST_F(HiveReaderCreateColumnIdsTest, test_create_column_ids_2) {
+ // Properties:
+ // iceberg.schema: {"type":"struct","schema-id":0,"fields":[{"id":1,"name":"id","required":true,"type":"long"},{"id":2,"name":"name","required":true,"type":"string"},{"id":3,"name":"profile","required":true,"type":{"type":"struct","fields":[{"id":4,"name":"address","required":false,"type":{"type":"struct","fields":[{"id":7,"name":"street","required":false,"type":"string"},{"id":8,"name":"city","required":false,"type":"string"},{"id":9,"name":"coordinates","required":false,"type": [...]
+ // ORC column IDs are assigned in a tree-increment order, root is 0 and children increment sequentially.
+ // 0: struct (table/root)
+ // 1: id (int64)
+ // 2: name (string)
+ // 3: profile (struct)
+ // 4: address (struct)
+ // 5: street (string)
+ // 6: city (string)
+ // 7: coordinates (struct)
+ // 8: lat (double)
+ // 9: lng (double)
+ // 10: contact (struct)
+ // 11: email (string)
+ // 12: phone (struct)
+ // 13: country_code (string)
+ // 14: number (string)
+ // 15: hobbies (list/array)
+ // 16: element (struct)
+ // 17: name (string)
+ // 18: level (int32)
+
+ // 配置profile列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"profile"}};
+ access_config.predicate_paths = {{"profile", "address", "coordinates", "lat"},
+ {"profile", "contact", "email"}};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {3, 4, 7, 8, 10, 11};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+}
+
+TEST_F(HiveReaderCreateColumnIdsTest, test_create_column_ids_3) {
+ // Properties:
+ // iceberg.schema: {"type":"struct","schema-id":0,"fields":[{"id":1,"name":"id","required":true,"type":"long"},{"id":2,"name":"name","required":true,"type":"string"},{"id":3,"name":"profile","required":true,"type":{"type":"struct","fields":[{"id":4,"name":"address","required":false,"type":{"type":"struct","fields":[{"id":7,"name":"street","required":false,"type":"string"},{"id":8,"name":"city","required":false,"type":"string"},{"id":9,"name":"coordinates","required":false,"type": [...]
+ // ORC column IDs are assigned in a tree-increment order, root is 0 and children increment sequentially.
+ // 0: struct (table/root)
+ // 1: id (int64)
+ // 2: name (string)
+ // 3: profile (struct)
+ // 4: address (struct)
+ // 5: street (string)
+ // 6: city (string)
+ // 7: coordinates (struct)
+ // 8: lat (double)
+ // 9: lng (double)
+ // 10: contact (struct)
+ // 11: email (string)
+ // 12: phone (struct)
+ // 13: country_code (string)
+ // 14: number (string)
+ // 15: hobbies (list/array)
+ // 16: element (struct)
+ // 17: name (string)
+ // 18: level (int32)
+
+ // 配置profile列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"profile", "contact"}, {"profile", "address"}};
+ access_config.predicate_paths = {{"profile", "address", "coordinates"},
+ {"profile", "contact", "email"}};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {3, 4, 7, 8, 9, 10, 11};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+}
+
+TEST_F(HiveReaderCreateColumnIdsTest, test_create_column_ids_4) {
+ // 配置profile列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {};
+ access_config.predicate_paths = {};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids,
+ true);
+}
+
+TEST_F(HiveReaderCreateColumnIdsTest, test_create_column_ids_5) {
+ // 19: tags (array)
+ // 20: element (string)
+ // 21: friends (array)
+ // 22: element (struct)
+ // 23: user_id (bigint)
+ // 24: nickname (string)
+ // 25: friendship_level (int)
+ // 26: recent_activity (array)
+ // 27: element (struct)
+ // 28: action (string)
+ // 29: details (array)
+ // 30: element (struct)
+ // 31: key (string)
+ // 32: value (string)
+
+ std::vector<ColumnAccessPathConfig> access_configs;
+
+ {
+ // Configure access paths for friends column
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "friends";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"friends", "*", "nickname"},
+ {"friends", "*", "friendship_level"}};
+ access_config.predicate_paths = {
+ {"friends", "*", "nickname"},
+ };
+ access_configs.push_back(access_config);
+ }
+
+ {
+ // Configure access paths for recent_activity column
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "recent_activity";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"recent_activity", "*", "action"},
+ {"recent_activity", "*", "details", "*", "value"}};
+ access_config.predicate_paths = {
+ {"recent_activity", "*", "action"},
+ };
+ access_configs.push_back(access_config);
+ }
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 21, 22, 24, 25, 26, 27, 28, 29, 30, 32};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {21, 22, 24, 26, 27, 28};
+
+ run_parquet_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids);
+ run_parquet_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids,
+ true);
+
+ run_orc_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids, true);
+}
+
+TEST_F(HiveReaderCreateColumnIdsTest, test_create_column_ids_6) {
+ // 33: attributes (map)
+ // 34: key (string)
+ // 35: value (string)
+ // 36: complex_attributes (map)
+ // 37: key (string)
+ // 38: value (struct)
+ // 39: metadata (struct)
+ // 40: version (string)
+ // 41: created_time (timestamp)
+ // 42: last_updated (timestamp)
+ // 43: quality_score (double)
+ // 44: historical_scores (array)
+ // 45: element (struct)
+ // 46: period (string)
+ // 47: score (double)
+ // 48: components (array)
+ // 49: element (struct)
+ // 50: component_name (string)
+ // 51: weight (float)
+ // 52: sub_scores (map)
+ // 53: key (string)
+ // 54: value (double)
+ // 55: trends (struct)
+ // 56: direction (string)
+ // 57: magnitude (double)
+ // 58: confidence_interval (struct)
+ // 59: lower (double)
+ // 60: upper (double)
+ // 61: hierarchical_data (map)
+ // 62: key (string)
+ // 63: value (struct)
+ // 64: category (string)
+ // 65: sub_items (array)
+ // 66: element (struct)
+ // 67: item_id (bigint)
+ // 68: properties (map)
+ // 69: key (string)
+ // 70: value (string)
+ // 71: metrics (struct)
+ // 72: value (double)
+ // 73: unit (string)
+ // 74: summary (struct)
+ // 75: total_count (int)
+ // 76: average_value (double)
+ // 77: validation_rules (struct)
+ // 78: required (boolean)
+ // 79: constraints (array)
+ // 80: element (struct)
+ // 81: rule_type (string)
+ // 82: parameters (map)
+ // 83: key (string)
+ // 84: value (string)
+ // 85: error_message (string)
+ // 86: complex_constraint (struct)
+ // 87: logic_operator (string)
+ // 88: operands (array)
+ // 89: element (struct)
+ // 90: field_path (string)
+ // 91: operator (string)
+ // 92: comparison_value (string)
+ std::vector<ColumnAccessPathConfig> access_configs;
+
+ {
+ // Configure access paths for complex_attributes column
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "complex_attributes";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {
+ {"complex_attributes", "*", "metadata", "version"},
+ {"complex_attributes", "*", "historical_scores", "*", "components", "*",
+ "sub_scores"},
+ {"complex_attributes", "*", "hierarchical_data", "VALUES"},
+ {"complex_attributes", "VALUES", "validation_rules", "constraints", "*",
+ "parameters", "KEYS"}};
+ access_config.predicate_paths = {{"complex_attributes", "*", "metadata", "version"}
+
+ };
+ access_configs.push_back(access_config);
+ }
+
+ {
+ // parquet values should access keys
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> parquet_expected_column_ids = {
+ 2, 36, 37, 38, 39, 40, 44, 45, 48, 49, 52, 53, 54, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79, 80, 82, 83};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> parquet_expected_filter_column_ids = {36, 37, 38, 39, 40};
+
+ run_parquet_test_with_method(access_configs, parquet_expected_column_ids,
+ parquet_expected_filter_column_ids);
+ run_parquet_test_with_method(access_configs, parquet_expected_column_ids,
+ parquet_expected_filter_column_ids, true);
+ }
+
+ {
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> orc_expected_column_ids = {2, 36, 37, 38, 39, 40, 44, 45, 48, 49, 52,
+ 53, 54, 61, 63, 64, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76, 77, 79, 80, 82, 83};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> orc_expected_filter_column_ids = {36, 37, 38, 39, 40};
+ run_orc_test_with_method(access_configs, orc_expected_column_ids,
+ orc_expected_filter_column_ids);
+ run_orc_test_with_method(access_configs, orc_expected_column_ids,
+ orc_expected_filter_column_ids, true);
+ }
+}
+
+} // namespace doris::vectorized
\ No newline at end of file
diff --git a/be/test/vec/exec/format/table/hive/hive_reader_test.cpp b/be/test/vec/exec/format/table/hive/hive_reader_test.cpp
new file mode 100644
index 00000000000..16bde5c7bae
--- /dev/null
+++ b/be/test/vec/exec/format/table/hive/hive_reader_test.cpp
@@ -0,0 +1,743 @@
+// 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 "vec/exec/format/table/hive_reader.h"
+
+#include <cctz/time_zone.h>
+#include <gen_cpp/Descriptors_types.h>
+#include <gen_cpp/PaloInternalService_types.h>
+#include <gen_cpp/PlanNodes_types.h>
+#include <gen_cpp/Types_types.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "common/object_pool.h"
+#include "exec/olap_common.h"
+#include "io/fs/file_meta_cache.h"
+#include "io/fs/file_reader_writer_fwd.h"
+#include "io/fs/file_system.h"
+#include "io/fs/local_file_system.h"
+#include "runtime/descriptors.h"
+#include "runtime/runtime_state.h"
+#include "util/timezone_utils.h"
+#include "vec/columns/column.h"
+#include "vec/columns/column_array.h"
+#include "vec/columns/column_nullable.h"
+#include "vec/columns/column_struct.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_factory.hpp"
+#include "vec/data_types/data_type_nullable.h"
+#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_string.h"
+#include "vec/data_types/data_type_struct.h"
+#include "vec/exec/format/parquet/vparquet_reader.h"
+
+namespace doris::vectorized::table {
+
+class HiveReaderTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ cache = std::make_unique<doris::FileMetaCache>(1024);
+
+ // Setup timezone
+ doris::TimezoneUtils::find_cctz_time_zone(doris::TimezoneUtils::default_time_zone,
+ timezone_obj);
+ }
+
+ void TearDown() override { cache.reset(); }
+
+ // Helper function to create complex struct types for testing
+ void create_complex_struct_types(DataTypePtr& coordinates_struct_type,
+ DataTypePtr& address_struct_type,
+ DataTypePtr& phone_struct_type,
+ DataTypePtr& contact_struct_type,
+ DataTypePtr& hobby_element_struct_type,
+ DataTypePtr& hobbies_array_type,
+ DataTypePtr& profile_struct_type, DataTypePtr& name_type) {
+ // Create name column type (direct field)
+ name_type = make_nullable(std::make_shared<DataTypeString>());
+
+ // First create coordinates struct type
+ std::vector<DataTypePtr> coordinates_types = {
+ make_nullable(std::make_shared<DataTypeFloat64>()), // lat (field ID 10)
+ make_nullable(std::make_shared<DataTypeFloat64>()) // lng (field ID 11)
+ };
+ std::vector<std::string> coordinates_names = {"lat", "lng"};
+ coordinates_struct_type = make_nullable(
+ std::make_shared<DataTypeStruct>(coordinates_types, coordinates_names));
+
+ // Create address struct type (with street, city, coordinates)
+ std::vector<DataTypePtr> address_types = {
+ make_nullable(std::make_shared<DataTypeString>()), // street (field ID 7)
+ make_nullable(std::make_shared<DataTypeString>()), // city (field ID 8)
+ coordinates_struct_type // coordinates (field ID 9)
+ };
+ std::vector<std::string> address_names = {"street", "city", "coordinates"};
+ address_struct_type =
+ make_nullable(std::make_shared<DataTypeStruct>(address_types, address_names));
+
+ // Create phone struct type
+ std::vector<DataTypePtr> phone_types = {
+ make_nullable(std::make_shared<DataTypeString>()), // country_code (field ID 14)
+ make_nullable(std::make_shared<DataTypeString>()) // number (field ID 15)
+ };
+ std::vector<std::string> phone_names = {"country_code", "number"};
+ phone_struct_type =
+ make_nullable(std::make_shared<DataTypeStruct>(phone_types, phone_names));
+
+ // Create contact struct type (with email, phone)
+ std::vector<DataTypePtr> contact_types = {
+ make_nullable(std::make_shared<DataTypeString>()), // email (field ID 12)
+ phone_struct_type // phone (field ID 13)
+ };
+ std::vector<std::string> contact_names = {"email", "phone"};
+ contact_struct_type =
+ make_nullable(std::make_shared<DataTypeStruct>(contact_types, contact_names));
+
+ // Create hobby element struct type for array elements
+ std::vector<DataTypePtr> hobby_element_types = {
+ make_nullable(std::make_shared<DataTypeString>()), // name (field ID 17)
+ make_nullable(std::make_shared<DataTypeInt32>()) // level (field ID 18)
+ };
+ std::vector<std::string> hobby_element_names = {"name", "level"};
+ hobby_element_struct_type = make_nullable(
+ std::make_shared<DataTypeStruct>(hobby_element_types, hobby_element_names));
+
+ // Create hobbies array type
+ hobbies_array_type =
+ make_nullable(std::make_shared<DataTypeArray>(hobby_element_struct_type));
+
+ // Create complete profile struct type (with address, contact, hobbies)
+ std::vector<DataTypePtr> profile_types = {
+ address_struct_type, // address (field ID 4)
+ contact_struct_type, // contact (field ID 5)
+ hobbies_array_type // hobbies (field ID 6)
+ };
+ std::vector<std::string> profile_names = {"address", "contact", "hobbies"};
+ profile_struct_type =
+ make_nullable(std::make_shared<DataTypeStruct>(profile_types, profile_names));
+ }
+
+ // Helper function to create tuple descriptor
+ const TupleDescriptor* create_tuple_descriptor(
+ DescriptorTbl** desc_tbl, ObjectPool& obj_pool, TDescriptorTable& t_desc_table,
+ TTableDescriptor& t_table_desc, const std::vector<std::string>& column_names,
+ const std::vector<int>& column_positions,
+ const std::vector<TPrimitiveType::type>& column_types) {
+ // Create table descriptor with complex schema
+ auto create_table_desc = [](TDescriptorTable& t_desc_table, TTableDescriptor& t_table_desc,
+ const std::vector<std::string>& table_column_names,
+ const std::vector<int>& table_column_positions,
+ const std::vector<TPrimitiveType::type>& types) {
+ t_table_desc.__set_id(0);
+ t_table_desc.__set_tableType(TTableType::OLAP_TABLE);
+ t_table_desc.__set_numCols(0);
+ t_table_desc.__set_numClusteringCols(0);
+ t_desc_table.tableDescriptors.push_back(t_table_desc);
+ t_desc_table.__isset.tableDescriptors = true;
+ for (int i = 0; i < table_column_names.size(); i++) {
+ TSlotDescriptor tslot_desc;
+ TTypeDesc type;
+ if (table_column_names[i] == "profile") {
+ // STRUCT/ARRAY nodes set contains_nulls; SCALAR nodes do not
+ TTypeNode struct_node;
+ struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> struct_fields;
+ TStructField address_field;
+ address_field.__set_name("address");
+ address_field.__set_contains_null(true);
+ struct_fields.push_back(address_field);
+ TStructField contact_field;
+ contact_field.__set_name("contact");
+ contact_field.__set_contains_null(true);
+ struct_fields.push_back(contact_field);
+ TStructField hobbies_field;
+ hobbies_field.__set_name("hobbies");
+ hobbies_field.__set_contains_null(true);
+ struct_fields.push_back(hobbies_field);
+ struct_node.__set_struct_fields(struct_fields);
+ type.types.push_back(struct_node);
+ TTypeNode address_node;
+ address_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> address_fields;
+ TStructField street_field;
+ street_field.__set_name("street");
+ street_field.__set_contains_null(true);
+ address_fields.push_back(street_field);
+ TStructField city_field;
+ city_field.__set_name("city");
+ city_field.__set_contains_null(true);
+ address_fields.push_back(city_field);
+ TStructField coordinates_field;
+ coordinates_field.__set_name("coordinates");
+ coordinates_field.__set_contains_null(true);
+ address_fields.push_back(coordinates_field);
+ address_node.__set_struct_fields(address_fields);
+ type.types.push_back(address_node);
+ TTypeNode street_node;
+ street_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType street_scalar;
+ street_scalar.__set_type(TPrimitiveType::STRING);
+ street_node.__set_scalar_type(street_scalar);
+ type.types.push_back(street_node);
+ TTypeNode city_node;
+ city_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType city_scalar;
+ city_scalar.__set_type(TPrimitiveType::STRING);
+ city_node.__set_scalar_type(city_scalar);
+ type.types.push_back(city_node);
+ TTypeNode coordinates_node;
+ coordinates_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> coordinates_fields;
+ TStructField lat_field;
+ lat_field.__set_name("lat");
+ lat_field.__set_contains_null(true);
+ coordinates_fields.push_back(lat_field);
+ TStructField lng_field;
+ lng_field.__set_name("lng");
+ lng_field.__set_contains_null(true);
+ coordinates_fields.push_back(lng_field);
+ coordinates_node.__set_struct_fields(coordinates_fields);
+ type.types.push_back(coordinates_node);
+ TTypeNode lat_node;
+ lat_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType lat_scalar;
+ lat_scalar.__set_type(TPrimitiveType::DOUBLE);
+ lat_node.__set_scalar_type(lat_scalar);
+ type.types.push_back(lat_node);
+ TTypeNode lng_node;
+ lng_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType lng_scalar;
+ lng_scalar.__set_type(TPrimitiveType::DOUBLE);
+ lng_node.__set_scalar_type(lng_scalar);
+ type.types.push_back(lng_node);
+ TTypeNode contact_node;
+ contact_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> contact_fields;
+ TStructField email_field;
+ email_field.__set_name("email");
+ email_field.__set_contains_null(true);
+ contact_fields.push_back(email_field);
+ TStructField phone_field;
+ phone_field.__set_name("phone");
+ phone_field.__set_contains_null(true);
+ contact_fields.push_back(phone_field);
+ contact_node.__set_struct_fields(contact_fields);
+ type.types.push_back(contact_node);
+ TTypeNode email_node;
+ email_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType email_scalar;
+ email_scalar.__set_type(TPrimitiveType::STRING);
+ email_node.__set_scalar_type(email_scalar);
+ type.types.push_back(email_node);
+ TTypeNode phone_node;
+ phone_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> phone_fields;
+ TStructField country_code_field;
+ country_code_field.__set_name("country_code");
+ country_code_field.__set_contains_null(true);
+ phone_fields.push_back(country_code_field);
+ TStructField number_field;
+ number_field.__set_name("number");
+ number_field.__set_contains_null(true);
+ phone_fields.push_back(number_field);
+ phone_node.__set_struct_fields(phone_fields);
+ type.types.push_back(phone_node);
+ TTypeNode country_code_node;
+ country_code_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType country_code_scalar;
+ country_code_scalar.__set_type(TPrimitiveType::STRING);
+ country_code_node.__set_scalar_type(country_code_scalar);
+ type.types.push_back(country_code_node);
+ TTypeNode number_node;
+ number_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType number_scalar;
+ number_scalar.__set_type(TPrimitiveType::STRING);
+ number_node.__set_scalar_type(number_scalar);
+ type.types.push_back(number_node);
+ TTypeNode hobbies_node;
+ hobbies_node.__set_type(TTypeNodeType::ARRAY);
+ hobbies_node.__set_contains_nulls({true});
+ type.types.push_back(hobbies_node);
+ TTypeNode hobby_element_node;
+ hobby_element_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> hobby_element_fields;
+ TStructField hobby_name_field;
+ hobby_name_field.__set_name("name");
+ hobby_name_field.__set_contains_null(true);
+ hobby_element_fields.push_back(hobby_name_field);
+ TStructField hobby_level_field;
+ hobby_level_field.__set_name("level");
+ hobby_level_field.__set_contains_null(true);
+ hobby_element_fields.push_back(hobby_level_field);
+ hobby_element_node.__set_struct_fields(hobby_element_fields);
+ type.types.push_back(hobby_element_node);
+ TTypeNode hobby_name_node;
+ hobby_name_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType hobby_name_scalar;
+ hobby_name_scalar.__set_type(TPrimitiveType::STRING);
+ hobby_name_node.__set_scalar_type(hobby_name_scalar);
+ type.types.push_back(hobby_name_node);
+ TTypeNode hobby_level_node;
+ hobby_level_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType hobby_level_scalar;
+ hobby_level_scalar.__set_type(TPrimitiveType::INT);
+ hobby_level_node.__set_scalar_type(hobby_level_scalar);
+ type.types.push_back(hobby_level_node);
+ tslot_desc.__set_slotType(type);
+ } else {
+ // Regular type
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(types[i]);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ }
+ tslot_desc.__set_id(i);
+ tslot_desc.__set_parent(0);
+ tslot_desc.__set_colName(table_column_names[i]);
+ tslot_desc.__set_columnPos(table_column_positions[i]);
+ tslot_desc.__set_byteOffset(0);
+ tslot_desc.__set_nullIndicatorByte(0);
+ tslot_desc.__set_nullIndicatorBit(-1);
+ tslot_desc.__set_slotIdx(0);
+ tslot_desc.__set_isMaterialized(true);
+ // Set column_access_paths only for the profile field
+ if (table_column_names[i] == "profile") {
+ {
+ std::vector<TColumnAccessPath> access_paths;
+ // address.coordinates.lat
+ TColumnAccessPath path1;
+ path1.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path1;
+ data_path1.__set_path({"profile", "address", "coordinates", "lat"});
+ path1.__set_data_access_path(data_path1);
+ access_paths.push_back(path1);
+ // address.coordinates.lng
+ TColumnAccessPath path2;
+ path2.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path2;
+ data_path2.__set_path({"profile", "address", "coordinates", "lng"});
+ path2.__set_data_access_path(data_path2);
+ access_paths.push_back(path2);
+ // contact.email
+ TColumnAccessPath path3;
+ path3.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path3;
+ data_path3.__set_path({"profile", "contact", "email"});
+ path3.__set_data_access_path(data_path3);
+ access_paths.push_back(path3);
+ // hobbies[].element.level
+ TColumnAccessPath path4;
+ path4.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path4;
+ data_path4.__set_path({"profile", "hobbies", "*", "level"});
+ path4.__set_data_access_path(data_path4);
+ access_paths.push_back(path4);
+ tslot_desc.__set_all_access_paths(access_paths);
+ }
+ }
+ t_desc_table.slotDescriptors.push_back(tslot_desc);
+ }
+ t_desc_table.__isset.slotDescriptors = true;
+ TTupleDescriptor t_tuple_desc;
+ t_tuple_desc.__set_id(0);
+ t_tuple_desc.__set_byteSize(16);
+ t_tuple_desc.__set_numNullBytes(0);
+ t_tuple_desc.__set_tableId(0);
+ t_tuple_desc.__isset.tableId = true;
+ t_desc_table.tupleDescriptors.push_back(t_tuple_desc);
+ };
+
+ create_table_desc(t_desc_table, t_table_desc, column_names, column_positions, column_types);
+ EXPECT_TRUE(DescriptorTbl::create(&obj_pool, t_desc_table, desc_tbl).ok());
+ return (*desc_tbl)->get_tuple_descriptor(0);
+ }
+
+ // Helper function to recursively print column row counts and check size > 0
+ void verify_test_results(Block& block, size_t read_rows) {
+ // Verify that we read some data
+ EXPECT_GT(read_rows, 0) << "Should read at least one row";
+ EXPECT_EQ(block.rows(), read_rows);
+
+ // Verify column count matches expected (2 columns: name, profile)
+ EXPECT_EQ(block.columns(), 2);
+
+ // Verify column names and types
+ auto columns_with_names = block.get_columns_with_type_and_name();
+ std::vector<std::string> expected_column_names = {"name", "profile"};
+ for (size_t i = 0; i < expected_column_names.size(); i++) {
+ EXPECT_EQ(columns_with_names[i].name, expected_column_names[i]);
+ }
+
+ // Verify column types
+ EXPECT_TRUE(columns_with_names[0].type->get_name().find("String") !=
+ std::string::npos); // name is STRING
+ EXPECT_TRUE(columns_with_names[1].type->get_name().find("Struct") !=
+ std::string::npos); // profile is STRUCT
+
+ // Print row count for each column and nested subcolumns
+ std::cout << "Block rows: " << block.rows() << std::endl;
+
+ // Helper function to recursively print column row counts
+ std::function<void(const ColumnPtr&, const DataTypePtr&, const std::string&, int)>
+ print_column_rows = [&](const ColumnPtr& col, const DataTypePtr& type,
+ const std::string& name, int depth) {
+ std::string indent(depth * 2, ' ');
+ std::cout << indent << name << " row count: " << col->size() << std::endl;
+ EXPECT_GT(col->size(), 0) << name << " column/subcolumn size should be > 0";
+
+ // Check if it's a nullable column
+ if (const auto* nullable_col = typeid_cast<const ColumnNullable*>(col.get())) {
+ auto nested_type =
+ assert_cast<const DataTypeNullable*>(type.get())->get_nested_type();
+
+ // Only add ".nested" suffix for non-leaf (complex) nullable columns
+ // Leaf columns like String, Int, etc. should not get the ".nested" suffix
+ bool is_complex_type =
+ (typeid_cast<const DataTypeStruct*>(nested_type.get()) !=
+ nullptr) ||
+ (typeid_cast<const DataTypeArray*>(nested_type.get()) != nullptr) ||
+ (typeid_cast<const DataTypeMap*>(nested_type.get()) != nullptr);
+
+ std::string nested_name = is_complex_type ? name + ".nested" : name;
+ print_column_rows(nullable_col->get_nested_column_ptr(), nested_type,
+ nested_name, depth + (is_complex_type ? 1 : 0));
+ }
+ // Check if it's a struct column
+ else if (const auto* struct_col = typeid_cast<const ColumnStruct*>(col.get())) {
+ auto struct_type = assert_cast<const DataTypeStruct*>(type.get());
+ for (size_t i = 0; i < struct_col->tuple_size(); ++i) {
+ std::string field_name = struct_type->get_element_name(i);
+ auto field_type = struct_type->get_element(i);
+ print_column_rows(struct_col->get_column_ptr(i), field_type,
+ name + "." + field_name, depth + 1);
+ }
+ }
+ // Check if it's an array column
+ else if (const auto* array_col = typeid_cast<const ColumnArray*>(col.get())) {
+ auto array_type = assert_cast<const DataTypeArray*>(type.get());
+ auto element_type = array_type->get_nested_type();
+ print_column_rows(array_col->get_data_ptr(), element_type, name + ".data",
+ depth + 1);
+ }
+ };
+
+ // Print row counts for all columns
+ for (size_t i = 0; i < block.columns(); ++i) {
+ const auto& column_with_name = block.get_by_position(i);
+ print_column_rows(column_with_name.column, column_with_name.type, column_with_name.name,
+ 0);
+ EXPECT_EQ(column_with_name.column->size(), block.rows())
+ << "Column " << column_with_name.name << " size mismatch";
+ }
+ }
+
+ std::unique_ptr<doris::FileMetaCache> cache;
+ cctz::time_zone timezone_obj;
+};
+
+// Test reading real Hive Parquet file using HiveTableReader
+TEST_F(HiveReaderTest, ReadHiveParquetFile) {
+ // Read only: name, profile.address.coordinates.lat, profile.address.coordinates.lng, profile.contact.email
+ // Setup table descriptor for test columns with new schema:
+ /**
+ Schema:
+ message table {
+ required int64 id = 1;
+ required binary name (STRING) = 2;
+ required group profile = 3 {
+ optional group address = 4 {
+ optional binary street (STRING) = 7;
+ optional binary city (STRING) = 8;
+ optional group coordinates = 9 {
+ optional double lat = 10;
+ optional double lng = 11;
+ }
+ }
+ optional group contact = 5 {
+ optional binary email (STRING) = 12;
+ optional group phone = 13 {
+ optional binary country_code (STRING) = 14;
+ optional binary number (STRING) = 15;
+ }
+ }
+ optional group hobbies (LIST) = 6 {
+ repeated group list {
+ optional group element = 16 {
+ optional binary name (STRING) = 17;
+ optional int32 level = 18;
+ }
+ }
+ }
+ }
+ }
+ */
+
+ // Open the Hive Parquet test file
+ auto local_fs = io::global_local_filesystem();
+ io::FileReaderSPtr file_reader;
+ std::string test_file =
+ "./be/test/exec/test_data/complex_user_profiles_iceberg_parquet/data/"
+ "00000-0-a0022aad-d3b6-4e73-b181-f0a09aac7034-0-00001.parquet";
+ auto st = local_fs->open_file(test_file, &file_reader);
+ if (!st.ok()) {
+ GTEST_SKIP() << "Test file not found: " << test_file;
+ return;
+ }
+
+ // Setup runtime state
+ RuntimeState runtime_state((TQueryGlobals()));
+
+ // Setup scan parameters
+ TFileScanRangeParams scan_params;
+ scan_params.format_type = TFileFormatType::FORMAT_PARQUET;
+
+ TFileRangeDesc scan_range;
+ scan_range.start_offset = 0;
+ scan_range.size = file_reader->size(); // Read entire file
+ scan_range.path = test_file;
+
+ // Create mock profile
+ RuntimeProfile profile("test_profile");
+
+ // Create ParquetReader as the underlying file format reader
+ cctz::time_zone ctz;
+ TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, ctz);
+
+ auto generic_reader = ParquetReader::create_unique(&profile, scan_params, scan_range, 1024,
+ &ctz, nullptr, &runtime_state, cache.get());
+ ASSERT_NE(generic_reader, nullptr);
+
+ // Set file reader for the generic reader
+ auto parquet_reader = static_cast<ParquetReader*>(generic_reader.get());
+ parquet_reader->set_file_reader(file_reader);
+
+ // Create HiveParquetReader
+ auto hive_reader = std::make_unique<HiveParquetReader>(std::move(generic_reader), &profile,
+ &runtime_state, scan_params, scan_range,
+ nullptr, nullptr, cache.get());
+
+ // Create complex struct types using helper function
+ DataTypePtr coordinates_struct_type, address_struct_type, phone_struct_type;
+ DataTypePtr contact_struct_type, hobby_element_struct_type, hobbies_array_type;
+ DataTypePtr profile_struct_type, name_type;
+ create_complex_struct_types(coordinates_struct_type, address_struct_type, phone_struct_type,
+ contact_struct_type, hobby_element_struct_type, hobbies_array_type,
+ profile_struct_type, name_type);
+
+ // Create tuple descriptor using helper function
+ DescriptorTbl* desc_tbl;
+ ObjectPool obj_pool;
+ TDescriptorTable t_desc_table;
+ TTableDescriptor t_table_desc;
+ std::vector<std::string> table_column_names = {"name", "profile"};
+ std::vector<int> table_column_positions = {1, 2};
+ std::vector<TPrimitiveType::type> table_column_types = {
+ TPrimitiveType::STRING, TPrimitiveType::STRUCT // profile 用 STRUCT 类型
+ };
+ const TupleDescriptor* tuple_descriptor =
+ create_tuple_descriptor(&desc_tbl, obj_pool, t_desc_table, t_table_desc,
+ table_column_names, table_column_positions, table_column_types);
+
+ VExprContextSPtrs conjuncts; // Empty conjuncts for this test
+ std::vector<std::string> table_col_names = {"name", "profile"};
+ const std::unordered_map<std::string, ColumnValueRangeType>* colname_to_value_range = nullptr;
+ const RowDescriptor* row_descriptor = nullptr;
+ const std::unordered_map<std::string, int>* colname_to_slot_id = nullptr;
+ const VExprContextSPtrs* not_single_slot_filter_conjuncts = nullptr;
+ const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts = nullptr;
+
+ st = hive_reader->init_reader(table_col_names, colname_to_value_range, conjuncts,
+ tuple_descriptor, row_descriptor, colname_to_slot_id,
+ not_single_slot_filter_conjuncts, slot_id_to_filter_conjuncts);
+ ASSERT_TRUE(st.ok()) << st;
+
+ std::unordered_map<std::string, std::tuple<std::string, const SlotDescriptor*>>
+ partition_columns;
+ std::unordered_map<std::string, VExprContextSPtr> missing_columns;
+ ASSERT_TRUE(hive_reader->set_fill_columns(partition_columns, missing_columns).ok());
+
+ // Create block for reading nested structure (not flattened)
+ Block block;
+ {
+ MutableColumnPtr name_column = name_type->create_column();
+ block.insert(ColumnWithTypeAndName(std::move(name_column), name_type, "name"));
+ // Add profile column (nested struct)
+ MutableColumnPtr profile_column = profile_struct_type->create_column();
+ block.insert(
+ ColumnWithTypeAndName(std::move(profile_column), profile_struct_type, "profile"));
+ }
+
+ // Read data from the file
+ size_t read_rows = 0;
+ bool eof = false;
+ st = hive_reader->get_next_block(&block, &read_rows, &eof);
+ ASSERT_TRUE(st.ok()) << st;
+
+ // Verify test results using helper function
+ verify_test_results(block, read_rows);
+}
+
+// Test reading real Hive Orc file using HiveTableReader
+TEST_F(HiveReaderTest, ReadHiveOrcFile) {
+ // Read only: name, profile.address.coordinates.lat, profile.address.coordinates.lng, profile.contact.email
+ // Setup table descriptor for test columns with new schema:
+ /**
+ Schema:
+ message table {
+ required int64 id = 1;
+ required binary name (STRING) = 2;
+ required group profile = 3 {
+ optional group address = 4 {
+ optional binary street (STRING) = 7;
+ optional binary city (STRING) = 8;
+ optional group coordinates = 9 {
+ optional double lat = 10;
+ optional double lng = 11;
+ }
+ }
+ optional group contact = 5 {
+ optional binary email (STRING) = 12;
+ optional group phone = 13 {
+ optional binary country_code (STRING) = 14;
+ optional binary number (STRING) = 15;
+ }
+ }
+ optional group hobbies (LIST) = 6 {
+ repeated group list {
+ optional group element = 16 {
+ optional binary name (STRING) = 17;
+ optional int32 level = 18;
+ }
+ }
+ }
+ }
+ }
+ */
+ // Open the Hive Orc test file
+ auto local_fs = io::global_local_filesystem();
+ io::FileReaderSPtr file_reader;
+ std::string test_file =
+ "./be/test/exec/test_data/complex_user_profiles_iceberg_orc/data/"
+ "00000-0-e4897963-0081-4127-bebe-35dc7dc1edeb-0-00001.orc";
+ auto st = local_fs->open_file(test_file, &file_reader);
+ if (!st.ok()) {
+ GTEST_SKIP() << "Test file not found: " << test_file;
+ return;
+ }
+
+ // Setup runtime state
+ RuntimeState runtime_state((TQueryGlobals()));
+
+ // Setup scan parameters
+ TFileScanRangeParams scan_params;
+ scan_params.format_type = TFileFormatType::FORMAT_ORC;
+
+ TFileRangeDesc scan_range;
+ scan_range.start_offset = 0;
+ scan_range.size = file_reader->size(); // Read entire file
+ scan_range.path = test_file;
+
+ // Create mock profile
+ RuntimeProfile profile("test_profile");
+
+ // Create OrcReader as the underlying file format reader
+ cctz::time_zone ctz;
+ TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, ctz);
+
+ auto generic_reader = OrcReader::create_unique(&profile, &runtime_state, scan_params,
+ scan_range, 1024, "CST", nullptr, cache.get());
+ ASSERT_NE(generic_reader, nullptr);
+
+ // Create HiveOrcReader
+ auto hive_reader =
+ std::make_unique<HiveOrcReader>(std::move(generic_reader), &profile, &runtime_state,
+ scan_params, scan_range, nullptr, nullptr, cache.get());
+
+ // Create complex struct types using helper function
+ DataTypePtr coordinates_struct_type, address_struct_type, phone_struct_type;
+ DataTypePtr contact_struct_type, hobby_element_struct_type, hobbies_array_type;
+ DataTypePtr profile_struct_type, name_type;
+ create_complex_struct_types(coordinates_struct_type, address_struct_type, phone_struct_type,
+ contact_struct_type, hobby_element_struct_type, hobbies_array_type,
+ profile_struct_type, name_type);
+
+ // Create tuple descriptor using helper function
+ DescriptorTbl* desc_tbl;
+ ObjectPool obj_pool;
+ TDescriptorTable t_desc_table;
+ TTableDescriptor t_table_desc;
+ std::vector<std::string> table_column_names = {"name", "profile"};
+ std::vector<int> table_column_positions = {1, 2};
+ std::vector<TPrimitiveType::type> table_column_types = {
+ TPrimitiveType::STRING, TPrimitiveType::STRUCT // profile 用 STRUCT 类型
+ };
+ const TupleDescriptor* tuple_descriptor =
+ create_tuple_descriptor(&desc_tbl, obj_pool, t_desc_table, t_table_desc,
+ table_column_names, table_column_positions, table_column_types);
+
+ VExprContextSPtrs conjuncts; // Empty conjuncts for this test
+ std::vector<std::string> table_col_names = {"name", "profile"};
+ const std::unordered_map<std::string, ColumnValueRangeType>* colname_to_value_range = nullptr;
+ const RowDescriptor* row_descriptor = nullptr;
+ const VExprContextSPtrs* not_single_slot_filter_conjuncts = nullptr;
+ const std::unordered_map<int, VExprContextSPtrs>* slot_id_to_filter_conjuncts = nullptr;
+
+ st = hive_reader->init_reader(table_col_names, colname_to_value_range, conjuncts,
+ tuple_descriptor, row_descriptor,
+ not_single_slot_filter_conjuncts, slot_id_to_filter_conjuncts);
+ ASSERT_TRUE(st.ok()) << st;
+
+ std::unordered_map<std::string, std::tuple<std::string, const SlotDescriptor*>>
+ partition_columns;
+ std::unordered_map<std::string, VExprContextSPtr> missing_columns;
+ ASSERT_TRUE(hive_reader->set_fill_columns(partition_columns, missing_columns).ok());
+
+ // Create block for reading nested structure (not flattened)
+ Block block;
+
+ {
+ MutableColumnPtr name_column = name_type->create_column();
+ block.insert(ColumnWithTypeAndName(std::move(name_column), name_type, "name"));
+ // Add profile column (nested struct)
+ MutableColumnPtr profile_column = profile_struct_type->create_column();
+ block.insert(
+ ColumnWithTypeAndName(std::move(profile_column), profile_struct_type, "profile"));
+ }
+
+ // Read data from the file
+ size_t read_rows = 0;
+ bool eof = false;
+ st = hive_reader->get_next_block(&block, &read_rows, &eof);
+ ASSERT_TRUE(st.ok()) << st;
+
+ // Verify test results using helper function
+ verify_test_results(block, read_rows);
+}
+
+} // namespace doris::vectorized::table
diff --git a/be/test/vec/exec/format/table/iceberg/iceberg_reader_create_column_ids_test.cpp b/be/test/vec/exec/format/table/iceberg/iceberg_reader_create_column_ids_test.cpp
new file mode 100644
index 00000000000..47e0ad87a4a
--- /dev/null
+++ b/be/test/vec/exec/format/table/iceberg/iceberg_reader_create_column_ids_test.cpp
@@ -0,0 +1,1177 @@
+// 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 <cctz/time_zone.h>
+#include <gen_cpp/Descriptors_types.h>
+#include <gen_cpp/PaloInternalService_types.h>
+#include <gen_cpp/PlanNodes_types.h>
+#include <gen_cpp/Types_types.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "common/object_pool.h"
+#include "exec/olap_common.h"
+#include "io/fs/file_meta_cache.h"
+#include "io/fs/file_reader_writer_fwd.h"
+#include "io/fs/file_system.h"
+#include "io/fs/local_file_system.h"
+#include "runtime/descriptors.h"
+#include "runtime/runtime_state.h"
+#include "util/timezone_utils.h"
+#include "vec/columns/column.h"
+#include "vec/columns/column_array.h"
+#include "vec/columns/column_nullable.h"
+#include "vec/columns/column_struct.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_factory.hpp"
+#include "vec/data_types/data_type_nullable.h"
+#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_string.h"
+#include "vec/data_types/data_type_struct.h"
+#include "vec/exec/format/parquet/vparquet_reader.h"
+#include "vec/exec/format/table/iceberg_reader.h"
+
+namespace doris::vectorized {
+
+// Define the column access path configuration structure
+struct ColumnAccessPathConfig {
+ std::string column_name;
+ std::vector<std::vector<std::string>> all_column_paths; // For all_column_access_paths
+ std::vector<std::vector<std::string>> predicate_paths; // For predicate_column_access_paths
+ bool use_name_paths = true; // true=use field names, false=use indices
+};
+
+// Iceberg 列ID分配 (基于 Protobuf Schema 定义)
+// 1: id (int64)
+// 2: name (string)
+// 3: profile (struct)
+// 9: address (struct)
+// 12: street (string)
+// 13: city (string)
+// 14: coordinates (struct)
+// 15: lat (double)
+// 16: lng (double)
+// 10: contact (struct)
+// 17: email (string)
+// 18: phone (struct)
+// 19: country_code (string)
+// 20: number (string)
+// 11: hobbies (list)
+// 21: element (struct)
+// 22: name (string)
+// 23: level (int32)
+// 4: tags (list)
+// 24: element (string)
+// 5: friends (list)
+// 25: element (struct)
+// 26: user_id (int64)
+// 27: nickname (string)
+// 28: friendship_level (int32)
+// 6: recent_activity (list)
+// 29: element (struct)
+// 30: action (string)
+// 31: details (list)
+// 32: element (struct)
+// 33: key (string)
+// 34: value (string)
+// 7: attributes (map)
+// 35: key (string)
+// 36: value (string)
+// 8: complex_attributes (map)
+// 37: key (string)
+// 38: value (struct)
+// 39: metadata (struct)
+// 43: version (string)
+// 44: created_time (timestamp)
+// 45: last_updated (timestamp)
+// 46: quality_score (double)
+// 40: historical_scores (list)
+// 47: element (struct)
+// 48: period (string)
+// 49: score (double)
+// 50: components (list)
+// 52: element (struct)
+// 53: component_name (string)
+// 54: weight (float)
+// 55: sub_scores (map)
+// 56: key (string)
+// 57: value (double)
+// 51: trends (struct)
+// 58: direction (string)
+// 59: magnitude (double)
+// 60: confidence_interval (struct)
+// 61: lower (double)
+// 62: upper (double)
+// 41: hierarchical_data (map)
+// 63: key (string)
+// 64: value (struct)
+// 65: category (string)
+// 66: sub_items (list)
+// 68: element (struct)
+// 69: item_id (int64)
+// 70: properties (map)
+// 72: key (string)
+// 73: value (string)
+// 71: metrics (struct)
+// 74: value (double)
+// 75: unit (string)
+// 67: summary (struct)
+// 76: total_count (int32)
+// 77: average_value (double)
+// 42: validation_rules (struct)
+// 78: required (boolean)
+// 79: constraints (list)
+// 81: element (struct)
+// 82: rule_type (string)
+// 83: parameters (map)
+// 85: key (string)
+// 86: value (string)
+// 84: error_message (string)
+// 80: complex_constraint (struct)
+// 87: logic_operator (string)
+// 88: operands (list)
+// 89: element (struct)
+// 90: field_path (string)
+// 91: operator (string)
+// 92: comparison_value (string)
+
+class IcebergReaderCreateColumnIdsTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ cache = std::make_unique<doris::FileMetaCache>(1024);
+
+ // Setup timezone
+ doris::TimezoneUtils::find_cctz_time_zone(doris::TimezoneUtils::default_time_zone,
+ timezone_obj);
+ }
+
+ void TearDown() override { cache.reset(); }
+
+ // Helper function to map column name to Iceberg field ID
+ int get_iceberg_field_id(const std::string& column_name) {
+ // Mapping based on the Iceberg schema definition in comments
+ static const std::unordered_map<std::string, int> column_to_field_id = {
+ {"id", 1}, {"name", 2},
+ {"profile", 3}, {"tags", 4},
+ {"friends", 5}, {"recent_activity", 6},
+ {"attributes", 7}, {"complex_attributes", 8}};
+
+ auto it = column_to_field_id.find(column_name);
+ if (it != column_to_field_id.end()) {
+ return it->second;
+ }
+ return -1; // Invalid field ID
+ }
+
+ // Helper function to create tuple descriptor
+ const TupleDescriptor* create_tuple_descriptor(
+ DescriptorTbl** desc_tbl, ObjectPool& obj_pool, TDescriptorTable& t_desc_table,
+ TTableDescriptor& t_table_desc, const std::vector<std::string>& column_names,
+ const std::vector<int>& column_positions,
+ const std::vector<TPrimitiveType::type>& column_types,
+ const std::vector<ColumnAccessPathConfig>& access_configs = {}) {
+ // Create table descriptor with complex schema
+ auto create_table_desc = [this, &access_configs](
+ TDescriptorTable& t_desc_table,
+ TTableDescriptor& t_table_desc,
+ const std::vector<std::string>& table_column_names,
+ const std::vector<int>& table_column_positions,
+ const std::vector<TPrimitiveType::type>& types) {
+ t_table_desc.__set_id(0);
+ t_table_desc.__set_tableType(TTableType::HIVE_TABLE);
+ t_table_desc.__set_numCols(0);
+ t_table_desc.__set_numClusteringCols(0);
+ t_desc_table.tableDescriptors.push_back(t_table_desc);
+ t_desc_table.__isset.tableDescriptors = true;
+ for (int i = 0; i < table_column_names.size(); i++) {
+ TSlotDescriptor tslot_desc;
+ TTypeDesc type;
+ if (table_column_names[i] == "id") {
+ // id: bigint
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(TPrimitiveType::BIGINT);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "name") {
+ // name: string
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(TPrimitiveType::STRING);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "tags") {
+ // tags: array<string>
+ TTypeNode array_node;
+ array_node.__set_type(TTypeNodeType::ARRAY);
+ array_node.__set_contains_nulls({true});
+ type.types.push_back(array_node);
+ TTypeNode element_node;
+ element_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType element_scalar;
+ element_scalar.__set_type(TPrimitiveType::STRING);
+ element_node.__set_scalar_type(element_scalar);
+ type.types.push_back(element_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "friends") {
+ // friends: array<struct<user_id:bigint, nickname:string, friendship_level:int>>
+ TTypeNode array_node;
+ array_node.__set_type(TTypeNodeType::ARRAY);
+ array_node.__set_contains_nulls({true});
+ type.types.push_back(array_node);
+ TTypeNode element_struct_node;
+ element_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> element_fields;
+ TStructField user_id_field;
+ user_id_field.__set_name("user_id");
+ user_id_field.__set_contains_null(true);
+ element_fields.push_back(user_id_field);
+ TStructField nickname_field;
+ nickname_field.__set_name("nickname");
+ nickname_field.__set_contains_null(true);
+ element_fields.push_back(nickname_field);
+ TStructField friendship_level_field;
+ friendship_level_field.__set_name("friendship_level");
+ friendship_level_field.__set_contains_null(true);
+ element_fields.push_back(friendship_level_field);
+ element_struct_node.__set_struct_fields(element_fields);
+ type.types.push_back(element_struct_node);
+ // user_id: bigint
+ TTypeNode user_id_node;
+ user_id_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType user_id_scalar;
+ user_id_scalar.__set_type(TPrimitiveType::BIGINT);
+ user_id_node.__set_scalar_type(user_id_scalar);
+ type.types.push_back(user_id_node);
+ // nickname: string
+ TTypeNode nickname_node;
+ nickname_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType nickname_scalar;
+ nickname_scalar.__set_type(TPrimitiveType::STRING);
+ nickname_node.__set_scalar_type(nickname_scalar);
+ type.types.push_back(nickname_node);
+ // friendship_level: int
+ TTypeNode friendship_level_node;
+ friendship_level_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType friendship_level_scalar;
+ friendship_level_scalar.__set_type(TPrimitiveType::INT);
+ friendship_level_node.__set_scalar_type(friendship_level_scalar);
+ type.types.push_back(friendship_level_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "recent_activity") {
+ // recent_activity: array<struct<action:string, details:array<struct<key:string, value:string>>>>
+ TTypeNode array_node;
+ array_node.__set_type(TTypeNodeType::ARRAY);
+ array_node.__set_contains_nulls({true});
+ type.types.push_back(array_node);
+ TTypeNode element_struct_node;
+ element_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> element_fields;
+ TStructField action_field;
+ action_field.__set_name("action");
+ action_field.__set_contains_null(true);
+ element_fields.push_back(action_field);
+ TStructField details_field;
+ details_field.__set_name("details");
+ details_field.__set_contains_null(true);
+ element_fields.push_back(details_field);
+ element_struct_node.__set_struct_fields(element_fields);
+ type.types.push_back(element_struct_node);
+ // action: string
+ TTypeNode action_node;
+ action_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType action_scalar;
+ action_scalar.__set_type(TPrimitiveType::STRING);
+ action_node.__set_scalar_type(action_scalar);
+ type.types.push_back(action_node);
+ // details: array<struct<key:string, value:string>>
+ TTypeNode details_array_node;
+ details_array_node.__set_type(TTypeNodeType::ARRAY);
+ details_array_node.__set_contains_nulls({true});
+ type.types.push_back(details_array_node);
+ TTypeNode details_element_struct_node;
+ details_element_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> details_element_fields;
+ TStructField key_field;
+ key_field.__set_name("key");
+ key_field.__set_contains_null(true);
+ details_element_fields.push_back(key_field);
+ TStructField value_field;
+ value_field.__set_name("value");
+ value_field.__set_contains_null(true);
+ details_element_fields.push_back(value_field);
+ details_element_struct_node.__set_struct_fields(details_element_fields);
+ type.types.push_back(details_element_struct_node);
+ // key: string
+ TTypeNode key_node;
+ key_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType key_scalar;
+ key_scalar.__set_type(TPrimitiveType::STRING);
+ key_node.__set_scalar_type(key_scalar);
+ type.types.push_back(key_node);
+ // value: string
+ TTypeNode value_node;
+ value_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType value_scalar;
+ value_scalar.__set_type(TPrimitiveType::STRING);
+ value_node.__set_scalar_type(value_scalar);
+ type.types.push_back(value_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "attributes") {
+ // attributes: map<string, string>
+ TTypeNode map_node;
+ map_node.__set_type(TTypeNodeType::MAP);
+ map_node.__set_contains_nulls({true, true});
+ type.types.push_back(map_node);
+ // key: string
+ TTypeNode key_node;
+ key_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType key_scalar;
+ key_scalar.__set_type(TPrimitiveType::STRING);
+ key_node.__set_scalar_type(key_scalar);
+ type.types.push_back(key_node);
+ // value: string
+ TTypeNode value_node;
+ value_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType value_scalar;
+ value_scalar.__set_type(TPrimitiveType::STRING);
+ value_node.__set_scalar_type(value_scalar);
+ type.types.push_back(value_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "complex_attributes") {
+ // complex_attributes: map<string, struct<metadata:struct<version:string, created_time:timestamp, last_updated:timestamp>>>
+ TTypeNode map_node;
+ map_node.__set_type(TTypeNodeType::MAP);
+ map_node.__set_contains_nulls({true, true});
+ type.types.push_back(map_node);
+ // key: string
+ TTypeNode key_node;
+ key_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType key_scalar;
+ key_scalar.__set_type(TPrimitiveType::STRING);
+ key_node.__set_scalar_type(key_scalar);
+ type.types.push_back(key_node);
+ // value: struct<metadata:struct<version:string, created_time:timestamp, last_updated:timestamp>>
+ TTypeNode value_struct_node;
+ value_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> value_fields;
+ TStructField metadata_field;
+ metadata_field.__set_name("metadata");
+ metadata_field.__set_contains_null(true);
+ value_fields.push_back(metadata_field);
+ value_struct_node.__set_struct_fields(value_fields);
+ type.types.push_back(value_struct_node);
+ // metadata: struct<version:string, created_time:timestamp, last_updated:timestamp>
+ TTypeNode metadata_struct_node;
+ metadata_struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> metadata_fields;
+ TStructField version_field;
+ version_field.__set_name("version");
+ version_field.__set_contains_null(true);
+ metadata_fields.push_back(version_field);
+ TStructField created_time_field;
+ created_time_field.__set_name("created_time");
+ created_time_field.__set_contains_null(true);
+ metadata_fields.push_back(created_time_field);
+ TStructField last_updated_field;
+ last_updated_field.__set_name("last_updated");
+ last_updated_field.__set_contains_null(true);
+ metadata_fields.push_back(last_updated_field);
+ metadata_struct_node.__set_struct_fields(metadata_fields);
+ type.types.push_back(metadata_struct_node);
+ // version: string
+ TTypeNode version_node;
+ version_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType version_scalar;
+ version_scalar.__set_type(TPrimitiveType::STRING);
+ version_node.__set_scalar_type(version_scalar);
+ type.types.push_back(version_node);
+ // created_time: timestamp
+ TTypeNode created_time_node;
+ created_time_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType created_time_scalar;
+ created_time_scalar.__set_type(TPrimitiveType::DATETIME);
+ created_time_node.__set_scalar_type(created_time_scalar);
+ type.types.push_back(created_time_node);
+ // last_updated: timestamp
+ TTypeNode last_updated_node;
+ last_updated_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType last_updated_scalar;
+ last_updated_scalar.__set_type(TPrimitiveType::DATETIME);
+ last_updated_node.__set_scalar_type(last_updated_scalar);
+ type.types.push_back(last_updated_node);
+ tslot_desc.__set_slotType(type);
+ } else if (table_column_names[i] == "profile") {
+ // STRUCT/ARRAY 节点设置 contains_nulls,SCALAR 节点不设置
+ TTypeNode struct_node;
+ struct_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> struct_fields;
+ TStructField address_field;
+ address_field.__set_name("address");
+ address_field.__set_contains_null(true);
+ struct_fields.push_back(address_field);
+ TStructField contact_field;
+ contact_field.__set_name("contact");
+ contact_field.__set_contains_null(true);
+ struct_fields.push_back(contact_field);
+ TStructField hobbies_field;
+ hobbies_field.__set_name("hobbies");
+ hobbies_field.__set_contains_null(true);
+ struct_fields.push_back(hobbies_field);
+ struct_node.__set_struct_fields(struct_fields);
+ type.types.push_back(struct_node);
+ TTypeNode address_node;
+ address_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> address_fields;
+ TStructField street_field;
+ street_field.__set_name("street");
+ street_field.__set_contains_null(true);
+ address_fields.push_back(street_field);
+ TStructField city_field;
+ city_field.__set_name("city");
+ city_field.__set_contains_null(true);
+ address_fields.push_back(city_field);
+ TStructField coordinates_field;
+ coordinates_field.__set_name("coordinates");
+ coordinates_field.__set_contains_null(true);
+ address_fields.push_back(coordinates_field);
+ address_node.__set_struct_fields(address_fields);
+ type.types.push_back(address_node);
+ TTypeNode street_node;
+ street_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType street_scalar;
+ street_scalar.__set_type(TPrimitiveType::STRING);
+ street_node.__set_scalar_type(street_scalar);
+ type.types.push_back(street_node);
+ TTypeNode city_node;
+ city_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType city_scalar;
+ city_scalar.__set_type(TPrimitiveType::STRING);
+ city_node.__set_scalar_type(city_scalar);
+ type.types.push_back(city_node);
+ TTypeNode coordinates_node;
+ coordinates_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> coordinates_fields;
+ TStructField lat_field;
+ lat_field.__set_name("lat");
+ lat_field.__set_contains_null(true);
+ coordinates_fields.push_back(lat_field);
+ TStructField lng_field;
+ lng_field.__set_name("lng");
+ lng_field.__set_contains_null(true);
+ coordinates_fields.push_back(lng_field);
+ coordinates_node.__set_struct_fields(coordinates_fields);
+ type.types.push_back(coordinates_node);
+ TTypeNode lat_node;
+ lat_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType lat_scalar;
+ lat_scalar.__set_type(TPrimitiveType::DOUBLE);
+ lat_node.__set_scalar_type(lat_scalar);
+ type.types.push_back(lat_node);
+ TTypeNode lng_node;
+ lng_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType lng_scalar;
+ lng_scalar.__set_type(TPrimitiveType::DOUBLE);
+ lng_node.__set_scalar_type(lng_scalar);
+ type.types.push_back(lng_node);
+ TTypeNode contact_node;
+ contact_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> contact_fields;
+ TStructField email_field;
+ email_field.__set_name("email");
+ email_field.__set_contains_null(true);
+ contact_fields.push_back(email_field);
+ TStructField phone_field;
+ phone_field.__set_name("phone");
+ phone_field.__set_contains_null(true);
+ contact_fields.push_back(phone_field);
+ contact_node.__set_struct_fields(contact_fields);
+ type.types.push_back(contact_node);
+ TTypeNode email_node;
+ email_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType email_scalar;
+ email_scalar.__set_type(TPrimitiveType::STRING);
+ email_node.__set_scalar_type(email_scalar);
+ type.types.push_back(email_node);
+ TTypeNode phone_node;
+ phone_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> phone_fields;
+ TStructField country_code_field;
+ country_code_field.__set_name("country_code");
+ country_code_field.__set_contains_null(true);
+ phone_fields.push_back(country_code_field);
+ TStructField number_field;
+ number_field.__set_name("number");
+ number_field.__set_contains_null(true);
+ phone_fields.push_back(number_field);
+ phone_node.__set_struct_fields(phone_fields);
+ type.types.push_back(phone_node);
+ TTypeNode country_code_node;
+ country_code_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType country_code_scalar;
+ country_code_scalar.__set_type(TPrimitiveType::STRING);
+ country_code_node.__set_scalar_type(country_code_scalar);
+ type.types.push_back(country_code_node);
+ TTypeNode number_node;
+ number_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType number_scalar;
+ number_scalar.__set_type(TPrimitiveType::STRING);
+ number_node.__set_scalar_type(number_scalar);
+ type.types.push_back(number_node);
+ TTypeNode hobbies_node;
+ hobbies_node.__set_type(TTypeNodeType::ARRAY);
+ hobbies_node.__set_contains_nulls({true});
+ type.types.push_back(hobbies_node);
+ TTypeNode hobby_element_node;
+ hobby_element_node.__set_type(TTypeNodeType::STRUCT);
+ std::vector<TStructField> hobby_element_fields;
+ TStructField hobby_name_field;
+ hobby_name_field.__set_name("name");
+ hobby_name_field.__set_contains_null(true);
+ hobby_element_fields.push_back(hobby_name_field);
+ TStructField hobby_level_field;
+ hobby_level_field.__set_name("level");
+ hobby_level_field.__set_contains_null(true);
+ hobby_element_fields.push_back(hobby_level_field);
+ hobby_element_node.__set_struct_fields(hobby_element_fields);
+ type.types.push_back(hobby_element_node);
+ TTypeNode hobby_name_node;
+ hobby_name_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType hobby_name_scalar;
+ hobby_name_scalar.__set_type(TPrimitiveType::STRING);
+ hobby_name_node.__set_scalar_type(hobby_name_scalar);
+ type.types.push_back(hobby_name_node);
+ TTypeNode hobby_level_node;
+ hobby_level_node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType hobby_level_scalar;
+ hobby_level_scalar.__set_type(TPrimitiveType::INT);
+ hobby_level_node.__set_scalar_type(hobby_level_scalar);
+ type.types.push_back(hobby_level_node);
+ tslot_desc.__set_slotType(type);
+ } else {
+ // 普通类型
+ TTypeNode node;
+ node.__set_type(TTypeNodeType::SCALAR);
+ TScalarType scalar_type;
+ scalar_type.__set_type(types[i]);
+ node.__set_scalar_type(scalar_type);
+ type.types.push_back(node);
+ tslot_desc.__set_slotType(type);
+ }
+ tslot_desc.__set_id(i);
+ tslot_desc.__set_parent(0);
+ tslot_desc.__set_colName(table_column_names[i]);
+ tslot_desc.__set_columnPos(table_column_positions[i]);
+ tslot_desc.__set_byteOffset(0);
+ tslot_desc.__set_nullIndicatorByte(0);
+ tslot_desc.__set_nullIndicatorBit(-1);
+ tslot_desc.__set_slotIdx(0);
+ tslot_desc.__set_isMaterialized(true);
+
+ // Set Iceberg field ID (col_unique_id)
+ int iceberg_field_id = get_iceberg_field_id(table_column_names[i]);
+ if (iceberg_field_id > 0) {
+ tslot_desc.__set_col_unique_id(iceberg_field_id);
+ }
+
+ // Use configuration to set column_access_paths
+ for (const auto& config : access_configs) {
+ set_column_access_paths(tslot_desc, config);
+ }
+
+ t_desc_table.slotDescriptors.push_back(tslot_desc);
+ }
+ t_desc_table.__isset.slotDescriptors = true;
+ TTupleDescriptor t_tuple_desc;
+ t_tuple_desc.__set_id(0);
+ t_tuple_desc.__set_byteSize(16);
+ t_tuple_desc.__set_numNullBytes(0);
+ t_tuple_desc.__set_tableId(0);
+ t_tuple_desc.__isset.tableId = true;
+ t_desc_table.tupleDescriptors.push_back(t_tuple_desc);
+ };
+
+ create_table_desc(t_desc_table, t_table_desc, column_names, column_positions, column_types);
+ EXPECT_TRUE(DescriptorTbl::create(&obj_pool, t_desc_table, desc_tbl).ok());
+ return (*desc_tbl)->get_tuple_descriptor(0);
+ }
+
+ // Helper function: set column access paths on a slot descriptor
+ void set_column_access_paths(TSlotDescriptor& tslot_desc,
+ const ColumnAccessPathConfig& config) {
+ if (config.column_name != tslot_desc.colName) {
+ return; // Not the target column, skip
+ }
+
+ // Set all_column_access_paths
+ if (!config.all_column_paths.empty()) {
+ std::vector<TColumnAccessPath> access_paths;
+ for (const auto& path_vector : config.all_column_paths) {
+ TColumnAccessPath access_path;
+ access_path.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path;
+ data_path.__set_path(path_vector);
+ access_path.__set_data_access_path(data_path);
+ access_paths.push_back(access_path);
+ }
+ tslot_desc.__set_all_access_paths(access_paths);
+ }
+
+ // Set predicate_column_access_paths
+ if (!config.predicate_paths.empty()) {
+ std::vector<TColumnAccessPath> access_paths;
+ for (const auto& path_vector : config.predicate_paths) {
+ TColumnAccessPath access_path;
+ access_path.__set_type(doris::TAccessPathType::DATA);
+ TDataAccessPath data_path;
+ data_path.__set_path(path_vector);
+ access_path.__set_data_access_path(data_path);
+ access_paths.push_back(access_path);
+ }
+ tslot_desc.__set_predicate_access_paths(access_paths);
+ }
+ }
+
+ std::unique_ptr<doris::FileMetaCache> cache;
+ cctz::time_zone timezone_obj;
+
+ // Helper function: create and setup ParquetReader
+ std::tuple<std::unique_ptr<IcebergParquetReader>, const FieldDescriptor*> create_parquet_reader(
+ const std::string& test_file) {
+ // Open the Iceberg Parquet test file
+ auto local_fs = io::global_local_filesystem();
+ io::FileReaderSPtr file_reader;
+ auto st = local_fs->open_file(test_file, &file_reader);
+ if (!st.ok()) {
+ return {nullptr, nullptr};
+ }
+
+ // Setup runtime state
+ RuntimeState runtime_state((TQueryGlobals()));
+
+ // Setup scan parameters
+ TFileScanRangeParams scan_params;
+ scan_params.format_type = TFileFormatType::FORMAT_PARQUET;
+
+ TFileRangeDesc scan_range;
+ scan_range.start_offset = 0;
+ scan_range.size = file_reader->size(); // Read entire file
+ scan_range.path = test_file;
+
+ // Create mock profile
+ RuntimeProfile profile("test_profile");
+
+ // Create ParquetReader as the underlying file format reader
+ cctz::time_zone ctz;
+ TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, ctz);
+
+ auto generic_reader =
+ ParquetReader::create_unique(&profile, scan_params, scan_range, 1024, &ctz, nullptr,
+ &runtime_state, cache.get());
+ if (!generic_reader) {
+ return {nullptr, nullptr};
+ }
+
+ // Set file reader for the generic reader
+ auto parquet_reader = static_cast<ParquetReader*>(generic_reader.get());
+ parquet_reader->set_file_reader(file_reader);
+
+ const FieldDescriptor* field_desc = nullptr;
+ st = parquet_reader->get_file_metadata_schema(&field_desc);
+ if (!st.ok() || !field_desc) {
+ return {nullptr, nullptr};
+ }
+
+ // Create IcebergParquetReader
+ auto iceberg_reader = std::make_unique<IcebergParquetReader>(
+ std::move(generic_reader), &profile, &runtime_state, scan_params, scan_range,
+ nullptr, nullptr, cache.get());
+
+ return {std::move(iceberg_reader), field_desc};
+ }
+
+ // Helper function: create and setup OrcReader
+ std::tuple<std::unique_ptr<IcebergOrcReader>, const orc::Type*> create_orc_reader(
+ const std::string& test_file) {
+ // Open the Iceberg Orc test file
+ auto local_fs = io::global_local_filesystem();
+ io::FileReaderSPtr file_reader;
+ auto st = local_fs->open_file(test_file, &file_reader);
+ if (!st.ok()) {
+ return {nullptr, nullptr};
+ }
+
+ // Setup runtime state
+ RuntimeState runtime_state((TQueryGlobals()));
+
+ // Setup scan parameters
+ TFileScanRangeParams scan_params;
+ scan_params.format_type = TFileFormatType::FORMAT_ORC;
+
+ TFileRangeDesc scan_range;
+ scan_range.start_offset = 0;
+ scan_range.size = file_reader->size(); // Read entire file
+ scan_range.path = test_file;
+
+ // Create mock profile
+ RuntimeProfile profile("test_profile");
+
+ // Create OrcReader as the underlying file format reader
+ cctz::time_zone ctz;
+ TimezoneUtils::find_cctz_time_zone(TimezoneUtils::default_time_zone, ctz);
+
+ auto generic_reader =
+ OrcReader::create_unique(&profile, &runtime_state, scan_params, scan_range, 1024,
+ "CST", nullptr, cache.get());
+ if (!generic_reader) {
+ return {nullptr, nullptr};
+ }
+
+ auto orc_reader = static_cast<OrcReader*>(generic_reader.get());
+ // Get FieldDescriptor from Orc file
+ const orc::Type* orc_type_ptr = nullptr;
+ st = orc_reader->get_file_type(&orc_type_ptr);
+ if (!st.ok() || !orc_type_ptr) {
+ return {nullptr, nullptr};
+ }
+
+ // Create IcebergOrcReader
+ auto iceberg_reader = std::make_unique<IcebergOrcReader>(
+ std::move(generic_reader), &profile, &runtime_state, scan_params, scan_range,
+ nullptr, nullptr, cache.get());
+
+ return {std::move(iceberg_reader), orc_type_ptr};
+ }
+
+ // Helper function: run Parquet test with different column ID extraction methods
+ void run_parquet_test_with_method(const std::vector<ColumnAccessPathConfig>& access_configs,
+ const std::set<uint64_t>& expected_column_ids,
+ const std::set<uint64_t>& expected_filter_column_ids,
+ bool use_top_level_method = false,
+ bool should_skip_assertion = false) {
+ std::string test_file =
+ "./be/test/exec/test_data/nested_user_profiles_iceberg_parquet/data/"
+ "00000-9-a7e0135f-d581-40e4-8d56-a929aded99e4-0-00001.parquet";
+
+ auto [iceberg_reader, field_desc] = create_parquet_reader(test_file);
+ if (!iceberg_reader || !field_desc) {
+ GTEST_SKIP() << "Test file not found or failed to create reader: " << test_file;
+ return;
+ }
+
+ // Create tuple descriptor based on full schema
+ DescriptorTbl* desc_tbl;
+ ObjectPool obj_pool;
+ TDescriptorTable t_desc_table;
+ TTableDescriptor t_table_desc;
+
+ // Define all columns according to the schema
+ std::vector<std::string> table_column_names = {
+ "name", "profile", "tags", "friends", "recent_activity",
+ "attributes", "complex_attributes"};
+ std::vector<int> table_column_positions = {1, 2, 3, 4, 5, 6, 7};
+ std::vector<TPrimitiveType::type> table_column_types = {
+ // TPrimitiveType::BIGINT, // id
+ TPrimitiveType::STRING, // name
+ TPrimitiveType::STRUCT, // profile
+ TPrimitiveType::ARRAY, // tags
+ TPrimitiveType::ARRAY, // friends
+ TPrimitiveType::ARRAY, // recent_activity
+ TPrimitiveType::MAP, // attributes
+ TPrimitiveType::MAP // complex_attributes
+ };
+
+ const TupleDescriptor* tuple_descriptor = create_tuple_descriptor(
+ &desc_tbl, obj_pool, t_desc_table, t_table_desc, table_column_names,
+ table_column_positions, table_column_types, access_configs);
+
+ // Execute test based on method choice
+ ColumnIdResult actual_result;
+ if (use_top_level_method) {
+ // actual_result = IcebergParquetReader::_create_column_ids_by_top_level_col_index(
+ // field_desc, tuple_descriptor);
+ } else {
+ actual_result = IcebergParquetReader::_create_column_ids(field_desc, tuple_descriptor);
+ }
+
+ if (!should_skip_assertion) {
+ EXPECT_EQ(actual_result.column_ids, expected_column_ids);
+ EXPECT_EQ(actual_result.filter_column_ids, expected_filter_column_ids);
+ }
+ }
+
+ // Helper function: run Orc test with different column ID extraction methods
+ void run_orc_test_with_method(const std::vector<ColumnAccessPathConfig>& access_configs,
+ const std::set<uint64_t>& expected_column_ids,
+ const std::set<uint64_t>& expected_filter_column_ids,
+ bool use_top_level_method = false,
+ bool should_skip_assertion = false) {
+ std::string test_file =
+ "./be/test/exec/test_data/nested_user_profiles_iceberg_orc/data/"
+ "00000-8-5a144c37-16a4-47c6-96db-0007175b5c90-0-00001.orc";
+
+ auto [iceberg_reader, orc_type] = create_orc_reader(test_file);
+ if (!iceberg_reader || !orc_type) {
+ GTEST_SKIP() << "Test file not found or failed to create reader: " << test_file;
+ return;
+ }
+
+ // Create tuple descriptor based on full schema
+ DescriptorTbl* desc_tbl;
+ ObjectPool obj_pool;
+ TDescriptorTable t_desc_table;
+ TTableDescriptor t_table_desc;
+
+ // Define all columns according to the schema
+ std::vector<std::string> table_column_names = {
+ "name", "profile", "tags", "friends", "recent_activity",
+ "attributes", "complex_attributes"};
+ std::vector<int> table_column_positions = {1, 2, 3, 4, 5, 6, 7};
+ std::vector<TPrimitiveType::type> table_column_types = {
+ // TPrimitiveType::BIGINT, // id
+ TPrimitiveType::STRING, // name
+ TPrimitiveType::STRUCT, // profile
+ TPrimitiveType::ARRAY, // tags
+ TPrimitiveType::ARRAY, // friends
+ TPrimitiveType::ARRAY, // recent_activity
+ TPrimitiveType::MAP, // attributes
+ TPrimitiveType::MAP // complex_attributes
+ };
+
+ const TupleDescriptor* tuple_descriptor = create_tuple_descriptor(
+ &desc_tbl, obj_pool, t_desc_table, t_table_desc, table_column_names,
+ table_column_positions, table_column_types, access_configs);
+
+ // Execute test based on method choice
+ ColumnIdResult actual_result;
+ if (use_top_level_method) {
+ // actual_result = IcebergOrcReader::_create_column_ids_by_top_level_col_index(
+ // orc_type, tuple_descriptor);
+ } else {
+ actual_result = IcebergOrcReader::_create_column_ids(orc_type, tuple_descriptor);
+ }
+
+ if (!should_skip_assertion) {
+ EXPECT_EQ(actual_result.column_ids, expected_column_ids);
+ EXPECT_EQ(actual_result.filter_column_ids, expected_filter_column_ids);
+ }
+ }
+};
+
+TEST_F(IcebergReaderCreateColumnIdsTest, test_create_column_ids_1) {
+ // Properties:
+
+ // Configure access paths for the profile column
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"3", "9", "14", "15"},
+ {"3", "9", "14", "16"},
+ {"3", "10", "17"},
+ {"3", "11", "*", "23"}};
+ access_config.predicate_paths = {{"3", "9", "14", "15"}, {"3", "10", "17"}};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 3, 4, 7, 8, 9, 10, 11, 15, 16, 18};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {3, 4, 7, 8, 10, 11};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+}
+
+TEST_F(IcebergReaderCreateColumnIdsTest, test_create_column_ids_2) {
+ // Properties:
+ // iceberg.schema: {"type":"struct","schema-id":0,"fields":[{"id":1,"name":"id","required":true,"type":"long"},{"id":2,"name":"name","required":true,"type":"string"},{"id":3,"name":"profile","required":true,"type":{"type":"struct","fields":[{"id":4,"name":"address","required":false,"type":{"type":"struct","fields":[{"id":7,"name":"street","required":false,"type":"string"},{"id":8,"name":"city","required":false,"type":"string"},{"id":9,"name":"coordinates","required":false,"type": [...]
+ // ORC 列ID分配为树型递增,根节点为0,子节点依次递增。
+ // 0: struct (table/root)
+ // 1: id (int64)
+ // 2: name (string)
+ // 3: profile (struct)
+ // 4: address (struct)
+ // 5: street (string)
+ // 6: city (string)
+ // 7: coordinates (struct)
+ // 8: lat (double)
+ // 9: lng (double)
+ // 10: contact (struct)
+ // 11: email (string)
+ // 12: phone (struct)
+ // 13: country_code (string)
+ // 14: number (string)
+ // 15: hobbies (list/array)
+ // 16: element (struct)
+ // 17: name (string)
+ // 18: level (int32)
+
+ // 配置profile列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"3"}};
+ access_config.predicate_paths = {{"3", "9", "14", "15"}, {"3", "10", "17"}};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 3, 4, 5, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 15, 16, 17, 18};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {3, 4, 7, 8, 10, 11};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+}
+
+TEST_F(IcebergReaderCreateColumnIdsTest, test_create_column_ids_3) {
+ // Properties:
+ // iceberg.schema: {"type":"struct","schema-id":0,"fields":[{"id":1,"name":"id","required":true,"type":"long"},{"id":2,"name":"name","required":true,"type":"string"},{"id":3,"name":"profile","required":true,"type":{"type":"struct","fields":[{"id":4,"name":"address","required":false,"type":{"type":"struct","fields":[{"id":7,"name":"street","required":false,"type":"string"},{"id":8,"name":"city","required":false,"type":"string"},{"id":9,"name":"coordinates","required":false,"type": [...]
+ // ORC 列ID分配为树型递增,根节点为0,子节点依次递增。
+ // 0: struct (table/root)
+ // 1: id (int64)
+ // 2: name (string)
+ // 3: profile (struct)
+ // 4: address (struct)
+ // 5: street (string)
+ // 6: city (string)
+ // 7: coordinates (struct)
+ // 8: lat (double)
+ // 9: lng (double)
+ // 10: contact (struct)
+ // 11: email (string)
+ // 12: phone (struct)
+ // 13: country_code (string)
+ // 14: number (string)
+ // 15: hobbies (list/array)
+ // 16: element (struct)
+ // 17: name (string)
+ // 18: level (int32)
+
+ // 配置profile列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"3", "10"}, {"3", "9"}};
+ access_config.predicate_paths = {{"3", "9", "14"}, {"3", "10", "17"}};
+
+ // access_config.all_column_paths = {{"profile", "contact"}, {"profile", "address"}};
+ // access_config.predicate_paths = {{"profile", "address", "coordinates"},
+ // {"profile", "contact", "email"}};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {3, 4, 7, 8, 9, 10, 11};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+}
+
+TEST_F(IcebergReaderCreateColumnIdsTest, test_create_column_ids_4) {
+ // 配置profile列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "profile";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {};
+ access_config.predicate_paths = {};
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {};
+
+ run_parquet_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method({access_config}, expected_column_ids, expected_filter_column_ids);
+}
+
+TEST_F(IcebergReaderCreateColumnIdsTest, test_create_column_ids_5) {
+ // 19: tags (array)
+ // 20: element (string)
+ // 21: friends (array)
+ // 22: element (struct)
+ // 23: user_id (bigint)
+ // 24: nickname (string)
+ // 25: friendship_level (int)
+ // 26: recent_activity (array)
+ // 27: element (struct)
+ // 28: action (string)
+ // 29: details (array)
+ // 30: element (struct)
+ // 31: key (string)
+ // 32: value (string)
+
+ std::vector<ColumnAccessPathConfig> access_configs;
+
+ {
+ // 配置friends列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "friends";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"5", "*", "27"}, {"5", "*", "28"}};
+ access_config.predicate_paths = {
+ {"5", "*", "27"},
+ };
+ access_configs.push_back(access_config);
+ }
+
+ {
+ // 配置recent_activity列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "recent_activity";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"6", "*", "30"}, {"6", "*", "31", "*", "34"}};
+ access_config.predicate_paths = {
+ {"6", "*", "30"},
+ };
+ access_configs.push_back(access_config);
+ }
+
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 21, 22, 24, 25, 26, 27, 28, 29, 30, 32};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {21, 22, 24, 26, 27, 28};
+
+ run_parquet_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids);
+ run_orc_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids);
+}
+
+TEST_F(IcebergReaderCreateColumnIdsTest, test_create_column_ids_6) {
+ // 33: attributes (map)
+ // 34: key (string)
+ // 35: value (string)
+ // 36: complex_attributes (map)
+ // 37: key (string)
+ // 38: value (struct)
+ // 39: metadata (struct)
+ // 40: version (string)
+ // 41: created_time (timestamp)
+ // 42: last_updated (timestamp)
+ // 43: quality_score (double)
+ // 44: historical_scores (array)
+ // 45: element (struct)
+ // 46: period (string)
+ // 47: score (double)
+ // 48: components (array)
+ // 49: element (struct)
+ // 50: component_name (string)
+ // 51: weight (float)
+ // 52: sub_scores (map)
+ // 53: key (string)
+ // 54: value (double)
+ // 55: trends (struct)
+ // 56: direction (string)
+ // 57: magnitude (double)
+ // 58: confidence_interval (struct)
+ // 59: lower (double)
+ // 60: upper (double)
+ // 61: hierarchical_data (map)
+ // 62: key (string)
+ // 63: value (struct)
+ // 64: category (string)
+ // 65: sub_items (array)
+ // 66: element (struct)
+ // 67: item_id (bigint)
+ // 68: properties (map)
+ // 69: key (string)
+ // 70: value (string)
+ // 71: metrics (struct)
+ // 72: value (double)
+ // 73: unit (string)
+ // 74: summary (struct)
+ // 75: total_count (int)
+ // 76: average_value (double)
+ // 77: validation_rules (struct)
+ // 78: required (boolean)
+ // 79: constraints (array)
+ // 80: element (struct)
+ // 81: rule_type (string)
+ // 82: parameters (map)
+ // 83: key (string)
+ // 84: value (string)
+ // 85: error_message (string)
+ // 86: complex_constraint (struct)
+ // 87: logic_operator (string)
+ // 88: operands (array)
+ // 89: element (struct)
+ // 90: field_path (string)
+ // 91: operator (string)
+ // 92: comparison_value (string)
+ std::vector<ColumnAccessPathConfig> access_configs;
+
+ {
+ // 配置complex_attributes列的访问路径
+ ColumnAccessPathConfig access_config;
+ access_config.column_name = "complex_attributes";
+ access_config.use_name_paths = true;
+
+ access_config.all_column_paths = {{"8", "*", "39", "43"},
+ {"8", "*", "40", "*", "50", "*", "55"},
+ {"8", "*", "41", "VALUES"},
+ {"8", "VALUES", "42", "79", "*", "83", "KEYS"}};
+ access_config.predicate_paths = {{"8", "*", "39", "43"}};
+ access_configs.push_back(access_config);
+ }
+
+ {
+ // parquet values should access keys
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 36, 37, 38, 39, 40, 44, 45, 48, 49, 52, 53,
+ 54, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 79, 80, 82, 83};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {36, 37, 38, 39, 40};
+
+ run_parquet_test_with_method(access_configs, expected_column_ids,
+ expected_filter_column_ids);
+ }
+
+ {
+ // column_ids should contain all necessary column IDs (set automatically deduplicates)
+ // Expected IDs based on the schema: name(2), profile(3), address(4), coordinates(7), lat(8), lng(9), contact(10), email(11), hobbies(15), element(16), level(18)
+ std::set<uint64_t> expected_column_ids = {2, 36, 37, 38, 39, 40, 44, 45, 48, 49, 52,
+ 53, 54, 61, 63, 64, 65, 66, 67, 68, 69, 70,
+ 71, 72, 73, 74, 75, 76, 77, 79, 80, 82, 83};
+ // Expected IDs based on the schema: profile(3), address(4), coordinates(7), lat(8), contact(10), email(11)
+ std::set<uint64_t> expected_filter_column_ids = {36, 37, 38, 39, 40};
+
+ run_orc_test_with_method(access_configs, expected_column_ids, expected_filter_column_ids);
+ }
+}
+
+} // namespace doris::vectorized
\ No newline at end of file
diff --git a/be/test/vec/exec/format/table/iceberg/iceberg_reader_test.cpp b/be/test/vec/exec/format/table/iceberg/iceberg_reader_test.cpp
new file mode 100644
index 00000000000..12d6bb0299f
--- /dev/null
+++ b/be/test/vec/exec/format/table/iceberg/iceberg_reader_test.cpp
@@ -0,0 +1,739 @@
+// 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 "vec/exec/format/table/iceberg_reader.h"
+
+#include <cctz/time_zone.h>
+#include <gen_cpp/Descriptors_types.h>
+#include <gen_cpp/PaloInternalService_types.h>
+#include <gen_cpp/PlanNodes_types.h>
+#include <gen_cpp/Types_types.h>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "common/object_pool.h"
+#include "exec/olap_common.h"
+#include "io/fs/file_meta_cache.h"
+#include "io/fs/file_reader_writer_fwd.h"
+#include "io/fs/file_system.h"
+#include "io/fs/local_file_system.h"
+#include "runtime/descriptors.h"
+#include "runtime/runtime_state.h"
+#include "util/timezone_utils.h"
+#include "vec/columns/column.h"
+#include "vec/columns/column_array.h"
+#include "vec/columns/column_nullable.h"
+#include "vec/columns/column_struct.h"
+#include "vec/core/block.h"
+#include "vec/core/column_with_type_and_name.h"
+#include "vec/data_types/data_type.h"
+#include "vec/data_types/data_type_array.h"
+#include "vec/data_types/data_type_factory.hpp"
+#include "vec/data_types/data_type_nullable.h"
+#include "vec/data_types/data_type_number.h"
+#include "vec/data_types/data_type_string.h"
+#include "vec/data_types/data_type_struct.h"
+#include "vec/exec/format/parquet/vparquet_reader.h"
+
+namespace doris::vectorized {
+
+class IcebergReaderTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ cache = std::make_unique<doris::FileMetaCache>(1024);
+
+ // Setup timezone
+ doris::TimezoneUtils::find_cctz_time_zone(doris::TimezoneUtils::default_time_zone,
+ timezone_obj);
+ }
+
+ void TearDown() override { cache.reset(); }
+
+ // Helper function to create complex struct types for testing
+ void create_complex_struct_types(DataTypePtr& coordinates_struct_type,
+ DataTypePtr& address_struct_type,
+ DataTypePtr& phone_struct_type,
+ DataTypePtr& contact_struct_type,
+ DataTypePtr& hobby_element_struct_type,
+ DataTypePtr& hobbies_array_type,
+ DataTypePtr& profile_struct_type, DataTypePtr& name_type) {
+ // Create name column type (direct field)
+ name_type = make_nullable(std::make_shared<DataTypeString>());
+
+ // First create coordinates struct type
+ std::vector<DataTypePtr> coordinates_types = {
+ make_nullable(std::make_shared<DataTypeFloat64>()), // lat (field ID 10)
+ make_nullable(std::make_shared<DataTypeFloat64>()) // lng (field ID 11)
+ };
+ std::vector<std::string> coordinates_names = {"lat", "lng"};
+ coordinates_struct_type = make_nullable(
+ std::make_shared<DataTypeStruct>(coordinates_types, coordinates_names));
+
+ // Create address struct type (with street, city, coordinates)
+ std::vector<DataTypePtr> address_types = {
+ make_nullable(std::make_shared<DataTypeString>()), // street (field ID 7)
+ make_nullable(std::make_shared<DataTypeString>()), // city (field ID 8)
+ coordinates_struct_type // coordinates (field ID 9)
... 739 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@doris.apache.org
For additional commands, e-mail: commits-help@doris.apache.org