diff --git a/tree/ntuple/inc/ROOT/RMiniFile.hxx b/tree/ntuple/inc/ROOT/RMiniFile.hxx index 2345716f6e626..c4199b9bfa37f 100644 --- a/tree/ntuple/inc/ROOT/RMiniFile.hxx +++ b/tree/ntuple/inc/ROOT/RMiniFile.hxx @@ -67,9 +67,6 @@ private: /// Used when the file turns out to be a TFile container. The ntuplePath variable is either the ntuple name /// or an ntuple name preceded by a directory (`myNtuple` or `foo/bar/myNtuple` or `/foo/bar/myNtuple`) RResult GetNTupleProper(std::string_view ntuplePath); - /// Loads an RNTuple anchor from a TFile at the given file offset (unzipping it if necessary). - RResult - GetNTupleProperAtOffset(std::uint64_t payloadOffset, std::uint64_t compSize, std::uint64_t uncompLen); /// Searches for a key with the given name and type in the key index of the directory starting at offsetDir. /// The offset points to the start of the TDirectory DATA section, without the key and without the name and title @@ -83,6 +80,9 @@ public: explicit RMiniFileReader(ROOT::Internal::RRawFile *rawFile); /// Extracts header and footer location for the RNTuple identified by ntupleName RResult GetNTuple(std::string_view ntupleName); + /// Loads an RNTuple anchor from a TFile at the given file offset (unzipping it if necessary). + RResult + GetNTupleProperAtOffset(std::uint64_t payloadOffset, std::uint64_t compSize, std::uint64_t uncompLen); /// Reads a given byte range from the file into the provided memory buffer. /// If `nbytes > fMaxKeySize` it will perform chunked read from multiple blobs, /// whose addresses are listed at the end of the first chunk. diff --git a/tree/ntuple/inc/ROOT/RPageStorageFile.hxx b/tree/ntuple/inc/ROOT/RPageStorageFile.hxx index bbef8c11b0efc..a3d98ef2a0abd 100644 --- a/tree/ntuple/inc/ROOT/RPageStorageFile.hxx +++ b/tree/ntuple/inc/ROOT/RPageStorageFile.hxx @@ -179,6 +179,11 @@ public: RPageSourceFile &operator=(RPageSourceFile &&) = delete; ~RPageSourceFile() override; + /// Creates a new PageSourceFile using the same underlying file as this but referring to a different RNTuple, + /// represented by `anchor`. + std::unique_ptr + OpenWithDifferentAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options = ROOT::RNTupleReadOptions()); + void LoadSealedPage(ROOT::DescriptorId_t physicalColumnId, RNTupleLocalIndex localIndex, RSealedPage &sealedPage) final; diff --git a/tree/ntuple/src/RPageStorageFile.cxx b/tree/ntuple/src/RPageStorageFile.cxx index dd60247c1658a..2c984c15269b5 100644 --- a/tree/ntuple/src/RPageStorageFile.cxx +++ b/tree/ntuple/src/RPageStorageFile.cxx @@ -350,6 +350,15 @@ ROOT::Internal::RPageSourceFile::CreateFromAnchor(const RNTuple &anchor, const R ROOT::Internal::RPageSourceFile::~RPageSourceFile() = default; +std::unique_ptr +ROOT::Internal::RPageSourceFile::OpenWithDifferentAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options) +{ + auto pageSource = std::make_unique("", fFile->Clone(), options); + pageSource->fAnchor = anchor; + // NOTE: fNTupleName gets set only upon Attach(). + return pageSource; +} + void ROOT::Internal::RPageSourceFile::LoadStructureImpl() { // If we constructed the page source with (ntuple name, path), we need to find the anchor first. diff --git a/tree/ntuple/test/ntuple_storage.cxx b/tree/ntuple/test/ntuple_storage.cxx index 0274258512d03..f97ee8d40fb3c 100644 --- a/tree/ntuple/test/ntuple_storage.cxx +++ b/tree/ntuple/test/ntuple_storage.cxx @@ -1145,3 +1145,62 @@ TEST(RPageSourceFile, NameFromAnchor) source->Attach(); EXPECT_EQ(source->GetNTupleName(), "ntpl"); } + +TEST(RPageSourceFile, OpenDifferentAnchor) +{ + FileRaii fileGuard("test_ntuple_open_diff_anchor.root"); + + auto model = RNTupleModel::Create(); + auto pF = model->MakeField("f"); + auto file = std::unique_ptr(TFile::Open(fileGuard.GetPath().c_str(), "RECREATE")); + { + auto writer = RNTupleWriter::Append(std::move(model), "ntpl1", *file); + for (auto i = 0; i < 100; ++i) { + *pF = i; + writer->Fill(); + } + } + { + model = RNTupleModel::Create(); + auto pI = model->MakeField("i"); + auto pC = model->MakeField("c"); + + auto writer = RNTupleWriter::Append(std::move(model), "ntpl2", *file); + for (auto i = 0; i < 20; ++i) { + *pI = i; + *pC = i; + writer->Fill(); + } + } + + auto source = std::make_unique("ntpl1", fileGuard.GetPath(), RNTupleReadOptions()); + source->Attach(); + EXPECT_EQ(source->GetNEntries(), 100); + { + auto desc = source->GetSharedDescriptorGuard(); + EXPECT_NE(desc->FindFieldId("f"), ROOT::kInvalidDescriptorId); + } + + auto anchor2 = file->Get("ntpl2"); + ASSERT_NE(anchor2, nullptr); + auto source2 = source->OpenWithDifferentAnchor(*anchor2); + source2->Attach(); + EXPECT_EQ(source2->GetNTupleName(), "ntpl2"); + EXPECT_EQ(source2->GetNEntries(), 20); + { + auto desc2 = source2->GetSharedDescriptorGuard(); + EXPECT_EQ(desc2->FindFieldId("f"), ROOT::kInvalidDescriptorId); + EXPECT_NE(desc2->FindFieldId("i"), ROOT::kInvalidDescriptorId); + EXPECT_NE(desc2->FindFieldId("c"), ROOT::kInvalidDescriptorId); + } + + source.reset(); + // source2 should still be valid after dropping the first source. + EXPECT_EQ(source2->GetNEntries(), 20); + { + auto desc2 = source2->GetSharedDescriptorGuard(); + EXPECT_EQ(desc2->FindFieldId("f"), ROOT::kInvalidDescriptorId); + EXPECT_NE(desc2->FindFieldId("i"), ROOT::kInvalidDescriptorId); + EXPECT_NE(desc2->FindFieldId("c"), ROOT::kInvalidDescriptorId); + } +}