Skip to content
Open
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; // for field->Read() in LoadEntry()
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
246 changes: 246 additions & 0 deletions tree/ntuple/inc/ROOT/RNTupleAttrReading.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/// \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 <optional>
#include <utility>
#include <vector>

#include <ROOT/RNTupleFillContext.hxx>
#include <ROOT/RNTupleAttrUtils.hxx>
Comment thread
silverweed marked this conversation as resolved.
#include <ROOT/RNTupleUtils.hxx>

namespace ROOT {

class REntry;
class RNTupleDescriptor;
class RNTupleModel;

namespace Experimental {

class RNTupleAttrEntryIterable;

// clang-format off
/**
\class ROOT::Experimental::RNTupleAttrRange
\ingroup NTuple
\brief A range of main entries referred to by an attribute entry

Each attribute entry contains a set of values referring to 0 or more contiguous entries in the main RNTuple.
This class represents that contiguous range of entries.
*/
// 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 {fStart, fLength}; }
};

// 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 LoadEntry.

~~ {.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->LoadEntry(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::uint16_t vMajor, std::uint16_t vMinor);

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 LoadEntry.
/// 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 LoadEntry(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 LoadEntry(NTupleSize_t index, REntry &entry);

/// Returns the number of all attribute entries in this attribute set.
std::size_t GetNEntries() 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);
};

// clang-format off
/**
\class ROOT::Experimental::RNTupleAttrEntryIterable
\ingroup NTuple
\brief Iterable class used to loop over attribute entries.

This class allows to perform range-for iteration on some set of attributes, typically returned by the
RNTupleAttrSetReader::GetAttributes family of methods.

See the documentation of RNTupleAttrSetReader for example usage.
*/
// clang-format on
class RNTupleAttrEntryIterable final {
public:
struct RFilter {
RNTupleAttrRange fRange;
bool fIsContained;
};

private:
RNTupleAttrSetReader *fReader = nullptr;
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 SkipFiltered() 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 = SkipFiltered();
}
}
iterator operator++()
{
++fCur;
fCur = SkipFiltered();
return *this;
}
iterator operator++(int)
{
iterator it = *this;
operator++();
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
16 changes: 9 additions & 7 deletions tree/ntuple/inc/ROOT/RNTupleAttrUtils.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ The order and name of the meta Model's fields is defined by the schema version.
inline const std::uint16_t kSchemaVersionMajor = 1;
inline const std::uint16_t kSchemaVersionMinor = 0;

inline const char *const kRangeStartName = "_rangeStart";
inline const char *const kRangeLenName = "_rangeLen";
inline const char *const kUserDataName = "_userData";

inline constexpr std::size_t kRangeStartIndex = 0;
inline constexpr std::size_t kRangeLenIndex = 1;
inline constexpr std::size_t kUserDataIndex = 2;
enum : std::size_t {
kMetaFieldIndex_RangeStart,
kMetaFieldIndex_RangeLen,
kMetaFieldIndex_UserData,

kMetaFieldIndex_Count
};
inline constexpr const char *kMetaFieldNames[] = {"_rangeStart", "_rangeLen", "_userData"};
static_assert(kMetaFieldIndex_Count == sizeof(kMetaFieldNames) / sizeof(kMetaFieldNames[0]));

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

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

/// Looks for an attribute set with the given name and creates an RNTupleAttrSetReader for it, with the provided
/// read options.
/// The returned reader has an independent lifetime from this RNTupleReader.
std::unique_ptr<Experimental::RNTupleAttrSetReader>
OpenAttributeSet(std::string_view attrSetName, const ROOT::RNTupleReadOptions &options = {});
}; // class RNTupleReader

} // namespace ROOT
Expand Down
5 changes: 5 additions & 0 deletions tree/ntuple/inc/ROOT/RPageStorage.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,11 @@ 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;

/// Creates a new PageSource using the same underlying file as this but referring to a different RNTuple,
/// described by `anchorLink`.
virtual std::unique_ptr<RPageSource> OpenWithDifferentAnchor(const ROOT::Internal::RNTupleLink &anchorLink,
const ROOT::RNTupleReadOptions &options = {}) = 0;
}; // class RPageSource

} // namespace Internal
Expand Down
3 changes: 3 additions & 0 deletions tree/ntuple/inc/ROOT/RPageStorageDaos.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ public:
std::string GetObjectClass() const;

void LoadStreamerInfo() final;

std::unique_ptr<RPageSource> OpenWithDifferentAnchor(const ROOT::Internal::RNTupleLink &anchorLink,
const ROOT::RNTupleReadOptions &options = {}) final;
}; // class RPageSourceDaos

} // namespace Internal
Expand Down
6 changes: 2 additions & 4 deletions tree/ntuple/inc/ROOT/RPageStorageFile.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -188,10 +188,8 @@ 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());
std::unique_ptr<RPageSource> OpenWithDifferentAnchor(const ROOT::Internal::RNTupleLink &anchorLink,
const ROOT::RNTupleReadOptions &options = {}) final;

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