Skip to content

Commit 0b91676

Browse files
committed
[ntuple] Prevent late model extension with Streamer Fields
Also add [[nodiscard]] to RResultBase::ForwardError to prevent erroneous use of R__FORWARD_ERROR
1 parent 44073ef commit 0b91676

File tree

5 files changed

+27
-7
lines changed

5 files changed

+27
-7
lines changed

core/foundation/inc/ROOT/RError.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public:
125125
void Throw();
126126

127127
/// Used by R__FORWARD_ERROR in order to keep track of the stack trace.
128+
[[nodiscard]]
128129
static RError ForwardError(RResultBase &&result, RError::RLocation &&sourceLocation)
129130
{
130131
if (!result.fError) {

tree/ntuple/inc/ROOT/RPageStorage.hxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,8 +320,8 @@ public:
320320
if (fIsInitialized) {
321321
throw RException(R__FAIL("already initialized"));
322322
}
323-
fIsInitialized = true;
324323
InitImpl(model);
324+
fIsInitialized = true;
325325
}
326326

327327
protected:

tree/ntuple/src/RNTupleMerger.cxx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,10 @@ CompareDescriptorStructure(const ROOT::RNTupleDescriptor &dst, const ROOT::RNTup
586586
}
587587

588588
// Applies late model extension to `destination`, adding all `newFields` to it.
589-
static void ExtendDestinationModel(std::span<const ROOT::RFieldDescriptor *> newFields, ROOT::RNTupleModel &dstModel,
590-
RNTupleMergeData &mergeData, std::vector<RCommonField> &commonFields)
589+
[[nodiscard]]
590+
static ROOT::RResult<void>
591+
ExtendDestinationModel(std::span<const ROOT::RFieldDescriptor *> newFields, ROOT::RNTupleModel &dstModel,
592+
RNTupleMergeData &mergeData, std::vector<RCommonField> &commonFields)
591593
{
592594
assert(newFields.size() > 0); // no point in calling this with 0 new cols
593595

@@ -636,14 +638,20 @@ static void ExtendDestinationModel(std::span<const ROOT::RFieldDescriptor *> new
636638
ROOT::Internal::GetProjectedFieldsOfModel(dstModel).Add(std::move(field), fieldMap);
637639
}
638640
dstModel.Freeze();
639-
mergeData.fDestination.UpdateSchema(changeset, mergeData.fNumDstEntries);
641+
try {
642+
mergeData.fDestination.UpdateSchema(changeset, mergeData.fNumDstEntries);
643+
} catch (const ROOT::RException &ex) {
644+
return R__FAIL(ex.GetError().GetReport());
645+
}
640646

641647
commonFields.reserve(commonFields.size() + newFields.size());
642648
for (const auto *field : newFields) {
643649
const auto newFieldInDstId = mergeData.fDstDescriptor.FindFieldId(field->GetFieldName());
644650
const auto &newFieldInDst = mergeData.fDstDescriptor.GetFieldDescriptor(newFieldInDstId);
645651
commonFields.emplace_back(*field, newFieldInDst);
646652
}
653+
654+
return ROOT::RResult<void>::Success();
647655
}
648656

649657
// Generates default (zero) values for the given columns
@@ -1184,7 +1192,9 @@ ROOT::RResult<void> RNTupleMerger::Merge(std::span<RPageSource *> sources, const
11841192
if (descCmp.fExtraSrcFields.size()) {
11851193
if (mergeOpts.fMergingMode == ENTupleMergingMode::kUnion) {
11861194
// late model extension for all fExtraSrcFields in Union mode
1187-
ExtendDestinationModel(descCmp.fExtraSrcFields, *fModel, mergeData, descCmp.fCommonFields);
1195+
auto res = ExtendDestinationModel(descCmp.fExtraSrcFields, *fModel, mergeData, descCmp.fCommonFields);
1196+
if (!res)
1197+
return R__FORWARD_ERROR(res);
11881198
} else if (mergeOpts.fMergingMode == ENTupleMergingMode::kStrict) {
11891199
// If the current source has extra fields and we're in Strict mode, error
11901200
std::string msg = "Source RNTuple has extra fields that the destination RNTuple doesn't have:";

tree/ntuple/src/RPageStorage.cxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,6 +830,11 @@ ROOT::Internal::RPagePersistentSink::AddColumn(ROOT::DescriptorId_t fieldId, RCo
830830
void ROOT::Internal::RPagePersistentSink::UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset,
831831
ROOT::NTupleSize_t firstEntry)
832832
{
833+
if (fIsInitialized)
834+
for (const auto &field : changeset.fAddedFields)
835+
if (field->GetStructure() == ENTupleStructure::kStreamer)
836+
throw ROOT::RException(R__FAIL("a Model cannot be extended with Streamer fields"));
837+
833838
const auto &descriptor = fDescriptorBuilder.GetDescriptor();
834839

835840
if (descriptor.GetNLogicalColumns() > descriptor.GetNPhysicalColumns()) {

tree/ntuple/test/ntuple_merger.cxx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3561,6 +3561,7 @@ TEST(RNTupleMerger, MergeStreamerFields)
35613561

35623562
// Now merge the inputs
35633563
for (const auto mmode : {ENTupleMergingMode::kFilter, ENTupleMergingMode::kStrict, ENTupleMergingMode::kUnion}) {
3564+
SCOPED_TRACE(std::string("with merging mode = ") + ToString(mmode));
35643565
FileRaii fileGuardOut("test_ntuple_merge_streamer_out.root");
35653566
{
35663567
auto destination = std::make_unique<RPageSinkFile>("ntuple", fileGuardOut.GetPath(), RNTupleWriteOptions());
@@ -3575,6 +3576,8 @@ TEST(RNTupleMerger, MergeStreamerFields)
35753576
EXPECT_EQ(reader->GetNEntries(), 20);
35763577
auto pFoo = reader->GetModel().GetDefaultEntry().GetPtr<CustomStruct>("foo");
35773578
auto pBar = reader->GetModel().GetDefaultEntry().GetPtr<CustomStruct>("bar");
3579+
EXPECT_EQ(reader->GetModel().GetConstField("foo").GetStructure(), ROOT::ENTupleStructure::kStreamer);
3580+
EXPECT_EQ(reader->GetModel().GetConstField("bar").GetStructure(), ROOT::ENTupleStructure::kStreamer);
35783581
for (auto idx : reader->GetEntryRange()) {
35793582
reader->LoadEntry(idx);
35803583
ASSERT_EQ(pFoo->v1.size(), 1);
@@ -3588,7 +3591,7 @@ TEST(RNTupleMerger, MergeStreamerFields)
35883591

35893592
TEST(RNTupleMerger, MergeStreamerFieldsFirstMissing)
35903593
{
3591-
// Merge two files where the second has an additional streamer field - should succeed.
3594+
// Merge two files where the second has an additional streamer field - should fail except in Filter mode.
35923595
FileRaii fileGuard1("test_ntuple_merge_streamer_firstmissing1.root");
35933596
{
35943597
auto model = RNTupleModel::Create();
@@ -3634,14 +3637,15 @@ TEST(RNTupleMerger, MergeStreamerFieldsFirstMissing)
36343637

36353638
// Now merge the inputs
36363639
for (const auto mmode : {ENTupleMergingMode::kFilter, ENTupleMergingMode::kStrict, ENTupleMergingMode::kUnion}) {
3640+
SCOPED_TRACE(std::string("with merging mode = ") + ToString(mmode));
36373641
FileRaii fileGuardOut("test_ntuple_merge_streamer_firstmissing_out.root");
36383642
{
36393643
auto destination = std::make_unique<RPageSinkFile>("ntuple", fileGuardOut.GetPath(), RNTupleWriteOptions());
36403644
RNTupleMerger merger{std::move(destination)};
36413645
RNTupleMergeOptions opts;
36423646
opts.fMergingMode = mmode;
36433647
auto res = merger.Merge(sourcePtrs, opts);
3644-
if (mmode != ENTupleMergingMode::kStrict)
3648+
if (mmode == ENTupleMergingMode::kFilter)
36453649
EXPECT_TRUE(bool(res));
36463650
else
36473651
EXPECT_FALSE(bool(res));

0 commit comments

Comments
 (0)