Skip to content

Commit 915c757

Browse files
committed
support to read fixed size list array with nulls
1 parent d091957 commit 915c757

2 files changed

Lines changed: 133 additions & 11 deletions

File tree

cpp/src/parquet/arrow/arrow_reader_writer_test.cc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3412,6 +3412,75 @@ TEST(ArrowReadWrite, FixedSizeList) {
34123412
CheckSimpleRoundtrip(table, 2, props_store_schema);
34133413
}
34143414

3415+
TEST(ArrowReadWrite, FixedSizeListNull) {
3416+
using ::arrow::field;
3417+
using ::arrow::fixed_size_list;
3418+
3419+
auto type = fixed_size_list(::arrow::int16(), /*size=*/3);
3420+
3421+
const char* json = R"([
3422+
null,
3423+
[1, 2, 3],
3424+
null,
3425+
[4, 5, 6],
3426+
null])";
3427+
auto array = ::arrow::ArrayFromJSON(type, json);
3428+
auto table = ::arrow::Table::Make(::arrow::schema({field("root", type)}), {array});
3429+
auto props_store_schema = ArrowWriterProperties::Builder().store_schema()->build();
3430+
CheckSimpleRoundtrip(table, 2, props_store_schema);
3431+
}
3432+
3433+
TEST(ArrowReadWrite, FixedSizeListAllNull) {
3434+
using ::arrow::field;
3435+
using ::arrow::fixed_size_list;
3436+
3437+
auto type = fixed_size_list(::arrow::int16(), /*size=*/3);
3438+
3439+
const char* json = R"([
3440+
null,
3441+
null,
3442+
null])";
3443+
auto array = ::arrow::ArrayFromJSON(type, json);
3444+
auto table = ::arrow::Table::Make(::arrow::schema({field("root", type)}), {array});
3445+
auto props_store_schema = ArrowWriterProperties::Builder().store_schema()->build();
3446+
CheckSimpleRoundtrip(table, 2, props_store_schema);
3447+
}
3448+
3449+
TEST(ArrowReadWrite, NestedFixedSizeList) {
3450+
using ::arrow::field;
3451+
using ::arrow::fixed_size_list;
3452+
3453+
auto type = fixed_size_list(fixed_size_list(::arrow::int16(), 2), /*size=*/2);
3454+
3455+
const char* json = R"([
3456+
[[1, 2], [3, 4]],
3457+
null,
3458+
[[5, 6], null],
3459+
[null, [7, 8]]])";
3460+
auto array = ::arrow::ArrayFromJSON(type, json);
3461+
auto table = ::arrow::Table::Make(::arrow::schema({field("root", type)}), {array});
3462+
auto props_store_schema = ArrowWriterProperties::Builder().store_schema()->build();
3463+
CheckSimpleRoundtrip(table, 2, props_store_schema);
3464+
}
3465+
3466+
TEST(ArrowReadWrite, ListOfFixedSizeList) {
3467+
using ::arrow::field;
3468+
using ::arrow::fixed_size_list;
3469+
using ::arrow::list;
3470+
3471+
auto type = list(fixed_size_list(::arrow::int16(), 2));
3472+
3473+
const char* json = R"([
3474+
[[1, 2], null, [3, 4]],
3475+
null,
3476+
[null, [5, 6]],
3477+
[]])";
3478+
auto array = ::arrow::ArrayFromJSON(type, json);
3479+
auto table = ::arrow::Table::Make(::arrow::schema({field("root", type)}), {array});
3480+
auto props_store_schema = ArrowWriterProperties::Builder().store_schema()->build();
3481+
CheckSimpleRoundtrip(table, 2, props_store_schema);
3482+
}
3483+
34153484
TEST(ArrowReadWrite, ListOfStructOfList2) {
34163485
using ::arrow::field;
34173486
using ::arrow::list;

cpp/src/parquet/arrow/reader.cc

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919

2020
#include <algorithm>
2121
#include <cstring>
22+
#include <iterator>
2223
#include <memory>
23-
#include <random>
24+
#include <span>
2425
#include <unordered_set>
2526
#include <utility>
2627
#include <vector>
2728

28-
#include "arrow/array.h"
29+
#include "arrow/array.h" // IWYU pragma: keep
30+
#include "arrow/array/concatenate.h"
2931
#include "arrow/buffer.h"
3032
#include "arrow/extension_type.h"
3133
#include "arrow/io/memory.h"
@@ -35,6 +37,7 @@
3537
#include "arrow/type.h"
3638
#include "arrow/type_traits.h"
3739
#include "arrow/util/async_generator.h"
40+
#include "arrow/util/bit_run_reader.h"
3841
#include "arrow/util/bit_util.h"
3942
#include "arrow/util/future.h"
4043
#include "arrow/util/iterator.h"
@@ -45,13 +48,10 @@
4548
#include "arrow/util/type_traits.h"
4649

4750
#include "parquet/arrow/reader_internal.h"
48-
#include "parquet/bloom_filter.h"
49-
#include "parquet/bloom_filter_reader.h"
5051
#include "parquet/column_reader.h"
5152
#include "parquet/exception.h"
5253
#include "parquet/file_reader.h"
5354
#include "parquet/metadata.h"
54-
#include "parquet/page_index.h"
5555
#include "parquet/properties.h"
5656
#include "parquet/schema.h"
5757

@@ -725,13 +725,66 @@ class PARQUET_NO_EXPORT FixedSizeListReader : public ListReader<int32_t> {
725725
DCHECK_EQ(data->buffers.size(), 2);
726726
DCHECK_EQ(field()->type()->id(), ::arrow::Type::FIXED_SIZE_LIST);
727727
const auto& type = checked_cast<::arrow::FixedSizeListType&>(*field()->type());
728-
const int32_t* offsets = reinterpret_cast<const int32_t*>(data->buffers[1]->data());
729-
for (int x = 1; x <= data->length; x++) {
730-
int32_t size = offsets[x] - offsets[x - 1];
731-
if (size != type.list_size()) {
732-
return Status::Invalid("Expected all lists to be of size=", type.list_size(),
733-
" but index ", x, " had size=", size);
728+
const auto* offsets = reinterpret_cast<const int32_t*>(data->buffers[1]->data());
729+
const int32_t list_size = type.list_size();
730+
const uint8_t* valid_bits =
731+
data->buffers[0] != nullptr ? data->buffers[0]->data() : nullptr;
732+
auto validate_offsets = [&](int64_t start, int64_t length,
733+
int32_t expected_size) -> Status {
734+
std::span<const int32_t> run_offsets(offsets + start,
735+
static_cast<size_t>(length + 1));
736+
const auto first_invalid_offset = std::ranges::adjacent_find(
737+
run_offsets,
738+
[&](int32_t left, int32_t right) { return right - left != expected_size; });
739+
if (first_invalid_offset != run_offsets.end()) {
740+
const int64_t x =
741+
start + std::ranges::distance(run_offsets.begin(), first_invalid_offset);
742+
return Status::Invalid("Expected offset at index ", x + 1, " to be ",
743+
offsets[x] + expected_size, " but got ", offsets[x + 1]);
734744
}
745+
return Status::OK();
746+
};
747+
if (valid_bits != nullptr) {
748+
bool needs_padding = false;
749+
::arrow::ArrayVector child_arrays;
750+
751+
auto append_child_run = [&](int64_t start, int64_t length, bool valid) -> Status {
752+
const int64_t child_length = length * list_size;
753+
if (!valid) {
754+
ARROW_ASSIGN_OR_RAISE(
755+
auto null_array,
756+
::arrow::MakeArrayOfNull(type.value_type(), child_length, ctx_->pool));
757+
child_arrays.push_back(std::move(null_array));
758+
return Status::OK();
759+
}
760+
child_arrays.push_back(
761+
::arrow::MakeArray(data->child_data[0]->Slice(offsets[start], child_length)));
762+
return Status::OK();
763+
};
764+
765+
auto visit_run = [&](int64_t start, int64_t length, bool valid) -> Status {
766+
RETURN_NOT_OK(validate_offsets(start, length, valid ? list_size : 0));
767+
if (valid && !needs_padding) {
768+
return Status::OK();
769+
}
770+
if (!needs_padding) {
771+
needs_padding = true;
772+
if (start > 0) {
773+
RETURN_NOT_OK(append_child_run(/*start=*/0, start, /*valid=*/true));
774+
}
775+
}
776+
return append_child_run(start, length, valid);
777+
};
778+
779+
RETURN_NOT_OK(::arrow::internal::VisitBitRuns(valid_bits, data->offset,
780+
data->length, visit_run));
781+
if (needs_padding) {
782+
ARROW_ASSIGN_OR_RAISE(auto child_array_with_padding,
783+
::arrow::Concatenate(child_arrays, ctx_->pool));
784+
data->child_data[0] = child_array_with_padding->data();
785+
}
786+
} else {
787+
RETURN_NOT_OK(validate_offsets(/*start=*/0, data->length, list_size));
735788
}
736789
data->buffers.resize(1);
737790
std::shared_ptr<Array> result = ::arrow::MakeArray(data);

0 commit comments

Comments
 (0)