Skip to content

Commit 047a4e8

Browse files
committed
[ntuple] Start introducing RNTupleAttrSetReader
1 parent e8f1237 commit 047a4e8

6 files changed

Lines changed: 252 additions & 2 deletions

File tree

tree/ntuple/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ HEADERS
2323
ROOT/RFieldVisitor.hxx
2424
ROOT/RMiniFile.hxx
2525
ROOT/RNTuple.hxx
26+
ROOT/RNTupleAttrReading.hxx
2627
ROOT/RNTupleAttrUtils.hxx
2728
ROOT/RNTupleAttrWriting.hxx
2829
ROOT/RNTupleDescriptor.hxx
@@ -68,6 +69,7 @@ SOURCES
6869
src/RFieldVisitor.cxx
6970
src/RMiniFile.cxx
7071
src/RNTuple.cxx
72+
src/RNTupleAttrReading.cxx
7173
src/RNTupleAttrWriting.cxx
7274
src/RNTupleDescriptor.cxx
7375
src/RNTupleDescriptorFmt.cxx

tree/ntuple/inc/ROOT/REntry.hxx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ namespace ROOT {
3434
class RNTupleFillContext;
3535
class RNTupleReader;
3636

37-
namespace Experimental::Internal {
37+
namespace Experimental {
38+
class RNTupleAttrSetReader;
39+
40+
namespace Internal {
3841
struct RNTupleAttrEntry;
3942
}
43+
} // namespace Experimental
4044

4145
// clang-format off
4246
/**
@@ -52,6 +56,7 @@ class REntry {
5256
friend class RNTupleFillContext;
5357
friend class RNTupleModel;
5458
friend class RNTupleReader;
59+
friend class Experimental::RNTupleAttrSetReader;
5560
friend struct Experimental::Internal::RNTupleAttrEntry;
5661

5762
private:

tree/ntuple/inc/ROOT/RFieldBase.hxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class RFieldVisitor;
4444
class RRawPtrWriteEntry;
4545
} // namespace Detail
4646

47+
namespace Experimental {
48+
class RNTupleAttrSetReader;
49+
}
50+
4751
namespace Internal {
4852

4953
class RPageSink;
@@ -84,6 +88,7 @@ This is and can only be partially enforced through C++.
8488
class RFieldBase {
8589
friend class RFieldZero; // to reset fParent pointer in ReleaseSubfields()
8690
friend class ROOT::Detail::RRawPtrWriteEntry; // to call Append()
91+
friend class ROOT::Experimental::RNTupleAttrSetReader; // for field->Read() in LoadEntry()
8792
friend struct ROOT::Internal::RFieldCallbackInjector; // used for unit tests
8893
friend struct ROOT::Internal::RFieldRepresentationModifier; // used for unit tests
8994
friend void Internal::CallFlushColumnsOnField(RFieldBase &);
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/// \file ROOT/RNTupleAttrReading.hxx
2+
/// \ingroup NTuple
3+
/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
4+
/// \date 2026-04-01
5+
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6+
/// is welcome!
7+
8+
#ifndef ROOT7_RNTuple_Attr_Reading
9+
#define ROOT7_RNTuple_Attr_Reading
10+
11+
#include <memory>
12+
#include <vector>
13+
14+
#include <ROOT/RNTupleFillContext.hxx>
15+
#include <ROOT/RNTupleAttrUtils.hxx>
16+
17+
namespace ROOT {
18+
19+
class REntry;
20+
class RNTupleDescriptor;
21+
class RNTupleModel;
22+
23+
namespace Experimental {
24+
25+
// clang-format off
26+
/**
27+
\class ROOT::Experimental::RNTupleAttrRange
28+
\ingroup NTuple
29+
\brief A range of main entries referred to by an attribute entry
30+
31+
Each attribute entry contains a set of values referring to 0 or more contiguous entries in the main RNTuple.
32+
This class represents that contiguous range of entries.
33+
*/
34+
// clang-format on
35+
class RNTupleAttrRange final {
36+
ROOT::NTupleSize_t fStart = 0;
37+
ROOT::NTupleSize_t fLength = 0;
38+
39+
RNTupleAttrRange(ROOT::NTupleSize_t start, ROOT::NTupleSize_t length) : fStart(start), fLength(length) {}
40+
41+
public:
42+
static RNTupleAttrRange FromStartLength(ROOT::NTupleSize_t start, ROOT::NTupleSize_t length)
43+
{
44+
return RNTupleAttrRange{start, length};
45+
}
46+
47+
/// Creates an AttributeRange from [start, end), where `end` is one past the last valid entry of the range
48+
/// (`FromStartEnd(0, 10)` will create a range whose last valid index is 9).
49+
static RNTupleAttrRange FromStartEnd(ROOT::NTupleSize_t start, ROOT::NTupleSize_t end)
50+
{
51+
R__ASSERT(end >= start);
52+
return RNTupleAttrRange{start, end - start};
53+
}
54+
55+
RNTupleAttrRange() = default;
56+
57+
/// Returns the first valid entry index in the range. Returns nullopt if the range has zero length.
58+
std::optional<ROOT::NTupleSize_t> GetFirst() const { return fLength ? std::make_optional(fStart) : std::nullopt; }
59+
/// Returns the beginning of the range. Note that this is *not* a valid index in the range if the range has zero
60+
/// length.
61+
ROOT::NTupleSize_t GetStart() const { return fStart; }
62+
/// Returns the last valid entry index in the range. Returns nullopt if the range has zero length.
63+
std::optional<ROOT::NTupleSize_t> GetLast() const
64+
{
65+
return fLength ? std::make_optional(fStart + fLength - 1) : std::nullopt;
66+
}
67+
/// Returns one past the last valid index of the range, equal to `GetStart() + GetLength()`.
68+
ROOT::NTupleSize_t GetEnd() const { return fStart + fLength; }
69+
ROOT::NTupleSize_t GetLength() const { return fLength; }
70+
71+
/// Returns the pair { firstEntryIdx, lastEntryIdx } (inclusive). Returns nullopt if the range has zero length.
72+
std::optional<std::pair<ROOT::NTupleSize_t, ROOT::NTupleSize_t>> GetFirstLast() const
73+
{
74+
return fLength ? std::make_optional(std::make_pair(fStart, fStart + fLength - 1)) : std::nullopt;
75+
}
76+
/// Returns the pair { start, length }.
77+
std::pair<ROOT::NTupleSize_t, ROOT::NTupleSize_t> GetStartLength() const { return {GetStart(), GetLength()}; }
78+
};
79+
80+
class RNTupleAttrEntryIterable;
81+
82+
// clang-format off
83+
/**
84+
\class ROOT::Experimental::RNTupleAttrSetReader
85+
\ingroup NTuple
86+
\brief Class used to read a RNTupleAttrSet in the context of a RNTupleReader
87+
88+
An RNTupleAttrSetReader is created via RNTupleReader::OpenAttributeSet. Once created, it may outlive its parent Reader.
89+
Reading Attributes works similarly to reading regular RNTuple entries: you can either create entries or just use the
90+
AttrSetReader Model's default entry and load data into it via LoadEntry.
91+
92+
~~ {.cpp}
93+
// Reading Attributes via RNTupleAttrSetReader
94+
// -------------------------------------------
95+
96+
// Assuming `reader` is a RNTupleReader:
97+
auto attrSet = reader->OpenAttributeSet("MyAttrSet");
98+
99+
// Just like how you would read a regular RNTuple, first get the pointer to the fields you want to read:
100+
auto &attrEntry = attrSet->GetModel().GetDefaultEntry();
101+
auto pAttr = attrEntry->GetPtr<std::string>("myAttr");
102+
103+
// Then select which attributes you want to read. E.g. read all attributes linked to the entry at index 10:
104+
for (auto idx : attrSet->GetAttributes(10)) {
105+
attrSet->LoadEntry(idx);
106+
cout << "entry " << idx << " has attribute " << *pAttr << "\n";
107+
}
108+
~~
109+
*/
110+
// clang-format on
111+
class RNTupleAttrSetReader final {
112+
friend class ROOT::RNTupleReader;
113+
114+
/// List containing pairs { entryRange, entryIndex }, used to quickly find out which entries in the Attribute
115+
/// RNTuple contain entries that overlap a given range. The list is sorted by range start, i.e.
116+
/// entryRange.first.Start().
117+
std::vector<std::pair<RNTupleAttrRange, NTupleSize_t>> fEntryRanges;
118+
/// The internal Reader used to read the AttributeSet RNTuple
119+
std::unique_ptr<RNTupleReader> fReader;
120+
/// The reconstructed user model
121+
std::unique_ptr<ROOT::RNTupleModel> fUserModel;
122+
123+
explicit RNTupleAttrSetReader(std::unique_ptr<RNTupleReader> reader);
124+
125+
public:
126+
RNTupleAttrSetReader(const RNTupleAttrSetReader &) = delete;
127+
RNTupleAttrSetReader &operator=(const RNTupleAttrSetReader &) = delete;
128+
RNTupleAttrSetReader(RNTupleAttrSetReader &&) = default;
129+
RNTupleAttrSetReader &operator=(RNTupleAttrSetReader &&) = default;
130+
~RNTupleAttrSetReader() = default;
131+
132+
/// Returns the read-only descriptor of this attribute set
133+
const ROOT::RNTupleDescriptor &GetDescriptor() const;
134+
/// Returns the read-only model of this attribute set
135+
const ROOT::RNTupleModel &GetModel() const { return *fUserModel; }
136+
137+
/// Creates an entry suitable for use with LoadEntry.
138+
/// This is a convenience method equivalent to GetModel().CreateEntry().
139+
std::unique_ptr<REntry> CreateEntry();
140+
141+
/// Loads the attribute entry at position `index` into the default entry.
142+
/// Returns the range of main RNTuple entries that the loaded set of attributes refers to.
143+
RNTupleAttrRange LoadEntry(NTupleSize_t index);
144+
/// Loads the attribute entry at position `index` into the given entry.
145+
/// Returns the range of main RNTuple entries that the loaded set of attributes refers to.
146+
RNTupleAttrRange LoadEntry(NTupleSize_t index, REntry &entry);
147+
148+
/// Returns the number of all attribute entries in this attribute set.
149+
std::size_t GetNEntries() const { return fEntryRanges.size(); }
150+
};
151+
152+
} // namespace Experimental
153+
} // namespace ROOT
154+
155+
#endif
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/// \file RNTupleAttrReading.cxx
2+
/// \ingroup NTuple
3+
/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
4+
/// \date 2026-04-01
5+
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6+
/// is welcome!
7+
8+
#include <ROOT/RNTupleAttrReading.hxx>
9+
#include <ROOT/RNTupleAttrUtils.hxx>
10+
#include <ROOT/RNTupleReader.hxx>
11+
12+
using namespace ROOT::Experimental::Internal::RNTupleAttributes;
13+
14+
ROOT::Experimental::RNTupleAttrSetReader::RNTupleAttrSetReader(std::unique_ptr<RNTupleReader> reader)
15+
: fReader(std::move(reader))
16+
{
17+
// Initialize user model
18+
fUserModel = RNTupleModel::Create();
19+
const auto *userFieldRoot = fReader->GetModel().GetConstFieldZero().GetConstSubfields()[kUserModelIndex];
20+
for (const auto *field : userFieldRoot->GetConstSubfields()) {
21+
fUserModel->AddField(field->Clone(field->GetFieldName()));
22+
}
23+
fUserModel->Freeze();
24+
25+
// Collect all entry ranges
26+
auto entryRangeStartView = fReader->GetView<ROOT::NTupleSize_t>(kRangeStartName);
27+
auto entryRangeLenView = fReader->GetView<ROOT::NTupleSize_t>(kRangeLenName);
28+
fEntryRanges.reserve(fReader->GetNEntries());
29+
for (auto i : fReader->GetEntryRange()) {
30+
auto start = entryRangeStartView(i);
31+
auto len = entryRangeLenView(i);
32+
fEntryRanges.push_back({RNTupleAttrRange::FromStartLength(start, len), i});
33+
}
34+
35+
std::sort(fEntryRanges.begin(), fEntryRanges.end(),
36+
[](const auto &a, const auto &b) { return a.first.GetStart() < b.first.GetStart(); });
37+
38+
R__LOG_INFO(ROOT::Internal::NTupleLog()) << "Loaded " << fEntryRanges.size() << " attribute entries.";
39+
}
40+
41+
const ROOT::RNTupleDescriptor &ROOT::Experimental::RNTupleAttrSetReader::GetDescriptor() const
42+
{
43+
return fReader->GetDescriptor();
44+
}
45+
46+
ROOT::Experimental::RNTupleAttrRange
47+
ROOT::Experimental::RNTupleAttrSetReader::LoadEntry(ROOT::NTupleSize_t index, REntry &entry)
48+
{
49+
auto &metaModel = const_cast<ROOT::RNTupleModel &>(fReader->GetModel());
50+
auto &metaEntry = metaModel.GetDefaultEntry();
51+
52+
if (R__unlikely(entry.GetModelId() != fUserModel->GetModelId()))
53+
throw RException(R__FAIL("mismatch between entry and model"));
54+
55+
// Load the meta fields
56+
metaEntry.fValues[kRangeStartIndex].Read(index);
57+
metaEntry.fValues[kRangeLenIndex].Read(index);
58+
59+
// Load the user fields into `entry`
60+
auto *userRootField = ROOT::Internal::GetFieldZeroOfModel(metaModel).GetMutableSubfields()[kUserModelIndex];
61+
const auto userFields = userRootField->GetMutableSubfields();
62+
assert(entry.fValues.size() == userFields.size());
63+
for (std::size_t i = 0; i < userFields.size(); ++i) {
64+
auto *field = userFields[i];
65+
field->Read(index, entry.fValues[i].GetPtr<void>().get());
66+
}
67+
68+
auto pStart = metaEntry.GetPtr<NTupleSize_t>(kRangeStartName);
69+
auto pLen = metaEntry.GetPtr<NTupleSize_t>(kRangeLenName);
70+
71+
return RNTupleAttrRange::FromStartLength(*pStart, *pLen);
72+
}
73+
74+
ROOT::Experimental::RNTupleAttrRange ROOT::Experimental::RNTupleAttrSetReader::LoadEntry(ROOT::NTupleSize_t index)
75+
{
76+
auto &entry = fUserModel->GetDefaultEntry();
77+
return LoadEntry(index, entry);
78+
}
79+
80+
std::unique_ptr<ROOT::REntry> ROOT::Experimental::RNTupleAttrSetReader::CreateEntry()
81+
{
82+
return fUserModel->CreateEntry();
83+
}

tree/ntuple/src/RNTupleAttrWriting.cxx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/// \file RNTupleAttrWriting.cxx
2-
/// \ingroup NTuple ROOT7
2+
/// \ingroup NTuple
33
/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
44
/// \date 2026-01-27
55
/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback

0 commit comments

Comments
 (0)