blob: 619e9959a1bfa47129365f7deb34239a7e780166 [file]
// 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 "format_v2/table/hive_reader.h"
#include <algorithm>
#include <cctype>
#include <string_view>
#include "common/consts.h"
#include "format_v2/column_mapper.h"
#include "format_v2/file_reader.h"
#include "runtime/runtime_state.h"
namespace doris::format::hive {
namespace {
TFileFormatType::type format_type_from_context(const format::ProjectedColumnBuildContext& context) {
DORIS_CHECK(context.scan_params != nullptr);
if (context.range != nullptr && context.range->__isset.format_type) {
return context.range->format_type;
}
return context.scan_params->format_type;
}
bool use_column_position_mapping(const format::ProjectedColumnBuildContext& context) {
if (context.runtime_state == nullptr || context.scan_params == nullptr) {
return false;
}
switch (format_type_from_context(context)) {
case TFileFormatType::FORMAT_PARQUET:
return !context.runtime_state->query_options().hive_parquet_use_column_names;
case TFileFormatType::FORMAT_ORC:
return !context.runtime_state->query_options().hive_orc_use_column_names;
default:
return false;
}
}
bool is_file_column_position_slot(const TFileScanSlotInfo& slot_info,
const std::string& column_name) {
if (column_name.starts_with(BeConsts::GLOBAL_ROWID_COL) ||
column_name == BeConsts::ICEBERG_ROWID_COL) {
return false;
}
if (slot_info.__isset.is_file_slot) {
return slot_info.is_file_slot;
}
return !slot_info.__isset.category || slot_info.category != TColumnCategory::PARTITION_KEY;
}
bool is_hive1_orc_column_name(std::string_view name) {
if (name.size() <= 4 || name.substr(0, 4) != "_col") {
return false;
}
return std::ranges::all_of(name.substr(4), [](unsigned char c) { return std::isdigit(c); });
}
bool is_hive1_orc_file_schema(const std::vector<format::ColumnDefinition>& file_schema) {
bool has_data_column = false;
for (const auto& field : file_schema) {
if (field.column_type != format::ColumnType::DATA_COLUMN) {
continue;
}
has_data_column = true;
if (!is_hive1_orc_column_name(field.name)) {
return false;
}
}
return has_data_column;
}
bool is_file_column_for_hive1_position_mapping(const format::ColumnDefinition& column) {
if (column.column_type != format::ColumnType::DATA_COLUMN || column.is_partition_key) {
return false;
}
return !column.name.starts_with(BeConsts::GLOBAL_ROWID_COL) &&
column.name != BeConsts::ICEBERG_ROWID_COL;
}
void add_name_mapping(std::vector<std::string>* name_mapping, const std::string& alias) {
DORIS_CHECK(name_mapping != nullptr);
if (std::ranges::find(*name_mapping, alias) == name_mapping->end()) {
name_mapping->push_back(alias);
}
}
} // namespace
Status HiveReader::prepare_split(const format::SplitReadOptions& options) {
{
// Keep derived validation visible without overlapping the base scopes on the same timers.
SCOPED_TIMER(_profile.total_timer);
SCOPED_TIMER(_profile.prepare_split_timer);
if (options.current_split_format != _format) {
return Status::InternalError(
"Hive scan expects all splits to use the same file format, "
"initialized_format={}, current_split_format={}",
static_cast<int>(_format), static_cast<int>(options.current_split_format));
}
}
return format::TableReader::prepare_split(options);
}
format::TableColumnMappingMode HiveReader::mapping_mode() const {
// Hive-specific behavior: choose the column matching mode based on file format and the
// matching session variable.
// - hive_orc_use_column_names / hive_parquet_use_column_names == true
// => BY_NAME (modern Hive default, match by column name)
// - those options == false
// => BY_INDEX (mainly for Hive1 ORC `_col0` / `_col1`, match by top-level position;
// Parquet exposes the same switch for consistency)
// TableReader updates `_format` in prepare_split(), so this is evaluated per split.
DORIS_CHECK(_runtime_state != nullptr);
const auto& query_options = _runtime_state->query_options();
bool use_column_names = true;
if (_format == format::FileFormat::ORC) {
use_column_names = query_options.hive_orc_use_column_names;
} else if (_format == format::FileFormat::PARQUET) {
use_column_names = query_options.hive_parquet_use_column_names;
} else if (_format == format::FileFormat::CSV || _format == format::FileFormat::TEXT ||
_format == format::FileFormat::JSON) {
// Hive CSV/TEXT/JSON readers synthesize a file-local schema from FE-provided file slots
// because these formats do not carry embedded column names or field ids. The scan params'
// format-specific attributes still tell the physical reader how to read values, while the
// table-level mapper can safely match the synthesized file schema by table column name.
use_column_names = true;
} else {
DORIS_CHECK(false) << "HiveReader does not support this file reader format";
}
return use_column_names ? format::TableColumnMappingMode::BY_NAME
: format::TableColumnMappingMode::BY_INDEX;
}
Status HiveReader::annotate_projected_column(const TFileScanSlotInfo& slot_info,
format::ProjectedColumnBuildContext* context,
format::ColumnDefinition* column) const {
RETURN_IF_ERROR(format::TableReader::annotate_projected_column(slot_info, context, column));
DORIS_CHECK(context != nullptr);
DORIS_CHECK(column != nullptr);
if (!use_column_position_mapping(*context) ||
!is_file_column_position_slot(slot_info, column->name)) {
return Status::OK();
}
const auto* scan_params = context->scan_params;
DORIS_CHECK(scan_params != nullptr);
if (!scan_params->__isset.column_idxs ||
context->next_file_column_idx >= scan_params->column_idxs.size()) {
return Status::InvalidArgument(
"Hive positional column mapping is missing file index for column '{}', "
"required file slot ordinal={}, column_idxs_size={}",
column->name, context->next_file_column_idx,
scan_params->__isset.column_idxs ? scan_params->column_idxs.size() : 0);
}
const auto file_index = scan_params->column_idxs[context->next_file_column_idx];
if (file_index < 0) {
return Status::InvalidArgument(
"Hive positional column mapping has negative file index {} for column '{}'",
file_index, column->name);
}
column->identifier = Field::create_field<TYPE_INT>(file_index);
++context->next_file_column_idx;
return Status::OK();
}
Status HiveReader::validate_projected_columns(
const format::ProjectedColumnBuildContext& context) const {
if (!use_column_position_mapping(context)) {
return Status::OK();
}
DORIS_CHECK(context.scan_params != nullptr);
if (context.scan_params->__isset.column_idxs &&
context.next_file_column_idx != context.scan_params->column_idxs.size()) {
return Status::InvalidArgument(
"Hive positional column mapping has unused file indexes: consumed={}, "
"column_idxs_size={}",
context.next_file_column_idx, context.scan_params->column_idxs.size());
}
return Status::OK();
}
Status HiveReader::annotate_file_schema(std::vector<format::ColumnDefinition>* file_schema) {
DORIS_CHECK(file_schema != nullptr);
if (_format != format::FileFormat::ORC || _runtime_state == nullptr ||
!_runtime_state->query_options().hive_orc_use_column_names ||
!is_hive1_orc_file_schema(*file_schema)) {
return Status::OK();
}
DORIS_CHECK(_scan_params != nullptr);
if (!_scan_params->__isset.column_idxs) {
return Status::InvalidArgument(
"Hive ORC Hive1-style name mapping is missing positional column indexes");
}
size_t next_file_column_idx = 0;
for (const auto& table_column : _projected_columns) {
if (!is_file_column_for_hive1_position_mapping(table_column)) {
continue;
}
if (next_file_column_idx >= _scan_params->column_idxs.size()) {
return Status::InvalidArgument(
"Hive ORC Hive1-style name mapping is missing file index for column '{}', "
"required file slot ordinal={}, column_idxs_size={}",
table_column.name, next_file_column_idx, _scan_params->column_idxs.size());
}
const auto file_index = _scan_params->column_idxs[next_file_column_idx];
if (file_index < 0) {
return Status::InvalidArgument(
"Hive ORC Hive1-style name mapping has negative file index {} for column '{}'",
file_index, table_column.name);
}
++next_file_column_idx;
if (static_cast<size_t>(file_index) >= file_schema->size()) {
continue;
}
auto& file_column = (*file_schema)[static_cast<size_t>(file_index)];
add_name_mapping(&file_column.name_mapping, table_column.name);
if (table_column.has_identifier_name()) {
add_name_mapping(&file_column.name_mapping, table_column.get_identifier_name());
}
for (const auto& alias : table_column.name_mapping) {
add_name_mapping(&file_column.name_mapping, alias);
}
}
if (next_file_column_idx != _scan_params->column_idxs.size()) {
return Status::InvalidArgument(
"Hive ORC Hive1-style name mapping has unused file indexes: consumed={}, "
"column_idxs_size={}",
next_file_column_idx, _scan_params->column_idxs.size());
}
return Status::OK();
}
} // namespace doris::format::hive