@@ -448,6 +448,91 @@ TEST_F(FieldMappingReaderTest, TestDictionaryTypeWithSchemaEvolution) {
448448 partition, expected_array);
449449}
450450
451+ TEST_F (FieldMappingReaderTest, TestSchemaEvolutionAddedFieldInsideList) {
452+ // A field `c`(id=12) was added inside the list's struct after the file was
453+ // written. Reading the old file with the new schema must null-fill `c`.
454+ auto id_field = [](const std::string& name, const std::shared_ptr<arrow::DataType>& type,
455+ int32_t id) {
456+ return DataField::ConvertDataFieldToArrowField (DataField (id, arrow::field (name, type)));
457+ };
458+ auto data_struct =
459+ arrow::struct_ ({id_field (" a" , arrow::int32 (), 10 ), id_field (" b" , arrow::utf8 (), 11 )});
460+ auto read_struct =
461+ arrow::struct_ ({id_field (" a" , arrow::int32 (), 10 ), id_field (" b" , arrow::utf8 (), 11 ),
462+ id_field (" c" , arrow::int32 (), 12 )});
463+ std::vector<DataField> data_fields = {
464+ DataField (100 , arrow::field (" items" , arrow::list (arrow::field (" item" , data_struct))))};
465+ std::vector<DataField> read_fields = {
466+ DataField (100 , arrow::field (" items" , arrow::list (arrow::field (" item" , read_struct))))};
467+ auto data_schema = DataField::ConvertDataFieldsToArrowSchema (data_fields);
468+ auto read_schema = DataField::ConvertDataFieldsToArrowSchema (read_fields);
469+
470+ auto data_array = std::dynamic_pointer_cast<arrow::StructArray>(
471+ arrow::ipc::internal::json::ArrayFromJSON (arrow::struct_ (data_schema->fields ()), R"( [
472+ [[[1, "x"], [2, "y"]]],
473+ [[[3, "z"]]]
474+ ])" )
475+ .ValueOrDie ());
476+
477+ ASSERT_OK_AND_ASSIGN (auto mapping_builder,
478+ FieldMappingBuilder::Create (read_schema, /* partition_keys=*/ {},
479+ /* predicate=*/ nullptr ));
480+ ASSERT_OK_AND_ASSIGN (auto mapping, mapping_builder->CreateFieldMapping (data_fields));
481+ auto mock = std::make_unique<MockFileBatchReader>(
482+ data_array, arrow::struct_ (data_schema->fields ()), /* read_batch_size=*/ 8 );
483+ ASSERT_OK_AND_ASSIGN (auto reader, FieldMappingReader::Create (
484+ read_schema->num_fields (), std::move (mock),
485+ BinaryRow::EmptyRow (), std::move (mapping),
486+ /* skip_map_selected_keys_filter_field_ids=*/ {}, pool_));
487+ ASSERT_OK_AND_ASSIGN (auto result_array, ReadResultCollector::CollectResult (reader.get ()));
488+
489+ auto expect_array =
490+ arrow::ipc::internal::json::ArrayFromJSON (arrow::struct_ (read_schema->fields ()), R"( [
491+ [[[1, "x", null], [2, "y", null]]],
492+ [[[3, "z", null]]]
493+ ])" )
494+ .ValueOrDie ();
495+ auto expected_chunk = std::make_shared<arrow::ChunkedArray>(arrow::ArrayVector ({expect_array}));
496+ ASSERT_TRUE (result_array->type ()->Equals (expected_chunk->type ()))
497+ << result_array->type ()->ToString () << " vs " << expected_chunk->type ()->ToString ();
498+ ASSERT_TRUE (result_array->Equals (expected_chunk))
499+ << result_array->ToString () << " vs " << expected_chunk->ToString ();
500+ }
501+
502+ TEST_F (FieldMappingReaderTest, TestSchemaEvolutionAddedFieldInsideListOrc) {
503+ // ORC round-trip: added field inside a list's struct is null-filled.
504+ auto id_field = [](const std::string& name, const std::shared_ptr<arrow::DataType>& type,
505+ int32_t id) {
506+ return DataField::ConvertDataFieldToArrowField (DataField (id, arrow::field (name, type)));
507+ };
508+ auto data_struct =
509+ arrow::struct_ ({id_field (" a" , arrow::int32 (), 10 ), id_field (" b" , arrow::int32 (), 11 )});
510+ auto read_struct =
511+ arrow::struct_ ({id_field (" a" , arrow::int32 (), 10 ), id_field (" b" , arrow::int32 (), 11 ),
512+ id_field (" c" , arrow::int32 (), 12 )});
513+ std::vector<DataField> data_fields = {
514+ DataField (100 , arrow::field (" items" , arrow::list (arrow::field (" item" , data_struct))))};
515+ std::vector<DataField> read_fields = {
516+ DataField (100 , arrow::field (" items" , arrow::list (arrow::field (" item" , read_struct))))};
517+ auto data_schema = DataField::ConvertDataFieldsToArrowSchema (data_fields);
518+ auto read_schema = DataField::ConvertDataFieldsToArrowSchema (read_fields);
519+
520+ auto data_array = std::dynamic_pointer_cast<arrow::StructArray>(
521+ arrow::ipc::internal::json::ArrayFromJSON (arrow::struct_ (data_schema->fields ()), R"( [
522+ [[[1, 2], [3, 4]]],
523+ [[[5, 6]]]
524+ ])" )
525+ .ValueOrDie ());
526+ auto expect_array =
527+ arrow::ipc::internal::json::ArrayFromJSON (arrow::struct_ (read_schema->fields ()), R"( [
528+ [[[1, 2, null], [3, 4, null]]],
529+ [[[5, 6, null]]]
530+ ])" )
531+ .ValueOrDie ();
532+ CheckResult (data_schema, data_array, read_schema, /* predicate=*/ nullptr , /* partition_keys=*/ {},
533+ BinaryRow::EmptyRow (), expect_array);
534+ }
535+
451536TEST_F (FieldMappingReaderTest, TestSchemaEvolutionWithModifyType) {
452537 std::vector<DataField> data_fields = {DataField (0 , arrow::field (" f0" , arrow::utf8 ())),
453538 DataField (1 , arrow::field (" f1" , arrow::float32 ())),
0 commit comments