Skip to content
276 changes: 172 additions & 104 deletions cpp/src/parquet/decoder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1782,9 +1782,30 @@ class DeltaLengthByteArrayDecoder : public TypedDecoderImpl<ByteArrayType> {
int Decode(ByteArray* buffer, int max_values) override {
// Decode up to `max_values` strings into an internal buffer
// and reference them into `buffer`.
const int32_t* lengths = nullptr;
const uint8_t* data_ptr = nullptr;
max_values = NextBatch(max_values, &lengths, &data_ptr);
for (int i = 0; i < max_values; ++i) {
buffer[i].len = static_cast<uint32_t>(lengths[i]);
buffer[i].ptr = data_ptr;
data_ptr += buffer[i].len;
}
return max_values;
}

// Advance over up to `max_values` values and expose them as a view: the
// per-value lengths, and a pointer to the start of their (contiguous) data.
// Returns the number of values available.
//
// The lengths are already fully decoded by `DecodeLengths`, and the values are
// stored contiguously in the page, so callers that only need the bytes can walk
// this view directly instead of materializing a `ByteArray` per value.
int NextBatch(int max_values, const int32_t** lengths, const uint8_t** data) {
max_values = std::min(max_values, num_valid_values_);
DCHECK_GE(max_values, 0);
if (max_values == 0) {
*lengths = nullptr;
*data = nullptr;
return 0;
}

Expand All @@ -1796,7 +1817,6 @@ class DeltaLengthByteArrayDecoder : public TypedDecoderImpl<ByteArrayType> {
if (ARROW_PREDICT_FALSE(len < 0)) {
throw ParquetException("negative string delta length");
}
buffer[i].len = len;
if (AddWithOverflow(data_size, len, &data_size)) {
throw ParquetException("excess expansion in DELTA_(LENGTH_)BYTE_ARRAY");
}
Expand All @@ -1805,11 +1825,8 @@ class DeltaLengthByteArrayDecoder : public TypedDecoderImpl<ByteArrayType> {
if (ARROW_PREDICT_FALSE(!decoder_->Advance(8 * static_cast<int64_t>(data_size)))) {
ParquetException::EofException();
}
const uint8_t* data_ptr = data_ + bytes_offset;
for (int i = 0; i < max_values; ++i) {
buffer[i].ptr = data_ptr;
data_ptr += buffer[i].len;
}
*lengths = length_ptr;
*data = data_ + bytes_offset;
this->num_values_ -= max_values;
num_valid_values_ -= max_values;
return max_values;
Expand Down Expand Up @@ -2082,93 +2099,154 @@ class DeltaByteArrayDecoderImpl : public TypedDecoderImpl<DType> {
}

protected:
template <bool is_first_run>
static void BuildBufferInternal(const int32_t* prefix_len_ptr, int i, ByteArray* buffer,
std::string_view* prefix, uint8_t** data_ptr) {
if (ARROW_PREDICT_FALSE(static_cast<size_t>(prefix_len_ptr[i]) > prefix->length())) {
throw ParquetException("prefix length too large in DELTA_BYTE_ARRAY");
}
// For now, `buffer` points to string suffixes, and the suffix decoder
// ensures that the suffix data has sufficient lifetime.
if (prefix_len_ptr[i] == 0) {
// prefix is empty: buffer[i] already points to the suffix.
*prefix = std::string_view{buffer[i]};
return;
struct ByteArrayOutput {
// `buffer` is used both as the suffix decode target and as the output.
ByteArray* buffer;
ResizableBuffer* buffered_data;
int type_length;
uint8_t* data_ptr = nullptr;
const uint8_t* data_end = nullptr;

int FetchSuffixes(DeltaLengthByteArrayDecoder* suffix_decoder, int max_values) {
// Decode the suffixes in place; `Emit` reads each one before overwriting it
// with the reconstructed value.
return suffix_decoder->Decode(buffer, max_values);
}

void Prepare(const int32_t* prefix_len_ptr, int max_values) {
int64_t data_size = 0;
for (int i = 0; i < max_values; ++i) {
if (prefix_len_ptr[i] == 0) {
// We don't need to copy the suffix if the prefix length is 0.
continue;
}
if (ARROW_PREDICT_FALSE(prefix_len_ptr[i] < 0)) {
throw ParquetException("negative prefix length in DELTA_BYTE_ARRAY");
}
if (buffer[i].len == 0 && i != 0) {
// We don't need to copy the prefix if the suffix length is 0
// and this is not the first run (that is, the prefix doesn't point
// to the mutable `last_value_`).
continue;
}
if (ARROW_PREDICT_FALSE(
AddWithOverflow(data_size, prefix_len_ptr[i], &data_size) ||
AddWithOverflow(data_size, buffer[i].len, &data_size))) {
throw ParquetException("excess expansion in DELTA_BYTE_ARRAY");
}
}
PARQUET_THROW_NOT_OK(buffered_data->Resize(data_size));
data_ptr = buffered_data->mutable_data();
data_end = data_ptr + data_size;
}
DCHECK_EQ(is_first_run, i == 0);
if constexpr (!is_first_run) {
if (buffer[i].len == 0) {

std::string_view Emit(int i, int32_t prefix_len, std::string_view prefix) {
// Read the suffix before `buffer[i]` is overwritten with the output.
const ByteArray suffix = buffer[i];
if (ARROW_PREDICT_FALSE(static_cast<size_t>(prefix_len) > prefix.length())) {
throw ParquetException("prefix length too large in DELTA_BYTE_ARRAY");
}
if constexpr (std::is_same_v<DType, FLBAType>) {
// The value is `prefix_len` bytes of the previous value followed by the
// suffix, whichever branch below produces it.
if (ARROW_PREDICT_FALSE(static_cast<int64_t>(prefix_len) + suffix.len !=
type_length)) {
throw ParquetException("FLBA type requires fixed-length ", type_length,
" but got ", prefix_len + suffix.len);
}
}
if (prefix_len == 0) {
// prefix is empty: buffer[i] already points to the suffix.
return std::string_view{buffer[i]};
}
if (i != 0 && suffix.len == 0) {
// suffix is empty: buffer[i] can simply point to the prefix.
// This is not possible for the first run since the prefix
// would point to the mutable `last_value_`.
*prefix = prefix->substr(0, prefix_len_ptr[i]);
buffer[i] = ByteArray(*prefix);
return;
prefix = prefix.substr(0, prefix_len);
buffer[i] = ByteArray(prefix);
return prefix;
}
// Both prefix and suffix are non-empty, so decode the string into
// `data_ptr`.
const uint32_t full_len = suffix.len + static_cast<uint32_t>(prefix_len);
DCHECK_LE(data_ptr + full_len, data_end);
memcpy(data_ptr, prefix.data(), prefix_len);
memcpy(data_ptr + prefix_len, suffix.ptr, suffix.len);
buffer[i].ptr = data_ptr;
buffer[i].len = full_len;
data_ptr += full_len;
return std::string_view{buffer[i]};
}
// Both prefix and suffix are non-empty, so we need to decode the string
// into `data_ptr`.
// 1. Copy the prefix
memcpy(*data_ptr, prefix->data(), prefix_len_ptr[i]);
// 2. Copy the suffix.
memcpy(*data_ptr + prefix_len_ptr[i], buffer[i].ptr, buffer[i].len);
// 3. Point buffer[i] to the decoded string.
buffer[i].ptr = *data_ptr;
buffer[i].len += prefix_len_ptr[i];
*data_ptr += buffer[i].len;
*prefix = std::string_view{buffer[i]};
}
};

int GetInternal(ByteArray* buffer, int max_values) {
// Decode up to `max_values` strings into an internal buffer
// and reference them into `buffer`.
// Output that writes fixed-length values contiguously into a caller-provided
// buffer. Used by the FLBA dense decode path; avoids `buffered_data_` and any
// temporary per-value `ByteArray` storage.
struct DenseOutput {
uint8_t* out;
int type_length;
// View of the suffixes still to be consumed: their lengths, and a cursor into
// their contiguous data. No per-value pointer is ever materialized.
const int32_t* suffix_lengths = nullptr;
const uint8_t* suffix_data = nullptr;

int FetchSuffixes(DeltaLengthByteArrayDecoder* suffix_decoder, int max_values) {
return suffix_decoder->NextBatch(max_values, &suffix_lengths, &suffix_data);
}

void Prepare(const int32_t* prefix_len_ptr, int max_values) {}

std::string_view Emit(int i, int32_t prefix_len, std::string_view prefix) {
const int32_t suffix_len = suffix_lengths[i];
const uint8_t* suffix_ptr = suffix_data;
suffix_data += suffix_len;

if (ARROW_PREDICT_FALSE(static_cast<size_t>(prefix_len) > prefix.length())) {
throw ParquetException("prefix length too large in DELTA_BYTE_ARRAY");
}
// Each reconstructed FLBA value must be exactly `type_length` bytes.
if (ARROW_PREDICT_FALSE(static_cast<int64_t>(prefix_len) + suffix_len !=
type_length)) {
throw ParquetException("FLBA type requires fixed-length ", type_length,
" but got ", prefix_len + suffix_len);
}
// Copy the prefix and the suffix straight into the caller's buffer.
uint8_t* dst = out + static_cast<int64_t>(i) * type_length;
memcpy(dst, prefix.data(), prefix_len);
memcpy(dst + prefix_len, suffix_ptr, suffix_len);
// The next value's prefix references this value, which now lives in the
// caller's buffer.
return std::string_view{reinterpret_cast<const char*>(dst),
static_cast<size_t>(type_length)};
}
};

template <typename Output>
int GetInternalImpl(Output&& output, int max_values) {
// Decode up to `max_values` values, delegating materialization to `output`.
max_values = std::min(max_values, num_valid_values_);
if (max_values == 0) {
return max_values;
}

int suffix_read = suffix_decoder_.Decode(buffer, max_values);
const int suffix_read = output.FetchSuffixes(&suffix_decoder_, max_values);
if (ARROW_PREDICT_FALSE(suffix_read != max_values)) {
ParquetException::EofException("Read " + std::to_string(suffix_read) +
", expecting " + std::to_string(max_values) +
" from suffix decoder");
}

int64_t data_size = 0;
const int32_t* prefix_len_ptr =
buffered_prefix_length_->data_as<int32_t>() + prefix_len_offset_;
for (int i = 0; i < max_values; ++i) {
if (prefix_len_ptr[i] == 0) {
// We don't need to copy the suffix if the prefix length is 0.
continue;
}
if (ARROW_PREDICT_FALSE(prefix_len_ptr[i] < 0)) {
throw ParquetException("negative prefix length in DELTA_BYTE_ARRAY");
}
if (buffer[i].len == 0 && i != 0) {
// We don't need to copy the prefix if the suffix length is 0
// and this is not the first run (that is, the prefix doesn't point
// to the mutable `last_value_`).
continue;
}
if (ARROW_PREDICT_FALSE(AddWithOverflow(data_size, prefix_len_ptr[i], &data_size) ||
AddWithOverflow(data_size, buffer[i].len, &data_size))) {
throw ParquetException("excess expansion in DELTA_BYTE_ARRAY");
}
}
PARQUET_THROW_NOT_OK(buffered_data_->Resize(data_size));

output.Prepare(prefix_len_ptr, max_values);

std::string_view prefix{last_value_};
uint8_t* data_ptr = buffered_data_->mutable_data();
if (max_values > 0) {
BuildBufferInternal</*is_first_run=*/true>(prefix_len_ptr, 0, buffer, &prefix,
&data_ptr);
}
for (int i = 1; i < max_values; ++i) {
BuildBufferInternal</*is_first_run=*/false>(prefix_len_ptr, i, buffer, &prefix,
&data_ptr);
for (int i = 0; i < max_values; ++i) {
prefix = output.Emit(i, prefix_len_ptr[i], prefix);
}
DCHECK_EQ(data_ptr - buffered_data_->mutable_data(), data_size);

prefix_len_offset_ += max_values;
this->num_values_ -= max_values;
num_valid_values_ -= max_values;
Expand All @@ -2178,19 +2256,31 @@ class DeltaByteArrayDecoderImpl : public TypedDecoderImpl<DType> {
last_value_in_previous_page_ = last_value_;
}

if constexpr (std::is_same_v<DType, FLBAType>) {
// Checks all values
for (int i = 0; i < max_values; i++) {
if (buffer[i].len != static_cast<uint32_t>(this->type_length_)) {
throw ParquetException("FLBA type requires fixed-length ", this->type_length_,
" but got ", buffer[i].len);
}
}
}

return max_values;
}

int GetInternal(ByteArray* buffer, int max_values) {
ByteArrayOutput output{buffer, buffered_data_.get(), this->type_length_};
return GetInternalImpl(output, max_values);
}

int DecodeDense(uint8_t* out, int max_values) {
DenseOutput output{out, this->type_length_};
return GetInternalImpl(output, max_values);
}

int DecodePointers(FixedLenByteArray* buffer, int max_values) {
PARQUET_THROW_NOT_OK(
buffered_data_->Resize(static_cast<int64_t>(max_values) * this->type_length_,
/*shrink_to_fit=*/false));
uint8_t* values = buffered_data_->mutable_data();
const int decoded_values_size = DecodeDense(values, max_values);
for (int i = 0; i < decoded_values_size; ++i) {
buffer[i].ptr = values + static_cast<int64_t>(i) * this->type_length_;
}
return decoded_values_size;
}

Status DecodeArrowDense(int num_values, int null_count, const uint8_t* valid_bits,
int64_t valid_bits_offset,
typename EncodingTraits<DType>::Accumulator* out,
Expand Down Expand Up @@ -2268,35 +2358,13 @@ class DeltaByteArrayFLBADecoder : public DeltaByteArrayDecoderImpl<FLBAType>,
using Base::pool_;

int Decode(FixedLenByteArray* buffer, int max_values) override {
// GetInternal currently only support ByteArray.
std::vector<ByteArray> decode_byte_array(max_values);
const int decoded_values_size = GetInternal(decode_byte_array.data(), max_values);
const uint32_t type_length = static_cast<uint32_t>(this->type_length_);

for (int i = 0; i < decoded_values_size; i++) {
if (ARROW_PREDICT_FALSE(decode_byte_array[i].len != type_length)) {
throw ParquetException("Fixed length byte array length mismatch");
}
buffer[i].ptr = decode_byte_array[i].ptr;
}
return decoded_values_size;
return this->DecodePointers(buffer, max_values);
}

// Same internal decode as above, but copy the bytes contiguously into the
// caller's buffer instead of materializing per-value pointers.
int Decode(uint8_t* buffer, int max_values) override {
std::vector<ByteArray> decode_byte_array(max_values);
const int decoded_values_size = GetInternal(decode_byte_array.data(), max_values);
const uint32_t type_length = static_cast<uint32_t>(this->type_length_);

for (int i = 0; i < decoded_values_size; i++) {
if (ARROW_PREDICT_FALSE(decode_byte_array[i].len != type_length)) {
throw ParquetException("Fixed length byte array length mismatch");
}
memcpy(buffer + static_cast<int64_t>(i) * type_length, decode_byte_array[i].ptr,
type_length);
}
return decoded_values_size;
return this->DecodeDense(buffer, max_values);
}
};

Expand Down
Loading
Loading