Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions tree/ntuple/inc/ROOT/RMiniFile.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ class TVirtualStreamerInfo;

namespace ROOT {

namespace Internal {
class RRawFile;
}

class RNTupleWriteOptions;

namespace Internal {

class RRawFile;

/// Holds status information of an open ROOT file during writing
struct RTFileControlBlock;

Expand Down Expand Up @@ -68,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<RNTuple> GetNTupleProper(std::string_view ntuplePath);
/// Loads an RNTuple anchor from a TFile at the given file offset (unzipping it if necessary).
RResult<RNTuple>
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
Expand All @@ -84,6 +80,9 @@ public:
explicit RMiniFileReader(ROOT::Internal::RRawFile *rawFile);
/// Extracts header and footer location for the RNTuple identified by ntupleName
RResult<RNTuple> GetNTuple(std::string_view ntupleName);
/// Loads an RNTuple anchor from a TFile at the given file offset (unzipping it if necessary).
RResult<RNTuple>
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.
Expand Down
2 changes: 1 addition & 1 deletion tree/ntuple/inc/ROOT/RPageStorage.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ public:
/// The underlying `std::shared_mutex`, however, is neither read nor write recursive:
/// within one thread, only one lock (shared or exclusive) must be acquired at the same time. This requires special
/// care in sections protected by `GetSharedDescriptorGuard()` and `GetExclDescriptorGuard()` especially to avoid
/// that the locks are acquired indirectly (e.g. by a call to `GetNEntries()`). As a general guideline, no other
/// that the locks are acquired indirectly. As a general guideline, no other
/// method of the page source should be called (directly or indirectly) in a guarded section.
const RSharedDescriptorGuard GetSharedDescriptorGuard() const
{
Expand Down
3 changes: 2 additions & 1 deletion tree/ntuple/inc/ROOT/RPageStorageDaos.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ struct RDaosNTupleAnchor {
/// The object class for user data OIDs, e.g. `SX`
std::string fObjClass{};

bool operator ==(const RDaosNTupleAnchor &other) const {
bool operator==(const RDaosNTupleAnchor &other) const
{
return fVersionAnchor == other.fVersionAnchor && fVersionEpoch == other.fVersionEpoch &&
fVersionMajor == other.fVersionMajor && fVersionMinor == other.fVersionMinor &&
fVersionPatch == other.fVersionPatch && fNBytesHeader == other.fNBytesHeader &&
Expand Down
5 changes: 5 additions & 0 deletions tree/ntuple/inc/ROOT/RPageStorageFile.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -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<RPageSourceFile>
OpenWithDifferentAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options = ROOT::RNTupleReadOptions());

void
LoadSealedPage(ROOT::DescriptorId_t physicalColumnId, RNTupleLocalIndex localIndex, RSealedPage &sealedPage) final;

Expand Down
32 changes: 20 additions & 12 deletions tree/ntuple/src/RPageStorageFile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,15 @@ ROOT::Internal::RPageSourceFile::CreateFromAnchor(const RNTuple &anchor, const R

ROOT::Internal::RPageSourceFile::~RPageSourceFile() = default;

std::unique_ptr<ROOT::Internal::RPageSourceFile>
ROOT::Internal::RPageSourceFile::OpenWithDifferentAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options)
{
auto pageSource = std::make_unique<RPageSourceFile>("", 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.
Expand Down Expand Up @@ -552,18 +561,17 @@ ROOT::Internal::RPageSourceFile::PrepareSingleCluster(const RCluster::RKey &clus
std::vector<ROnDiskPageLocator> onDiskPages;
auto activeSize = 0;
auto pageZeroMap = std::make_unique<ROnDiskPageMap>();
PrepareLoadCluster(clusterKey, *pageZeroMap,
[&](ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t pageNo,
const ROOT::RClusterDescriptor::RPageInfo &pageInfo) {
const auto &pageLocator = pageInfo.GetLocator();
if (pageLocator.GetType() == RNTupleLocator::kTypeUnknown)
throw RException(R__FAIL("tried to read a page with an unknown locator"));
const auto nBytes =
pageLocator.GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum;
activeSize += nBytes;
onDiskPages.push_back(
{physicalColumnId, pageNo, pageLocator.GetPosition<std::uint64_t>(), nBytes, 0});
});
PrepareLoadCluster(
clusterKey, *pageZeroMap,
[&](ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t pageNo,
const ROOT::RClusterDescriptor::RPageInfo &pageInfo) {
const auto &pageLocator = pageInfo.GetLocator();
if (pageLocator.GetType() == RNTupleLocator::kTypeUnknown)
throw RException(R__FAIL("tried to read a page with an unknown locator"));
const auto nBytes = pageLocator.GetNBytesOnStorage() + pageInfo.HasChecksum() * kNBytesPageChecksum;
activeSize += nBytes;
onDiskPages.push_back({physicalColumnId, pageNo, pageLocator.GetPosition<std::uint64_t>(), nBytes, 0});
});

// Linearize the page requests by file offset
std::sort(onDiskPages.begin(), onDiskPages.end(),
Expand Down
59 changes: 59 additions & 0 deletions tree/ntuple/test/ntuple_storage.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -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<float>("f");
auto file = std::unique_ptr<TFile>(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<int>("i");
auto pC = model->MakeField<char>("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<RPageSourceFile>("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<ROOT::RNTuple>("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);
}
}
Loading