From 3bb158999062e54c6a79a6e47621d3d2a6438b69 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Fri, 5 Jun 2026 17:54:35 +0200 Subject: [PATCH 1/3] [ntuple] fix late record extension with buffered sink (cherry picked from commit 57927cc5db30147d99e2dbcb818c43a21450e13d) --- tree/ntuple/src/RPageSinkBuf.cxx | 11 +++++++++- tree/ntuple/test/ntuple_modelext.cxx | 31 ++++++++++++++-------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/tree/ntuple/src/RPageSinkBuf.cxx b/tree/ntuple/src/RPageSinkBuf.cxx index 3d3f9c6896903..4079768f57b8e 100644 --- a/tree/ntuple/src/RPageSinkBuf.cxx +++ b/tree/ntuple/src/RPageSinkBuf.cxx @@ -109,7 +109,16 @@ void ROOT::Internal::RPageSinkBuf::UpdateSchema(const ROOT::Internal::RNTupleMod auto cloneAddField = [&](const ROOT::RFieldBase *field) { auto cloned = field->Clone(field->GetFieldName()); auto p = &(*cloned); - fInnerModel->AddField(std::move(cloned)); + + auto parent = field->GetParent(); + assert(parent); + if (typeid(*parent) != typeid(RFieldZero)) { + auto &innerParent = fInnerModel->GetMutableField(parent->GetQualifiedFieldName()); + assert(dynamic_cast(&innerParent)); + AddItemToRecord(static_cast(innerParent), std::move(cloned)); + } else { + fInnerModel->AddField(std::move(cloned)); + } return p; }; auto cloneAddProjectedField = [&](ROOT::RFieldBase *field) { diff --git a/tree/ntuple/test/ntuple_modelext.cxx b/tree/ntuple/test/ntuple_modelext.cxx index 9ac90f2446af8..7fd211f5a8b96 100644 --- a/tree/ntuple/test/ntuple_modelext.cxx +++ b/tree/ntuple/test/ntuple_modelext.cxx @@ -737,33 +737,33 @@ TEST(RNTuple, ModelExtensionRecordSimple) std::string expect{ R"({ "r": { - "pt": 1 - }, - "vec": [] + "pt": 1, + "vec": [] + } } { "r": { - "pt": 2 - }, - "vec": [] + "pt": 2, + "vec": [] + } } { "r": { - "pt": 3 - }, - "vec": [10, 11] + "pt": 3, + "vec": [10, 11] + } } { "r": { - "pt": 4 - }, - "vec": [] + "pt": 4, + "vec": [] + } } { "r": { - "pt": 5 - }, - "vec": [12] + "pt": 5, + "vec": [12] + } } )" }; // clang-format on @@ -782,7 +782,6 @@ TEST(RNTuple, ModelExtensionRecordNested) model->AddField(std::move(recordField)); RNTupleWriteOptions opts; - opts.SetUseBufferedWrite(false); auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath(), opts); auto entry = writer->CreateEntry(); From cc270c60374a24490aa285e10b5b2388becc7ad4 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Fri, 5 Jun 2026 17:28:40 +0200 Subject: [PATCH 2/3] [ntuple] fix late model extension of nested records (cherry picked from commit efca20da123a3aee2e65c5ad16d6b6abd19845e4) --- tree/ntuple/src/RField.cxx | 20 ++++++++++++++++++++ tree/ntuple/test/ntuple_modelext.cxx | 2 ++ 2 files changed, 22 insertions(+) diff --git a/tree/ntuple/src/RField.cxx b/tree/ntuple/src/RField.cxx index dc41bd91023de..65f2c8c35c175 100644 --- a/tree/ntuple/src/RField.cxx +++ b/tree/ntuple/src/RField.cxx @@ -644,6 +644,26 @@ void ROOT::Internal::AddItemToRecord(RRecordField &record, std::unique_ptr(parent) && parent->GetTypeName().empty()); + + auto precord = static_cast(parent); + std::vector> subfields; + std::swap(precord->fSubfields, subfields); + precord->fTraits = ROOT::RFieldBase::kTraitExtensible; + precord->fSubfieldNames.clear(); + precord->fMaxAlignment = 1; + precord->fSize = 0; + precord->fOffsets.clear(); + precord->AttachItemFields(std::move(subfields)); + + parent = precord->fParent; + } } std::size_t ROOT::RRecordField::GetItemPadding(std::size_t baseOffset, std::size_t itemAlignment) const diff --git a/tree/ntuple/test/ntuple_modelext.cxx b/tree/ntuple/test/ntuple_modelext.cxx index 7fd211f5a8b96..9a88c3a0a03a5 100644 --- a/tree/ntuple/test/ntuple_modelext.cxx +++ b/tree/ntuple/test/ntuple_modelext.cxx @@ -784,6 +784,7 @@ TEST(RNTuple, ModelExtensionRecordNested) RNTupleWriteOptions opts; auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath(), opts); + EXPECT_EQ(1u, writer->GetModel().GetConstField("r1").GetValueSize()); auto entry = writer->CreateEntry(); writer->Fill(*entry); writer->CommitCluster(); @@ -799,6 +800,7 @@ TEST(RNTuple, ModelExtensionRecordNested) modelUpdater->AddField(std::move(recordField), "r1.r2"); modelUpdater->CommitUpdate(); + EXPECT_EQ(sizeof(float), writer->GetModel().GetConstField("r1").GetValueSize()); entry = writer->CreateEntry(); auto ptrFloat = static_cast(entry->GetPtr("r1").get()); *ptrFloat = 1.0; From e2e7c299837e63f20e5337e58c535f8a59da5c4b Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Sat, 6 Jun 2026 20:59:39 +0200 Subject: [PATCH 3/3] [ntuple] extend late record extension test (cherry picked from commit 186d6034b1ef079ae531308b2c75cc403e90aaf0) --- tree/ntuple/test/ntuple_modelext.cxx | 40 +++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/tree/ntuple/test/ntuple_modelext.cxx b/tree/ntuple/test/ntuple_modelext.cxx index 9a88c3a0a03a5..8ec3886f9216d 100644 --- a/tree/ntuple/test/ntuple_modelext.cxx +++ b/tree/ntuple/test/ntuple_modelext.cxx @@ -805,15 +805,32 @@ TEST(RNTuple, ModelExtensionRecordNested) auto ptrFloat = static_cast(entry->GetPtr("r1").get()); *ptrFloat = 1.0; writer->Fill(*entry); + + modelUpdater->BeginUpdate(); + modelUpdater->AddField(std::make_unique>("ptHP"), "r1.r2.r3.r4"); + modelUpdater->CommitUpdate(); + + EXPECT_EQ(2 * sizeof(double), writer->GetModel().GetConstField("r1").GetValueSize()); + entry = writer->CreateEntry(); + struct FloatAndDouble { + float pt; + double ptHP; + }; + + auto ptrFloatAndDouble = static_cast(entry->GetPtr("r1").get()); + ptrFloatAndDouble->pt = 2.0; + ptrFloatAndDouble->ptHP = 3.0; + writer->Fill(*entry); } auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); - EXPECT_EQ(3u, reader->GetNEntries()); + EXPECT_EQ(4u, reader->GetNEntries()); std::ostringstream os; reader->Show(0, os); reader->Show(1, os); reader->Show(2, os); + reader->Show(3, os); // clang-format off std::string expect{ R"({ @@ -821,7 +838,20 @@ R"({ "r2": { "r3": { "r4": { - "pt": 0 + "pt": 0, + "ptHP": 0 + } + } + } + } +} +{ + "r1": { + "r2": { + "r3": { + "r4": { + "pt": 0, + "ptHP": 0 } } } @@ -832,7 +862,8 @@ R"({ "r2": { "r3": { "r4": { - "pt": 0 + "pt": 1, + "ptHP": 0 } } } @@ -843,7 +874,8 @@ R"({ "r2": { "r3": { "r4": { - "pt": 1 + "pt": 2, + "ptHP": 3 } } }