Skip to content

Commit b0ca7a5

Browse files
committed
test: add test cases for nested columns with offsets is zero while children type is non-zero
1 parent 8cd50be commit b0ca7a5

1 file changed

Lines changed: 82 additions & 0 deletions

File tree

src/paimon/common/utils/arrow/arrow_utils_test.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,26 @@ std::shared_ptr<arrow::RecordBatch> MakeSliceBatch(const std::shared_ptr<arrow::
535535
{array->Slice(offset, length)});
536536
}
537537

538+
/// Checks that normalization keeps the same rows with every offset zeroed. `array` is used as a
539+
/// single column batch, so its own offset is whatever the caller built it with.
540+
void CheckNormalizedArray(const std::shared_ptr<arrow::Array>& array) {
541+
SCOPED_TRACE("type=" + array->type()->ToString());
542+
std::shared_ptr<arrow::RecordBatch> batch = arrow::RecordBatch::Make(
543+
arrow::schema({arrow::field("f", array->type())}), array->length(), {array});
544+
545+
ASSERT_OK_AND_ASSIGN(
546+
std::shared_ptr<arrow::RecordBatch> normalized,
547+
ArrowUtils::NormalizeRecordBatchOffsets(batch, arrow::default_memory_pool()));
548+
// A batch that needs normalization must not be returned unchanged.
549+
ASSERT_NE(normalized.get(), batch.get());
550+
551+
arrow::Status validated = normalized->ValidateFull();
552+
ASSERT_TRUE(validated.ok()) << validated.ToString();
553+
ASSERT_TRUE(normalized->Equals(*batch))
554+
<< "expected " << batch->ToString() << " but got " << normalized->ToString();
555+
ExpectAllOffsetsZero(*normalized->column_data(0), "f");
556+
}
557+
538558
/// Slices `array` and checks that normalization keeps the same rows with every offset zeroed.
539559
void CheckNormalizedSlice(const std::shared_ptr<arrow::Array>& array, int64_t offset,
540560
int64_t length) {
@@ -599,6 +619,68 @@ TEST(ArrowUtilsTest, TestNormalizeRecordBatchOffsetsSharesValueBuffers) {
599619
}
600620
}
601621

622+
// Check the situation that child offsets are non-zero while parent offset is zero.
623+
TEST(ArrowUtilsTest, TestNormalizeRecordBatchOffsetsRebasesNestedOffsetsUnderZeroParent) {
624+
std::shared_ptr<arrow::Array> ints =
625+
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), "[0, 1, 2, 3, 4, 5, 6, 7]")
626+
.ValueOrDie();
627+
std::shared_ptr<arrow::Array> texts =
628+
arrow::ipc::internal::json::ArrayFromJSON(
629+
arrow::utf8(), R"(["a", "bb", null, "dddd", "e", "ff", "ggg", "h"])")
630+
.ValueOrDie();
631+
632+
{
633+
// struct whose children are sliced: parent offset 0, both children offset 2
634+
std::shared_ptr<arrow::Array> array =
635+
arrow::StructArray::Make({ints->Slice(2, 4), texts->Slice(2, 4)},
636+
std::vector<std::string>{"a", "b"})
637+
.ValueOrDie();
638+
ASSERT_EQ(array->offset(), 0);
639+
ASSERT_EQ(array->data()->child_data[0]->offset, 2);
640+
ASSERT_EQ(array->data()->child_data[1]->offset, 2);
641+
CheckNormalizedArray(array);
642+
}
643+
{
644+
// only the innermost array is sliced, so detection has to walk two levels down
645+
std::shared_ptr<arrow::Array> inner =
646+
arrow::StructArray::Make({ints->Slice(3, 4)}, std::vector<std::string>{"a"})
647+
.ValueOrDie();
648+
std::shared_ptr<arrow::Array> array =
649+
arrow::StructArray::Make({inner}, std::vector<std::string>{"inner"}).ValueOrDie();
650+
ASSERT_EQ(array->offset(), 0);
651+
ASSERT_EQ(array->data()->child_data[0]->offset, 0);
652+
ASSERT_EQ(array->data()->child_data[0]->child_data[0]->offset, 3);
653+
CheckNormalizedArray(array);
654+
}
655+
{
656+
// list built over sliced values: parent offset 0, values offset 2, and the list offsets
657+
// address the values relative to that slice
658+
std::shared_ptr<arrow::Array> offsets =
659+
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), "[0, 1, 1, 3, 4]")
660+
.ValueOrDie();
661+
std::shared_ptr<arrow::Array> array =
662+
arrow::ListArray::FromArrays(*offsets, *ints->Slice(2, 4)).ValueOrDie();
663+
ASSERT_EQ(array->offset(), 0);
664+
ASSERT_EQ(array->data()->child_data[0]->offset, 2);
665+
CheckNormalizedArray(array);
666+
}
667+
{
668+
// map built over sliced keys and items, which land under the entries struct. Map keys
669+
// cannot be null, so this slice avoids the null in `texts`.
670+
std::shared_ptr<arrow::Array> offsets =
671+
arrow::ipc::internal::json::ArrayFromJSON(arrow::int32(), "[0, 2, 2, 4]").ValueOrDie();
672+
std::shared_ptr<arrow::Array> array =
673+
arrow::MapArray::FromArrays(offsets, texts->Slice(3, 4), ints->Slice(4, 4))
674+
.ValueOrDie();
675+
ASSERT_EQ(array->offset(), 0);
676+
const arrow::ArrayData& entries = *array->data()->child_data[0];
677+
ASSERT_EQ(entries.offset, 0);
678+
ASSERT_EQ(entries.child_data[0]->offset, 3);
679+
ASSERT_EQ(entries.child_data[1]->offset, 4);
680+
CheckNormalizedArray(array);
681+
}
682+
}
683+
602684
TEST(ArrowUtilsTest, TestNormalizeRecordBatchOffsetsFallsBackForDictionary) {
603685
// A dictionary is not part of child_data, so this layout takes the copying fallback.
604686
std::shared_ptr<arrow::Array> indices =

0 commit comments

Comments
 (0)