Skip to content

Commit c3e4cad

Browse files
committed
[ntuple] automatic schema evolution from streamer to class field
1 parent ab96a8d commit c3e4cad

File tree

5 files changed

+55
-0
lines changed

5 files changed

+55
-0
lines changed

tree/ntuple/src/RFieldMeta.cxx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,14 @@ std::unique_ptr<ROOT::RFieldBase> ROOT::RClassField::BeforeConnectPageSource(ROO
457457
const ROOT::RNTupleDescriptor &desc = descriptorGuard.GetRef();
458458
const auto &fieldDesc = desc.GetFieldDescriptor(GetOnDiskId());
459459

460+
if (fieldDesc.GetStructure() == ENTupleStructure::kStreamer) {
461+
// Streamer field on disk but meanwhile the type can be represented as a class field; replace this field
462+
// by a streamer field to read the data from disk.
463+
auto substitute = std::make_unique<RStreamerField>(GetFieldName(), GetTypeName());
464+
substitute->SetOnDiskId(GetOnDiskId());
465+
return substitute;
466+
}
467+
460468
for (auto linkId : fieldDesc.GetLinkIds()) {
461469
const auto &subFieldDesc = desc.GetFieldDescriptor(linkId);
462470
regularSubfields.insert(subFieldDesc.GetFieldName());

tree/ntuple/test/StreamerField.cxx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,14 @@ void CustomStreamerForceNative::Streamer(TBuffer &R__b)
2525
R__b.WriteClassBuffer(CustomStreamerForceNative::Class(), this);
2626
}
2727
}
28+
29+
void CustomStreamerEvolution::Streamer(TBuffer &R__b)
30+
{
31+
if (R__b.IsReading()) {
32+
UInt_t R__s, R__c;
33+
Version_t R__v = R__b.ReadVersion(&R__s, &R__c);
34+
R__b.ReadClassBuffer(CustomStreamerEvolution::Class(), this, R__v, R__s, R__c);
35+
} else {
36+
R__b.WriteClassBuffer(CustomStreamerEvolution::Class(), this);
37+
}
38+
}

tree/ntuple/test/StreamerField.hxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ struct CustomStreamerForceStreamed {
5454
float a;
5555
};
5656

57+
struct CustomStreamerEvolution {
58+
int x;
59+
ClassDefNV(CustomStreamerEvolution, 1);
60+
};
61+
5762
// For the time being, RNTuple ignores the unsplit comment marker and does _not_ use an RStreamerField for such members.
5863
class IgnoreUnsplitComment {
5964
std::vector<float> v; //||

tree/ntuple/test/StreamerFieldLinkDef.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#pragma link C++ struct CyclicMember;
1111
#pragma link C++ class ClassWithStreamedMember + ;
1212
#pragma link C++ class CustomStreamer - ;
13+
#pragma link C++ class CustomStreamerEvolution - ;
1314
#pragma link C++ options = rntupleStreamerMode(false) class CustomStreamerForceNative - ;
1415
#pragma link C++ options = rntupleStreamerMode(true) class CustomStreamerForceStreamed + ;
1516
#pragma link C++ class IgnoreUnsplitComment + ;

tree/ntuple/test/rfield_streamer.cxx

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,36 @@ TEST(RField, StreamerSchemaEvolution)
350350
EXPECT_EQ(137, ptrF->fValue);
351351
}
352352

353+
TEST(RField, StreamerToClassFieldSupport)
354+
{
355+
FileRaii fileGuard("test_ntuple_rfield_streamer_to_class_field_support.root");
356+
357+
{
358+
auto model = RNTupleModel::Create();
359+
model->AddField(std::make_unique<ROOT::RStreamerField>("f", "CustomStreamerEvolution"));
360+
auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
361+
auto ptr = writer->GetModel().GetDefaultEntry().GetPtr<CustomStreamerEvolution>("f");
362+
ptr->x = 137;
363+
writer->Fill();
364+
}
365+
366+
auto cl = TClass::GetClass("CustomStreamerEvolution");
367+
ASSERT_TRUE(cl != nullptr);
368+
cl->CreateAttributeMap();
369+
cl->GetAttributeMap()->AddProperty("rntuple.streamerMode", "false");
370+
371+
const auto inMemoryField = RFieldBase::Create("f", "CustomStreamer").Unwrap();
372+
const auto inMemoryFieldPtr = inMemoryField.get(); // silence clang
373+
EXPECT_EQ(typeid(*inMemoryFieldPtr), typeid(ROOT::RClassField));
374+
375+
auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath());
376+
auto v = reader->GetView<CustomStreamerEvolution>("f");
377+
const auto &onDiskField = v.GetField();
378+
EXPECT_EQ(typeid(onDiskField), typeid(ROOT::RStreamerField));
379+
380+
EXPECT_FLOAT_EQ(137, v(0).x);
381+
}
382+
353383
TEST(RField, StreamerClassMismatch)
354384
{
355385
FileRaii fileGuard("test_ntuple_rfield_streamer_class_mismatch.root");

0 commit comments

Comments
 (0)