blob: fc628e60f97be1bbb189eb098b58a2cf22df0254 [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 "paimon/format/parquet/parquet_timestamp_converter.h"
#include <memory>
#include <string>
#include <vector>
#include "arrow/type.h"
#include "fmt/format.h"
#include "paimon/common/utils/arrow/status_utils.h"
#include "paimon/common/utils/checked_cast.h"
#include "paimon/common/utils/date_time_utils.h"
#include "paimon/core/casting/timestamp_to_timestamp_cast_executor.h"
namespace paimon::parquet {
Result<std::shared_ptr<arrow::DataType>> ParquetTimestampConverter::AdjustTimezone(
const std::shared_ptr<arrow::DataType>& src_data_type) {
arrow::Type::type type = src_data_type->id();
switch (type) {
case arrow::Type::type::STRUCT: {
auto* src_struct_type = checked_cast<arrow::StructType*>(src_data_type.get());
arrow::FieldVector new_fields;
new_fields.reserve(src_struct_type->num_fields());
for (int32_t i = 0; i < src_struct_type->num_fields(); ++i) {
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::DataType> sub_type,
AdjustTimezone(src_struct_type->field(i)->type()));
new_fields.push_back(src_struct_type->field(i)->WithType(sub_type));
}
return arrow::struct_(new_fields);
}
case arrow::Type::type::MAP: {
auto* src_map_type = checked_cast<arrow::MapType*>(src_data_type.get());
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::DataType> key_type,
AdjustTimezone(src_map_type->key_type()));
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::DataType> item_type,
AdjustTimezone(src_map_type->item_type()));
return std::make_shared<arrow::MapType>(
src_map_type->key_field()->WithType(key_type),
src_map_type->item_field()->WithType(item_type));
}
case arrow::Type::type::LIST: {
auto* src_list_type = checked_cast<arrow::ListType*>(src_data_type.get());
PAIMON_ASSIGN_OR_RAISE(std::shared_ptr<arrow::DataType> value_type,
AdjustTimezone(src_list_type->value_type()));
return arrow::list(src_list_type->value_field()->WithType(value_type));
}
case arrow::Type::type::TIMESTAMP: {
auto* src_ts_type = checked_cast<arrow::TimestampType*>(src_data_type.get());
if (!src_ts_type->timezone().empty()) {
return arrow::timestamp(src_ts_type->unit(), DateTimeUtils::GetLocalTimezoneName());
}
}
default:
return src_data_type;
}
}
Result<bool> ParquetTimestampConverter::NeedCastArrayForTimestamp(
const std::shared_ptr<arrow::DataType>& src_data_type,
const std::shared_ptr<arrow::DataType>& target_data_type) {
arrow::Type::type type = src_data_type->id();
if (type != target_data_type->id()) {
return Status::Invalid(fmt::format("src type {} and target type {} mismatch",
src_data_type->ToString(),
target_data_type->ToString()));
}
switch (type) {
case arrow::Type::type::STRUCT: {
auto* src_struct_type = checked_cast<arrow::StructType*>(src_data_type.get());
auto* target_struct_type = checked_cast<arrow::StructType*>(target_data_type.get());
if (src_struct_type->num_fields() != target_struct_type->num_fields()) {
return Status::Invalid(
fmt::format("src type {} and target type {} number of fields mismatch",
src_data_type->ToString(), target_data_type->ToString()));
}
for (int32_t i = 0; i < src_struct_type->num_fields(); ++i) {
PAIMON_ASSIGN_OR_RAISE(bool need_cast, NeedCastArrayForTimestamp(
src_struct_type->field(i)->type(),
target_struct_type->field(i)->type()));
if (need_cast) {
return true;
}
}
return false;
}
case arrow::Type::type::MAP: {
auto* src_map_type = checked_cast<arrow::MapType*>(src_data_type.get());
auto* target_map_type = checked_cast<arrow::MapType*>(target_data_type.get());
PAIMON_ASSIGN_OR_RAISE(
bool need_cast,
NeedCastArrayForTimestamp(src_map_type->key_type(), target_map_type->key_type()));
if (need_cast) {
return true;
}
PAIMON_ASSIGN_OR_RAISE(
need_cast,
NeedCastArrayForTimestamp(src_map_type->item_type(), target_map_type->item_type()));
return need_cast;
}
case arrow::Type::type::LIST: {
auto* src_list_type = checked_cast<arrow::ListType*>(src_data_type.get());
auto* target_list_type = checked_cast<arrow::ListType*>(target_data_type.get());
PAIMON_ASSIGN_OR_RAISE(bool need_cast,
NeedCastArrayForTimestamp(src_list_type->value_type(),
target_list_type->value_type()));
return need_cast;
}
case arrow::Type::type::TIMESTAMP: {
auto* src_ts_type = checked_cast<arrow::TimestampType*>(src_data_type.get());
auto* target_ts_type = checked_cast<arrow::TimestampType*>(target_data_type.get());
if (src_ts_type->unit() != target_ts_type->unit() ||
src_ts_type->timezone() != target_ts_type->timezone()) {
return true;
}
return false;
}
default:
return false;
}
}
Result<std::shared_ptr<arrow::Array>> ParquetTimestampConverter::CastArrayForTimestamp(
const std::shared_ptr<arrow::Array>& array,
const std::shared_ptr<arrow::DataType>& target_data_type,
const std::shared_ptr<arrow::MemoryPool>& arrow_pool) {
arrow::Type::type type = array->type()->id();
switch (type) {
case arrow::Type::type::STRUCT: {
auto* struct_array = checked_cast<arrow::StructArray*>(array.get());
arrow::ArrayVector target_sub_arrays;
std::vector<std::string> target_names;
target_sub_arrays.reserve(struct_array->num_fields());
target_names.reserve(struct_array->num_fields());
for (int32_t i = 0; i < struct_array->num_fields(); i++) {
const auto& field = struct_array->field(i);
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Array> sub_array,
CastArrayForTimestamp(field, target_data_type->field(i)->type(), arrow_pool));
target_sub_arrays.push_back(sub_array);
target_names.push_back(target_data_type->field(i)->name());
}
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(
std::shared_ptr<arrow::Array> new_array,
arrow::StructArray::Make(target_sub_arrays, target_names,
struct_array->null_bitmap(), struct_array->null_count(),
struct_array->offset()));
return new_array;
}
case arrow::Type::type::MAP: {
auto* map_array = checked_cast<arrow::MapArray*>(array.get());
auto* map_type = checked_cast<arrow::MapType*>(target_data_type.get());
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Array> key_array,
CastArrayForTimestamp(map_array->keys(), map_type->key_type(), arrow_pool));
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Array> item_array,
CastArrayForTimestamp(map_array->items(), map_type->item_type(), arrow_pool));
return std::make_shared<arrow::MapArray>(
arrow::map(key_array->type(), item_array->type()), map_array->length(),
map_array->value_offsets(), key_array, item_array, map_array->null_bitmap(),
map_array->null_count(), map_array->offset());
}
case arrow::Type::type::LIST: {
auto* list_array = checked_cast<arrow::ListArray*>(array.get());
auto* list_type = checked_cast<arrow::ListType*>(target_data_type.get());
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Array> value_array,
CastArrayForTimestamp(list_array->values(), list_type->value_type(), arrow_pool));
return std::make_shared<arrow::ListArray>(
arrow::list(value_array->type()), list_array->length(), list_array->value_offsets(),
value_array, list_array->null_bitmap(), list_array->null_count(),
list_array->offset());
}
case arrow::Type::type::TIMESTAMP: {
auto* ts_array = checked_cast<arrow::TimestampArray*>(array.get());
auto* src_type = checked_cast<arrow::TimestampType*>(ts_array->type().get());
auto* ts_target_type = checked_cast<arrow::TimestampType*>(target_data_type.get());
if (src_type->unit() == arrow::TimeUnit::type::MILLI &&
ts_target_type->unit() == arrow::TimeUnit::type::SECOND) {
// parquet writer do not support second, and it cast second to milli.
// Therefore, in paimon file reader, we cast from milli to second.
auto cast_executor = std::make_shared<TimestampToTimestampCastExecutor>();
PAIMON_ASSIGN_OR_RAISE(
std::shared_ptr<arrow::Array> target_array,
cast_executor->Cast(array, target_data_type, arrow_pool.get()));
return target_array;
}
if (src_type->timezone() != ts_target_type->timezone()) {
// 1. For nano type, parquet writer will write nano into int96 type, which does
// not contain any stats or zone info. Therefore in paimon file reader, we add
// zone info according to target type 2. For other precision, parquet reader
// will return UTC tz. Therefore, in paimon file reader, we add local zone info.
PAIMON_ASSIGN_OR_RAISE_FROM_ARROW(std::shared_ptr<arrow::Array> target_array,
ts_array->View(target_data_type));
return target_array;
}
}
default:
return array;
}
}
} // namespace paimon::parquet