blob: ff542d679af9babc1fba0979b86018782535e60a [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 "exec/iceberg-delete-sink-base.h"
#include <boost/algorithm/string.hpp>
#include "exec/iceberg-delete-sink-config.h"
#include "exec/output-partition.h"
#include "exprs/scalar-expr-evaluator.h"
#include "kudu/util/url-coding.h"
#include "util/debug-util.h"
#include "util/iceberg-utility-functions.h"
#include "common/names.h"
namespace impala {
IcebergDeleteSinkBase::IcebergDeleteSinkBase(TDataSinkId sink_id,
const IcebergDeleteSinkConfig& sink_config, const std::string& name,
RuntimeState* state): TableSinkBase(sink_id, sink_config, name, state),
referenced_deletion_vectors_(&sink_config.referenced_deletion_vectors) {}
Status IcebergDeleteSinkBase::Prepare(RuntimeState* state,
MemTracker* parent_mem_tracker) {
RETURN_IF_ERROR(TableSinkBase::Prepare(state, parent_mem_tracker));
unique_id_str_ = "delete-" + PrintId(state->fragment_instance_id(), "-");
// Resolve table id and set input tuple descriptor.
table_desc_ = static_cast<const HdfsTableDescriptor*>(
state->desc_tbl().GetTableDescriptor(table_id_));
if (table_desc_ == nullptr) {
stringstream error_msg;
error_msg << "Failed to get table descriptor for table id: " << table_id_;
return Status(error_msg.str());
}
DCHECK_GE(output_expr_evals_.size(),
table_desc_->num_cols() - table_desc_->num_clustering_cols()) << DebugString();
return Status::OK();
}
Status IcebergDeleteSinkBase::Open(RuntimeState* state) {
RETURN_IF_ERROR(TableSinkBase::Open(state));
DCHECK_EQ(partition_key_expr_evals_.size(), dynamic_partition_key_expr_evals_.size());
return Status::OK();
}
inline bool IsDate(const TScalarType& tscalar) {
return tscalar.type == TPrimitiveType::DATE;
}
inline bool IsTimestamp(const TScalarType& tscalar) {
return tscalar.type == TPrimitiveType::TIMESTAMP;
}
inline bool IsDateTime(const TScalarType& tscalar) {
return tscalar.type == TPrimitiveType::DATETIME;
}
std::string IcebergDeleteSinkBase::HumanReadablePartitionValue(
const TIcebergPartitionField& part_field, const std::string& value,
Status* transform_result) {
*transform_result = Status::OK();
TIcebergPartitionTransformType::type transform_type =
part_field.transform.transform_type;
if (value == table_desc_->null_partition_key_value()) {
// We don't need to transfrom NULL values.
return value;
}
if (transform_type == TIcebergPartitionTransformType::IDENTITY) {
const TScalarType& scalar_type = part_field.type;
// Timestamp and DateTime (not even implemented yet) types are not supported for
// IDENTITY-partitioning.
if (IsTimestamp(scalar_type) || IsDateTime(scalar_type)) {
*transform_result = Status(Substitute(
"Unsupported type for IDENTITY-partitioning: $0", to_string(scalar_type.type)));
return value;
}
if (IsDate(scalar_type)) {
// With IDENTITY partitioning, only DATEs are problematic, because DATEs are stored
// as an offset from the unix epoch. So we need to handle the values similarly to
// the DAY-transformed values.
return iceberg::HumanReadableTime(
TIcebergPartitionTransformType::DAY, value, transform_result);
}
}
if (!iceberg::IsTimeBasedPartition(transform_type)) {
// Don't need to convert values of non-time transforms.
return value;
}
return iceberg::HumanReadableTime(transform_type, value, transform_result);
}
Status IcebergDeleteSinkBase::ConstructPartitionInfo(
const TupleRow* row, OutputPartition* output_partition) {
DCHECK(output_partition != nullptr);
DCHECK(output_partition->raw_partition_names.empty());
if (partition_key_expr_evals_.empty()) {
output_partition->iceberg_spec_id = table_desc_->IcebergSpecId();
return Status::OK();
}
DCHECK_EQ(partition_key_expr_evals_.size(), 2);
ScalarExprEvaluator* spec_id_eval = partition_key_expr_evals_[0];
ScalarExprEvaluator* partition_values_eval = partition_key_expr_evals_[1];
int spec_id = spec_id_eval->GetIntVal(row).val;
impala_udf::StringVal partition_values_sval = partition_values_eval->GetStringVal(row);
string partition_values_str((const char*)partition_values_sval.ptr,
partition_values_sval.len);
return ConstructPartitionInfo(spec_id, partition_values_str, output_partition);
}
Status IcebergDeleteSinkBase::ConstructPartitionInfo(int32_t spec_id,
const std::string& partition_values_str, OutputPartition* output_partition) {
output_partition->referenced_deletion_vectors = referenced_deletion_vectors_;
if (partition_key_expr_evals_.empty()) {
DCHECK_EQ(spec_id, table_desc_->IcebergSpecId());
output_partition->iceberg_spec_id = spec_id;
return Status::OK();
}
output_partition->iceberg_spec_id = spec_id;
vector<TIcebergPartitionField> non_void_partition_fields;
if (LIKELY(spec_id == table_desc_->IcebergSpecId())) {
// If 'spec_id' is the default spec id, then just copy the already populated
// non void partition fields.
non_void_partition_fields = table_desc_->IcebergNonVoidPartitionFields();
} else {
// Otherwise collect the non-void partition names belonging to 'spec_id'.
const TIcebergPartitionSpec& partition_spec =
table_desc_->IcebergPartitionSpecs()[spec_id];
for (const TIcebergPartitionField& spec_field : partition_spec.partition_fields) {
auto transform_type = spec_field.transform.transform_type;
if (transform_type != TIcebergPartitionTransformType::VOID) {
non_void_partition_fields.push_back(spec_field);
}
}
}
if (non_void_partition_fields.empty()) {
DCHECK(partition_values_str.empty());
return Status::OK();
}
vector<string> partition_values_encoded;
boost::split(partition_values_encoded, partition_values_str, boost::is_any_of("."));
vector<string> partition_values_decoded;
partition_values_decoded.reserve(partition_values_encoded.size());
for (const string& encoded_part_val : partition_values_encoded) {
string decoded_val;
bool success = kudu::Base64Decode(encoded_part_val, &decoded_val);
// We encoded it, we must succeed decoding it.
DCHECK(success);
partition_values_decoded.push_back(std::move(decoded_val));
}
DCHECK_EQ(partition_values_decoded.size(), non_void_partition_fields.size());
stringstream url_encoded_partition_name_ss;
for (int i = 0; i < partition_values_decoded.size(); ++i) {
const TIcebergPartitionField& part_field = non_void_partition_fields[i];
stringstream raw_partition_key_value_ss;
stringstream url_encoded_partition_key_value_ss;
raw_partition_key_value_ss << part_field.field_name << "=";
url_encoded_partition_key_value_ss << part_field.field_name << "=";
string& value_str = partition_values_decoded[i];
Status transform_status;
value_str = HumanReadablePartitionValue(part_field, value_str, &transform_status);
if (!transform_status.ok()) return transform_status;
raw_partition_key_value_ss << value_str;
string part_key_value = UrlEncodePartitionValue(value_str);
url_encoded_partition_key_value_ss << part_key_value;
if (i < partition_values_decoded.size() - 1) {
url_encoded_partition_key_value_ss << "/";
}
url_encoded_partition_name_ss << url_encoded_partition_key_value_ss.str();
output_partition->raw_partition_names.push_back(raw_partition_key_value_ss.str());
}
output_partition->partition_name = url_encoded_partition_name_ss.str();
return Status::OK();
}
}