Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleDS.cxx
Go to the documentation of this file.
1/// \file RNTupleDS.cxx
2/// \author Jakob Blomer <jblomer@cern.ch>
3/// \author Enrico Guiraud <enrico.guiraud@cern.ch>
4/// \date 2018-10-04
5
6/*************************************************************************
7 * Copyright (C) 1995-2020, Rene Brun and Fons Rademakers. *
8 * All rights reserved. *
9 * *
10 * For the licensing terms see $ROOTSYS/LICENSE. *
11 * For the list of contributors see $ROOTSYS/README/CREDITS. *
12 *************************************************************************/
13
15#include <ROOT/RDataFrame.hxx>
16#include <ROOT/RDF/Utils.hxx>
17#include <ROOT/RField.hxx>
18#include <ROOT/RFieldUtils.hxx>
21#include <ROOT/RNTupleDS.hxx>
22#include <ROOT/RNTupleTypes.hxx>
23#include <ROOT/RPageStorage.hxx>
24
25#include "RNTupleDSFields.hxx"
26
27#include <TError.h>
28#include <TSystem.h>
29
30#include <cassert>
31#include <limits>
32#include <memory>
33#include <mutex>
34#include <string>
35#include <string_view>
36#include <vector>
37#include <typeinfo>
38#include <type_traits>
39#include <utility>
40
41// clang-format off
42/**
43* \class ROOT::RDF::RNTupleDS
44* \ingroup dataframe
45* \brief The RDataSource implementation for RNTuple. It lets RDataFrame read RNTuple data.
46*
47* An RDataFrame that reads RNTuple data can be constructed using FromRNTuple().
48*
49* For each column containing an array or a collection, a corresponding column `#colname` is available to access
50* `colname.size()` without reading and deserializing the collection values.
51*
52**/
53// clang-format on
54
55namespace ROOT::Internal::RDF {
56/// Every RDF column is represented by exactly one RNTuple field
60
61 RNTupleDS *fDataSource; ///< The data source that owns this column reader
62 RFieldBase *fProtoField; ///< The prototype field from which fField is cloned
63 std::unique_ptr<RFieldBase> fField; ///< The field backing the RDF column
64 std::unique_ptr<RFieldBase::RValue> fValue; ///< The memory location used to read from fField
65 std::shared_ptr<void> fValuePtr; ///< Used to reuse the object created by fValue when reconnecting sources
66 Long64_t fLastEntry = -1; ///< Last entry number that was read
67 /// For chains, the logical entry and the physical entry in any particular file can be different.
68 /// The entry offset stores the logical entry number (sum of all previous physical entries) when a file of the
69 /// corresponding data source was opened.
71
72public:
73 RNTupleColumnReader(RNTupleDS *ds, RFieldBase *protoField) : fDataSource(ds), fProtoField(protoField) {}
74 ~RNTupleColumnReader() override = default;
75
76 /// Connect the field and its subfields to the page source
77 void Connect(RPageSource &source, Long64_t entryOffset)
78 {
79 assert(fLastEntry == -1);
80
81 fEntryOffset = entryOffset;
82
83 // Create a new, real field from the prototype and set its field ID in the context of the given page source
85 {
86 auto descGuard = source.GetSharedDescriptorGuard();
87 // Set the on-disk field IDs for the field and the subfield
88 fField->SetOnDiskId(
89 descGuard->FindFieldId(fDataSource->fFieldId2QualifiedName.at(fProtoField->GetOnDiskId())));
90 auto iProto = fProtoField->cbegin();
91 auto iReal = fField->begin();
92 for (; iReal != fField->end(); ++iProto, ++iReal) {
93 iReal->SetOnDiskId(descGuard->FindFieldId(fDataSource->fFieldId2QualifiedName.at(iProto->GetOnDiskId())));
94 }
95 }
96
97 RFieldZero fieldZero;
99 fieldZero.Attach(std::move(fField));
100 try {
102 } catch (const ROOT::RException &) {
103 fField = std::move(fieldZero.ReleaseSubfields()[0]);
104 auto onDiskType = source.GetSharedDescriptorGuard()->GetFieldDescriptor(fField->GetOnDiskId()).GetTypeName();
105 std::string msg = "RNTupleDS: invalid type \"" + fField->GetTypeName() + "\" for column \"" +
106 fDataSource->fFieldId2QualifiedName[fField->GetOnDiskId()] + "\" with on-disk type \"" +
107 onDiskType + "\"";
108 throw std::runtime_error(msg);
109 }
110 fField = std::move(fieldZero.ReleaseSubfields()[0]);
111
112 if (fValuePtr) {
113 // When the reader reconnects to a new file, the fValuePtr is already set
114 fValue = std::make_unique<RFieldBase::RValue>(fField->BindValue(fValuePtr));
115 fValuePtr = nullptr;
116 } else {
117 // For the first file, create a new object for this field (reader)
118 fValue = std::make_unique<RFieldBase::RValue>(fField->CreateValue());
119 }
120 }
121
122 void Disconnect(bool keepValue)
123 {
124 if (fValue && keepValue) {
125 fValuePtr = fValue->GetPtr<void>();
126 }
127 fValue = nullptr;
128 fField = nullptr;
129 fLastEntry = -1;
130 }
131
132 void *GetImpl(Long64_t entry) final
133 {
134 if (entry != fLastEntry) {
135 fValue->Read(entry - fEntryOffset);
136 fLastEntry = entry;
137 }
138 return fValue->GetPtr<void>().get();
139 }
140};
141} // namespace ROOT::Internal::RDF
142
144
145void ROOT::RDF::RNTupleDS::AddField(const ROOT::RNTupleDescriptor &desc, std::string_view colName,
146 ROOT::DescriptorId_t fieldId, std::vector<RNTupleDS::RFieldInfo> fieldInfos,
147 bool convertToRVec)
148{
149 // As an example for the mapping of RNTuple fields to RDF columns, let's consider an RNTuple
150 // using the following types and with a top-level field named "event" of type Event:
151 //
152 // struct Event {
153 // int id;
154 // std::vector<Track> tracks;
155 // };
156 // struct Track {
157 // std::vector<Hit> hits;
158 // };
159 // struct Hit {
160 // float x;
161 // float y;
162 // };
163 //
164 // AddField() will be called from the constructor with the RNTuple root field (ENTupleStructure::kRecord).
165 // From there, we recurse into the "event" sub field (also ENTupleStructure::kRecord) and further down the
166 // tree of sub fields and expose the following RDF columns:
167 //
168 // "event" [Event]
169 // "event.id" [int]
170 // "event.tracks" [RVec<Track>]
171 // "R_rdf_sizeof_event.tracks" [unsigned int]
172 // "event.tracks.hits" [RVec<RVec<Hit>>]
173 // "R_rdf_sizeof_event.tracks.hits" [RVec<unsigned int>]
174 // "event.tracks.hits.x" [RVec<RVec<float>>]
175 // "R_rdf_sizeof_event.tracks.hits.x" [RVec<unsigned int>]
176 // "event.tracks.hits.y" [RVec<RVec<float>>]
177 // "R_rdf_sizeof_event.tracks.hits.y" [RVec<unsigned int>]
178
179 const auto &fieldDesc = desc.GetFieldDescriptor(fieldId);
180 const auto &nRepetitions = fieldDesc.GetNRepetitions();
181 if ((fieldDesc.GetStructure() == ROOT::ENTupleStructure::kCollection) || (nRepetitions > 0)) {
182 // The field is a collection or a fixed-size array.
183 // We open a new collection scope with fieldID being the inner most collection. E.g. for "event.tracks.hits",
184 // fieldInfos would already contain the fieldID of "event.tracks"
185 fieldInfos.emplace_back(fieldId, nRepetitions);
186 }
187
188 if (fieldDesc.GetStructure() == ROOT::ENTupleStructure::kCollection) {
189 // Inner fields of collections are provided as projected collections of only that inner field,
190 // E.g. we provide a projected collection RVec<RVec<float>> for "event.tracks.hits.x" in the example
191 // above.
192 bool representableAsRVec =
193 convertToRVec && (fieldDesc.GetTypeName().substr(0, 19) == "ROOT::VecOps::RVec<" ||
194 fieldDesc.GetTypeName().substr(0, 12) == "std::vector<" || fieldDesc.GetTypeName() == "");
195 const auto &f = *desc.GetFieldIterable(fieldDesc.GetId()).begin();
196 AddField(desc, colName, f.GetId(), fieldInfos, representableAsRVec);
197
198 // Note that at the end of the recursion, we handled the inner sub collections as well as the
199 // collection as whole, so we are done.
200 return;
201
202 } else if (nRepetitions > 0) {
203 // Fixed-size array, same logic as ROOT::RVec.
204 const auto &f = *desc.GetFieldIterable(fieldDesc.GetId()).begin();
205 AddField(desc, colName, f.GetId(), fieldInfos);
206 return;
207 } else if (fieldDesc.GetStructure() == ROOT::ENTupleStructure::kRecord) {
208 // Inner fields of records are provided as individual RDF columns, e.g. "event.id"
209 for (const auto &f : desc.GetFieldIterable(fieldDesc.GetId())) {
210 auto innerName = colName.empty() ? f.GetFieldName() : (std::string(colName) + "." + f.GetFieldName());
211 // Inner fields of collections of records are always exposed as ROOT::RVec
212 AddField(desc, innerName, f.GetId(), fieldInfos);
213 }
214 }
215
216 // The fieldID could be the root field or the class of fieldId might not be loaded.
217 // In these cases, only the inner fields are exposed as RDF columns.
218 auto fieldOrException = ROOT::RFieldBase::Create(fieldDesc.GetFieldName(), fieldDesc.GetTypeName());
219 if (!fieldOrException)
220 return;
221 auto valueField = fieldOrException.Unwrap();
222 if (const auto cardinalityField = dynamic_cast<const ROOT::RCardinalityField *>(valueField.get())) {
223 // Cardinality fields in RDataFrame are presented as integers
224 if (cardinalityField->As32Bit()) {
225 valueField =
226 std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint32_t>>(fieldDesc.GetFieldName());
227 } else if (cardinalityField->As64Bit()) {
228 valueField =
229 std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint64_t>>(fieldDesc.GetFieldName());
230 } else {
231 R__ASSERT(false && "cardinality field stored with an unexpected integer type");
232 }
233 }
234 valueField->SetOnDiskId(fieldId);
235 for (auto &f : *valueField) {
236 f.SetOnDiskId(desc.FindFieldId(f.GetFieldName(), f.GetParent()->GetOnDiskId()));
237 }
238 std::unique_ptr<ROOT::RFieldBase> cardinalityField;
239 // Collections get the additional "number of" RDF column (e.g. "R_rdf_sizeof_tracks")
240 if (!fieldInfos.empty()) {
241 const auto &info = fieldInfos.back();
242 const std::string name = "R_rdf_sizeof_" + desc.GetFieldDescriptor(info.fFieldId).GetFieldName();
243 if (info.fNRepetitions > 0) {
244 cardinalityField = std::make_unique<ROOT::Internal::RDF::RArraySizeField>(name, info.fNRepetitions);
245 } else {
246 cardinalityField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::size_t>>(name);
247 }
248 cardinalityField->SetOnDiskId(info.fFieldId);
249 }
250
251 for (auto i = fieldInfos.rbegin(); i != fieldInfos.rend(); ++i) {
252 const auto &fieldInfo = *i;
253
254 const auto valueFieldName = valueField->GetFieldName();
255
256 if (fieldInfo.fNRepetitions > 0) {
257 // Fixed-size array, read it as ROOT::RVec in memory
258 valueField =
259 std::make_unique<ROOT::RArrayAsRVecField>(valueFieldName, valueField->Clone("_0"), fieldInfo.fNRepetitions);
260 } else {
261 // Actual collection. A std::vector or ROOT::RVec gets added as a ROOT::RVec. All other collection types keep
262 // their original type.
263 if (convertToRVec) {
264 valueField = std::make_unique<ROOT::RRVecField>(valueFieldName, valueField->Clone("_0"));
265 } else {
266 auto outerFieldType = desc.GetFieldDescriptor(fieldInfo.fFieldId).GetTypeName();
267 valueField = ROOT::RFieldBase::Create(valueFieldName, outerFieldType).Unwrap();
268 }
269 }
270
271 valueField->SetOnDiskId(fieldInfo.fFieldId);
272
273 // Skip the inner-most collection level to construct the cardinality column
274 // It's taken care of by the `if (!fieldInfos.empty())` scope above
275 if (i != fieldInfos.rbegin()) {
276 const auto cardinalityFieldName = cardinalityField->GetFieldName();
277 if (fieldInfo.fNRepetitions > 0) {
278 // This collection level refers to a fixed-size array
279 cardinalityField = std::make_unique<ROOT::RArrayAsRVecField>(
280 cardinalityFieldName, cardinalityField->Clone("_0"), fieldInfo.fNRepetitions);
281 } else {
282 // This collection level refers to an RVec
283 cardinalityField = std::make_unique<ROOT::RRVecField>(cardinalityFieldName, cardinalityField->Clone("_0"));
284 }
285
286 cardinalityField->SetOnDiskId(fieldInfo.fFieldId);
287 }
288 }
289
290 if (cardinalityField) {
291 fColumnNames.emplace_back("R_rdf_sizeof_" + std::string(colName));
292 fColumnTypes.emplace_back(cardinalityField->GetTypeName());
293 fProtoFields.emplace_back(std::move(cardinalityField));
294 }
295
296 fieldInfos.emplace_back(fieldId, nRepetitions);
297 fColumnNames.emplace_back(colName);
298 fColumnTypes.emplace_back(valueField->GetTypeName());
299 fProtoFields.emplace_back(std::move(valueField));
300}
301
302ROOT::RDF::RNTupleDS::RNTupleDS(std::unique_ptr<ROOT::Internal::RPageSource> pageSource)
303{
304 pageSource->Attach();
305 fPrincipalDescriptor = pageSource->GetSharedDescriptorGuard()->Clone();
306 fStagingArea.emplace_back(std::move(pageSource));
307
308 AddField(fPrincipalDescriptor, "", fPrincipalDescriptor.GetFieldZeroId(),
309 std::vector<ROOT::RDF::RNTupleDS::RFieldInfo>());
310
311 auto topLevelFields = fPrincipalDescriptor.GetTopLevelFields();
312 const auto nTopLevelFields = std::distance(topLevelFields.begin(), topLevelFields.end());
313 fTopLevelFieldNames.reserve(nTopLevelFields);
314 for (const auto &field : topLevelFields)
315 fTopLevelFieldNames.push_back(field.GetFieldName());
316}
317
318namespace {
319
320const ROOT::RNTupleReadOptions &GetOpts()
321{
322 // The setting is for now a global one, must be decided before running the
323 // program by setting the appropriate environment variable. Make sure that
324 // option configuration is thread-safe and happens only once.
325 static ROOT::RNTupleReadOptions opts;
326 static std::once_flag flag;
327 std::call_once(flag, []() {
328 if (auto env = gSystem->Getenv("ROOT_RNTUPLE_CLUSTERBUNCHSIZE"); env != nullptr && strlen(env) > 0) {
329 std::string envStr{env};
330 auto envNum{std::stoul(envStr)};
331 envNum = envNum == 0 ? 1 : envNum;
333 }
334 });
335 return opts;
336}
337
338std::unique_ptr<ROOT::Internal::RPageSource> CreatePageSource(std::string_view ntupleName, std::string_view fileName)
339{
340 return ROOT::Internal::RPageSource::Create(ntupleName, fileName, GetOpts());
341}
342} // namespace
343
344ROOT::RDF::RNTupleDS::RNTupleDS(std::string_view ntupleName, std::string_view fileName)
345 : RNTupleDS(CreatePageSource(ntupleName, fileName))
346{
347 fNTupleName = ntupleName;
348 fFileNames = std::vector<std::string>{std::string{fileName}};
349}
350
351ROOT::RDF::RNTupleDS::RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames)
352 : RNTupleDS(CreatePageSource(ntupleName, fileNames[0]))
353{
354 fNTupleName = ntupleName;
355 fFileNames = fileNames;
356 fStagingArea.resize(fFileNames.size());
357}
358
359ROOT::RDF::RNTupleDS::RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames,
360 const std::pair<ULong64_t, ULong64_t> &range)
361 : RNTupleDS(ntupleName, fileNames)
362{
363 fGlobalEntryRange = range;
364}
365
367ROOT::RDF::RNTupleDS::GetColumnReadersImpl(std::string_view /* name */, const std::type_info & /* ti */)
368{
369 // This datasource uses the newer GetColumnReaders() API
370 return {};
371}
372
373ROOT::RFieldBase *ROOT::RDF::RNTupleDS::GetFieldWithTypeChecks(std::string_view fieldName, const std::type_info &tid)
374{
375 // At this point we can assume that `name` will be found in fColumnNames
376 const auto index =
377 std::distance(fColumnNames.begin(), std::find(fColumnNames.begin(), fColumnNames.end(), fieldName));
378
379 // A reader was requested but we don't have RTTI for it, this is encoded with the tag UseNativeDataType. We can just
380 // return the available protofield
381 if (tid == typeid(ROOT::Internal::RDF::UseNativeDataType)) {
382 return fProtoFields[index].get();
383 }
384
385 // The user explicitly requested a type
387
388 // If the requested type is different from the proto field that was created when the data source was constructed,
389 // we first have to create an alternative proto field for the column reader.
390 // Otherwise, we can directly use the existing proto field.
391 if (requestedType != fColumnTypes[index]) {
392 auto &altProtoFields = fAlternativeProtoFields[index];
393
394 // If we can find the requested type in the registered alternative protofields, return the corresponding field
395 if (auto altProtoField = std::find_if(altProtoFields.begin(), altProtoFields.end(),
396 [&requestedType](const std::unique_ptr<ROOT::RFieldBase> &fld) {
397 return fld->GetTypeName() == requestedType;
398 });
399 altProtoField != altProtoFields.end()) {
400 return altProtoField->get();
401 }
402
403 // Otherwise, create a new protofield and register it in the alternatives before returning
404 std::unique_ptr<RFieldBase> newAltProtoField;
405 const std::string strName = std::string(fieldName);
406 if (dynamic_cast<ROOT::Internal::RDF::RRDFCardinalityFieldBase *>(fProtoFields[index].get())) {
407 if (requestedType == "bool") {
408 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<bool>>(strName);
409 } else if (requestedType == "char") {
410 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<char>>(strName);
411 } else if (requestedType == "std::int8_t") {
412 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int8_t>>(strName);
413 } else if (requestedType == "std::uint8_t") {
414 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint8_t>>(strName);
415 } else if (requestedType == "std::int16_t") {
416 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int16_t>>(strName);
417 } else if (requestedType == "std::uint16_t") {
418 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint16_t>>(strName);
419 } else if (requestedType == "std::int32_t") {
420 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int32_t>>(strName);
421 } else if (requestedType == "std::uint32_t") {
422 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint32_t>>(strName);
423 } else if (requestedType == "std::int64_t") {
424 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::int64_t>>(strName);
425 } else if (requestedType == "std::uint64_t") {
426 newAltProtoField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField<std::uint64_t>>(strName);
427 } else {
428 throw std::runtime_error("RNTupleDS: Could not create field with type \"" + requestedType +
429 "\" for column \"" + std::string(fieldName) + "\"");
430 }
431 } else {
432 auto newAltProtoFieldOrException = ROOT::RFieldBase::Create(strName, requestedType);
433 if (!newAltProtoFieldOrException) {
434 throw std::runtime_error("RNTupleDS: Could not create field with type \"" + requestedType +
435 "\" for column \"" + std::string(fieldName) + "\"");
436 }
437 newAltProtoField = newAltProtoFieldOrException.Unwrap();
438 }
439 newAltProtoField->SetOnDiskId(fProtoFields[index]->GetOnDiskId());
440 auto *newField = newAltProtoField.get();
441 altProtoFields.emplace_back(std::move(newAltProtoField));
442 return newField;
443 }
444
445 // General case: there was a correspondence between the user-requested type and the corresponding column type
446 return fProtoFields[index].get();
447}
448
449std::unique_ptr<ROOT::Detail::RDF::RColumnReaderBase>
450ROOT::RDF::RNTupleDS::GetColumnReaders(unsigned int slot, std::string_view name, const std::type_info &tid)
451{
452 ROOT::RFieldBase *field = GetFieldWithTypeChecks(name, tid);
453 assert(field != nullptr);
454
455 // Map the field's and subfields' IDs to qualified names so that we can later connect the fields to
456 // other page sources from the chain
457 fFieldId2QualifiedName[field->GetOnDiskId()] = fPrincipalDescriptor.GetQualifiedFieldName(field->GetOnDiskId());
458 for (const auto &s : *field) {
459 fFieldId2QualifiedName[s.GetOnDiskId()] = fPrincipalDescriptor.GetQualifiedFieldName(s.GetOnDiskId());
460 }
461
462 auto reader = std::make_unique<ROOT::Internal::RDF::RNTupleColumnReader>(this, field);
463 fActiveColumnReaders[slot].emplace_back(reader.get());
464
465 return reader;
466}
467
469{
470 while (true) {
471 std::unique_lock lock(fMutexStaging);
472 fCvStaging.wait(lock, [this] { return fIsReadyForStaging || fStagingThreadShouldTerminate; });
473 if (fStagingThreadShouldTerminate)
474 return;
475
476 assert(!fHasNextSources);
477 StageNextSources();
478 fHasNextSources = true;
479 fIsReadyForStaging = false;
480
481 lock.unlock();
482 fCvStaging.notify_one();
483 }
484}
485
487{
488 const auto nFiles = fFileNames.empty() ? 1 : fFileNames.size();
489
490 for (auto i = fNextFileIndex; (i < nFiles) && ((i - fNextFileIndex) < fNSlots); ++i) {
491
492 if (fStagingThreadShouldTerminate)
493 return;
494
495 if (fStagingArea[i]) {
496 // The first file is already open and was used to read the schema
497 assert(i == 0);
498 } else {
499 fStagingArea[i] = CreatePageSource(fNTupleName, fFileNames[i]);
500 fStagingArea[i]->LoadStructure();
501 }
502 }
503}
504
506{
507 assert(fNextRanges.empty());
508
509 auto nFiles = fFileNames.empty() ? 1 : fFileNames.size();
510 auto nRemainingFiles = nFiles - fNextFileIndex;
511
512 if (nRemainingFiles == 0)
513 return;
514
515 // Easy work scheduling: one file per slot. We skip empty files (files without entries).
516
517 if ((nRemainingFiles >= fNSlots) || (fGlobalEntryRange.has_value())) {
518 while ((fNextRanges.size() < fNSlots) && (fNextFileIndex < nFiles)) {
519 REntryRangeDS range;
520
521 std::swap(fStagingArea[fNextFileIndex], range.fSource);
522
523 if (!range.fSource) {
524 // Typically, the prestaged source should have been present. Only if some of the files are empty, we need
525 // to open and attach files here.
526 range.fSource = CreatePageSource(fNTupleName, fFileNames[fNextFileIndex]);
527 }
528 range.fFileName = fFileNames[fNextFileIndex];
529 range.fSource->Attach();
530 fNextFileIndex++;
531 auto nEntries = range.fSource->GetNEntries();
532 if (nEntries == 0)
533 continue;
534 range.fLastEntry = nEntries; // whole file per slot, i.e. entry range [0..nEntries - 1]
535
536 fNextRanges.emplace_back(std::move(range));
537 }
538 return;
539 }
540
541 // Work scheduling of the tail: multiple slots work on the same file.
542 // Every slot still has its own page source but these page sources may open the same file.
543 // Again, we need to skip empty files.
544 unsigned int nSlotsPerFile = fNSlots / nRemainingFiles;
545 for (std::size_t i = 0; (fNextRanges.size() < fNSlots) && (fNextFileIndex < nFiles); ++i) {
546 std::unique_ptr<ROOT::Internal::RPageSource> source;
547 // Need to look for the file name to populate the sample info later
548 const auto &sourceFileName = fFileNames[fNextFileIndex];
549 std::swap(fStagingArea[fNextFileIndex], source);
550 if (!source) {
551 // Empty files trigger this condition
552 source = CreatePageSource(fNTupleName, fFileNames[fNextFileIndex]);
553 }
554 source->Attach();
555 source->LoadAllPageLists();
556 fNextFileIndex++;
557
558 auto nEntries = source->GetNEntries();
559 if (nEntries == 0)
560 continue;
561
562 // If last file: use all remaining slots
563 if (i == (nRemainingFiles - 1))
564 nSlotsPerFile = fNSlots - fNextRanges.size();
565
566 const auto rangesByCluster = [&source]() {
567 // Take the shared lock of the descriptor just for the time necessary
568 auto descGuard = source->GetSharedDescriptorGuard();
569 return ROOT::Internal::GetClusterBoundaries(descGuard.GetConstRef());
570 }();
571
572 const unsigned int nRangesByCluster = rangesByCluster.size();
573
574 // Distribute slots equidistantly over the entry range, aligned on cluster boundaries
575 const auto nClustersPerSlot = nRangesByCluster / nSlotsPerFile;
576 const auto remainder = nRangesByCluster % nSlotsPerFile;
577 std::size_t iRange = 0;
578 unsigned int iSlot = 0;
579 const unsigned int N = std::min(nSlotsPerFile, nRangesByCluster);
580 for (; iSlot < N; ++iSlot) {
581 auto start = rangesByCluster[iRange].fFirstEntry;
582 iRange += nClustersPerSlot + static_cast<int>(iSlot < remainder);
583 assert(iRange > 0);
584 auto end = rangesByCluster[iRange - 1].fLastEntryPlusOne;
585
586 REntryRangeDS range;
587 range.fFileName = sourceFileName;
588 // The last range for this file just takes the already opened page source. All previous ranges clone.
589 if (iSlot == N - 1) {
590 range.fSource = std::move(source);
591 } else {
592 range.fSource = source->Clone();
593 }
594 range.fSource->SetEntryRange({start, end - start});
595 range.fFirstEntry = start;
596 range.fLastEntry = end;
597 fNextRanges.emplace_back(std::move(range));
598 }
599 } // loop over tail of remaining files
600}
601
602std::vector<std::pair<ULong64_t, ULong64_t>> ROOT::RDF::RNTupleDS::GetEntryRanges()
603{
604 std::vector<std::pair<ULong64_t, ULong64_t>> ranges;
605
606 // We need to distinguish between single threaded and multi-threaded runs.
607 // In single threaded mode, InitSlot is only called once and column readers have to be rewired
608 // to new page sources of the chain in GetEntryRanges. In multi-threaded mode, on the other hand,
609 // InitSlot is called for every returned range, thus rewiring the column readers takes place in
610 // InitSlot and FinalizeSlot.
611
612 if (fNSlots == 1) {
613 for (auto r : fActiveColumnReaders[0]) {
614 r->Disconnect(true /* keepValue */);
615 }
616 }
617
618 // If we have fewer files than slots and we run multiple event loops, we can reuse fCurrentRanges and don't need
619 // to worry about loading the fNextRanges. I.e., in this case we don't enter the if block.
620 if (fCurrentRanges.empty() || fSeenEntriesNoGlobalRange > 0) {
621 // Otherwise, i.e. start of the first event loop or in the middle of the event loop, prepare the next ranges
622 // and swap with the current ones.
623 {
624 std::unique_lock lock(fMutexStaging);
625 fCvStaging.wait(lock, [this] { return fHasNextSources; });
626 }
627 PrepareNextRanges();
628 if (fNextRanges.empty()) {
629 // No more data
630 return ranges;
631 }
632
633 assert(fNextRanges.size() <= fNSlots);
634
635 fCurrentRanges.clear();
636 std::swap(fCurrentRanges, fNextRanges);
637 }
638
639 // Stage next batch of files for the next call to GetEntryRanges()
640 {
641 std::lock_guard _(fMutexStaging);
642 fIsReadyForStaging = true;
643 fHasNextSources = false;
644 }
645 fCvStaging.notify_one();
646
647 // Create ranges for the RDF loop manager from the list of REntryRangeDS records.
648 // The entry ranges that are relative to the page source in REntryRangeDS are translated into absolute
649 // entry ranges, given the current state of the entry cursor.
650 // We remember the connection from first absolute entry index of a range to its REntryRangeDS record
651 // so that we can properly rewire the column reader in InitSlot
652 fFirstEntry2RangeIdx.clear();
653 fOriginalRanges.clear();
654
655 ULong64_t nEntriesPerSource = 0;
656
657 for (std::size_t i = 0; i < fCurrentRanges.size(); ++i) {
658
659 // Several consecutive ranges may operate on the same file (each with their own page source clone).
660 // We can detect a change of file when the first entry number jumps back to 0.
661 if (fCurrentRanges[i].fFirstEntry == 0) {
662 // New source
663 fSeenEntriesNoGlobalRange += nEntriesPerSource;
664 nEntriesPerSource = 0;
665 }
666
667 auto start = fCurrentRanges[i].fFirstEntry + fSeenEntriesNoGlobalRange;
668 auto end = fCurrentRanges[i].fLastEntry + fSeenEntriesNoGlobalRange;
669
670 nEntriesPerSource += end - start;
671
672 if (fGlobalEntryRange.has_value()) {
673
674 // We need to consider different scenarios for when we have GlobalRanges set by the user.
675 // Consider a simple case of 3 files, with original ranges set as (consecutive entries of 3 files):
676 // [0, 20], [20, 45], [45, 65]
677 // we will now see what happens in each of the scenarios below when GlobalRanges can be set to different
678 // values:
679 // a) [2, 5] - we stay in file 1
680 // - hence we will use the 1st case and get the range [2,5], in this case we also need to quit further
681 // processing from the other files by entering case 3
682 // b) [2, 21] - we start in file 1 and finish in file 2
683 // - use the 2nd case first, as 21 > 20 (end of first file), then we will go to case 1, resulting in ranges:
684 // [2, 20], [20, 21], c) [21 - 40] - we skip file 1, start in file 2 and stay in file 2
685 // - to skip the first file, we use the 4th case, followed by the 1st case, resulting range is: [21, 40]
686 // d) [21 - 65] - we skip file 1, start in file 2 and continue to file 3
687 // - to skip the first file, we use the 4th case, we continue with the 2nd case, and use the 1st case at the
688 // end, resulting ranges are [21, 45], [45, 65]
689 // The first case
690 if (fGlobalEntryRange->first >= start && fGlobalEntryRange->second <= end) {
691 fOriginalRanges.emplace_back(start, end);
692 fFirstEntry2RangeIdx[fGlobalEntryRange->first] = i;
693 ranges.emplace_back(fGlobalEntryRange->first, fGlobalEntryRange->second);
694 }
695
696 // The second case:
697 // The `fGlobalEntryRange->first < end` condition is to distinguish this case from the 4th case.
698 else if (fGlobalEntryRange->second > end && fGlobalEntryRange->first < end) {
699 fOriginalRanges.emplace_back(start, end);
700 fFirstEntry2RangeIdx[fGlobalEntryRange->first] = i;
701 ranges.emplace_back(fGlobalEntryRange->first, end);
702 std::optional<std::pair<ULong64_t, ULong64_t>> newvalues({end, fGlobalEntryRange->second});
703 fGlobalEntryRange.swap(newvalues);
704 }
705 // The third case, needed to correctly quit processing if we only stay in the first file
706 else if (fGlobalEntryRange->second < start) {
707 return ranges;
708 }
709
710 // The fourth case:
711 else if (fGlobalEntryRange->first >= end) {
712 fOriginalRanges.emplace_back(start, end);
713 fFirstEntry2RangeIdx[start] = i;
714 ranges.emplace_back(start, start);
715 }
716 }
717
718 else {
719 fFirstEntry2RangeIdx[start] = i;
720 fOriginalRanges.emplace_back(start, end);
721 ranges.emplace_back(start, end);
722 }
723 }
724
725 fSeenEntriesNoGlobalRange += nEntriesPerSource;
726
727 if ((fNSlots == 1) && (fCurrentRanges[0].fSource)) {
728 for (auto r : fActiveColumnReaders[0]) {
729 r->Connect(*fCurrentRanges[0].fSource, fOriginalRanges[0].first);
730 }
731 }
732
733 return ranges;
734}
735
736void ROOT::RDF::RNTupleDS::InitSlot(unsigned int slot, ULong64_t firstEntry)
737{
738 if (fNSlots == 1) {
739 // Ensure the connection between slot and range is valid also in single-thread mode
740 fSlotsToRangeIdxs[0] = 0;
741 return;
742 }
743
744 // The same slot ID could be picked multiple times in the same execution, thus
745 // ending up processing different page sources. Here we re-establish the
746 // connection between the slot and the correct page source by finding which
747 // range index corresponds to the first entry passed.
748 auto idxRange = fFirstEntry2RangeIdx.at(firstEntry);
749
750 // We also remember this connection so it can later be retrieved in CreateSampleInfo
751 fSlotsToRangeIdxs[slot * ROOT::Internal::RDF::CacheLineStep<std::size_t>()] = idxRange;
752
753 for (auto r : fActiveColumnReaders[slot]) {
754 r->Connect(*fCurrentRanges[idxRange].fSource,
755 fOriginalRanges[idxRange].first - fCurrentRanges[idxRange].fFirstEntry);
756 }
757}
758
760{
761 if (fNSlots == 1)
762 return;
763
764 for (auto r : fActiveColumnReaders[slot]) {
765 r->Disconnect(true /* keepValue */);
766 }
767}
768
769std::string ROOT::RDF::RNTupleDS::GetTypeName(std::string_view colName) const
770{
771 auto colNamePos = std::find(fColumnNames.begin(), fColumnNames.end(), colName);
772
773 if (colNamePos == fColumnNames.end()) {
774 auto msg = std::string("RNTupleDS: There is no column with name \"") + std::string(colName) + "\"";
775 throw std::runtime_error(msg);
776 }
777
778 const auto index = std::distance(fColumnNames.begin(), colNamePos);
779 return fColumnTypes[index];
780}
781
782bool ROOT::RDF::RNTupleDS::HasColumn(std::string_view colName) const
783{
784 return std::find(fColumnNames.begin(), fColumnNames.end(), colName) != fColumnNames.end();
785}
786
788{
789 fSeenEntriesNoGlobalRange = 0;
790 fNextFileIndex = 0;
791 fIsReadyForStaging = fHasNextSources = fStagingThreadShouldTerminate = false;
792 fThreadStaging = std::thread(&RNTupleDS::ExecStaging, this);
793 assert(fNextRanges.empty());
794
795 if (fCurrentRanges.empty() || (fFileNames.size() > fNSlots)) {
796 // First event loop or large number of files: start the staging process.
797 {
798 std::lock_guard _(fMutexStaging);
799 fIsReadyForStaging = true;
800 }
801 fCvStaging.notify_one();
802 } else {
803 // Otherwise, we will reuse fCurrentRanges. Make sure that staging and preparing next ranges will be a noop
804 // (already at the end of the list of files).
805 fNextFileIndex = std::max(fFileNames.size(), std::size_t(1));
806 }
807}
808
810{
811 for (unsigned int i = 0; i < fNSlots; ++i) {
812 for (auto r : fActiveColumnReaders[i]) {
813 r->Disconnect(false /* keepValue */);
814 }
815 }
816 {
817 std::lock_guard _(fMutexStaging);
818 fStagingThreadShouldTerminate = true;
819 }
820 fCvStaging.notify_one();
821 fThreadStaging.join();
822 // If we have a chain with more files than the number of slots, the files opened at the end of the
823 // event loop won't be reused when the event loop restarts, so we can close them.
824 if (fFileNames.size() > fNSlots) {
825 fCurrentRanges.clear();
826 fNextRanges.clear();
827 fStagingArea.clear();
828 fStagingArea.resize(fFileNames.size());
829 }
830}
831
832void ROOT::RDF::RNTupleDS::SetNSlots(unsigned int nSlots)
833{
834 assert(fNSlots == 0);
835 assert(nSlots > 0);
836 fNSlots = nSlots;
837 fActiveColumnReaders.resize(fNSlots);
838 fSlotsToRangeIdxs.resize(fNSlots * ROOT::Internal::RDF::CacheLineStep<std::size_t>());
839}
840
841ROOT::RDataFrame ROOT::RDF::FromRNTuple(std::string_view ntupleName, std::string_view fileName)
842{
843 return ROOT::RDataFrame(std::make_unique<ROOT::RDF::RNTupleDS>(ntupleName, fileName));
844}
845
846ROOT::RDataFrame ROOT::RDF::FromRNTuple(std::string_view ntupleName, const std::vector<std::string> &fileNames)
847{
848 return ROOT::RDataFrame(std::make_unique<ROOT::RDF::RNTupleDS>(ntupleName, fileNames));
849}
850
851ROOT::RDF::RSampleInfo ROOT::Internal::RDF::RNTupleDS::CreateSampleInfo(
852 unsigned int slot, const std::unordered_map<std::string, ROOT::RDF::Experimental::RSample *> &sampleMap) const
853{
854 // The same slot ID could be picked multiple times in the same execution, thus
855 // ending up processing different page sources. Here we re-establish the
856 // connection between the slot and the correct page source by retrieving
857 // which range is connected currently to the slot
858
859 const auto &rangeIdx = fSlotsToRangeIdxs.at(slot * ROOT::Internal::RDF::CacheLineStep<std::size_t>());
860
861 // Missing source if a file does not exist
862 if (!fCurrentRanges[rangeIdx].fSource)
863 return ROOT::RDF::RSampleInfo{};
864
865 const auto &ntupleName = fCurrentRanges[rangeIdx].fSource->GetNTupleName();
866 const auto &ntuplePath = fCurrentRanges[rangeIdx].fFileName;
867 const auto ntupleID = std::string(ntuplePath) + '/' + ntupleName;
868
869 if (sampleMap.empty())
871 ntupleID, std::make_pair(fCurrentRanges[rangeIdx].fFirstEntry, fCurrentRanges[rangeIdx].fLastEntry), nullptr,
872 fPrincipalDescriptor.GetNEntries());
873
874 if (sampleMap.find(ntupleID) == sampleMap.end())
875 throw std::runtime_error("Full sample identifier '" + ntupleID + "' cannot be found in the available samples.");
876
878 ntupleID, std::make_pair(fCurrentRanges[rangeIdx].fFirstEntry, fCurrentRanges[rangeIdx].fLastEntry),
879 sampleMap.at(ntupleID), fPrincipalDescriptor.GetNEntries());
880}
881
883 const ROOT::RDF::ColumnNames_t &fileNames,
884 const std::pair<ULong64_t, ULong64_t> &range)
885{
886 std::unique_ptr<ROOT::RDF::RNTupleDS> ds{new ROOT::RDF::RNTupleDS(ntupleName, fileNames, range)};
887 return ROOT::RDataFrame(std::move(ds));
888}
889
890std::pair<std::vector<ROOT::Internal::RNTupleClusterBoundaries>, ROOT::NTupleSize_t>
891ROOT::Internal::RDF::GetClustersAndEntries(std::string_view ntupleName, std::string_view location)
892{
893 auto source = ROOT::Internal::RPageSource::Create(ntupleName, location);
894 source->Attach();
895 // TODO(jblomer): improve MT scheduling such that not all page lists are required right away
896 source->LoadAllPageLists();
897 auto descGuard = source->GetSharedDescriptorGuard();
898 return std::make_pair(ROOT::Internal::GetClusterBoundaries(descGuard.GetConstRef()), descGuard->GetNEntries());
899}
#define f(i)
Definition RSha256.hxx:104
long long Long64_t
Portable signed long integer 8 bytes.
Definition RtypesCore.h:84
unsigned long long ULong64_t
Portable unsigned long integer 8 bytes.
Definition RtypesCore.h:85
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:139
#define N
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t r
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
char name[80]
Definition TGX11.cxx:142
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
#define _(A, B)
Definition cfortran.h:112
Every RDF column is represented by exactly one RNTuple field.
Definition RNTupleDS.cxx:57
void * GetImpl(Long64_t entry) final
void Connect(RPageSource &source, Long64_t entryOffset)
Connect the field and its subfields to the page source.
Definition RNTupleDS.cxx:77
RNTupleColumnReader(RNTupleDS *ds, RFieldBase *protoField)
Definition RNTupleDS.cxx:73
std::unique_ptr< RFieldBase::RValue > fValue
The memory location used to read from fField.
Definition RNTupleDS.cxx:64
std::unique_ptr< RFieldBase > fField
The field backing the RDF column.
Definition RNTupleDS.cxx:63
Long64_t fEntryOffset
For chains, the logical entry and the physical entry in any particular file can be different.
Definition RNTupleDS.cxx:70
std::shared_ptr< void > fValuePtr
Used to reuse the object created by fValue when reconnecting sources.
Definition RNTupleDS.cxx:65
RNTupleDS * fDataSource
The data source that owns this column reader.
Definition RNTupleDS.cxx:61
RFieldBase * fProtoField
The prototype field from which fField is cloned.
Definition RNTupleDS.cxx:62
Long64_t fLastEntry
Last entry number that was read.
Definition RNTupleDS.cxx:66
Base for an artificial field that transforms an RNTuple column containing the offset of collections i...
static void SetClusterBunchSize(RNTupleReadOptions &options, unsigned int val)
Abstract interface to read data from an ntuple.
RSharedDescriptorGuard GetSharedDescriptorGuard() const
Takes the read lock for the descriptor.
static std::unique_ptr< RPageSource > Create(std::string_view ntupleName, std::string_view location, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Guess the concrete derived page source from the file name (location)
An artificial field that transforms an RNTuple column that contains the offset of collections into co...
Definition RField.hxx:369
std::vector< void * > Record_t
std::optional< std::pair< ULong64_t, ULong64_t > > fGlobalEntryRange
The RDataSource implementation for RNTuple.
Definition RNTupleDS.hxx:82
void AddField(const ROOT::RNTupleDescriptor &desc, std::string_view colName, ROOT::DescriptorId_t fieldId, std::vector< RFieldInfo > fieldInfos, bool convertToRVec=true)
Provides the RDF column "colName" given the field identified by fieldID.
std::vector< std::pair< ULong64_t, ULong64_t > > GetEntryRanges() final
Return ranges of entries to distribute to tasks.
void ExecStaging()
The main function of the fThreadStaging background thread.
std::vector< std::unique_ptr< ROOT::Internal::RPageSource > > fStagingArea
The staging area is relevant for chains of files, i.e.
std::unique_ptr< ROOT::Detail::RDF::RColumnReaderBase > GetColumnReaders(unsigned int, std::string_view, const std::type_info &) final
If the other GetColumnReaders overload returns an empty vector, this overload will be called instead.
std::vector< std::unique_ptr< ROOT::RFieldBase > > fProtoFields
We prepare a prototype field for every column.
void SetNSlots(unsigned int nSlots) final
Inform RDataSource of the number of processing slots (i.e.
ROOT::RFieldBase * GetFieldWithTypeChecks(std::string_view fieldName, const std::type_info &tid)
std::vector< std::string > fFileNames
void InitSlot(unsigned int slot, ULong64_t firstEntry) final
Convenience method called at the start of the data processing associated to a slot.
RNTupleDS(std::unique_ptr< ROOT::Internal::RPageSource > pageSource)
std::string GetTypeName(std::string_view colName) const final
Type of a column as a string, e.g.
std::unordered_map< ROOT::DescriptorId_t, std::string > fFieldId2QualifiedName
Connects the IDs of active proto fields and their subfields to their fully qualified name (a....
std::string fNTupleName
The data source may be constructed with an ntuple name and a list of files.
void PrepareNextRanges()
Populates fNextRanges with the next set of entry ranges.
void StageNextSources()
Starting from fNextFileIndex, opens the next fNSlots files.
void Finalize() final
Convenience method called after concluding an event-loop.
std::vector< std::string > fColumnTypes
void Initialize() final
Convenience method called before starting an event-loop.
std::vector< std::string > fColumnNames
bool HasColumn(std::string_view colName) const final
Checks if the dataset has a certain column.
Record_t GetColumnReadersImpl(std::string_view name, const std::type_info &) final
type-erased vector of pointers to pointers to column values - one per slot
void FinalizeSlot(unsigned int slot) final
Convenience method called at the end of the data processing associated to a slot.
This type represents a sample identifier, to be used in conjunction with RDataFrame features such as ...
ROOT's RDataFrame offers a modern, high-level interface for analysis of data stored in TTree ,...
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
A field translates read and write calls from/to underlying columns to/from tree values.
RConstSchemaIterator cbegin() const
const std::string & GetFieldName() const
static RResult< std::unique_ptr< RFieldBase > > Create(const std::string &fieldName, const std::string &typeName, const ROOT::RCreateFieldOptions &options, const ROOT::RNTupleDescriptor *desc, ROOT::DescriptorId_t fieldId)
Factory method to resurrect a field from the stored on-disk type information.
ROOT::DescriptorId_t GetOnDiskId() const
std::unique_ptr< RFieldBase > Clone(std::string_view newName) const
Copies the field and its subfields using a possibly new name and a new, unconnected set of columns.
std::uint64_t GetNRepetitions() const
const std::string & GetFieldName() const
const std::string & GetTypeName() const
The container field for an ntuple model, which itself has no physical representation.
Definition RField.hxx:58
std::vector< std::unique_ptr< RFieldBase > > ReleaseSubfields()
Moves all subfields into the returned vector.
Definition RField.cxx:64
void Attach(std::unique_ptr< RFieldBase > child)
A public version of the Attach method that allows piece-wise construction of the zero field.
Definition RField.cxx:40
The on-storage metadata of an RNTuple.
RFieldDescriptorIterable GetFieldIterable(const RFieldDescriptor &fieldDesc) const
const RFieldDescriptor & GetFieldDescriptor(ROOT::DescriptorId_t fieldId) const
ROOT::DescriptorId_t FindFieldId(std::string_view fieldName, ROOT::DescriptorId_t parentId) const
Common user-tunable settings for reading RNTuples.
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1687
std::string TypeID2TypeName(const std::type_info &id)
Returns the name of a type starting from its type_info An empty string is returned in case of failure...
Definition RDFUtils.cxx:200
ROOT::RDataFrame FromRNTuple(std::string_view ntupleName, const std::vector< std::string > &fileNames, const std::pair< ULong64_t, ULong64_t > &range)
Internal overload of the function that allows passing a range of entries.
std::pair< std::vector< ROOT::Internal::RNTupleClusterBoundaries >, ROOT::NTupleSize_t > GetClustersAndEntries(std::string_view ntupleName, std::string_view location)
Retrieves the cluster boundaries and the number of entries for the input RNTuple.
void SetAllowFieldSubstitutions(RFieldZero &fieldZero, bool val)
Definition RField.cxx:35
void CallConnectPageSourceOnField(RFieldBase &, ROOT::Internal::RPageSource &)
std::vector< ROOT::Internal::RNTupleClusterBoundaries > GetClusterBoundaries(const RNTupleDescriptor &desc)
Return the cluster boundaries for each cluster in this RNTuple.
std::string GetRenormalizedTypeName(const std::string &metaNormalizedName)
Given a type name normalized by ROOT meta, renormalize it for RNTuple. E.g., insert std::prefix.
RDataFrame FromRNTuple(std::string_view ntupleName, std::string_view fileName)
std::vector< std::string > ColumnNames_t
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
Tag to let data sources use the native data type when creating a column reader.
Definition Utils.hxx:332
The PrepareNextRanges() method populates the fNextRanges list with REntryRangeDS records.
Definition RNTupleDS.hxx:88
std::string_view fFileName
Storage location of the current RNTuple.
Definition RNTupleDS.hxx:93
ULong64_t fLastEntry
End entry index in fSource, e.g. the number of entries in the range is fLastEntry - fFirstEntry.
Definition RNTupleDS.hxx:92
std::unique_ptr< ROOT::Internal::RPageSource > fSource
Definition RNTupleDS.hxx:89
ULong64_t fFirstEntry
First entry index in fSource.
Definition RNTupleDS.hxx:90