-
Notifications
You must be signed in to change notification settings - Fork 1.3k
[ntuple] fix schema evolution with streamer fields #18451
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a76ae80
14b96e9
4d4bf55
6cf0ca0
29ba77a
04b27d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -612,6 +612,7 @@ private: | |||||||||
REntryRange fEntryRange; ///< Used by the cluster pool to prevent reading beyond the given range | ||||||||||
bool fHasStructure = false; ///< Set to true once `LoadStructure()` is called | ||||||||||
bool fIsAttached = false; ///< Set to true once `Attach()` is called | ||||||||||
bool fHasStreamerInfosRegistered = false; ///< Set to true when ReisterStreamerInfos() is called. | ||||||||||
|
||||||||||
/// Remembers the last cluster id from which a page was requested | ||||||||||
ROOT::DescriptorId_t fLastUsedCluster = ROOT::kInvalidDescriptorId; | ||||||||||
|
@@ -818,6 +819,10 @@ public: | |||||||||
// TODO(gparolini): for symmetry with SealPage(), we should either make this private or SealPage() public. | ||||||||||
RResult<ROOT::Internal::RPage> | ||||||||||
UnsealPage(const RSealedPage &sealedPage, const ROOT::Internal::RColumnElementBase &element); | ||||||||||
|
||||||||||
// Builds the streamer info records from the descriptor's extra type info section. This is necessary when | ||||||||||
// connecting streamer fields so that emulated classes can be read. | ||||||||||
Comment on lines
+823
to
+824
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
void RegisterStreamerInfos(); | ||||||||||
}; // class RPageSource | ||||||||||
|
||||||||||
} // namespace Internal | ||||||||||
|
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -863,3 +863,61 @@ struct RenamedIntermediateDerived : public RenamedIntermediate2 { | |||
EXPECT_THAT(err.what(), testing::HasSubstr("incompatible type name for field")); | ||||
} | ||||
} | ||||
|
||||
TEST(RNTupleEvolution, StreamerField) | ||||
{ | ||||
FileRaii fileGuard("test_ntuple_evolution_streamer_field.root"); | ||||
fileGuard.PreserveFile(); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be needed, probably a left-over from debugging |
||||
|
||||
ExecInFork([&] { | ||||
// The child process writes the file and exits, but the file must be preserved to be read by the parent. | ||||
fileGuard.PreserveFile(); | ||||
|
||||
ASSERT_TRUE(gInterpreter->Declare(R"( | ||||
struct StreamerField { | ||||
int fInt = 1; | ||||
int fAnotherInt = 3; | ||||
|
||||
ClassDefNV(StreamerField, 2) | ||||
}; | ||||
)")); | ||||
|
||||
auto model = RNTupleModel::Create(); | ||||
model->AddField(std::make_unique<ROOT::RStreamerField>("f", "StreamerField")); | ||||
|
||||
auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); | ||||
writer->Fill(); | ||||
|
||||
void *ptr = writer->GetModel().GetDefaultEntry().GetPtr<void>("f").get(); | ||||
DeclarePointer("StreamerField", "ptrStreamerField", ptr); | ||||
ProcessLine("ptrStreamerField->fInt = 2;"); | ||||
writer->Fill(); | ||||
|
||||
// Reset / close the writer and flush the file. | ||||
writer.reset(); | ||||
}); | ||||
|
||||
ASSERT_TRUE(gInterpreter->Declare(R"( | ||||
struct StreamerField { | ||||
int fInt = 0; | ||||
int fAdded = 137; | ||||
Comment on lines
+902
to
+903
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To test the indirect collection of StreamerInfo, there should be here a pointer to another class (possibly initialized with an instance of a derived class). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we already test that with root/tree/ntuple/test/rfield_streamer.cxx Line 126 in 031f6bd
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically the test linked will succeed whether or not the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i.e. like here to test the feature, one need 2 separate processes and 2 distinct schemas/versions. |
||||
// removed fAnotherInt | ||||
|
||||
ClassDefNV(StreamerField, 3) | ||||
}; | ||||
)")); | ||||
|
||||
auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); | ||||
ASSERT_EQ(2, reader->GetNEntries()); | ||||
|
||||
void *ptr = reader->GetModel().GetDefaultEntry().GetPtr<void>("f").get(); | ||||
DeclarePointer("StreamerField", "ptrStreamerField", ptr); | ||||
|
||||
reader->LoadEntry(0); | ||||
EXPECT_EVALUATE_EQ("ptrStreamerField->fInt", 1); | ||||
EXPECT_EVALUATE_EQ("ptrStreamerField->fAdded", 137); | ||||
|
||||
reader->LoadEntry(1); | ||||
EXPECT_EVALUATE_EQ("ptrStreamerField->fInt", 2); | ||||
EXPECT_EVALUATE_EQ("ptrStreamerField->fAdded", 137); | ||||
} |
Original file line number | Diff line number | Diff line change | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -305,3 +305,53 @@ TEST(RField, StreamerMergeIncremental) | ||||||||||||||||
EXPECT_TRUE(seenStreamerInfos[2]); | |||||||||||||||||
EXPECT_TRUE(seenStreamerInfos[3]); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
TEST(RField, StreamerSchemaEvolution) | |||||||||||||||||
{ | |||||||||||||||||
FileRaii fileGuard("test_ntuple_rfield_streamer_schema_evolution.root"); | |||||||||||||||||
{ | |||||||||||||||||
auto model = RNTupleModel::Create(); | |||||||||||||||||
model->AddField(RFieldBase::Create("f", "OldStreamerName<int>").Unwrap()); | |||||||||||||||||
auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); | |||||||||||||||||
auto ptrF = writer->GetModel().GetDefaultEntry().GetPtr<OldStreamerName<int>>("f"); | |||||||||||||||||
ptrF->fValue = 137; | |||||||||||||||||
writer->Fill(); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
auto model = RNTupleModel::Create(); | |||||||||||||||||
model->AddField(RFieldBase::Create("f", "NewStreamerName<int>").Unwrap()); | |||||||||||||||||
auto reader = RNTupleReader::Open(std::move(model), "ntpl", fileGuard.GetPath()); | |||||||||||||||||
|
|||||||||||||||||
ASSERT_EQ(1U, reader->GetNEntries()); | |||||||||||||||||
auto ptrF = reader->GetModel().GetDefaultEntry().GetPtr<NewStreamerName<int>>("f"); | |||||||||||||||||
reader->LoadEntry(0); | |||||||||||||||||
EXPECT_EQ(137, ptrF->fValue); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
TEST(RField, StreamerClassMismatch) | |||||||||||||||||
{ | |||||||||||||||||
FileRaii fileGuard("test_ntuple_rfield_streamer_class_mismatch.root"); | |||||||||||||||||
{ | |||||||||||||||||
auto model = RNTupleModel::Create(); | |||||||||||||||||
model->AddField(RFieldBase::Create("f", "TemperatureCelsius").Unwrap()); | |||||||||||||||||
auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); | |||||||||||||||||
auto ptrF = writer->GetModel().GetDefaultEntry().GetPtr<TemperatureCelsius>("f"); | |||||||||||||||||
ptrF->fValue = 100.; | |||||||||||||||||
writer->Fill(); | |||||||||||||||||
} | |||||||||||||||||
|
|||||||||||||||||
auto model = RNTupleModel::Create(); | |||||||||||||||||
model->AddField(RFieldBase::Create("f", "TemperatureKelvin").Unwrap()); | |||||||||||||||||
auto reader = RNTupleReader::Open(std::move(model), "ntpl", fileGuard.GetPath()); | |||||||||||||||||
|
|||||||||||||||||
ASSERT_EQ(1U, reader->GetNEntries()); | |||||||||||||||||
auto ptrF = reader->GetModel().GetDefaultEntry().GetPtr<TemperatureKelvin>("f"); | |||||||||||||||||
|
|||||||||||||||||
// TODO(jblomer): this should fail with an exception when we connect the page source | |||||||||||||||||
ROOT::TestSupport::CheckDiagsRAII diagRAII; | |||||||||||||||||
diagRAII.requiredDiag(kError, "TBufferFile::ReadVersion", "Could not find the StreamerInfo with a checksum of", | |||||||||||||||||
false /* matchFullMessage */); | |||||||||||||||||
diagRAII.requiredDiag(kError, "TBufferFile::CheckByteCount", "object of class TemperatureKelvin read too few bytes", | |||||||||||||||||
false /* matchFullMessage */); | |||||||||||||||||
Comment on lines
+352
to
+355
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand those error message. What is the case being tested here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I write a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Humm ... why isn't the error message closer to "Can not convert (or read) a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a proper exception is part of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am probably mis-reading the actual test but ...
This message should appear only if there is something wrong with the file and the StreamerInfo record has not been properly stored. Seeing this indicates either a file corruption or a serious deficiency in ROOT (or maybe the user's code)
This message should only appear if there is an unexpected impedance mismatch between the StreamerInfo and the data onfile. Seeing this indicates either a file corruption or a serious deficiency in ROOT (or maybe the user's code). As far as my examples is concern, let me try to clarify it. With:
i.e. 3 distinct classes with the exact same layout one with a slight difference (And because they have a different name, 4 different CheckSums).
you have the following consequences:
In the not allowed cases, we should get an error message akin to Note (1), since the user specified a checksum, if the file also contains instance of A with a different schema (eg. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
humm ... Maybe the |
|||||||||||||||||
reader->LoadEntry(0); | |||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.