blob: a1d765d2c857b698ee68ab03e1ad2d774d6a85f0 [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 <cstdint>
#include <limits>
#include <memory>
#include <mutex>
#include <span>
#include <utility>
#include <vector>
#include <nanoarrow/nanoarrow.h>
#include "iceberg/arrow/nanoarrow_status_internal.h"
#include "iceberg/arrow_c_data_guard_internal.h"
#include "iceberg/arrow_c_data_util_internal.h"
#include "iceberg/file_reader.h"
#include "iceberg/result.h"
#include "iceberg/schema.h"
#include "iceberg/schema_field.h"
#include "iceberg/schema_internal.h"
#include "iceberg/util/macros.h"
namespace iceberg {
Result<ArrowArrayStream> MakeArrowArrayStream(std::unique_ptr<Reader> reader) {
return MakeArrowArrayStream<Reader>(std::move(reader));
}
namespace {
Result<size_t> FindFieldIndexById(std::span<const SchemaField> fields, int32_t field_id) {
for (size_t index = 0; index < fields.size(); ++index) {
if (fields[index].field_id() == field_id) {
return index;
}
}
return InvalidArgument("Required schema does not contain projected field id {}",
field_id);
}
std::mutex g_project_batch_function_mutex;
ProjectionContext::ProjectBatchFunction g_project_batch_function = nullptr;
ProjectionContext::ProjectBatchFunction GetProjectBatchFunction() {
std::lock_guard lock(g_project_batch_function_mutex);
return g_project_batch_function;
}
Result<std::vector<int32_t>> BuildSelectedFieldIndices(
std::span<const SchemaField> input_fields,
std::span<const SchemaField> output_fields) {
std::vector<int32_t> selected_field_indices;
selected_field_indices.reserve(output_fields.size());
for (const auto& output_field : output_fields) {
ICEBERG_ASSIGN_OR_RAISE(auto input_index,
FindFieldIndexById(input_fields, output_field.field_id()));
const auto& input_field = input_fields[input_index];
if (*input_field.type() != *output_field.type()) {
return InvalidArgument(
"ProjectBatch only supports complete top-level fields, but field id "
"{} changes type from {} to {}",
output_field.field_id(), input_field.type()->ToString(),
output_field.type()->ToString());
}
ICEBERG_PRECHECK(
input_index <= static_cast<size_t>(std::numeric_limits<int32_t>::max()),
"Input field index {} exceeds int32 range", input_index);
selected_field_indices.push_back(static_cast<int32_t>(input_index));
}
return selected_field_indices;
}
Status AppendValue(const ArrowSchema& input_schema, const ArrowArray& input_array,
const ArrowArrayView& input_view, int64_t row_index,
ArrowArray* output_array);
Status AppendListValues(const ArrowSchema& input_schema, const ArrowArray& input_array,
const ArrowArrayView& input_view, int64_t row_index,
ArrowArray* output_array) {
const int64_t begin = ArrowArrayViewListChildOffset(&input_view, row_index);
const int64_t end = ArrowArrayViewListChildOffset(&input_view, row_index + 1);
for (int64_t element_index = begin; element_index < end; ++element_index) {
ICEBERG_RETURN_UNEXPECTED(
AppendValue(*input_schema.children[0], *input_array.children[0],
*input_view.children[0], element_index, output_array->children[0]));
}
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(output_array));
return {};
}
Status AppendMapValues(const ArrowSchema& input_schema, const ArrowArray& input_array,
const ArrowArrayView& input_view, int64_t row_index,
ArrowArray* output_array) {
const int64_t begin = ArrowArrayViewListChildOffset(&input_view, row_index);
const int64_t end = ArrowArrayViewListChildOffset(&input_view, row_index + 1);
for (int64_t entry_index = begin; entry_index < end; ++entry_index) {
ICEBERG_RETURN_UNEXPECTED(
AppendValue(*input_schema.children[0], *input_array.children[0],
*input_view.children[0], entry_index, output_array->children[0]));
}
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(output_array));
return {};
}
Status AppendDecimal(const ArrowSchema& input_schema, const ArrowArrayView& input_view,
int64_t row_index, ArrowArray* output_array) {
ArrowError error;
ArrowSchemaView schema_view;
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
ArrowSchemaViewInit(&schema_view, &input_schema, &error), error);
ArrowDecimal value;
ArrowDecimalInit(&value, schema_view.decimal_bitwidth, schema_view.decimal_precision,
schema_view.decimal_scale);
ArrowArrayViewGetDecimalUnsafe(&input_view, row_index, &value);
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendDecimal(output_array, &value));
return {};
}
Status AppendValue(const ArrowSchema& input_schema, const ArrowArray& input_array,
const ArrowArrayView& input_view, int64_t row_index,
ArrowArray* output_array) {
if (ArrowArrayViewIsNull(&input_view, row_index)) {
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendNull(output_array, 1));
return {};
}
switch (input_view.storage_type) {
case NANOARROW_TYPE_NA:
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendNull(output_array, 1));
return {};
case NANOARROW_TYPE_BOOL:
case NANOARROW_TYPE_INT8:
case NANOARROW_TYPE_INT16:
case NANOARROW_TYPE_INT32:
case NANOARROW_TYPE_INT64:
case NANOARROW_TYPE_DATE32:
case NANOARROW_TYPE_DATE64:
case NANOARROW_TYPE_TIME32:
case NANOARROW_TYPE_TIME64:
case NANOARROW_TYPE_TIMESTAMP:
case NANOARROW_TYPE_DURATION:
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendInt(
output_array, ArrowArrayViewGetIntUnsafe(&input_view, row_index)));
return {};
case NANOARROW_TYPE_UINT8:
case NANOARROW_TYPE_UINT16:
case NANOARROW_TYPE_UINT32:
case NANOARROW_TYPE_UINT64:
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendUInt(
output_array, ArrowArrayViewGetUIntUnsafe(&input_view, row_index)));
return {};
case NANOARROW_TYPE_HALF_FLOAT:
case NANOARROW_TYPE_FLOAT:
case NANOARROW_TYPE_DOUBLE:
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendDouble(
output_array, ArrowArrayViewGetDoubleUnsafe(&input_view, row_index)));
return {};
case NANOARROW_TYPE_STRING:
case NANOARROW_TYPE_LARGE_STRING:
case NANOARROW_TYPE_STRING_VIEW: {
auto value = ArrowArrayViewGetStringUnsafe(&input_view, row_index);
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendString(output_array, value));
return {};
}
case NANOARROW_TYPE_BINARY:
case NANOARROW_TYPE_LARGE_BINARY:
case NANOARROW_TYPE_FIXED_SIZE_BINARY:
case NANOARROW_TYPE_BINARY_VIEW: {
auto value = ArrowArrayViewGetBytesUnsafe(&input_view, row_index);
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayAppendBytes(output_array, value));
return {};
}
case NANOARROW_TYPE_DECIMAL128:
case NANOARROW_TYPE_DECIMAL256:
return AppendDecimal(input_schema, input_view, row_index, output_array);
case NANOARROW_TYPE_STRUCT: {
for (int64_t child_index = 0; child_index < input_schema.n_children;
++child_index) {
ICEBERG_RETURN_UNEXPECTED(AppendValue(
*input_schema.children[child_index], *input_array.children[child_index],
*input_view.children[child_index], row_index,
output_array->children[child_index]));
}
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(output_array));
return {};
}
case NANOARROW_TYPE_LIST:
case NANOARROW_TYPE_LARGE_LIST:
case NANOARROW_TYPE_FIXED_SIZE_LIST:
return AppendListValues(input_schema, input_array, input_view, row_index,
output_array);
case NANOARROW_TYPE_MAP:
return AppendMapValues(input_schema, input_array, input_view, row_index,
output_array);
default:
return NotImplemented("Unsupported Arrow type for merge-on-read projection: {}",
static_cast<int>(input_view.storage_type));
}
}
} // namespace
ProjectionContext::ProjectionContext(ProjectionContext&& other) noexcept
: input_schema_(std::exchange(other.input_schema_, nullptr)),
output_schema_(std::exchange(other.output_schema_, nullptr)),
selected_field_indices_(std::move(other.selected_field_indices_)),
input_arrow_schema_(other.input_arrow_schema_),
output_arrow_schema_(other.output_arrow_schema_),
project_batch_function_(std::exchange(other.project_batch_function_, nullptr)),
project_batch_state_(std::move(other.project_batch_state_)) {
other.input_arrow_schema_.release = nullptr;
other.output_arrow_schema_.release = nullptr;
}
ProjectionContext& ProjectionContext::operator=(ProjectionContext&& other) noexcept {
if (this == &other) {
return *this;
}
if (input_arrow_schema_.release != nullptr) {
input_arrow_schema_.release(&input_arrow_schema_);
}
if (output_arrow_schema_.release != nullptr) {
output_arrow_schema_.release(&output_arrow_schema_);
}
input_schema_ = std::exchange(other.input_schema_, nullptr);
output_schema_ = std::exchange(other.output_schema_, nullptr);
selected_field_indices_ = std::move(other.selected_field_indices_);
input_arrow_schema_ = other.input_arrow_schema_;
other.input_arrow_schema_.release = nullptr;
output_arrow_schema_ = other.output_arrow_schema_;
other.output_arrow_schema_.release = nullptr;
project_batch_function_ = std::exchange(other.project_batch_function_, nullptr);
project_batch_state_ = std::move(other.project_batch_state_);
return *this;
}
ProjectionContext::~ProjectionContext() {
if (input_arrow_schema_.release != nullptr) {
input_arrow_schema_.release(&input_arrow_schema_);
}
if (output_arrow_schema_.release != nullptr) {
output_arrow_schema_.release(&output_arrow_schema_);
}
}
Result<ProjectionContext> ProjectionContext::Make(
const Schema& input_schema, const Schema& output_schema,
ProjectionContext::ProjectBatchFunction project_batch_function) {
ICEBERG_ASSIGN_OR_RAISE(
auto selected_field_indices,
BuildSelectedFieldIndices(input_schema.fields(), output_schema.fields()));
ProjectionContext context;
context.input_schema_ = &input_schema;
context.output_schema_ = &output_schema;
context.selected_field_indices_ = std::move(selected_field_indices);
ICEBERG_RETURN_UNEXPECTED(ToArrowSchema(input_schema, &context.input_arrow_schema_));
ICEBERG_RETURN_UNEXPECTED(ToArrowSchema(output_schema, &context.output_arrow_schema_));
context.project_batch_function_ = project_batch_function;
return context;
}
const Schema& ProjectionContext::input_schema() const { return *input_schema_; }
const Schema& ProjectionContext::output_schema() const { return *output_schema_; }
const ArrowSchema& ProjectionContext::input_arrow_schema() const {
return input_arrow_schema_;
}
const ArrowSchema& ProjectionContext::output_arrow_schema() const {
return output_arrow_schema_;
}
std::span<const int32_t> ProjectionContext::selected_field_indices() const {
return selected_field_indices_;
}
ProjectionContext::ProjectBatchFunction ProjectionContext::project_batch_function()
const {
return project_batch_function_;
}
ProjectionContext::ProjectBatchState& ProjectionContext::project_batch_state() {
return project_batch_state_;
}
void ProjectionContext::RegisterProjectBatchFunction(
ProjectionContext::ProjectBatchFunction project_batch_function) {
ICEBERG_DCHECK(project_batch_function != nullptr,
"ProjectBatch implementation must not be null");
if (project_batch_function == nullptr) {
return;
}
std::lock_guard lock(g_project_batch_function_mutex);
g_project_batch_function = project_batch_function;
}
bool ProjectionContext::HasProjectBatchFunction() {
return GetProjectBatchFunction() != nullptr;
}
auto ProjectionContext::ResolveProjectBatchFunction()
-> ProjectionContext::ProjectBatchFunction {
return GetProjectBatchFunction();
}
namespace {
Result<ArrowArray> ProjectBatchNanoarrow(
const ArrowSchema& input_arrow_schema, const ArrowArray& input_batch,
std::span<const int32_t> row_indices, const ArrowSchema& output_arrow_schema,
std::span<const int32_t> selected_field_indices) {
ArrowArrayView input_view;
ArrowError error;
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
ArrowArrayViewInitFromSchema(&input_view, &input_arrow_schema, &error), error);
internal::ArrowArrayViewGuard input_view_guard(&input_view);
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
ArrowArrayViewSetArray(&input_view, &input_batch, &error), error);
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
ArrowArrayViewValidate(&input_view, NANOARROW_VALIDATION_LEVEL_DEFAULT, &error),
error);
ArrowArray output_array;
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
ArrowArrayInitFromSchema(&output_array, &output_arrow_schema, &error), error);
internal::ArrowArrayGuard output_array_guard(&output_array);
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayStartAppending(&output_array));
ICEBERG_NANOARROW_RETURN_UNEXPECTED(
ArrowArrayReserve(&output_array, static_cast<int64_t>(row_indices.size())));
for (int64_t row_index : row_indices) {
ICEBERG_PRECHECK(row_index >= 0 && row_index < input_batch.length,
"Row index {} out of range for batch length {}", row_index,
input_batch.length);
for (size_t output_index = 0; output_index < selected_field_indices.size();
++output_index) {
const int32_t input_index = selected_field_indices[output_index];
ICEBERG_RETURN_UNEXPECTED(AppendValue(*input_arrow_schema.children[input_index],
*input_batch.children[input_index],
*input_view.children[input_index], row_index,
output_array.children[output_index]));
}
ICEBERG_NANOARROW_RETURN_UNEXPECTED(ArrowArrayFinishElement(&output_array));
}
ICEBERG_NANOARROW_RETURN_UNEXPECTED_WITH_ERROR(
ArrowArrayFinishBuildingDefault(&output_array, &error), error);
return std::exchange(output_array, ArrowArray{});
}
} // namespace
Result<ArrowArray> ProjectBatch(ArrowArray* input_batch,
std::span<const int32_t> row_indices,
ProjectionContext& projection) {
ICEBERG_PRECHECK(input_batch != nullptr, "input_batch must not be null");
internal::ArrowArrayGuard input_batch_guard(input_batch);
auto project_batch_function = projection.project_batch_function();
if (project_batch_function != nullptr) {
// ProjectBatch owns input_batch. Arrow-backed implementations import it and clear
// release, so input_batch_guard becomes a no-op instead of double-releasing.
return project_batch_function(input_batch, row_indices, projection);
}
return ProjectBatchNanoarrow(projection.input_arrow_schema(), *input_batch, row_indices,
projection.output_arrow_schema(),
projection.selected_field_indices());
}
} // namespace iceberg