From 50324a6d5494d98c49336caba9eb0847e6fcb003 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Thu, 6 Nov 2025 21:08:28 +0100 Subject: [PATCH 1/3] [ntuple] fix TClass initializatin in RField In the templated version of the RClassField, the type name passed to TClass::GetClass() must not use the RNTuple normalized name but the demangled name or the meta normalized name. Otherwise, RNTuple may normalize, e.g., `long long` to `std::int64_t`, which in turn gets normalized by Meta to `unsigned long`. (cherry picked from commit 4a789b0939db138ea0bcd8cf39ad3cac9a3914a9) --- tree/ntuple/inc/ROOT/RField.hxx | 4 +++- tree/ntuple/test/CustomStruct.hxx | 6 ++++++ tree/ntuple/test/CustomStructLinkDef.h | 2 ++ tree/ntuple/test/rfield_class.cxx | 16 ++++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/tree/ntuple/inc/ROOT/RField.hxx b/tree/ntuple/inc/ROOT/RField.hxx index c8eb19871e737..ebe5ea4549b31 100644 --- a/tree/ntuple/inc/ROOT/RField.hxx +++ b/tree/ntuple/inc/ROOT/RField.hxx @@ -225,6 +225,8 @@ public: /// For polymorphic classes (that declare or inherit at least one virtual method), return the expected dynamic type /// of any user object. If the class is not polymorphic, return nullptr. const std::type_info *GetPolymorphicTypeInfo() const; + /// Return the TClass instance backing this field. + const TClass *GetClass() const { return fClass; } void AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const final; }; @@ -316,7 +318,7 @@ template class RField final : public RClassField { public: static std::string TypeName() { return ROOT::Internal::GetRenormalizedTypeName(typeid(T)); } - RField(std::string_view name) : RClassField(name, TypeName()) + RField(std::string_view name) : RClassField(name, Internal::GetDemangledTypeName(typeid(T))) { static_assert(std::is_class_v, "no I/O support for this basic C++ type"); } diff --git a/tree/ntuple/test/CustomStruct.hxx b/tree/ntuple/test/CustomStruct.hxx index 634ab2b901d38..212ee76d292c0 100644 --- a/tree/ntuple/test/CustomStruct.hxx +++ b/tree/ntuple/test/CustomStruct.hxx @@ -103,6 +103,12 @@ public: T fMember; }; +class EdmContainer { +public: + // Used to test that the streamer info for fWrapper will use long long + EdmWrapper fWrapper; +}; + template struct EdmHashTrait { using value_type = T; diff --git a/tree/ntuple/test/CustomStructLinkDef.h b/tree/ntuple/test/CustomStructLinkDef.h index d8a6f2224a662..8e596a41ce666 100644 --- a/tree/ntuple/test/CustomStructLinkDef.h +++ b/tree/ntuple/test/CustomStructLinkDef.h @@ -29,6 +29,8 @@ #pragma link C++ class EdmWrapper +; #pragma link C++ class EdmHash < 1> + ; +#pragma link C++ class EdmWrapper+; +#pragma link C++ class EdmContainer; #pragma link C++ class DataVector < int, double> + ; #pragma link C++ class DataVector < int, float> + ; diff --git a/tree/ntuple/test/rfield_class.cxx b/tree/ntuple/test/rfield_class.cxx index 05c53e52d8909..7e8d9293d4eb8 100644 --- a/tree/ntuple/test/rfield_class.cxx +++ b/tree/ntuple/test/rfield_class.cxx @@ -371,3 +371,19 @@ TEST(RNTuple, PolymorphicPointer) EXPECT_THROW(writer->Fill(), ROOT::RException); } } + +TEST(RNTuple, TClassMetaName) +{ + auto f1 = ROOT::RClassField("f", "EdmWrapper"); + EXPECT_STREQ("EdmWrapper", f1.GetClass()->GetName()); + + auto f2 = std::make_unique>>("f"); + EXPECT_STREQ("EdmWrapper", static_cast(f2.get())->GetClass()->GetName()); + + auto f3 = RFieldBase::Create("f", "EdmWrapper").Unwrap(); + EXPECT_STREQ("EdmWrapper", static_cast(f3.get())->GetClass()->GetName()); + + auto f4 = RFieldBase::Create("f", "EdmContainer").Unwrap(); + EXPECT_STREQ("EdmWrapper", + static_cast(f4->GetConstSubfields()[0])->GetClass()->GetName()); +} From 81857543420a5720e8623dfd272ee567b957dae2 Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Thu, 6 Nov 2025 23:39:53 +0100 Subject: [PATCH 2/3] [ntuple] RPagePersistentSink::fStreamerInfos --> fInfosOfStreamerFields (cherry picked from commit 2d27aed7a4bff75c0895b404532d2bbc62c7ba4d) --- tree/ntuple/inc/ROOT/RPageStorage.hxx | 2 +- tree/ntuple/src/RPageStorage.cxx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tree/ntuple/inc/ROOT/RPageStorage.hxx b/tree/ntuple/inc/ROOT/RPageStorage.hxx index d1f4e4f99d870..454982a1eeefd 100644 --- a/tree/ntuple/inc/ROOT/RPageStorage.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorage.hxx @@ -447,7 +447,7 @@ private: std::vector fOpenPageRanges; /// Union of the streamer info records that are sent from streamer fields to the sink before committing the dataset. - ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fStreamerInfos; + ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fInfosOfStreamerFields; protected: /// Set of optional features supported by the persistent sink diff --git a/tree/ntuple/src/RPageStorage.cxx b/tree/ntuple/src/RPageStorage.cxx index 362f903418bb1..c8498fe9c616b 100644 --- a/tree/ntuple/src/RPageStorage.cxx +++ b/tree/ntuple/src/RPageStorage.cxx @@ -927,7 +927,7 @@ void ROOT::Internal::RPagePersistentSink::UpdateExtraTypeInfo(const ROOT::RExtra if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) throw RException(R__FAIL("ROOT bug: unexpected type extra info in UpdateExtraTypeInfo()")); - fStreamerInfos.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); + fInfosOfStreamerFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); } void ROOT::Internal::RPagePersistentSink::InitImpl(ROOT::RNTupleModel &model) @@ -1255,7 +1255,7 @@ void ROOT::Internal::RPagePersistentSink::CommitClusterGroup() void ROOT::Internal::RPagePersistentSink::CommitDatasetImpl() { - if (!fStreamerInfos.empty()) { + if (!fInfosOfStreamerFields.empty()) { // De-duplicate extra type infos before writing. Usually we won't have them already in the descriptor, but // this may happen when we are writing back an already-existing RNTuple, e.g. when doing incremental merging. for (const auto &etDesc : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) { @@ -1265,13 +1265,13 @@ void ROOT::Internal::RPagePersistentSink::CommitDatasetImpl() R__ASSERT(etDesc.GetTypeName().empty()); R__ASSERT(etDesc.GetTypeVersion() == 0); auto etInfo = RNTupleSerializer::DeserializeStreamerInfos(etDesc.GetContent()).Unwrap(); - fStreamerInfos.merge(etInfo); + fInfosOfStreamerFields.merge(etInfo); } } RExtraTypeInfoDescriptorBuilder extraInfoBuilder; extraInfoBuilder.ContentId(EExtraTypeInfoIds::kStreamerInfo) - .Content(RNTupleSerializer::SerializeStreamerInfos(fStreamerInfos)); + .Content(RNTupleSerializer::SerializeStreamerInfos(fInfosOfStreamerFields)); fDescriptorBuilder.ReplaceExtraTypeInfo(extraInfoBuilder.MoveDescriptor().Unwrap()); } From 1657cd54d14d968184d53f96a80b90c478ff562f Mon Sep 17 00:00:00 2001 From: Jakob Blomer Date: Fri, 7 Nov 2025 00:13:09 +0100 Subject: [PATCH 3/3] [ntuple] create streamer infos from TClass names Get the streamer info records of class fields and streamer fields from their meta (TClass) normalized name. Previously, the names came from the descriptor, which has them already RNTuple normalized. (cherry picked from commit 1f6a481a582afab517f6b1395084d0b4f7b26b8c) --- tree/ntuple/inc/ROOT/RField.hxx | 1 + tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx | 3 - tree/ntuple/inc/ROOT/RPageStorage.hxx | 2 +- tree/ntuple/inc/ROOT/RPageStorageFile.hxx | 6 + tree/ntuple/src/RNTupleDescriptor.cxx | 43 ----- tree/ntuple/src/RPageStorageFile.cxx | 45 ++++- tree/ntuple/test/CMakeLists.txt | 5 - tree/ntuple/test/CustomStruct.hxx | 1 + tree/ntuple/test/CustomStructLinkDef.h | 3 + tree/ntuple/test/RNTupleDescriptorDict.hxx | 9 - tree/ntuple/test/RNTupleDescriptorLinkDef.h | 7 - tree/ntuple/test/ntuple_descriptor.cxx | 82 --------- tree/ntuple/test/ntuple_emulated.cxx | 33 ++-- tree/ntuple/test/ntuple_evolution_shape.cxx | 176 ++++++++------------ tree/ntuple/test/rfield_class.cxx | 51 ++++++ 15 files changed, 188 insertions(+), 279 deletions(-) delete mode 100644 tree/ntuple/test/RNTupleDescriptorDict.hxx delete mode 100644 tree/ntuple/test/RNTupleDescriptorLinkDef.h diff --git a/tree/ntuple/inc/ROOT/RField.hxx b/tree/ntuple/inc/ROOT/RField.hxx index ebe5ea4549b31..d4590349992f3 100644 --- a/tree/ntuple/inc/ROOT/RField.hxx +++ b/tree/ntuple/inc/ROOT/RField.hxx @@ -281,6 +281,7 @@ public: size_t GetAlignment() const final; std::uint32_t GetTypeVersion() const final; std::uint32_t GetTypeChecksum() const final; + TClass *GetClass() const { return fClass; } void AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const final; }; diff --git a/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx b/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx index d997874b4ea5b..d988e72656f3a 100644 --- a/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx +++ b/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx @@ -1762,9 +1762,6 @@ public: /// - Logical columns of piece two /// - ... void ShiftAliasColumns(std::uint32_t offset); - - /// Get the streamer info records for custom classes. Currently requires the corresponding dictionaries to be loaded. - ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t BuildStreamerInfos() const; }; inline RNTupleDescriptor CloneDescriptorSchema(const RNTupleDescriptor &desc) diff --git a/tree/ntuple/inc/ROOT/RPageStorage.hxx b/tree/ntuple/inc/ROOT/RPageStorage.hxx index 454982a1eeefd..0d3c7e6299d2f 100644 --- a/tree/ntuple/inc/ROOT/RPageStorage.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorage.hxx @@ -521,7 +521,7 @@ public: /// Updates the descriptor and calls InitImpl() that handles the backend-specific details (file, DAOS, etc.) void InitImpl(RNTupleModel &model) final; - void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) final; + void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) override; void UpdateExtraTypeInfo(const ROOT::RExtraTypeInfoDescriptor &extraTypeInfo) final; /// Initialize sink based on an existing descriptor and fill into the descriptor builder, optionally copying over diff --git a/tree/ntuple/inc/ROOT/RPageStorageFile.hxx b/tree/ntuple/inc/ROOT/RPageStorageFile.hxx index 81adeed6b339b..bbef8c11b0efc 100644 --- a/tree/ntuple/inc/ROOT/RPageStorageFile.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorageFile.hxx @@ -65,6 +65,10 @@ private: std::unique_ptr fWriter; /// Number of bytes committed to storage in the current cluster std::uint64_t fNBytesCurrentCluster = 0; + /// On UpdateSchema(), the new class fields register the corresponding streamer info here so that the + /// streamer info records in the file can be properly updated on dataset commit + ROOT::Internal::RNTupleSerializer::StreamerInfoMap_t fInfosOfClassFields; + RPageSinkFile(std::string_view ntupleName, const ROOT::RNTupleWriteOptions &options); /// We pass bytesPacked so that TFile::ls() reports a reasonable value for the compression ratio of the corresponding @@ -98,6 +102,8 @@ public: RPageSinkFile(RPageSinkFile &&) = default; RPageSinkFile &operator=(RPageSinkFile &&) = default; ~RPageSinkFile() override; + + void UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, ROOT::NTupleSize_t firstEntry) final; }; // class RPageSinkFile // clang-format off diff --git a/tree/ntuple/src/RNTupleDescriptor.cxx b/tree/ntuple/src/RNTupleDescriptor.cxx index 464ffbb0d25c8..7cfd9a16761e9 100644 --- a/tree/ntuple/src/RNTupleDescriptor.cxx +++ b/tree/ntuple/src/RNTupleDescriptor.cxx @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -1412,48 +1411,6 @@ ROOT::Internal::RNTupleDescriptorBuilder::AddAttributeSet(Experimental::RNTupleA return RResult::Success(); } -RNTupleSerializer::StreamerInfoMap_t ROOT::Internal::RNTupleDescriptorBuilder::BuildStreamerInfos() const -{ - RNTupleSerializer::StreamerInfoMap_t streamerInfoMap; - const auto &desc = GetDescriptor(); - - std::function fnWalkFieldTree; - fnWalkFieldTree = [&desc, &streamerInfoMap, &fnWalkFieldTree](const RFieldDescriptor &fieldDesc) { - if (fieldDesc.IsCustomClass()) { - // Add streamer info for this class to streamerInfoMap - auto cl = TClass::GetClass(fieldDesc.GetTypeName().c_str()); - if (!cl) { - throw RException(R__FAIL(std::string("cannot get TClass for ") + fieldDesc.GetTypeName())); - } - auto streamerInfo = cl->GetStreamerInfo(fieldDesc.GetTypeVersion()); - if (!streamerInfo) { - throw RException(R__FAIL(std::string("cannot get streamerInfo for ") + fieldDesc.GetTypeName())); - } - streamerInfoMap[streamerInfo->GetNumber()] = streamerInfo; - } - - // Recursively traverse sub fields - for (const auto &subFieldDesc : desc.GetFieldIterable(fieldDesc)) { - fnWalkFieldTree(subFieldDesc); - } - }; - - fnWalkFieldTree(desc.GetFieldZero()); - - // Add the streamer info records from streamer fields: because of runtime polymorphism we may need to add additional - // types not covered by the type names stored in the field headers - for (const auto &extraTypeInfo : desc.GetExtraTypeInfoIterable()) { - if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) - continue; - // Ideally, we would avoid deserializing the streamer info records of the streamer fields that we just serialized. - // However, this happens only once at the end of writing and only when streamer fields are used, so the - // preference here is for code simplicity. - streamerInfoMap.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); - } - - return streamerInfoMap; -} - ROOT::RClusterDescriptor::RColumnRangeIterable ROOT::RClusterDescriptor::GetColumnRangeIterable() const { return RColumnRangeIterable(*this); diff --git a/tree/ntuple/src/RPageStorageFile.cxx b/tree/ntuple/src/RPageStorageFile.cxx index 0222d717d8ce8..5bd280d55f787 100644 --- a/tree/ntuple/src/RPageStorageFile.cxx +++ b/tree/ntuple/src/RPageStorageFile.cxx @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -85,6 +86,37 @@ void ROOT::Internal::RPageSinkFile::InitImpl(unsigned char *serializedHeader, st fWriter->WriteNTupleHeader(zipBuffer.get(), szZipHeader, length); } +void ROOT::Internal::RPageSinkFile::UpdateSchema(const ROOT::Internal::RNTupleModelChangeset &changeset, + ROOT::NTupleSize_t firstEntry) +{ + RPagePersistentSink::UpdateSchema(changeset, firstEntry); + + auto fnAddStreamerInfo = [this](const ROOT::RFieldBase *field) { + const TClass *cl = nullptr; + if (auto classField = dynamic_cast(field)) { + cl = classField->GetClass(); + } else if (auto streamerField = dynamic_cast(field)) { + cl = streamerField->GetClass(); + } + if (!cl) + return; + + auto streamerInfo = cl->GetStreamerInfo(field->GetTypeVersion()); + if (!streamerInfo) { + throw RException(R__FAIL(std::string("cannot get streamerInfo for ") + cl->GetName() + " [" + + std::to_string(field->GetTypeVersion()) + "]")); + } + fInfosOfClassFields[streamerInfo->GetNumber()] = streamerInfo; + }; + + for (const auto field : changeset.fAddedFields) { + fnAddStreamerInfo(field); + for (const auto &subField : *field) { + fnAddStreamerInfo(&subField); + } + } +} + inline ROOT::RNTupleLocator ROOT::Internal::RPageSinkFile::WriteSealedPage(const RPageStorage::RSealedPage &sealedPage, std::size_t bytesPacked) { @@ -244,7 +276,18 @@ ROOT::Internal::RPageSinkFile::CommitClusterGroupImpl(unsigned char *serializedP void ROOT::Internal::RPageSinkFile::CommitDatasetImpl(unsigned char *serializedFooter, std::uint32_t length) { - fWriter->UpdateStreamerInfos(fDescriptorBuilder.BuildStreamerInfos()); + // Add the streamer info records from streamer fields: because of runtime polymorphism we may need to add additional + // types not covered by the type names of the class fields + for (const auto &extraTypeInfo : fDescriptorBuilder.GetDescriptor().GetExtraTypeInfoIterable()) { + if (extraTypeInfo.GetContentId() != EExtraTypeInfoIds::kStreamerInfo) + continue; + // Ideally, we would avoid deserializing the streamer info records of the streamer fields that we just serialized. + // However, this happens only once at the end of writing and only when streamer fields are used, so the + // preference here is for code simplicity. + fInfosOfClassFields.merge(RNTupleSerializer::DeserializeStreamerInfos(extraTypeInfo.GetContent()).Unwrap()); + } + fWriter->UpdateStreamerInfos(fInfosOfClassFields); + auto bufFooterZip = MakeUninitArray(length); auto szFooterZip = RNTupleCompressor::Zip(serializedFooter, length, GetWriteOptions().GetCompression(), bufFooterZip.get()); diff --git a/tree/ntuple/test/CMakeLists.txt b/tree/ntuple/test/CMakeLists.txt index fbbbb849a26ba..1df3d41dc0ef5 100644 --- a/tree/ntuple/test/CMakeLists.txt +++ b/tree/ntuple/test/CMakeLists.txt @@ -30,11 +30,6 @@ ROOT_GENERATE_DICTIONARY(RXTupleDict ${CMAKE_CURRENT_SOURCE_DIR}/RXTuple.hxx LINKDEF RXTupleLinkDef.h DEPENDENCIES RIO) ROOT_ADD_GTEST(ntuple_descriptor ntuple_descriptor.cxx LIBRARIES ROOTNTuple) -ROOT_GENERATE_DICTIONARY(RNTupleDescriptorDict ${CMAKE_CURRENT_SOURCE_DIR}/RNTupleDescriptorDict.hxx - MODULE ntuple_descriptor - LINKDEF RNTupleDescriptorLinkDef.h - OPTIONS -inlineInputHeader - DEPENDENCIES RIO) ROOT_ADD_GTEST(ntuple_endian ntuple_endian.cxx LIBRARIES ROOTNTuple) ROOT_ADD_GTEST(ntuple_evolution_type ntuple_evolution_type.cxx LIBRARIES ROOTNTuple) diff --git a/tree/ntuple/test/CustomStruct.hxx b/tree/ntuple/test/CustomStruct.hxx index 212ee76d292c0..135fdfae150ce 100644 --- a/tree/ntuple/test/CustomStruct.hxx +++ b/tree/ntuple/test/CustomStruct.hxx @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/tree/ntuple/test/CustomStructLinkDef.h b/tree/ntuple/test/CustomStructLinkDef.h index 8e596a41ce666..dc7d0f6e7ed65 100644 --- a/tree/ntuple/test/CustomStructLinkDef.h +++ b/tree/ntuple/test/CustomStructLinkDef.h @@ -27,6 +27,9 @@ #pragma link C++ class IOConstructor+; #pragma link C++ class LowPrecisionFloats+; +#pragma link C++ class std::map+ ; +#pragma link C++ class std::map+ ; + #pragma link C++ class EdmWrapper +; #pragma link C++ class EdmHash < 1> + ; #pragma link C++ class EdmWrapper+; diff --git a/tree/ntuple/test/RNTupleDescriptorDict.hxx b/tree/ntuple/test/RNTupleDescriptorDict.hxx deleted file mode 100644 index 5992255dae874..0000000000000 --- a/tree/ntuple/test/RNTupleDescriptorDict.hxx +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef ROOT_RNTuple_Test_RNTupleDescriptorDict -#define ROOT_RNTuple_Test_RNTupleDescriptorDict - -#include "CustomStruct.hxx" - -#include -#include - -#endif // ROOT_RNTuple_Test_RNTupleDescriptorDict diff --git a/tree/ntuple/test/RNTupleDescriptorLinkDef.h b/tree/ntuple/test/RNTupleDescriptorLinkDef.h deleted file mode 100644 index bd8d72f485a35..0000000000000 --- a/tree/ntuple/test/RNTupleDescriptorLinkDef.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifdef __CLING__ - -#pragma link C++ class std::map < int, CustomStruct> + ; -#pragma link C++ class std::map < int, float> + ; -#pragma link C++ class std::unordered_map < int, float> + ; - -#endif diff --git a/tree/ntuple/test/ntuple_descriptor.cxx b/tree/ntuple/test/ntuple_descriptor.cxx index b99e794abe7d8..72590a9b5f30d 100644 --- a/tree/ntuple/test/ntuple_descriptor.cxx +++ b/tree/ntuple/test/ntuple_descriptor.cxx @@ -609,88 +609,6 @@ TEST(RNTupleDescriptor, Clone) EXPECT_EQ(desc, clone); } -TEST(RNTupleDescriptor, BuildStreamerInfos) -{ - auto fnBuildStreamerInfosOf = [](const RFieldBase &field) -> RNTupleSerializer::StreamerInfoMap_t { - RNTupleDescriptorBuilder descBuilder; - descBuilder.SetNTuple("test", ""); - descBuilder.AddField( - RFieldDescriptorBuilder().FieldId(0).Structure(ROOT::ENTupleStructure::kRecord).MakeDescriptor().Unwrap()); - auto fieldBuilder = RFieldDescriptorBuilder::FromField(field); - descBuilder.AddField(fieldBuilder.FieldId(1).MakeDescriptor().Unwrap()); - descBuilder.AddFieldLink(0, 1); - int i = 2; - // In this test, we only support field hierarchies up to 2 levels - for (const auto &child : field.GetConstSubfields()) { - fieldBuilder = RFieldDescriptorBuilder::FromField(*child); - descBuilder.AddField(fieldBuilder.FieldId(i).MakeDescriptor().Unwrap()); - descBuilder.AddFieldLink(1, i); - const auto childId = i; - i++; - for (const auto &grandChild : child->GetConstSubfields()) { - fieldBuilder = RFieldDescriptorBuilder::FromField(*grandChild); - descBuilder.AddField(fieldBuilder.FieldId(i).MakeDescriptor().Unwrap()); - descBuilder.AddFieldLink(childId, i); - i++; - } - } - return descBuilder.BuildStreamerInfos(); - }; - - RNTupleSerializer::StreamerInfoMap_t streamerInfoMap; - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "float").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::vector").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::pair").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::map").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::unordered_map").Unwrap()); - EXPECT_TRUE(streamerInfoMap.empty()); - - std::vector> itemFields; - streamerInfoMap = fnBuildStreamerInfosOf(ROOT::RRecordField("f", std::move(itemFields))); - EXPECT_TRUE(streamerInfoMap.empty()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "CustomStruct").Unwrap()); - EXPECT_EQ(1u, streamerInfoMap.size()); - EXPECT_STREQ("CustomStruct", streamerInfoMap.begin()->second->GetName()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::vector").Unwrap()); - EXPECT_EQ(1u, streamerInfoMap.size()); - EXPECT_STREQ("CustomStruct", streamerInfoMap.begin()->second->GetName()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::map").Unwrap()); - EXPECT_EQ(1u, streamerInfoMap.size()); - EXPECT_STREQ("CustomStruct", streamerInfoMap.begin()->second->GetName()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "DerivedA").Unwrap()); - EXPECT_EQ(2u, streamerInfoMap.size()); - std::vector typeNames; - for (const auto &[_, si] : streamerInfoMap) { - typeNames.emplace_back(si->GetName()); - } - std::sort(typeNames.begin(), typeNames.end()); - EXPECT_STREQ("CustomStruct", typeNames[0].c_str()); - EXPECT_STREQ("DerivedA", typeNames[1].c_str()); - - streamerInfoMap = fnBuildStreamerInfosOf(*RFieldBase::Create("f", "std::pair").Unwrap()); - EXPECT_EQ(2u, streamerInfoMap.size()); - typeNames.clear(); - for (const auto &[_, si] : streamerInfoMap) { - typeNames.emplace_back(si->GetName()); - } - std::sort(typeNames.begin(), typeNames.end()); - EXPECT_STREQ("CustomStruct", typeNames[0].c_str()); - EXPECT_STREQ("DerivedA", typeNames[1].c_str()); -} - TEST(RNTupleDescriptor, CloneSchema) { FileRaii fileGuard("test_ntuple_desc_cloneschema.root"); diff --git a/tree/ntuple/test/ntuple_emulated.cxx b/tree/ntuple/test/ntuple_emulated.cxx index 893cca7bd3b67..adee6f6cad1b9 100644 --- a/tree/ntuple/test/ntuple_emulated.cxx +++ b/tree/ntuple/test/ntuple_emulated.cxx @@ -32,6 +32,12 @@ TEST(RNTupleEmulated, EmulatedFields_Simple) auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "Outer_Simple").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -42,13 +48,6 @@ TEST(RNTupleEmulated, EmulatedFields_Simple) ProcessLine("ptrOuter->fInner.fInt2 = 82;"); ProcessLine("ptrOuter->fInt1 = 93;"); writer->Fill(); - - // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); }); auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); @@ -145,6 +144,12 @@ TEST(RNTupleEmulated, EmulatedFields_Vecs) auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("outers", "std::vector").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -155,13 +160,6 @@ TEST(RNTupleEmulated, EmulatedFields_Vecs) ProcessLine("(*ptrOuters)[0].fInners.push_back(Inner_Vecs{42.f});"); ProcessLine("(*ptrOuters)[0].fInner.fFlt = 84.f;"); writer->Fill(); - - // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); }); auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); @@ -318,15 +316,14 @@ TEST(RNTupleEmulated, EmulatedFields_EmptyStruct) auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "Outer_EmptyStruct").Unwrap()); - auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); - writer->Fill(); - // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). // See also https://github.com/root-project/root/issues/9371 ROOT::TestSupport::CheckDiagsRAII diagRAII; diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", /*matchFullMessage=*/false); - writer.reset(); + + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + writer->Fill(); }); auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); diff --git a/tree/ntuple/test/ntuple_evolution_shape.cxx b/tree/ntuple/test/ntuple_evolution_shape.cxx index 6e8bb76b85054..4798893d8df64 100644 --- a/tree/ntuple/test/ntuple_evolution_shape.cxx +++ b/tree/ntuple/test/ntuple_evolution_shape.cxx @@ -150,6 +150,12 @@ struct AddedMemberObject { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedMemberObject").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -158,16 +164,6 @@ struct AddedMemberObject { ProcessLine("ptrAddedMemberObject->fMember1.fInt = 71;"); ProcessLine("ptrAddedMemberObject->fMember3.fInt = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -396,18 +392,14 @@ struct RenamedMemberClass { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RenamedMemberClass").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for members). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -491,6 +483,12 @@ struct AddedBaseDerived : public AddedBaseIntermediate { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -499,16 +497,6 @@ struct AddedBaseDerived : public AddedBaseIntermediate { ProcessLine("ptrAddedBaseDerived->fIntermediate = 82;"); ProcessLine("ptrAddedBaseDerived->fDerived = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -560,6 +548,12 @@ struct AddedSecondBaseDerived : public AddedSecondBaseFirst { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedSecondBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -568,16 +562,6 @@ struct AddedSecondBaseDerived : public AddedSecondBaseFirst { ProcessLine("ptrAddedSecondBaseDerived->fFirstBase = 71;"); ProcessLine("ptrAddedSecondBaseDerived->fDerived = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -623,18 +607,14 @@ struct PrependSecondBaseDerived : public PrependSecondBaseFirst { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "PrependSecondBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -678,18 +658,14 @@ struct AddedIntermediateDerived : public AddedIntermediateBase { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "AddedIntermediateDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -736,6 +712,12 @@ struct RemovedBaseDerived : public RemovedBaseIntermediate { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RemovedBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); @@ -745,16 +727,6 @@ struct RemovedBaseDerived : public RemovedBaseIntermediate { ProcessLine("ptrRemovedBaseDerived->fIntermediate = 82;"); ProcessLine("ptrRemovedBaseDerived->fDerived = 93;"); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -804,18 +776,14 @@ struct RemovedIntermediateDerived : public RemovedIntermediate { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RemovedIntermediateDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -859,18 +827,14 @@ struct RemovedSecondBaseDerived : public RemovedSecondBaseFirst, public RemovedS auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RemovedSecondBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -912,18 +876,14 @@ struct RenamedBaseDerived : public RenamedBase1 { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RenamedBaseDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( @@ -968,18 +928,14 @@ struct RenamedIntermediateDerived : public RenamedIntermediate1 { auto model = RNTupleModel::Create(); model->AddField(RFieldBase::Create("f", "RenamedIntermediateDerived").Unwrap()); + // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). + // See also https://github.com/root-project/root/issues/9371 + ROOT::TestSupport::CheckDiagsRAII diagRAII; + diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", + /*matchFullMessage=*/false); + auto writer = RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); writer->Fill(); - - // Reset / close the writer and flush the file. - { - // TStreamerInfo::Build will report a warning for interpreted classes (but only for base classes). - // See also https://github.com/root-project/root/issues/9371 - ROOT::TestSupport::CheckDiagsRAII diagRAII; - diagRAII.optionalDiag(kWarning, "TStreamerInfo::Build", "has no streamer or dictionary", - /*matchFullMessage=*/false); - writer.reset(); - } }); ASSERT_TRUE(gInterpreter->Declare(R"( diff --git a/tree/ntuple/test/rfield_class.cxx b/tree/ntuple/test/rfield_class.cxx index 7e8d9293d4eb8..cdb13f62b273d 100644 --- a/tree/ntuple/test/rfield_class.cxx +++ b/tree/ntuple/test/rfield_class.cxx @@ -4,9 +4,14 @@ #include #include #include +#include #include #include +#include +#include +#include +#include namespace { class RNoDictionary {}; @@ -387,3 +392,49 @@ TEST(RNTuple, TClassMetaName) EXPECT_STREQ("EdmWrapper", static_cast(f4->GetConstSubfields()[0])->GetClass()->GetName()); } + +TEST(RNTuple, StreamerInfoRecords) +{ + // Every testee consists of the type stored on disk and the expected streamer info records + std::vector>> testees{ + {"float", {}}, + {"std::vector", {}}, + {"std::pair", {}}, + {"std::map", {}}, + {"CustomStruct", {"CustomStruct"}}, + {"std::vector", {"CustomStruct"}}, + {"std::map", {"CustomStruct"}}, + {"DerivedA", {"DerivedA", "CustomStruct"}}, + {"std::pair", {"DerivedA", "CustomStruct"}}, + {"EdmWrapper", {"EdmWrapper"}}, + {"TRotation", {"TRotation"}}}; + + for (const auto &t : testees) { + FileRaii fileGuard("test_ntuple_streamer_info_records.root"); + + { + auto model = ROOT::RNTupleModel::Create(); + if (t.first == "TRotation") { + model->AddField(std::make_unique("f", t.first)); + } else { + model->AddField(ROOT::RFieldBase::Create("f", t.first).Unwrap()); + } + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + } + + auto f = std::unique_ptr(TFile::Open(fileGuard.GetPath().c_str())); + ASSERT_TRUE(f && !f->IsZombie()); + + std::unordered_set expectedInfos{t.second.begin(), t.second.end()}; + expectedInfos.insert("ROOT::RNTuple"); + for (const auto info : TRangeDynCast(*f->GetStreamerInfoList())) { + auto itr = expectedInfos.find(info->GetName()); + if (itr == expectedInfos.end()) { + FAIL() << "unexpected streamer info: " << info->GetName(); + continue; + } + expectedInfos.erase(itr); + } + EXPECT_TRUE(expectedInfos.empty()); + } +}