blob: 083711a5ce34a331a39846c20f87892eb3da85d3 [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.
#pragma once
#include <cctz/time_zone.h>
#include <gen_cpp/parquet_types.h>
#include <libdivide.h>
#include <chrono>
#include <limits>
#include "common/cast_set.h"
#include "core/column/column_fixed_length_object.h"
#include "core/column/column_varbinary.h"
#include "core/column/column_vector.h"
#include "core/data_type/data_type_factory.hpp"
#include "core/data_type/primitive_type.h"
#include "core/extended_types.h"
#include "core/field.h"
#include "core/types.h"
#include "format/column_type_convert.h"
#include "format/format_common.h"
#include "format/parquet/decoder.h"
#include "format/parquet/parquet_common.h"
#include "format/parquet/schema_desc.h"
#include "util/timezone_utils.h"
namespace doris::parquet {
namespace detail {
struct EpochSecondsAndMicros {
int64_t seconds;
uint32_t microseconds;
};
inline EpochSecondsAndMicros split_epoch_time(int64_t timestamp, int64_t units_per_second,
int64_t nanos_per_unit) {
int64_t seconds = timestamp / units_per_second;
int64_t subsecond = timestamp % units_per_second;
// C++ division truncates toward zero. Normalize to floor division so the fractional part is
// always non-negative for timestamps before the Unix epoch.
if (subsecond < 0) {
subsecond += units_per_second;
--seconds;
}
return {.seconds = seconds,
.microseconds = static_cast<uint32_t>(subsecond * nanos_per_unit / 1000)};
}
inline bool try_split_local_time(int64_t local_time, uint16_t* year, uint8_t* month, uint8_t* day,
uint8_t* hour, uint8_t* minute, uint8_t* second) {
static const libdivide::divider<int64_t> fast_div_86400(86400);
static const libdivide::divider<int64_t> fast_div_3600(3600);
static const libdivide::divider<int64_t> fast_div_60(60);
static constexpr int64_t kMinSupportedDays = -365LL * 10000;
static constexpr int64_t kMaxSupportedDays = 365LL * 10000;
int64_t days = local_time / fast_div_86400;
int64_t second_of_day = local_time - days * 86400;
if (second_of_day < 0) {
second_of_day += 86400;
--days;
}
if (days < kMinSupportedDays || days > kMaxSupportedDays) {
return false;
}
const auto ymd = std::chrono::year_month_day {std::chrono::sys_days {std::chrono::days {days}}};
const int y = static_cast<int>(ymd.year());
if (y < 0 || y > std::numeric_limits<uint16_t>::max()) {
return false;
}
const int64_t h = second_of_day / fast_div_3600;
const int64_t minute_second = second_of_day - h * 3600;
const int64_t m = minute_second / fast_div_60;
const int64_t s = minute_second - m * 60;
*year = static_cast<uint16_t>(y);
*month = static_cast<uint8_t>(static_cast<unsigned>(ymd.month()));
*day = static_cast<uint8_t>(static_cast<unsigned>(ymd.day()));
*hour = static_cast<uint8_t>(h);
*minute = static_cast<uint8_t>(m);
*second = static_cast<uint8_t>(s);
return true;
}
template <typename DateType>
inline bool try_convert_timestamp_with_fixed_offset(DateType& value, int64_t epoch_seconds,
int32_t offset_seconds) {
uint16_t year = 0;
uint8_t month = 0;
uint8_t day = 0;
uint8_t hour = 0;
uint8_t minute = 0;
uint8_t second = 0;
if (!try_split_local_time(epoch_seconds + offset_seconds, &year, &month, &day, &hour, &minute,
&second)) {
return false;
}
// The caller sets sub-second precision immediately after this conversion.
value.unchecked_set_time(year, month, day, hour, minute, second, 0);
return true;
}
template <typename DateType>
inline bool try_convert_timestamp_with_lookup(DateType& value, int64_t epoch_seconds,
const cctz::time_zone& ctz) {
static const auto epoch = std::chrono::time_point_cast<cctz::sys_seconds>(
std::chrono::system_clock::from_time_t(0));
cctz::time_point<cctz::sys_seconds> t = epoch + cctz::seconds(epoch_seconds);
const int32_t offset = ctz.lookup_offset(t).offset;
return try_convert_timestamp_with_fixed_offset(value, epoch_seconds, offset);
}
} // namespace detail
struct ConvertParams {
// schema.logicalType.TIMESTAMP.isAdjustedToUTC == false
static const cctz::time_zone utc0;
// schema.logicalType.TIMESTAMP.isAdjustedToUTC == true, we should set local time zone
const cctz::time_zone* ctz = nullptr;
bool is_fixed_offset = false;
int32_t fixed_offset_seconds = 0;
int64_t second_mask = 1;
int64_t scale_to_nano_factor = 1;
const FieldSchema* field_schema = nullptr;
//For UInt8 -> Int16,UInt16 -> Int32,UInt32 -> Int64,UInt64 -> Int128.
bool is_type_compatibility = false;
/**
* Some frameworks like paimon maybe writes non-standard parquet files. Timestamp field doesn't have
* logicalType or converted_type to indicates its precision. We have to reset the time mask.
*/
void reset_time_scale_if_missing(int scale) {
const auto& schema = field_schema->parquet_schema;
if (!schema.__isset.logicalType && !schema.__isset.converted_type) {
int ts_scale = 9;
if (scale <= 3) {
ts_scale = 3;
} else if (scale <= 6) {
ts_scale = 6;
}
second_mask = common::exp10_i64(ts_scale);
scale_to_nano_factor = common::exp10_i64(9 - ts_scale);
// The missing parque metadata makes it impossible for us to know the time zone information,
// so we default to UTC here.
if (ctz == nullptr) {
ctz = &utc0;
}
}
}
void init(const FieldSchema* field_schema_, const cctz::time_zone* ctz_) {
field_schema = field_schema_;
if (ctz_ != nullptr) {
ctz = ctz_;
}
const auto& schema = field_schema->parquet_schema;
if (schema.__isset.logicalType && schema.logicalType.__isset.TIMESTAMP) {
const auto& timestamp_info = schema.logicalType.TIMESTAMP;
if (!timestamp_info.isAdjustedToUTC) {
// should set timezone to utc+0
// Reference: https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#instant-semantics-timestamps-normalized-to-utc
// If isAdjustedToUTC = false, the reader should display the same value no mater what local time zone is. For example:
// When a timestamp is stored as `1970-01-03 12:00:00`,
// if isAdjustedToUTC = true, UTC8 should read as `1970-01-03 20:00:00`, UTC6 should read as `1970-01-03 18:00:00`
// if isAdjustedToUTC = false, UTC8 and UTC6 should read as `1970-01-03 12:00:00`, which is the same as `1970-01-03 12:00:00` in UTC0
ctz = &utc0;
}
const auto& time_unit = timestamp_info.unit;
if (time_unit.__isset.MILLIS) {
second_mask = 1000;
scale_to_nano_factor = 1000000;
} else if (time_unit.__isset.MICROS) {
second_mask = 1000000;
scale_to_nano_factor = 1000;
} else if (time_unit.__isset.NANOS) {
second_mask = 1000000000;
scale_to_nano_factor = 1;
}
} else if (schema.__isset.converted_type) {
const auto& converted_type = schema.converted_type;
if (converted_type == tparquet::ConvertedType::TIMESTAMP_MILLIS) {
second_mask = 1000;
scale_to_nano_factor = 1000000;
} else if (converted_type == tparquet::ConvertedType::TIMESTAMP_MICROS) {
second_mask = 1000000;
scale_to_nano_factor = 1000;
}
}
if (ctz != nullptr) {
is_fixed_offset =
TimezoneUtils::try_get_fixed_offset_seconds(*ctz, &fixed_offset_seconds);
}
is_type_compatibility = field_schema_->is_type_compatibility;
}
};
inline IColumn* get_mutable_inner_column(ColumnPtr& column) {
column = IColumn::mutate(std::move(column));
auto mutable_column = column->assert_mutable();
if (is_column_nullable(*mutable_column)) {
return &assert_cast<ColumnNullable*>(mutable_column.get())->get_nested_column();
}
return mutable_column.get();
}
inline size_t get_mutable_inner_column_size(const ColumnPtr& column) {
if (is_column_nullable(*column)) {
const auto* nullable = assert_cast<const ColumnNullable*>(column.get());
return nullable->get_nested_column().size();
}
return column->size();
}
inline size_t get_null_map_size_or_inner_column_size(const ColumnPtr& column) {
if (is_column_nullable(*column)) {
const auto* nullable = assert_cast<const ColumnNullable*>(column.get());
return nullable->get_null_map_column().size();
}
return column->size();
}
inline size_t get_appended_null_map_start(const ColumnPtr& column, size_t new_rows) {
if (!is_column_nullable(*column)) {
return 0;
}
const auto* nullable = assert_cast<const ColumnNullable*>(column.get());
const size_t null_map_size = nullable->get_null_map_column().size();
DCHECK_GE(null_map_size, new_rows);
return null_map_size - new_rows;
}
inline void align_null_map(ColumnPtr& src_column, ColumnPtr& dst_column, size_t old_null_map_size,
size_t new_rows, size_t src_null_map_start = 0) {
if (!is_column_nullable(*dst_column)) {
return;
}
dst_column = IColumn::mutate(std::move(dst_column));
auto* dst_nullable = assert_cast<ColumnNullable*>(dst_column->assert_mutable().get());
auto& dst_null_map = dst_nullable->get_null_map_column();
const size_t expected_rows = old_null_map_size + new_rows;
if (dst_null_map.size() == expected_rows) {
return;
}
DCHECK_EQ(dst_null_map.size(), old_null_map_size);
if (is_column_nullable(*src_column)) {
const auto* src_nullable = assert_cast<const ColumnNullable*>(src_column.get());
DCHECK_GE(src_nullable->get_null_map_column().size(), src_null_map_start + new_rows);
dst_null_map.insert_range_from(src_nullable->get_null_map_column(), src_null_map_start,
new_rows);
} else {
dst_null_map.insert_many_vals(0, new_rows);
}
}
struct FixedLengthPhysicalData {
const uint8_t* data = nullptr;
size_t byte_size = 0;
size_t rows = 0;
};
inline FixedLengthPhysicalData get_fixed_length_physical_data(const IColumn& column,
size_t type_length) {
if (const auto* fixed_length_column = check_and_get_column<ColumnFixedLengthObject>(column)) {
DCHECK_EQ(fixed_length_column->item_size(), type_length);
return {fixed_length_column->get_data().data(), fixed_length_column->byte_size(),
fixed_length_column->size()};
}
const auto& uint8_column = assert_cast<const ColumnUInt8&>(column);
DCHECK_EQ(uint8_column.size() % type_length, 0);
return {uint8_column.get_data().data(), uint8_column.size(), uint8_column.size() / type_length};
}
/**
* Convert parquet physical column to logical column
* In parquet document(https://github.com/apache/parquet-format/blob/master/LogicalTypes.md),
* Logical or converted type is the data type of column, physical type is the stored type of column chunk.
* eg, decimal type can be stored as INT32, INT64, BYTE_ARRAY, FIXED_LENGTH_BYTE_ARRAY.
* So there is a convert process from physical type to logical type.
* In addition, Schema change will bring about a change in logical type.
*
* `PhysicalToLogicalConverter` strips away the conversion of logical type, and reuse `ColumnTypeConverter`
* to resolve schema change, allowing parquet reader to only focus on the conversion of physical types.
*
* Therefore, tow layers converters are designed:
* First, read parquet data with the physical type
* Second, convert physical type to logical type
* Third, convert logical type to the final type planned by FE(schema change)
*
* Ultimate performance optimization:
* 1. If process of (First => Second) is consistent, eg. from BYTE_ARRAY to string, no additional copies and conversions will be introduced;
* 2. If process of (Second => Third) is consistent, no additional copies and conversions will be introduced;
* 3. Null maps are owned by each temporary nullable column, and only appended null slices are
* copied between conversion stages;
* 4. Only create one physical column in physical conversion, and reused in each loop;
* 5. Only create one logical column in logical conversion, and reused in each loop;
* 6. FIXED_LENGTH_BYTE_ARRAY is read as ColumnFixedLengthObject instead of ColumnString, so
* the decoder can copy fixed-size values as a whole while keeping nullable row counts valid.
*/
class PhysicalToLogicalConverter {
protected:
ColumnPtr _cached_src_physical_column = nullptr;
DataTypePtr _cached_src_physical_type = nullptr;
std::unique_ptr<converter::ColumnTypeConverter> _logical_converter = nullptr;
std::string _error_msg;
std::unique_ptr<ConvertParams> _convert_params;
public:
static std::unique_ptr<PhysicalToLogicalConverter> get_converter(
const FieldSchema* field_schema, DataTypePtr src_logical_type,
const DataTypePtr& dst_logical_type, const cctz::time_zone* ctz,
bool is_dict_filter = false, bool preserve_binary_uuid = false);
static bool is_parquet_native_type(PrimitiveType type);
static bool is_decimal_type(PrimitiveType type);
PhysicalToLogicalConverter() = default;
virtual ~PhysicalToLogicalConverter() = default;
virtual Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) {
return Status::OK();
}
Status convert(ColumnPtr& src_physical_col, DataTypePtr src_logical_type,
const DataTypePtr& dst_logical_type, ColumnPtr& dst_logical_col,
bool is_dict_filter) {
if (is_dict_filter) {
src_logical_type = DataTypeFactory::instance().create_data_type(
PrimitiveType::TYPE_INT, dst_logical_type->is_nullable());
}
if (is_consistent() && _logical_converter->is_consistent()) {
dst_logical_col = std::move(src_physical_col);
return Status::OK();
}
if (_logical_converter->is_consistent()) {
const size_t old_rows = get_mutable_inner_column_size(dst_logical_col);
const size_t old_null_map_size =
get_null_map_size_or_inner_column_size(dst_logical_col);
RETURN_IF_ERROR(physical_convert(src_physical_col, dst_logical_col));
const size_t new_rows = get_mutable_inner_column_size(dst_logical_col) - old_rows;
align_null_map(src_physical_col, dst_logical_col, old_null_map_size, new_rows,
get_appended_null_map_start(src_physical_col, new_rows));
return Status::OK();
}
ColumnPtr src_logical_column;
if (is_consistent()) {
src_logical_column = src_physical_col;
} else {
src_logical_column = _logical_converter->get_column(src_logical_type, dst_logical_col,
dst_logical_type);
}
const size_t src_old_rows = get_mutable_inner_column_size(src_logical_column);
const size_t src_old_null_map_size =
get_null_map_size_or_inner_column_size(src_logical_column);
RETURN_IF_ERROR(physical_convert(src_physical_col, src_logical_column));
const size_t src_new_rows =
get_mutable_inner_column_size(src_logical_column) - src_old_rows;
align_null_map(src_physical_col, src_logical_column, src_old_null_map_size, src_new_rows,
get_appended_null_map_start(src_physical_col, src_new_rows));
dst_logical_col = IColumn::mutate(std::move(dst_logical_col));
const size_t dst_old_rows = get_mutable_inner_column_size(dst_logical_col);
const size_t dst_old_null_map_size =
get_null_map_size_or_inner_column_size(dst_logical_col);
auto converted_column = dst_logical_col->assert_mutable();
RETURN_IF_ERROR(_logical_converter->convert(src_logical_column, converted_column));
const size_t dst_new_rows = get_mutable_inner_column_size(dst_logical_col) - dst_old_rows;
align_null_map(src_logical_column, dst_logical_col, dst_old_null_map_size, dst_new_rows,
get_appended_null_map_start(src_logical_column, dst_new_rows));
return Status::OK();
}
virtual ColumnPtr get_physical_column(tparquet::Type::type src_physical_type,
DataTypePtr src_logical_type,
ColumnPtr& dst_logical_column,
const DataTypePtr& dst_logical_type, bool is_dict_filter);
DataTypePtr& get_physical_type() { return _cached_src_physical_type; }
bool read_directly_into_dst_logical_column() {
return !_convert_params->is_type_compatibility && is_consistent() &&
_logical_converter->is_consistent();
}
virtual bool is_consistent() { return false; }
virtual bool support() { return true; }
std::string get_error_msg() { return _error_msg; }
};
class ConsistentPhysicalConverter : public PhysicalToLogicalConverter {
bool is_consistent() override { return true; }
};
class UnsupportedConverter : public PhysicalToLogicalConverter {
public:
UnsupportedConverter(std::string error_msg) { _error_msg = error_msg; }
UnsupportedConverter(tparquet::Type::type src_physical_type,
const DataTypePtr& src_logical_type) {
std::string src_physical_str = tparquet::to_string(src_physical_type);
std::string src_logical_str = src_logical_type->get_name();
_error_msg = src_physical_str + " => " + src_logical_str;
}
bool support() override { return false; }
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
return Status::InternalError("Unsupported physical to logical type: {}", _error_msg);
}
};
// for tinyint, smallint
template <PrimitiveType IntPrimitiveType>
class LittleIntPhysicalConverter : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
using DstCppType = typename PrimitiveTypeTraits<IntPrimitiveType>::CppType;
using DstColumnType = typename PrimitiveTypeTraits<IntPrimitiveType>::ColumnType;
ColumnPtr from_col = remove_nullable(src_physical_col);
IColumn* to_col = get_mutable_inner_column(src_logical_column);
size_t rows = from_col->size();
// always comes from tparquet::Type::INT32
auto& src_data = assert_cast<const ColumnInt32*>(from_col.get())->get_data();
size_t start_idx = to_col->size();
to_col->resize(start_idx + rows);
auto& data = assert_cast<DstColumnType&>(*to_col).get_data();
for (int i = 0; i < rows; ++i) {
data[start_idx + i] = static_cast<DstCppType>(src_data[i]);
}
return Status::OK();
}
};
template <PrimitiveType type>
struct UnsignedTypeTraits;
template <>
struct UnsignedTypeTraits<TYPE_SMALLINT> {
using UnsignedCppType = UInt8;
//https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#unsigned-integers
//INT(8, false), INT(16, false), and INT(32, false) must annotate an int32 primitive type and INT(64, false)
//must annotate an int64 primitive type.
using StorageCppType = Int32;
using StorageColumnType = ColumnInt32;
};
template <>
struct UnsignedTypeTraits<TYPE_INT> {
using UnsignedCppType = UInt16;
using StorageCppType = Int32;
using StorageColumnType = ColumnInt32;
};
template <>
struct UnsignedTypeTraits<TYPE_BIGINT> {
using UnsignedCppType = UInt32;
using StorageCppType = Int32;
using StorageColumnType = ColumnInt32;
};
template <>
struct UnsignedTypeTraits<TYPE_LARGEINT> {
using UnsignedCppType = UInt64;
using StorageCppType = Int64;
using StorageColumnType = ColumnInt64;
};
template <PrimitiveType IntPrimitiveType>
class UnsignedIntegerConverter : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
using UnsignedCppType = typename UnsignedTypeTraits<IntPrimitiveType>::UnsignedCppType;
using StorageCppType = typename UnsignedTypeTraits<IntPrimitiveType>::StorageCppType;
using StorageColumnType = typename UnsignedTypeTraits<IntPrimitiveType>::StorageColumnType;
using DstColumnType = typename PrimitiveTypeTraits<IntPrimitiveType>::ColumnType;
ColumnPtr from_col = remove_nullable(src_physical_col);
IColumn* to_col = get_mutable_inner_column(src_logical_column);
auto& src_data = assert_cast<const StorageColumnType*>(from_col.get())->get_data();
size_t rows = src_data.size();
size_t start_idx = to_col->size();
to_col->resize(start_idx + rows);
auto& data = assert_cast<DstColumnType&>(*to_col).get_data();
for (int i = 0; i < rows; i++) {
StorageCppType src_value = src_data[i];
auto unsigned_value = static_cast<UnsignedCppType>(src_value);
data[start_idx + i] = unsigned_value;
}
return Status::OK();
}
};
class FixedSizeBinaryConverter : public PhysicalToLogicalConverter {
private:
int _type_length;
public:
FixedSizeBinaryConverter(int type_length) : _type_length(type_length) {}
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr from_col = remove_nullable(src_physical_col);
IColumn* to_col = get_mutable_inner_column(src_logical_column);
const auto src_data = get_fixed_length_physical_data(*from_col, _type_length);
size_t length = src_data.byte_size;
size_t num_values = src_data.rows;
auto& string_col = static_cast<ColumnString&>(*to_col);
auto& offsets = string_col.get_offsets();
auto& chars = string_col.get_chars();
size_t origin_size = chars.size();
chars.resize(origin_size + length);
memcpy(chars.data() + origin_size, src_data.data, length);
origin_size = offsets.size();
offsets.resize(origin_size + num_values);
auto end_offset = offsets[origin_size - 1];
for (int i = 0; i < num_values; ++i) {
end_offset += _type_length;
offsets[origin_size + i] = end_offset;
}
return Status::OK();
}
};
class Float16PhysicalConverter : public PhysicalToLogicalConverter {
private:
int _type_length;
public:
Float16PhysicalConverter(int type_length) : _type_length(type_length) {
DCHECK_EQ(_type_length, 2);
}
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr from_col = remove_nullable(src_physical_col);
IColumn* to_col = get_mutable_inner_column(src_logical_column);
const auto src_data = get_fixed_length_physical_data(*from_col, _type_length);
size_t num_values = src_data.rows;
auto* to_float_column = assert_cast<ColumnFloat32*>(to_col);
size_t start_idx = to_float_column->size();
to_float_column->resize(start_idx + num_values);
auto& to_float_column_data = to_float_column->get_data();
const auto* ptr = src_data.data;
for (int i = 0; i < num_values; ++i) {
size_t offset = i * _type_length;
const auto* data_ptr = ptr + offset;
uint16_t raw;
memcpy(&raw, data_ptr, sizeof(uint16_t));
float value = half_to_float(raw);
to_float_column_data[start_idx + i] = value;
}
return Status::OK();
}
float half_to_float(uint16_t h) {
// uint16_t h: half precision floating point
// bit 15: sign(1 bit)
// bits 14..10 : exponent(5 bits)
// bits 9..0 : mantissa(10 bits)
// sign bit placed to float32 bit31
uint32_t sign = (h & 0x8000U) << 16; // 0x8000 << 16 = 0x8000_0000
// exponent:(5 bits)
uint32_t exp = (h & 0x7C00U) >> 10; // 0x7C00 = 0111 1100 0000 (half exponent mask)
// mantissa(10 bits)
uint32_t mant = (h & 0x03FFU); // 10-bit fraction
// cases:Zero/Subnormal, Normal, Inf/NaN
if (exp == 0) {
// exp==0: Zero or Subnormal ----------
if (mant == 0) {
// ±0.0
// sign = either 0x00000000 or 0x80000000
return std::bit_cast<float>(sign);
} else {
// ---------- Subnormal ----------
// half subnormal:
// value = (-1)^sign * (mant / 2^10) * 2^(1 - bias)
// half bias = 15 → exponent = 1 - 15 = -14
float f = (static_cast<float>(mant) / 1024.0F) * std::powf(2.0F, -14.0F);
return sign ? -f : f;
}
} else if (exp == 0x1F) {
// exp==31: Inf or NaN ----------
// float32:
// exponent = 255 (0xFF)
// mantissa = mant << 13
uint32_t f = sign | 0x7F800000U | (mant << 13);
return std::bit_cast<float>(f);
} else {
// Normalized ----------
// float32 exponent:
// exp32 = exp16 - bias16 + bias32
// bias16 = 15
// bias32 = 127
//
// so: exp32 = exp + (127 - 15)
uint32_t f = sign | ((exp + (127 - 15)) << 23) // place to float32 exponent
| (mant << 13); // mantissa align to 23 bits
return std::bit_cast<float>(f);
}
}
};
class UUIDVarBinaryConverter : public PhysicalToLogicalConverter {
public:
UUIDVarBinaryConverter(int type_length) : _type_length(type_length) {}
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
DCHECK(!is_column_const(*src_physical_col)) << src_physical_col->dump_structure();
DCHECK(!is_column_const(*src_logical_column)) << src_logical_column->dump_structure();
const ColumnPtr from_col = remove_nullable(src_physical_col);
const auto src_data = get_fixed_length_physical_data(*from_col, _type_length);
IColumn* to_col = get_mutable_inner_column(src_logical_column);
auto* to_varbinary_column = assert_cast<ColumnVarbinary*>(to_col);
size_t num_values = src_data.rows;
const auto* ptr = src_data.data;
for (int i = 0; i < num_values; ++i) {
auto offset = i * _type_length;
const char* data_ptr = reinterpret_cast<const char*>(ptr + offset);
to_varbinary_column->insert_data(data_ptr, _type_length);
}
return Status::OK();
}
private:
int _type_length;
};
template <PrimitiveType DecimalPType>
class FixedSizeToDecimal : public PhysicalToLogicalConverter {
public:
using DecimalType = typename PrimitiveTypeTraits<DecimalPType>::CppType;
FixedSizeToDecimal(int32_t type_length) : _type_length(type_length) {}
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
#define M(FixedTypeLength, ValueCopyType) \
case FixedTypeLength: \
return _convert_internal<FixedTypeLength, ValueCopyType>(src_col, dst_col);
#define APPLY_FOR_DECIMALS() \
M(1, int64_t) \
M(2, int64_t) \
M(3, int64_t) \
M(4, int64_t) \
M(5, int64_t) \
M(6, int64_t) \
M(7, int64_t) \
M(8, int64_t) \
M(9, int128_t) \
M(10, int128_t) \
M(11, int128_t) \
M(12, int128_t) \
M(13, int128_t) \
M(14, int128_t) \
M(15, int128_t) \
M(16, int128_t) \
M(17, wide::Int256) \
M(18, wide::Int256) \
M(19, wide::Int256) \
M(20, wide::Int256) \
M(21, wide::Int256) \
M(22, wide::Int256) \
M(23, wide::Int256) \
M(24, wide::Int256) \
M(25, wide::Int256) \
M(26, wide::Int256) \
M(27, wide::Int256) \
M(28, wide::Int256) \
M(29, wide::Int256) \
M(30, wide::Int256) \
M(31, wide::Int256) \
M(32, wide::Int256)
switch (_type_length) {
APPLY_FOR_DECIMALS()
default:
throw Exception(Status::FatalError("__builtin_unreachable"));
}
return Status::OK();
#undef APPLY_FOR_DECIMALS
#undef M
}
template <int fixed_type_length, typename ValueCopyType>
Status _convert_internal(ColumnPtr& src_col, IColumn* dst_col) {
const auto src_data = get_fixed_length_physical_data(*src_col, fixed_type_length);
size_t rows = src_data.rows;
const auto* buf = src_data.data;
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
auto& data = static_cast<ColumnDecimal<DecimalPType>*>(dst_col)->get_data();
size_t offset = 0;
for (int i = 0; i < rows; i++) {
// When Decimal in parquet is stored in byte arrays, binary and fixed,
// the unscaled number must be encoded as two's complement using big-endian byte order.
ValueCopyType value = 0;
memcpy(reinterpret_cast<char*>(&value), buf + offset, sizeof(value));
offset += fixed_type_length;
value = to_endian<std::endian::big>(value);
value = value >> ((sizeof(value) - fixed_type_length) * 8);
auto& v = reinterpret_cast<DecimalType&>(data[start_idx + i]);
v = (DecimalType)value;
}
return Status::OK();
}
private:
int32_t _type_length;
};
template <PrimitiveType DecimalPType>
class StringToDecimal : public PhysicalToLogicalConverter {
using DecimalType = typename PrimitiveTypeTraits<DecimalPType>::CppType;
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
using ValueCopyType = DecimalType::NativeType;
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size();
auto buf = static_cast<const ColumnString*>(src_col.get())->get_chars().data();
auto& offset = static_cast<const ColumnString*>(src_col.get())->get_offsets();
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
auto& data = static_cast<ColumnDecimal<DecimalPType>*>(dst_col)->get_data();
for (int i = 0; i < rows; i++) {
size_t len = offset[i] - offset[i - 1];
// When Decimal in parquet is stored in byte arrays, binary and fixed,
// the unscaled number must be encoded as two's complement using big-endian byte order.
ValueCopyType value = 0;
if (len > 0) {
memcpy(reinterpret_cast<char*>(&value), buf + offset[i - 1], len);
value = to_endian<std::endian::big>(value);
value = value >> ((sizeof(value) - len) * 8);
}
auto& v = reinterpret_cast<DecimalType&>(data[start_idx + i]);
v = (DecimalType)value;
}
return Status::OK();
}
};
template <PrimitiveType NumberType, PrimitiveType DecimalPType>
class NumberToDecimal : public PhysicalToLogicalConverter {
using DecimalType = typename PrimitiveTypeTraits<DecimalPType>::CppType;
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
using ValueCopyType = typename DecimalType::NativeType;
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size();
auto* src_data =
static_cast<const ColumnVector<NumberType>*>(src_col.get())->get_data().data();
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
auto* data = static_cast<ColumnDecimal<DecimalPType>*>(dst_col)->get_data().data();
for (int i = 0; i < rows; i++) {
ValueCopyType value;
if constexpr (std::is_same_v<DecimalType, Decimal256>) {
value = src_data[i];
} else {
value = cast_set<ValueCopyType, typename PrimitiveTypeTraits<NumberType>::CppType,
false>(src_data[i]);
}
data[start_idx + i] = (DecimalType)value;
}
return Status::OK();
}
};
class Int32ToDate : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size();
size_t start_idx = dst_col->size();
dst_col->reserve(start_idx + rows);
auto& src_data = static_cast<const ColumnInt32*>(src_col.get())->get_data();
auto& data = static_cast<ColumnDateV2*>(dst_col)->get_data();
date_day_offset_dict& date_dict = date_day_offset_dict::get();
for (int i = 0; i < rows; i++) {
data.push_back_without_reserve(date_dict[src_data[i]].to_date_int_val());
}
return Status::OK();
}
};
struct Int64ToTimestamp : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size();
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
auto src_data = static_cast<const ColumnInt64*>(src_col.get())->get_data().data();
auto& data = static_cast<ColumnDateTimeV2*>(dst_col)->get_data();
for (int i = 0; i < rows; i++) {
int64_t x = src_data[i];
auto& num = data[start_idx + i];
auto& value = reinterpret_cast<DateV2Value<DateTimeV2ValueType>&>(num);
const auto epoch_time = detail::split_epoch_time(x, _convert_params->second_mask,
_convert_params->scale_to_nano_factor);
if (_convert_params->is_fixed_offset) {
if (!detail::try_convert_timestamp_with_fixed_offset(
value, epoch_time.seconds, _convert_params->fixed_offset_seconds)) {
value.from_unixtime(epoch_time.seconds, *_convert_params->ctz);
}
} else if (!detail::try_convert_timestamp_with_lookup(value, epoch_time.seconds,
*_convert_params->ctz)) {
value.from_unixtime(epoch_time.seconds, *_convert_params->ctz);
}
value.set_microsecond(epoch_time.microseconds);
}
return Status::OK();
}
};
struct Int64ToTimestampTz : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size();
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
const auto& src_data = assert_cast<const ColumnInt64*>(src_col.get())->get_data();
auto& dest_data = assert_cast<ColumnTimeStampTz*>(dst_col)->get_data();
static const cctz::time_zone UTC = cctz::utc_time_zone();
for (int i = 0; i < rows; i++) {
int64_t x = src_data[i];
auto& tz = dest_data[start_idx + i];
const auto epoch_time = detail::split_epoch_time(x, _convert_params->second_mask,
_convert_params->scale_to_nano_factor);
tz.from_unixtime(epoch_time.seconds, UTC);
tz.set_microsecond(epoch_time.microseconds);
}
return Status::OK();
}
};
struct Int96toTimestamp : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size() / sizeof(ParquetInt96);
auto& src_data = static_cast<const ColumnInt8*>(src_col.get())->get_data();
auto ParquetInt96_data = (ParquetInt96*)src_data.data();
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
auto& data = static_cast<ColumnDateTimeV2*>(dst_col)->get_data();
for (int i = 0; i < rows; i++) {
ParquetInt96 src_cell_data = ParquetInt96_data[i];
auto& dst_value =
reinterpret_cast<DateV2Value<DateTimeV2ValueType>&>(data[start_idx + i]);
const auto epoch_time =
detail::split_epoch_time(src_cell_data.to_timestamp_micros(), 1000000, 1000);
if (_convert_params->is_fixed_offset) {
if (!detail::try_convert_timestamp_with_fixed_offset(
dst_value, epoch_time.seconds, _convert_params->fixed_offset_seconds)) {
dst_value.from_unixtime(epoch_time.seconds, *_convert_params->ctz);
}
} else if (!detail::try_convert_timestamp_with_lookup(dst_value, epoch_time.seconds,
*_convert_params->ctz)) {
dst_value.from_unixtime(epoch_time.seconds, *_convert_params->ctz);
}
dst_value.set_microsecond(epoch_time.microseconds);
}
return Status::OK();
}
};
struct Int96toTimestampTz : public PhysicalToLogicalConverter {
Status physical_convert(ColumnPtr& src_physical_col, ColumnPtr& src_logical_column) override {
ColumnPtr src_col = remove_nullable(src_physical_col);
IColumn* dst_col = get_mutable_inner_column(src_logical_column);
size_t rows = src_col->size() / sizeof(ParquetInt96);
const auto& src_data = assert_cast<const ColumnInt8*>(src_col.get())->get_data();
auto* ParquetInt96_data = (ParquetInt96*)src_data.data();
size_t start_idx = dst_col->size();
dst_col->resize(start_idx + rows);
auto& data = assert_cast<ColumnTimeStampTz*>(dst_col)->get_data();
static const cctz::time_zone UTC = cctz::utc_time_zone();
for (int i = 0; i < rows; i++) {
ParquetInt96 src_cell_data = ParquetInt96_data[i];
auto& tz = data[start_idx + i];
const auto epoch_time =
detail::split_epoch_time(src_cell_data.to_timestamp_micros(), 1000000, 1000);
tz.from_unixtime(epoch_time.seconds, UTC);
tz.set_microsecond(epoch_time.microseconds);
}
return Status::OK();
}
};
} // namespace doris::parquet