From 879e819089793fdd60f2bdde2cb696693b97e065 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Thu, 16 Apr 2026 17:22:36 +0200 Subject: [PATCH 1/2] [ntuple] add one more test for rename rules --- tree/ntuple/test/CustomStructLinkDef.h | 3 +++ tree/ntuple/test/rfield_class.cxx | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/tree/ntuple/test/CustomStructLinkDef.h b/tree/ntuple/test/CustomStructLinkDef.h index b616570d586a0..51aadb41c34c2 100644 --- a/tree/ntuple/test/CustomStructLinkDef.h +++ b/tree/ntuple/test/CustomStructLinkDef.h @@ -142,6 +142,9 @@ #pragma link C++ options = version(3) class NewName < int> + ; #pragma link C++ options = version(3) class NewName < NewName < int>> + ; #pragma read sourceClass = "OldName>" targetClass = "NewName>" version = "[3]" +#pragma link C++ options = version(4) class OldName+; +#pragma link C++ options = version(5) class NewName+; +#pragma read sourceClass = "OldName" targetClass = "NewName" #pragma link C++ struct SourceStruct + ; #pragma link C++ struct StructWithSourceStruct + ; diff --git a/tree/ntuple/test/rfield_class.cxx b/tree/ntuple/test/rfield_class.cxx index 2f9877c5ff8ef..326a6140f394e 100644 --- a/tree/ntuple/test/rfield_class.cxx +++ b/tree/ntuple/test/rfield_class.cxx @@ -260,12 +260,14 @@ TEST(RNTuple, TClassReadRules) auto ptrOldCoord = model->MakeField("oldCoord"); auto ptrLowPrecisionFloat = model->MakeField("lowPrecisionFloat"); auto ptrOldName = model->MakeField>>("rename"); + auto ptrOldNameVersionChange = model->MakeField>("renameVersionChange"); auto ptrWithSource = model->MakeField("withSource"); ptrCoord->fX = ptrOldCoord->fOldX = 1.0; ptrCoord->fY = ptrOldCoord->fOldY = 1.0; ptrLowPrecisionFloat->fFoo = 1.0; ptrLowPrecisionFloat->fLast8BitsZero = last8BitsZero; ptrOldName->fValue.fValue = 42; + ptrOldNameVersionChange->fValue = 1.0; // The following two members are transient and should not be stored. ptrWithSource->fSource.fTransient = 1; ptrWithSource->fTransient = 2; @@ -322,6 +324,10 @@ TEST(RNTuple, TClassReadRules) auto viewRename = reader->GetView>>("rename"); EXPECT_EQ(42, viewRename(0).fValue.fValue); + + EXPECT_NE(ROOT::RField>("").GetTypeVersion(), ROOT::RField>("").GetTypeVersion()); + auto viewRenameVersionChange = reader->GetView>("renameVersionChange"); + EXPECT_FLOAT_EQ(1.0, viewRenameVersionChange(0).fValue); } // Adjusted from From 9120b668c9ae065280fe266fc97f4faa3375bd1b Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Tue, 21 Apr 2026 15:35:13 +0200 Subject: [PATCH 2/2] [ntuple] fix handling of I/O rules with empty source --- roottest/root/ntuple/evolution/CMakeLists.txt | 38 +++++++++++++++++++ .../root/ntuple/evolution/NtplEvolv_v2.hxx | 12 ++++++ .../ntuple/evolution/NtplEvolv_v2_LinkDef.h | 5 +++ .../root/ntuple/evolution/NtplEvolv_v3.hxx | 13 +++++++ .../ntuple/evolution/NtplEvolv_v3_LinkDef.h | 7 ++++ .../root/ntuple/evolution/read_ntplevolv.cxx | 17 +++++++++ .../root/ntuple/evolution/write_ntplevolv.cxx | 18 +++++++++ tree/ntuple/src/RFieldMeta.cxx | 22 +++++++---- 8 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 roottest/root/ntuple/evolution/CMakeLists.txt create mode 100644 roottest/root/ntuple/evolution/NtplEvolv_v2.hxx create mode 100644 roottest/root/ntuple/evolution/NtplEvolv_v2_LinkDef.h create mode 100644 roottest/root/ntuple/evolution/NtplEvolv_v3.hxx create mode 100644 roottest/root/ntuple/evolution/NtplEvolv_v3_LinkDef.h create mode 100644 roottest/root/ntuple/evolution/read_ntplevolv.cxx create mode 100644 roottest/root/ntuple/evolution/write_ntplevolv.cxx diff --git a/roottest/root/ntuple/evolution/CMakeLists.txt b/roottest/root/ntuple/evolution/CMakeLists.txt new file mode 100644 index 0000000000000..c50457719bdce --- /dev/null +++ b/roottest/root/ntuple/evolution/CMakeLists.txt @@ -0,0 +1,38 @@ +ROOTTEST_GENERATE_DICTIONARY( + ntplevolv_v2_dict + ${CMAKE_CURRENT_SOURCE_DIR}/NtplEvolv_v2.hxx + LINKDEF ${CMAKE_CURRENT_SOURCE_DIR}/NtplEvolv_v2_LinkDef.h + NO_ROOTMAP NO_CXXMODULE + FIXTURES_SETUP generated_ntplevolv_v2_dictionary +) + +ROOTTEST_GENERATE_EXECUTABLE( + write_ntplevolv + write_ntplevolv.cxx ntplevolv_v2_dict.cxx + LIBRARIES Core RIO ROOTNTuple + FIXTURES_REQUIRED generated_ntplevolv_v2_dictionary + FIXTURES_SETUP write_ntplevolv_excutable) + +ROOTTEST_ADD_TEST(write_ntplevolv + EXEC ./write_ntplevolv + FIXTURES_REQUIRED write_ntplevolv_excutable + FIXTURES_SETUP write_ntplevolv_done) + +ROOTTEST_GENERATE_DICTIONARY( + ntplevolv_v3_dict + ${CMAKE_CURRENT_SOURCE_DIR}/NtplEvolv_v3.hxx + LINKDEF ${CMAKE_CURRENT_SOURCE_DIR}/NtplEvolv_v3_LinkDef.h + NO_ROOTMAP NO_CXXMODULE + FIXTURES_SETUP generated_ntplevolv_v3_dictionary +) + +ROOTTEST_GENERATE_EXECUTABLE( + read_ntplevolv + read_ntplevolv.cxx ntplevolv_v3_dict.cxx + LIBRARIES Core RIO ROOTNTuple + FIXTURES_REQUIRED generated_ntplevolv_v3_dictionary + FIXTURES_SETUP read_ntplevolv_excutable) + +ROOTTEST_ADD_TEST(read_ntplevolv + EXEC ./read_ntplevolv + FIXTURES_REQUIRED read_ntplevolv_excutable write_ntplevolv_done) diff --git a/roottest/root/ntuple/evolution/NtplEvolv_v2.hxx b/roottest/root/ntuple/evolution/NtplEvolv_v2.hxx new file mode 100644 index 0000000000000..f991cd62a21f6 --- /dev/null +++ b/roottest/root/ntuple/evolution/NtplEvolv_v2.hxx @@ -0,0 +1,12 @@ +#ifndef NTPL_EVOLV_V2_H +#define NTPL_EVOLV_V2_H + +#include + +struct NtplEvolv { + int fA = 0; + + ClassDefNV(NtplEvolv, 2) +}; + +#endif // NTPL_EVOLV_V2_H diff --git a/roottest/root/ntuple/evolution/NtplEvolv_v2_LinkDef.h b/roottest/root/ntuple/evolution/NtplEvolv_v2_LinkDef.h new file mode 100644 index 0000000000000..dcbbf2964c783 --- /dev/null +++ b/roottest/root/ntuple/evolution/NtplEvolv_v2_LinkDef.h @@ -0,0 +1,5 @@ +#ifdef __ROOTCLING__ + +#pragma link C++ class NtplEvolv+; + +#endif diff --git a/roottest/root/ntuple/evolution/NtplEvolv_v3.hxx b/roottest/root/ntuple/evolution/NtplEvolv_v3.hxx new file mode 100644 index 0000000000000..1c6af7e5b307f --- /dev/null +++ b/roottest/root/ntuple/evolution/NtplEvolv_v3.hxx @@ -0,0 +1,13 @@ +#ifndef NTPL_EVOLV_V3_H +#define NTPL_EVOLV_V3_H + +#include + +struct NtplEvolv { + int fA = 0; + int fB = 0; + + ClassDefNV(NtplEvolv, 3) +}; + +#endif // NTPL_EVOLV_V3_H diff --git a/roottest/root/ntuple/evolution/NtplEvolv_v3_LinkDef.h b/roottest/root/ntuple/evolution/NtplEvolv_v3_LinkDef.h new file mode 100644 index 0000000000000..4ceada4adf165 --- /dev/null +++ b/roottest/root/ntuple/evolution/NtplEvolv_v3_LinkDef.h @@ -0,0 +1,7 @@ +#ifdef __ROOTCLING__ + +#pragma link C++ class NtplEvolv+; + +#pragma read sourceClass="NtplEvolv" version="[1-]" source="" targetClass="NtplEvolv" target="fA" code = "{ fA = 13; }" + +#endif diff --git a/roottest/root/ntuple/evolution/read_ntplevolv.cxx b/roottest/root/ntuple/evolution/read_ntplevolv.cxx new file mode 100644 index 0000000000000..8819c75db45a2 --- /dev/null +++ b/roottest/root/ntuple/evolution/read_ntplevolv.cxx @@ -0,0 +1,17 @@ +#include + +#include "NtplEvolv_v3.hxx" + +#include + +int main() +{ + auto reader = ROOT::RNTupleReader::Open("ntpl", "root_test_ntpl_evolution.root"); + + reader->LoadEntry(0); + + auto a = reader->GetModel().GetDefaultEntry().GetPtr("event")->fA; + std::cout << "Result of event.fA: " << a << std::endl; + + return a != 13; +} diff --git a/roottest/root/ntuple/evolution/write_ntplevolv.cxx b/roottest/root/ntuple/evolution/write_ntplevolv.cxx new file mode 100644 index 0000000000000..b18683bcff353 --- /dev/null +++ b/roottest/root/ntuple/evolution/write_ntplevolv.cxx @@ -0,0 +1,18 @@ +#include +#include + +#include "NtplEvolv_v2.hxx" + +#include + +int main() +{ + auto model = ROOT::RNTupleModel::Create(); + auto event = model->MakeField("event"); + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", "root_test_ntpl_evolution.root"); + + event->fA = 1; + writer->Fill(); + + return 0; +} diff --git a/tree/ntuple/src/RFieldMeta.cxx b/tree/ntuple/src/RFieldMeta.cxx index 5aac2e3bcd89d..c8e331f30a3f5 100644 --- a/tree/ntuple/src/RFieldMeta.cxx +++ b/tree/ntuple/src/RFieldMeta.cxx @@ -541,22 +541,28 @@ std::unique_ptr ROOT::RClassField::BeforeConnectPageSource(ROO } } - if (!rules.empty()) { + const bool hasSources = std::any_of(rules.begin(), rules.end(), [](const auto &r) { + return r->GetSource() && (r->GetSource()->GetEntries() > 0); + }); + + // A staging class (conversion streamer info) only exists if there is at least one rule that has an + // on disk source member defined. + if (hasSources) { SetStagingClass(fieldDesc.GetTypeName(), fieldDesc.GetTypeVersion()); PrepareStagingArea(rules, desc, fieldDesc); for (auto &[_, si] : fStagingItems) { Internal::CallConnectPageSourceOnField(*si.fField, pageSource); si.fField = std::move(static_cast(si.fField.get())->ReleaseSubfields()[0]); } + } - // Remove target member of read rules from the list of regular members of the underlying on-disk field - for (const auto rule : rules) { - if (!rule->GetTarget()) - continue; + // Remove target member of read rules from the list of regular members of the underlying on-disk field + for (const auto rule : rules) { + if (!rule->GetTarget()) + continue; - for (const auto target : ROOT::Detail::TRangeStaticCast(*rule->GetTarget())) { - regularSubfields.erase(std::string(target->GetString())); - } + for (const auto target : ROOT::Detail::TRangeStaticCast(*rule->GetTarget())) { + regularSubfields.erase(std::string(target->GetString())); } } }