blob: ac1bfd31d6886322ae44256b057e73eb4aaaf091 [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 <cstdint>
#include <vector>
#include "arrow/api.h"
#include "arrow/util/type_fwd.h"
#include "paimon/result.h"
struct ArrowArray;
namespace paimon {
class PAIMON_EXPORT ArrowUtils {
public:
ArrowUtils() = delete;
~ArrowUtils() = delete;
static const char* kArrowSchemaMetadataKey;
static Result<std::shared_ptr<arrow::Schema>> DataTypeToSchema(
const std::shared_ptr<arrow::DataType>& data_type);
static Result<std::vector<int32_t>> CreateProjection(
const std::shared_ptr<arrow::Schema>& src_schema, const arrow::FieldVector& target_fields);
/// Checks that every logically visible null matches the schema. Values hidden by a null
/// STRUCT, LIST, MAP, or FIXED_SIZE_LIST parent are ignored.
static Status CheckNullabilityMatch(const std::shared_ptr<arrow::Schema>& schema,
const std::shared_ptr<arrow::Array>& data);
// For struct array, arrow is unsafe for fields() and field(); for dict array, arrow is unsafe
// for dictionary(). Therefore, access array in advance before merge sort and projection to
// avoid subsequent multi-threading problems.
static void TraverseArray(const std::shared_ptr<arrow::Array>& array);
static uint64_t GetArrayMemoryUsage(const std::shared_ptr<arrow::ArrayData>& data);
/// Expands the booleans of `array` into one byte per row, the shape `LeafFunction::Test`
/// returns. Row `i` becomes 1 when it holds a value and that value, once `negate` has been
/// applied to it, is true; a null row becomes 0.
///
/// A kernel a leaf function probes with writes a boolean bitmap, one bit per row, so the rows
/// it selects have to be spread over a byte each before the caller can use them. Reading that
/// bitmap a byte at a time and writing eight rows at once keeps the spread off the per row
/// path, where reading it a bit at a time costs a shift, a mask and a byte store per row.
///
/// @param array The bitmap to expand. Its offset and its validity are honoured exactly as
/// `BooleanArray::Value()` and `Array::IsValid()` honour them.
/// @param negate Whether to take the rows whose bit is clear rather than the rows whose bit is
/// set, which is what `NOT IN` needs. A null row stays 0 either way.
/// @return One byte per row of `array`.
static std::vector<char> UnpackBooleansToBytes(const arrow::BooleanArray& array, bool negate);
static Result<std::shared_ptr<arrow::StructArray>> RemoveFieldFromStructArray(
const std::shared_ptr<arrow::StructArray>& struct_array, const std::string& field_name);
/// Returns a RecordBatch whose columns, including their nested children, all have a zero
/// offset, as required by `BatchReader`. Offsets are rebased by slicing buffers (zero copy)
/// wherever the layout allows it; only layouts that cannot be rebased fall back to a full copy.
static Result<std::shared_ptr<arrow::RecordBatch>> NormalizeRecordBatchOffsets(
const std::shared_ptr<arrow::RecordBatch>& record_batch, arrow::MemoryPool* pool);
/// Returns an Array with zero offsets. Struct children are also sliced to the parent's visible
/// range so the result can be exported and imported as a RecordBatch.
static Result<std::shared_ptr<arrow::Array>> NormalizeArrayOffsets(
const std::shared_ptr<arrow::Array>& array, arrow::MemoryPool* pool);
static bool EqualsIgnoreNullable(const std::shared_ptr<arrow::DataType>& type,
const std::shared_ptr<arrow::DataType>& other_type);
/// Normalize and resolve a compression string to an Arrow compression type.
/// Handles "none" and empty string by mapping them to "uncompressed".
static Result<arrow::Compression::type> GetCompressionType(const std::string& compression);
/// Whether `dictionary(int32(), type)` survives the Arrow C data interface, which drops the
/// type and leaves only the layout behind. `utf8()` and `binary()` do; `large_utf8()` does not,
/// because the layout reports neither the index nor the offset width. The definition says why.
///
/// This is what the writer can recognise on the other side of the interface, not what a reader
/// should hand over: a producer may narrow it further for reasons of its own, and
/// ParquetFileBatchReader does, forwarding STRING alone.
///
/// @param type The column's value type, not its dictionary type.
/// @return True when `dictionary(int32(), type)` round-trips through an `ArrowArray`.
static bool IsDictionaryLayoutRecoverableValueType(const arrow::DataType& type);
/// Recovers dictionary fields omitted from a batch's declared logical type by inspecting its
/// layout: `logical_type` with every top-level field whose matching child in `batch` carries a
/// dictionary replaced by `dictionary(int32(), field type)`, or `logical_type` itself when no
/// child is dictionary-encoded.
///
/// The `int32` index width is assumed rather than inferred, so this is a contract on whoever
/// produces the batch, not a check the callers can rely on: the producer must either provide a
/// batch whose dictionaries all have `int32` indices, or run
/// FlattenUnresolvableDictionaries() while the type is still known. The definition spells out
/// what that buys and what it does not.
///
/// A field that already carries a dictionary type is left alone: `logical_type` then comes
/// from a caller that declared the encoding up front and already describes the batch.
///
/// @param logical_type The struct type the caller declares for the batch. Returned unchanged
/// when it is not a struct or its field count does not match `batch`,
/// leaving the mismatch to the import's own diagnostics.
/// @param batch Only its structure is inspected, never its data, and it is not consumed.
/// @return `logical_type` or a copy of it carrying the recovered dictionary fields, or
/// NotImplemented for a dictionary this cannot describe.
static Result<std::shared_ptr<arrow::DataType>> ResolveDictionaryStructTypeFromLayout(
const std::shared_ptr<arrow::DataType>& logical_type, const ::ArrowArray* batch);
/// Returns a copy of `batch` in which every top-level column that cannot be preserved for the
/// destination has been decoded to the type its field carries in `logical_type`. A layout-
/// recoverable column may stay dictionary-encoded, so one column that has to be decoded does
/// not cost the others their encoding, and a batch that needs no decoding is returned
/// unchanged.
///
/// The counterpart of the restriction above: an encoding the destination cannot take has to be
/// decoded while the type is still known.
///
/// @param batch The batch to decode, matched to `logical_type` by field name; a column with no
/// matching field is left alone.
/// @param logical_type The struct type the decoded columns are cast to. `batch` is returned
/// unchanged when it is not a struct.
/// @param pool Allocates the decoded columns. Only used when a column is actually decoded.
/// @param preserve_layout_recoverable_dictionaries Whether dictionaries recoverable through
/// ResolveDictionaryStructTypeFromLayout() may remain encoded. Pass false
/// to decode every dictionary not already declared by `logical_type`,
/// whatever its shape.
/// @return `batch` itself when nothing had to be decoded, otherwise a copy of it with the
/// offset, length and validity of the original and the decoded columns swapped in.
static Result<std::shared_ptr<arrow::StructArray>> FlattenUnresolvableDictionaries(
const std::shared_ptr<arrow::StructArray>& batch,
const std::shared_ptr<arrow::DataType>& logical_type, arrow::MemoryPool* pool,
bool preserve_layout_recoverable_dictionaries);
};
} // namespace paimon