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
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>
#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);

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