Skip to content
Draft
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
2 changes: 2 additions & 0 deletions tree/ntuple/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ HEADERS
ROOT/RFieldVisitor.hxx
ROOT/RMiniFile.hxx
ROOT/RNTuple.hxx
ROOT/RNTupleAttrReading.hxx
ROOT/RNTupleAttrUtils.hxx
ROOT/RNTupleAttrWriting.hxx
ROOT/RNTupleDescriptor.hxx
Expand Down Expand Up @@ -68,6 +69,7 @@ SOURCES
src/RFieldVisitor.cxx
src/RMiniFile.cxx
src/RNTuple.cxx
src/RNTupleAttrReading.cxx
src/RNTupleAttrWriting.cxx
src/RNTupleDescriptor.cxx
src/RNTupleDescriptorFmt.cxx
Expand Down
7 changes: 6 additions & 1 deletion tree/ntuple/inc/ROOT/REntry.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,13 @@ namespace ROOT {
class RNTupleFillContext;
class RNTupleReader;

namespace Experimental::Internal {
namespace Experimental {
class RNTupleAttrSetReader;

namespace Internal {
struct RNTupleAttrEntry;
}
} // namespace Experimental

// clang-format off
/**
Expand All @@ -52,6 +56,7 @@ class REntry {
friend class RNTupleFillContext;
friend class RNTupleModel;
friend class RNTupleReader;
friend class Experimental::RNTupleAttrSetReader;
friend struct Experimental::Internal::RNTupleAttrEntry;

private:
Expand Down
5 changes: 5 additions & 0 deletions tree/ntuple/inc/ROOT/RFieldBase.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class RFieldVisitor;
class RRawPtrWriteEntry;
} // namespace Detail

namespace Experimental {
class RNTupleAttrSetReader;
}

namespace Internal {

class RPageSink;
Expand Down Expand Up @@ -84,6 +88,7 @@ This is and can only be partially enforced through C++.
class RFieldBase {
friend class RFieldZero; // to reset fParent pointer in ReleaseSubfields()
friend class ROOT::Detail::RRawPtrWriteEntry; // to call Append()
friend class ROOT::Experimental::RNTupleAttrSetReader;
friend struct ROOT::Internal::RFieldCallbackInjector; // used for unit tests
friend struct ROOT::Internal::RFieldRepresentationModifier; // used for unit tests
friend void Internal::CallFlushColumnsOnField(RFieldBase &);
Expand Down
180 changes: 180 additions & 0 deletions tree/ntuple/inc/ROOT/RNTupleAttrReading.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/// \file ROOT/RNTupleAttrReading.hxx
/// \ingroup NTuple
/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
/// \date 2026-04-01
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
/// is welcome!

#ifndef ROOT7_RNTuple_Attr_Reading
#define ROOT7_RNTuple_Attr_Reading

#include <memory>
#include <vector>

#include <ROOT/RNTupleFillContext.hxx>
#include <ROOT/RNTupleAttrUtils.hxx>

namespace ROOT {

class REntry;
class RNTupleDescriptor;
class RNTupleModel;

namespace Experimental {

class RNTupleAttrEntryIterable;

// clang-format off
/**
\class ROOT::Experimental::RNTupleAttrSetReader
\ingroup NTuple
\brief Class used to read a RNTupleAttrSet in the context of a RNTupleReader

An RNTupleAttrSetReader is created via RNTupleReader::OpenAttributeSet. Once created, it may outlive its parent Reader.
Reading Attributes works similarly to reading regular RNTuple entries: you can either create entries or just use the
AttrSetReader Model's default entry and load data into it via LoadAttrEntry.

~~ {.cpp}
// Reading Attributes via RNTupleAttrSetReader
// -------------------------------------------

// Assuming `reader` is a RNTupleReader:
auto attrSet = reader->OpenAttributeSet("MyAttrSet");

// Just like how you would read a regular RNTuple, first get the pointer to the fields you want to read:
auto &attrEntry = attrSet->GetModel().GetDefaultEntry();
auto pAttr = attrEntry->GetPtr<std::string>("myAttr");

// Then select which attributes you want to read. E.g. read all attributes linked to the entry at index 10:
for (auto idx : attrSet->GetAttributes(10)) {
attrSet->LoadAttrEntry(idx);
cout << "entry " << idx << " has attribute " << *pAttr << "\n";
}
~~
*/
// clang-format on
class RNTupleAttrSetReader final {
friend class ROOT::RNTupleReader;
friend class RNTupleAttrEntryIterable;

/// List containing pairs { entryRange, entryIndex }, used to quickly find out which entries in the Attribute
/// RNTuple contain entries that overlap a given range. The list is sorted by range start, i.e.
/// entryRange.first.Start().
std::vector<std::pair<RNTupleAttrRange, NTupleSize_t>> fEntryRanges;
/// The internal Reader used to read the AttributeSet RNTuple
std::unique_ptr<RNTupleReader> fReader;
/// The reconstructed user model
std::unique_ptr<ROOT::RNTupleModel> fUserModel;

static bool EntryRangesAreSorted(const decltype(fEntryRanges) &ranges);

explicit RNTupleAttrSetReader(std::unique_ptr<RNTupleReader> reader);

std::vector<ROOT::NTupleSize_t>
GetAttributesRangeInternal(NTupleSize_t startEntry, NTupleSize_t endEntry, bool rangeIsContained);

public:
RNTupleAttrSetReader(const RNTupleAttrSetReader &) = delete;
RNTupleAttrSetReader &operator=(const RNTupleAttrSetReader &) = delete;
RNTupleAttrSetReader(RNTupleAttrSetReader &&) = default;
RNTupleAttrSetReader &operator=(RNTupleAttrSetReader &&) = default;
~RNTupleAttrSetReader() = default;

/// Returns the read-only descriptor of this attribute set
const ROOT::RNTupleDescriptor &GetDescriptor() const;
/// Returns the read-only model of this attribute set
const ROOT::RNTupleModel &GetModel() const { return *fUserModel; }

/// Creates an entry suitable for use with LoadAttrEntry.
/// This is a convenience method equivalent to GetModel().CreateEntry().
std::unique_ptr<REntry> CreateEntry();

/// Loads the attribute entry at position `index` into the default entry.
/// Returns the range of main RNTuple entries that the loaded set of attributes refers to.
RNTupleAttrRange LoadAttrEntry(NTupleSize_t index);
/// Loads the attribute entry at position `index` into the given entry.
/// Returns the range of main RNTuple entries that the loaded set of attributes refers to.
RNTupleAttrRange LoadAttrEntry(NTupleSize_t index, REntry &entry);

/// Returns the number of all attribute entries in this attribute set.
std::size_t GetNAttrEntries() const { return fEntryRanges.size(); }

/// Returns all the attributes in this Set. The returned attributes are sorted by entry range start.
RNTupleAttrEntryIterable GetAttributes();
/// Returns all the attributes whose range contains index `entryIndex`.
RNTupleAttrEntryIterable GetAttributes(NTupleSize_t entryIndex);
/// Returns all the attributes whose range fully contains `[startEntry, endEntry)`
RNTupleAttrEntryIterable GetAttributesContainingRange(NTupleSize_t startEntry, NTupleSize_t endEntry);
/// Returns all the attributes whose range is fully contained in `[startEntry, endEntry)`
RNTupleAttrEntryIterable GetAttributesInRange(NTupleSize_t startEntry, NTupleSize_t endEntry);
};

class RNTupleAttrEntryIterable final {
public:
struct RFilter {
RNTupleAttrRange fRange;
bool fIsContained;
};

private:
RNTupleAttrSetReader &fReader;
std::optional<RFilter> fFilter;

public:
class RIterator final {
private:
using Iter_t = decltype(std::declval<RNTupleAttrSetReader>().fEntryRanges.begin());
Iter_t fCur, fEnd;
std::optional<RFilter> fFilter;

Iter_t Next() const;
bool FullyContained(RNTupleAttrRange range) const;

public:
using iterator_category = std::forward_iterator_tag;
using iterator = RIterator;
using value_type = NTupleSize_t;
using difference_type = std::ptrdiff_t;
using pointer = const value_type *;
using reference = const value_type &;

RIterator(Iter_t iter, Iter_t end, std::optional<RFilter> filter) : fCur(iter), fEnd(end), fFilter(filter)
{
if (fFilter) {
if (fFilter->fRange.GetLength() == 0)
fCur = end;
else
fCur = Next();
}
}
iterator operator++()
{
++fCur;
fCur = Next();
return *this;
}
iterator operator++(int)
{
iterator it = *this;
++fCur;
fCur = Next();
return it;
}
reference operator*() { return fCur->second; }
bool operator!=(const iterator &rh) const { return !operator==(rh); }
bool operator==(const iterator &rh) const { return fCur == rh.fCur; }
};

explicit RNTupleAttrEntryIterable(RNTupleAttrSetReader &reader, std::optional<RFilter> filter = {})
: fReader(reader), fFilter(filter)
{
}

RIterator begin() { return RIterator{fReader.fEntryRanges.begin(), fReader.fEntryRanges.end(), fFilter}; }
RIterator end() { return RIterator{fReader.fEntryRanges.end(), fReader.fEntryRanges.end(), fFilter}; }
};

} // namespace Experimental
} // namespace ROOT

#endif
64 changes: 62 additions & 2 deletions tree/ntuple/inc/ROOT/RNTupleAttrUtils.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@

#include <cstddef>
#include <cstdint>
#include <optional>

namespace ROOT::Experimental::Internal::RNTupleAttributes {
#include <ROOT/RNTupleTypes.hxx>
#include <TError.h>

namespace ROOT::Experimental {

namespace Internal::RNTupleAttributes {

/*
Attribute RNTuples have a fixed Model schema that looks like this:
Expand Down Expand Up @@ -47,6 +53,60 @@ inline constexpr std::size_t kRangeStartIndex = 0;
inline constexpr std::size_t kRangeLenIndex = 1;
inline constexpr std::size_t kUserModelIndex = 2;

} // namespace ROOT::Experimental::Internal::RNTupleAttributes
} // namespace Internal::RNTupleAttributes

// clang-format off
/**
\class ROOT::Experimental::RNTupleAttrRange
\ingroup NTuple
\brief A range of entries linked to an Attribute Entry
*/
// clang-format on
class RNTupleAttrRange final {
ROOT::NTupleSize_t fStart = 0;
ROOT::NTupleSize_t fLength = 0;

RNTupleAttrRange(ROOT::NTupleSize_t start, ROOT::NTupleSize_t length) : fStart(start), fLength(length) {}

public:
static RNTupleAttrRange FromStartLength(ROOT::NTupleSize_t start, ROOT::NTupleSize_t length)
{
return RNTupleAttrRange{start, length};
}

/// Creates an AttributeRange from [start, end), where `end` is one past the last valid entry of the range
/// (`FromStartEnd(0, 10)` will create a range whose last valid index is 9).
static RNTupleAttrRange FromStartEnd(ROOT::NTupleSize_t start, ROOT::NTupleSize_t end)
{
R__ASSERT(end >= start);
return RNTupleAttrRange{start, end - start};
}

RNTupleAttrRange() = default;

/// Returns the first valid entry index in the range. Returns nullopt if the range has zero length.
std::optional<ROOT::NTupleSize_t> GetFirst() const { return fLength ? std::make_optional(fStart) : std::nullopt; }
/// Returns the beginning of the range. Note that this is *not* a valid index in the range if the range has zero
/// length.
ROOT::NTupleSize_t GetStart() const { return fStart; }
/// Returns the last valid entry index in the range. Returns nullopt if the range has zero length.
std::optional<ROOT::NTupleSize_t> GetLast() const
{
return fLength ? std::make_optional(fStart + fLength - 1) : std::nullopt;
}
/// Returns one past the last valid index of the range, equal to `GetStart() + GetLength()`.
ROOT::NTupleSize_t GetEnd() const { return fStart + fLength; }
ROOT::NTupleSize_t GetLength() const { return fLength; }

/// Returns the pair { firstEntryIdx, lastEntryIdx } (inclusive). Returns nullopt if the range has zero length.
std::optional<std::pair<ROOT::NTupleSize_t, ROOT::NTupleSize_t>> GetFirstLast() const
{
return fLength ? std::make_optional(std::make_pair(fStart, fStart + fLength - 1)) : std::nullopt;
}
/// Returns the pair { start, length }.
std::pair<ROOT::NTupleSize_t, ROOT::NTupleSize_t> GetStartLength() const { return {GetStart(), GetLength()}; }
};

} // namespace ROOT::Experimental

#endif
2 changes: 2 additions & 0 deletions tree/ntuple/inc/ROOT/RNTupleReader.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,8 @@ public:
/// ~~~
void EnableMetrics() { fMetrics.Enable(); }
const Experimental::Detail::RNTupleMetrics &GetMetrics() const { return fMetrics; }

std::unique_ptr<Experimental::RNTupleAttrSetReader> OpenAttributeSet(std::string_view attrSetName);
}; // class RNTupleReader

} // namespace ROOT
Expand Down
4 changes: 4 additions & 0 deletions tree/ntuple/inc/ROOT/RPageStorage.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,10 @@ public:
/// Forces the loading of ROOT StreamerInfo from the underlying file. This currently only has an effect for
/// TFile-backed sources.
virtual void LoadStreamerInfo() = 0;

/// Reads an attribute set at the given location and returns a page source for it.
virtual std::unique_ptr<ROOT::Internal::RPageSource>
ReadAttributeSet(ROOT::RNTupleLocator anchorLocator, std::uint64_t anchorUncompLen);
}; // class RPageSource

} // namespace Internal
Expand Down
3 changes: 3 additions & 0 deletions tree/ntuple/inc/ROOT/RPageStorageFile.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ public:
LoadClusters(std::span<ROOT::Internal::RCluster::RKey> clusterKeys) final;

void LoadStreamerInfo() final;

std::unique_ptr<ROOT::Internal::RPageSource>
ReadAttributeSet(ROOT::RNTupleLocator anchorLocator, std::uint64_t anchorUncompLen) final;
}; // class RPageSourceFile

} // namespace Internal
Expand Down
Loading
Loading