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#include <string_view>
25
26#include <TError.h>
27#include <TSystem.h>
28
29#include <cassert>
30#include <memory>
31#include <mutex>
32#include <string>
33#include <vector>
34#include <typeinfo>
35#include <utility>
36
37// clang-format off
38/**
39* \class ROOT::RDF::RNTupleDS
40* \ingroup dataframe
41* \brief The RDataSource implementation for RNTuple. It lets RDataFrame read RNTuple data.
42*
43* An RDataFrame that reads RNTuple data can be constructed using FromRNTuple().
44*
45* For each column containing an array or a collection, a corresponding column `#colname` is available to access
46* `colname.size()` without reading and deserializing the collection values.
47*
48**/
49// clang-format on
50
51namespace ROOT::Internal::RDF {
52/// An artificial field that transforms an RNTuple column that contains the offset of collections into
53/// collection sizes. It is used to provide the "number of" RDF columns for collections, e.g.
54/// `R_rdf_sizeof_jets` for a collection named `jets`.
55///
56/// This field owns the collection offset field but instead of exposing the collection offsets it exposes
57/// the collection sizes (offset(N+1) - offset(N)). For the time being, we offer this functionality only in RDataFrame.
58/// TODO(jblomer): consider providing a general set of useful virtual fields as part of RNTuple.
60protected:
61 std::unique_ptr<ROOT::RFieldBase> CloneImpl(std::string_view newName) const final
62 {
63 return std::make_unique<RRDFCardinalityField>(newName);
64 }
65 void ConstructValue(void *where) const final { *static_cast<std::size_t *>(where) = 0; }
66
67 // We construct these fields and know that they match the page source
69
70public:
71 RRDFCardinalityField(std::string_view name)
72 : ROOT::RFieldBase(name, "std::size_t", ROOT::ENTupleStructure::kPlain, false /* isSimple */)
73 {
74 }
77 ~RRDFCardinalityField() override = default;
78
88 // Field is only used for reading
89 void GenerateColumns() final { throw RException(R__FAIL("Cardinality fields must only be used for reading")); }
94
95 size_t GetValueSize() const final { return sizeof(std::size_t); }
96 size_t GetAlignment() const final { return alignof(std::size_t); }
97
98 /// Get the number of elements of the collection identified by globalIndex
106
107 /// Get the number of elements of the collection identified by clusterIndex
115};
116
117/**
118 * @brief An artificial field that provides the size of a fixed-size array
119 *
120 * This is the implementation of `R_rdf_sizeof_column` in case `column` contains
121 * fixed-size arrays on disk.
122 */
124private:
125 std::size_t fArrayLength;
126
127 std::unique_ptr<ROOT::RFieldBase> CloneImpl(std::string_view newName) const final
128 {
129 return std::make_unique<RArraySizeField>(newName, fArrayLength);
130 }
131 void GenerateColumns() final { throw RException(R__FAIL("RArraySizeField fields must only be used for reading")); }
133 void ReadGlobalImpl(ROOT::NTupleSize_t /*globalIndex*/, void *to) final
134 {
135 *static_cast<std::size_t *>(to) = fArrayLength;
136 }
137 void ReadInClusterImpl(RNTupleLocalIndex /*localIndex*/, void *to) final
138 {
139 *static_cast<std::size_t *>(to) = fArrayLength;
140 }
141
142 // We construct these fields and know that they match the page source
144
145public:
146 RArraySizeField(std::string_view name, std::size_t arrayLength)
147 : ROOT::RFieldBase(name, "std::size_t", ROOT::ENTupleStructure::kPlain, false /* isSimple */),
149 {
150 }
156
157 void ConstructValue(void *where) const final { *static_cast<std::size_t *>(where) = 0; }
158 std::size_t GetValueSize() const final { return sizeof(std::size_t); }
159 std::size_t GetAlignment() const final { return alignof(std::size_t); }
160};
161
162/// Every RDF column is represented by exactly one RNTuple field
166
167 RNTupleDS *fDataSource; ///< The data source that owns this column reader
168 RFieldBase *fProtoField; ///< The prototype field from which fField is cloned
169 std::unique_ptr<RFieldBase> fField; ///< The field backing the RDF column
170 std::unique_ptr<RFieldBase::RValue> fValue; ///< The memory location used to read from fField
171 std::shared_ptr<void> fValuePtr; ///< Used to reuse the object created by fValue when reconnecting sources
172 Long64_t fLastEntry = -1; ///< Last entry number that was read
173 /// For chains, the logical entry and the physical entry in any particular file can be different.
174 /// The entry offset stores the logical entry number (sum of all previous physical entries) when a file of the
175 /// corresponding data source was opened.
177
178public:
180 ~RNTupleColumnReader() override = default;
181
182 /// Connect the field and its subfields to the page source
184 {
185 assert(fLastEntry == -1);
186
188
189 // Create a new, real field from the prototype and set its field ID in the context of the given page source
191 {
192 auto descGuard = source.GetSharedDescriptorGuard();
193 // Set the on-disk field IDs for the field and the subfield
194 fField->SetOnDiskId(
196 auto iProto = fProtoField->cbegin();
197 auto iReal = fField->begin();
198 for (; iReal != fField->end(); ++iProto, ++iReal) {
199 iReal->SetOnDiskId(descGuard->FindFieldId(fDataSource->fFieldId2QualifiedName.at(iProto->GetOnDiskId())));
200 }
201 }
202
205 fieldZero.Attach(std::move(fField));
206 try {
208 } catch (const ROOT::RException &err) {
209 fField = std::move(fieldZero.ReleaseSubfields()[0]);
210 auto onDiskType = source.GetSharedDescriptorGuard()->GetFieldDescriptor(fField->GetOnDiskId()).GetTypeName();
211 std::string msg = "RNTupleDS: invalid type \"" + fField->GetTypeName() + "\" for column \"" +
212 fDataSource->fFieldId2QualifiedName[fField->GetOnDiskId()] + "\" with on-disk type \"" +
213 onDiskType + "\"";
214 throw std::runtime_error(msg);
215 }
216 fField = std::move(fieldZero.ReleaseSubfields()[0]);
217
218 if (fValuePtr) {
219 // When the reader reconnects to a new file, the fValuePtr is already set
220 fValue = std::make_unique<RFieldBase::RValue>(fField->BindValue(fValuePtr));
221 fValuePtr = nullptr;
222 } else {
223 // For the first file, create a new object for this field (reader)
224 fValue = std::make_unique<RFieldBase::RValue>(fField->CreateValue());
225 }
226 }
227
229 {
230 if (fValue && keepValue) {
231 fValuePtr = fValue->GetPtr<void>();
232 }
233 fValue = nullptr;
234 fField = nullptr;
235 fLastEntry = -1;
236 }
237
238 void *GetImpl(Long64_t entry) final
239 {
240 if (entry != fLastEntry) {
241 fValue->Read(entry - fEntryOffset);
243 }
244 return fValue->GetPtr<void>().get();
245 }
246};
247} // namespace ROOT::Internal::RDF
248
250
252 ROOT::DescriptorId_t fieldId, std::vector<RNTupleDS::RFieldInfo> fieldInfos,
253 bool convertToRVec)
254{
255 // As an example for the mapping of RNTuple fields to RDF columns, let's consider an RNTuple
256 // using the following types and with a top-level field named "event" of type Event:
257 //
258 // struct Event {
259 // int id;
260 // std::vector<Track> tracks;
261 // };
262 // struct Track {
263 // std::vector<Hit> hits;
264 // };
265 // struct Hit {
266 // float x;
267 // float y;
268 // };
269 //
270 // AddField() will be called from the constructor with the RNTuple root field (ENTupleStructure::kRecord).
271 // From there, we recurse into the "event" sub field (also ENTupleStructure::kRecord) and further down the
272 // tree of sub fields and expose the following RDF columns:
273 //
274 // "event" [Event]
275 // "event.id" [int]
276 // "event.tracks" [RVec<Track>]
277 // "R_rdf_sizeof_event.tracks" [unsigned int]
278 // "event.tracks.hits" [RVec<RVec<Hit>>]
279 // "R_rdf_sizeof_event.tracks.hits" [RVec<unsigned int>]
280 // "event.tracks.hits.x" [RVec<RVec<float>>]
281 // "R_rdf_sizeof_event.tracks.hits.x" [RVec<unsigned int>]
282 // "event.tracks.hits.y" [RVec<RVec<float>>]
283 // "R_rdf_sizeof_event.tracks.hits.y" [RVec<unsigned int>]
284
285 const auto &fieldDesc = desc.GetFieldDescriptor(fieldId);
286 const auto &nRepetitions = fieldDesc.GetNRepetitions();
287 if ((fieldDesc.GetStructure() == ROOT::ENTupleStructure::kCollection) || (nRepetitions > 0)) {
288 // The field is a collection or a fixed-size array.
289 // We open a new collection scope with fieldID being the inner most collection. E.g. for "event.tracks.hits",
290 // fieldInfos would already contain the fieldID of "event.tracks"
291 fieldInfos.emplace_back(fieldId, nRepetitions);
292 }
293
294 if (fieldDesc.GetStructure() == ROOT::ENTupleStructure::kCollection) {
295 // Inner fields of collections are provided as projected collections of only that inner field,
296 // E.g. we provide a projected collection RVec<RVec<float>> for "event.tracks.hits.x" in the example
297 // above.
299 convertToRVec && (fieldDesc.GetTypeName().substr(0, 19) == "ROOT::VecOps::RVec<" ||
300 fieldDesc.GetTypeName().substr(0, 12) == "std::vector<" || fieldDesc.GetTypeName() == "");
301 const auto &f = *desc.GetFieldIterable(fieldDesc.GetId()).begin();
303
304 // Note that at the end of the recursion, we handled the inner sub collections as well as the
305 // collection as whole, so we are done.
306 return;
307
308 } else if (nRepetitions > 0) {
309 // Fixed-size array, same logic as ROOT::RVec.
310 const auto &f = *desc.GetFieldIterable(fieldDesc.GetId()).begin();
311 AddField(desc, colName, f.GetId(), fieldInfos);
312 return;
313 } else if (fieldDesc.GetStructure() == ROOT::ENTupleStructure::kRecord) {
314 // Inner fields of records are provided as individual RDF columns, e.g. "event.id"
315 for (const auto &f : desc.GetFieldIterable(fieldDesc.GetId())) {
316 auto innerName = colName.empty() ? f.GetFieldName() : (std::string(colName) + "." + f.GetFieldName());
317 // Inner fields of collections of records are always exposed as ROOT::RVec
318 AddField(desc, innerName, f.GetId(), fieldInfos);
319 }
320 }
321
322 // The fieldID could be the root field or the class of fieldId might not be loaded.
323 // In these cases, only the inner fields are exposed as RDF columns.
324 auto fieldOrException = ROOT::RFieldBase::Create(fieldDesc.GetFieldName(), fieldDesc.GetTypeName());
325 if (!fieldOrException)
326 return;
327 auto valueField = fieldOrException.Unwrap();
328 valueField->SetOnDiskId(fieldId);
329 for (auto &f : *valueField) {
330 f.SetOnDiskId(desc.FindFieldId(f.GetFieldName(), f.GetParent()->GetOnDiskId()));
331 }
332 std::unique_ptr<ROOT::RFieldBase> cardinalityField;
333 // Collections get the additional "number of" RDF column (e.g. "R_rdf_sizeof_tracks")
334 if (!fieldInfos.empty()) {
335 const auto &info = fieldInfos.back();
336 const std::string name = "R_rdf_sizeof_" + desc.GetFieldDescriptor(info.fFieldId).GetFieldName();
337 if (info.fNRepetitions > 0) {
338 cardinalityField = std::make_unique<ROOT::Internal::RDF::RArraySizeField>(name, info.fNRepetitions);
339 } else {
340 cardinalityField = std::make_unique<ROOT::Internal::RDF::RRDFCardinalityField>(name);
341 }
342 cardinalityField->SetOnDiskId(info.fFieldId);
343 }
344
345 for (auto i = fieldInfos.rbegin(); i != fieldInfos.rend(); ++i) {
346 const auto &fieldInfo = *i;
347
348 const auto valueFieldName = valueField->GetFieldName();
349
350 if (fieldInfo.fNRepetitions > 0) {
351 // Fixed-size array, read it as ROOT::RVec in memory
352 valueField =
353 std::make_unique<ROOT::RArrayAsRVecField>(valueFieldName, valueField->Clone("_0"), fieldInfo.fNRepetitions);
354 } else {
355 // Actual collection. A std::vector or ROOT::RVec gets added as a ROOT::RVec. All other collection types keep
356 // their original type.
357 if (convertToRVec) {
358 valueField = std::make_unique<ROOT::RRVecField>(valueFieldName, valueField->Clone("_0"));
359 } else {
360 auto outerFieldType = desc.GetFieldDescriptor(fieldInfo.fFieldId).GetTypeName();
362 }
363 }
364
365 valueField->SetOnDiskId(fieldInfo.fFieldId);
366
367 // Skip the inner-most collection level to construct the cardinality column
368 // It's taken care of by the `if (!fieldInfos.empty())` scope above
369 if (i != fieldInfos.rbegin()) {
370 const auto cardinalityFieldName = cardinalityField->GetFieldName();
371 if (fieldInfo.fNRepetitions > 0) {
372 // This collection level refers to a fixed-size array
373 cardinalityField = std::make_unique<ROOT::RArrayAsRVecField>(
374 cardinalityFieldName, cardinalityField->Clone("_0"), fieldInfo.fNRepetitions);
375 } else {
376 // This collection level refers to an RVec
377 cardinalityField = std::make_unique<ROOT::RRVecField>(cardinalityFieldName, cardinalityField->Clone("_0"));
378 }
379
380 cardinalityField->SetOnDiskId(fieldInfo.fFieldId);
381 }
382 }
383
384 if (cardinalityField) {
385 fColumnNames.emplace_back("R_rdf_sizeof_" + std::string(colName));
386 fColumnTypes.emplace_back(cardinalityField->GetTypeName());
387 fProtoFields.emplace_back(std::move(cardinalityField));
388 }
389
390 fieldInfos.emplace_back(fieldId, nRepetitions);
391 fColumnNames.emplace_back(colName);
392 fColumnTypes.emplace_back(valueField->GetTypeName());
393 fProtoFields.emplace_back(std::move(valueField));
394}
395
396ROOT::RDF::RNTupleDS::RNTupleDS(std::unique_ptr<ROOT::Internal::RPageSource> pageSource)
397{
398 pageSource->Attach();
399 fPrincipalDescriptor = pageSource->GetSharedDescriptorGuard()->Clone();
400 fStagingArea.emplace_back(std::move(pageSource));
401
402 AddField(fPrincipalDescriptor, "", fPrincipalDescriptor.GetFieldZeroId(),
403 std::vector<ROOT::RDF::RNTupleDS::RFieldInfo>());
404}
405
406namespace {
407
409{
410 // The setting is for now a global one, must be decided before running the
411 // program by setting the appropriate environment variable. Make sure that
412 // option configuration is thread-safe and happens only once.
414 static std::once_flag flag;
415 std::call_once(flag, []() {
416 if (auto env = gSystem->Getenv("ROOT_RNTUPLE_CLUSTERBUNCHSIZE"); env != nullptr && strlen(env) > 0) {
417 std::string envStr{env};
418 auto envNum{std::stoul(envStr)};
419 envNum = envNum == 0 ? 1 : envNum;
421 }
422 });
423 return opts;
424}
425
426std::unique_ptr<ROOT::Internal::RPageSource> CreatePageSource(std::string_view ntupleName, std::string_view fileName)
427{
429}
430} // namespace
431
432ROOT::RDF::RNTupleDS::RNTupleDS(std::string_view ntupleName, std::string_view fileName)
433 : RNTupleDS(CreatePageSource(ntupleName, fileName))
434{
435 fFileNames = std::vector<std::string>{std::string{fileName}};
436}
437
438ROOT::RDF::RNTupleDS::RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames)
439 : RNTupleDS(CreatePageSource(ntupleName, fileNames[0]))
440{
443 fStagingArea.resize(fFileNames.size());
444}
445
446ROOT::RDF::RNTupleDS::RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames,
447 const std::pair<ULong64_t, ULong64_t> &range)
449{
451}
452
454ROOT::RDF::RNTupleDS::GetColumnReadersImpl(std::string_view /* name */, const std::type_info & /* ti */)
455{
456 // This datasource uses the newer GetColumnReaders() API
457 return {};
458}
459
461{
462 // At this point we can assume that `name` will be found in fColumnNames
463 const auto index =
464 std::distance(fColumnNames.begin(), std::find(fColumnNames.begin(), fColumnNames.end(), fieldName));
465
466 // A reader was requested but we don't have RTTI for it, this is encoded with the tag UseNativeDataType. We can just
467 // return the available protofield
469 return fProtoFields[index].get();
470 }
471
472 // The user explicitly requested a type
474
475 // If the field corresponding to the provided name is not a cardinality column and the requested type is different
476 // from the proto field that was created when the data source was constructed, we first have to create an
477 // alternative proto field for the column reader. Otherwise, we can directly use the existing proto field.
478 if (fieldName.substr(0, 13) != "R_rdf_sizeof_" && requestedType != fColumnTypes[index]) {
479 auto &altProtoFields = fAlternativeProtoFields[index];
480
481 // If we can find the requested type in the registered alternative protofields, return the corresponding field
482 if (auto altProtoField = std::find_if(altProtoFields.begin(), altProtoFields.end(),
483 [&requestedType](const std::unique_ptr<ROOT::RFieldBase> &fld) {
484 return fld->GetTypeName() == requestedType;
485 });
487 return altProtoField->get();
488 }
489
490 // Otherwise, create a new protofield and register it in the alternatives before returning
493 throw std::runtime_error("RNTupleDS: Could not create field with type \"" + requestedType +
494 "\" for column \"" + std::string(fieldName) + "\"");
495 }
497 newAltProtoField->SetOnDiskId(fProtoFields[index]->GetOnDiskId());
498 auto *newField = newAltProtoField.get();
499 altProtoFields.emplace_back(std::move(newAltProtoField));
500 return newField;
501 }
502
503 // General case: there was a correspondence between the user-requested type and the corresponding column type
504 return fProtoFields[index].get();
505}
506
507std::unique_ptr<ROOT::Detail::RDF::RColumnReaderBase>
508ROOT::RDF::RNTupleDS::GetColumnReaders(unsigned int slot, std::string_view name, const std::type_info &tid)
509{
510 ROOT::RFieldBase *field = GetFieldWithTypeChecks(name, tid);
511 assert(field != nullptr);
512
513 // Map the field's and subfields' IDs to qualified names so that we can later connect the fields to
514 // other page sources from the chain
515 fFieldId2QualifiedName[field->GetOnDiskId()] = fPrincipalDescriptor.GetQualifiedFieldName(field->GetOnDiskId());
516 for (const auto &s : *field) {
517 fFieldId2QualifiedName[s.GetOnDiskId()] = fPrincipalDescriptor.GetQualifiedFieldName(s.GetOnDiskId());
518 }
519
520 auto reader = std::make_unique<ROOT::Internal::RDF::RNTupleColumnReader>(this, field);
521 fActiveColumnReaders[slot].emplace_back(reader.get());
522
523 return reader;
524}
525
527{
528 while (true) {
529 std::unique_lock lock(fMutexStaging);
530 fCvStaging.wait(lock, [this] { return fIsReadyForStaging || fStagingThreadShouldTerminate; });
531 if (fStagingThreadShouldTerminate)
532 return;
533
534 assert(!fHasNextSources);
535 StageNextSources();
536 fHasNextSources = true;
537 fIsReadyForStaging = false;
538
539 lock.unlock();
540 fCvStaging.notify_one();
541 }
542}
543
545{
546 const auto nFiles = fFileNames.empty() ? 1 : fFileNames.size();
547
548 for (auto i = fNextFileIndex; (i < nFiles) && ((i - fNextFileIndex) < fNSlots); ++i) {
549
550 if (fStagingThreadShouldTerminate)
551 return;
552
553 if (fStagingArea[i]) {
554 // The first file is already open and was used to read the schema
555 assert(i == 0);
556 } else {
557 fStagingArea[i] = CreatePageSource(fNTupleName, fFileNames[i]);
558 fStagingArea[i]->LoadStructure();
559 }
560 }
561}
562
564{
565 assert(fNextRanges.empty());
566
567 auto nFiles = fFileNames.empty() ? 1 : fFileNames.size();
568 auto nRemainingFiles = nFiles - fNextFileIndex;
569
570 if (nRemainingFiles == 0)
571 return;
572
573 // Easy work scheduling: one file per slot. We skip empty files (files without entries).
574
575 if ((nRemainingFiles >= fNSlots) || (fGlobalEntryRange.has_value())) {
576 while ((fNextRanges.size() < fNSlots) && (fNextFileIndex < nFiles)) {
578
579 std::swap(fStagingArea[fNextFileIndex], range.fSource);
580
581 if (!range.fSource) {
582 // Typically, the prestaged source should have been present. Only if some of the files are empty, we need
583 // to open and attach files here.
584 range.fSource = CreatePageSource(fNTupleName, fFileNames[fNextFileIndex]);
585 }
586 range.fFileName = fFileNames[fNextFileIndex];
587 range.fSource->Attach();
588 fNextFileIndex++;
589 auto nEntries = range.fSource->GetNEntries();
590 if (nEntries == 0)
591 continue;
592 range.fLastEntry = nEntries; // whole file per slot, i.e. entry range [0..nEntries - 1]
593
594 fNextRanges.emplace_back(std::move(range));
595 }
596 return;
597 }
598
599 // Work scheduling of the tail: multiple slots work on the same file.
600 // Every slot still has its own page source but these page sources may open the same file.
601 // Again, we need to skip empty files.
602 unsigned int nSlotsPerFile = fNSlots / nRemainingFiles;
603 for (std::size_t i = 0; (fNextRanges.size() < fNSlots) && (fNextFileIndex < nFiles); ++i) {
604 std::unique_ptr<ROOT::Internal::RPageSource> source;
605 // Need to look for the file name to populate the sample info later
606 const auto &sourceFileName = fFileNames[fNextFileIndex];
607 std::swap(fStagingArea[fNextFileIndex], source);
608 if (!source) {
609 // Empty files trigger this condition
610 source = CreatePageSource(fNTupleName, fFileNames[fNextFileIndex]);
611 }
612 source->Attach();
613 fNextFileIndex++;
614
615 auto nEntries = source->GetNEntries();
616 if (nEntries == 0)
617 continue;
618
619 // If last file: use all remaining slots
620 if (i == (nRemainingFiles - 1))
621 nSlotsPerFile = fNSlots - fNextRanges.size();
622
623 const auto rangesByCluster = [&source]() {
624 // Take the shared lock of the descriptor just for the time necessary
625 const auto descGuard = source->GetSharedDescriptorGuard();
627 }();
628
629 const unsigned int nRangesByCluster = rangesByCluster.size();
630
631 // Distribute slots equidistantly over the entry range, aligned on cluster boundaries
633 const auto remainder = nRangesByCluster % nSlotsPerFile;
634 std::size_t iRange = 0;
635 unsigned int iSlot = 0;
636 const unsigned int N = std::min(nSlotsPerFile, nRangesByCluster);
637 for (; iSlot < N; ++iSlot) {
638 auto start = rangesByCluster[iRange].fFirstEntry;
639 iRange += nClustersPerSlot + static_cast<int>(iSlot < remainder);
640 assert(iRange > 0);
641 auto end = rangesByCluster[iRange - 1].fLastEntryPlusOne;
642
644 range.fFileName = sourceFileName;
645 // The last range for this file just takes the already opened page source. All previous ranges clone.
646 if (iSlot == N - 1) {
647 range.fSource = std::move(source);
648 } else {
649 range.fSource = source->Clone();
650 }
651 range.fSource->SetEntryRange({start, end - start});
652 range.fFirstEntry = start;
653 range.fLastEntry = end;
654 fNextRanges.emplace_back(std::move(range));
655 }
656 } // loop over tail of remaining files
657}
658
659std::vector<std::pair<ULong64_t, ULong64_t>> ROOT::RDF::RNTupleDS::GetEntryRanges()
660{
661 std::vector<std::pair<ULong64_t, ULong64_t>> ranges;
662
663 // We need to distinguish between single threaded and multi-threaded runs.
664 // In single threaded mode, InitSlot is only called once and column readers have to be rewired
665 // to new page sources of the chain in GetEntryRanges. In multi-threaded mode, on the other hand,
666 // InitSlot is called for every returned range, thus rewiring the column readers takes place in
667 // InitSlot and FinalizeSlot.
668
669 if (fNSlots == 1) {
670 for (auto r : fActiveColumnReaders[0]) {
671 r->Disconnect(true /* keepValue */);
672 }
673 }
674
675 // If we have fewer files than slots and we run multiple event loops, we can reuse fCurrentRanges and don't need
676 // to worry about loading the fNextRanges. I.e., in this case we don't enter the if block.
677 if (fCurrentRanges.empty() || fSeenEntriesNoGlobalRange > 0) {
678 // Otherwise, i.e. start of the first event loop or in the middle of the event loop, prepare the next ranges
679 // and swap with the current ones.
680 {
681 std::unique_lock lock(fMutexStaging);
682 fCvStaging.wait(lock, [this] { return fHasNextSources; });
683 }
684 PrepareNextRanges();
685 if (fNextRanges.empty()) {
686 // No more data
687 return ranges;
688 }
689
690 assert(fNextRanges.size() <= fNSlots);
691
692 fCurrentRanges.clear();
693 std::swap(fCurrentRanges, fNextRanges);
694 }
695
696 // Stage next batch of files for the next call to GetEntryRanges()
697 {
698 std::lock_guard _(fMutexStaging);
699 fIsReadyForStaging = true;
700 fHasNextSources = false;
701 }
702 fCvStaging.notify_one();
703
704 // Create ranges for the RDF loop manager from the list of REntryRangeDS records.
705 // The entry ranges that are relative to the page source in REntryRangeDS are translated into absolute
706 // entry ranges, given the current state of the entry cursor.
707 // We remember the connection from first absolute entry index of a range to its REntryRangeDS record
708 // so that we can properly rewire the column reader in InitSlot
709 fFirstEntry2RangeIdx.clear();
710 fOriginalRanges.clear();
711
713
714 for (std::size_t i = 0; i < fCurrentRanges.size(); ++i) {
715
716 // Several consecutive ranges may operate on the same file (each with their own page source clone).
717 // We can detect a change of file when the first entry number jumps back to 0.
718 if (fCurrentRanges[i].fFirstEntry == 0) {
719 // New source
720 fSeenEntriesNoGlobalRange += nEntriesPerSource;
722 }
723
724 auto start = fCurrentRanges[i].fFirstEntry + fSeenEntriesNoGlobalRange;
725 auto end = fCurrentRanges[i].fLastEntry + fSeenEntriesNoGlobalRange;
726
727 nEntriesPerSource += end - start;
728
729 if (fGlobalEntryRange.has_value()) {
730
731 // We need to consider different scenarios for when we have GlobalRanges set by the user.
732 // Consider a simple case of 3 files, with original ranges set as (consecutive entries of 3 files):
733 // [0, 20], [20, 45], [45, 65]
734 // we will now see what happens in each of the scenarios below when GlobalRanges can be set to different
735 // values:
736 // a) [2, 5] - we stay in file 1
737 // - hence we will use the 1st case and get the range [2,5], in this case we also need to quit further
738 // processing from the other files by entering case 3
739 // b) [2, 21] - we start in file 1 and finish in file 2
740 // - use the 2nd case first, as 21 > 20 (end of first file), then we will go to case 1, resulting in ranges:
741 // [2, 20], [20, 21], c) [21 - 40] - we skip file 1, start in file 2 and stay in file 2
742 // - to skip the first file, we use the 4th case, followed by the 1st case, resulting range is: [21, 40]
743 // d) [21 - 65] - we skip file 1, start in file 2 and continue to file 3
744 // - to skip the first file, we use the 4th case, we continue with the 2nd case, and use the 1st case at the
745 // end, resulting ranges are [21, 45], [45, 65]
746 // The first case
747 if (fGlobalEntryRange->first >= start && fGlobalEntryRange->second <= end) {
748 fOriginalRanges.emplace_back(start, end);
749 fFirstEntry2RangeIdx[fGlobalEntryRange->first] = i;
750 ranges.emplace_back(fGlobalEntryRange->first, fGlobalEntryRange->second);
751 }
752
753 // The second case:
754 // The `fGlobalEntryRange->first < end` condition is to distinguish this case from the 4th case.
755 else if (fGlobalEntryRange->second > end && fGlobalEntryRange->first < end) {
756 fOriginalRanges.emplace_back(start, end);
757 fFirstEntry2RangeIdx[fGlobalEntryRange->first] = i;
758 ranges.emplace_back(fGlobalEntryRange->first, end);
759 std::optional<std::pair<ULong64_t, ULong64_t>> newvalues({end, fGlobalEntryRange->second});
760 fGlobalEntryRange.swap(newvalues);
761 }
762 // The third case, needed to correctly quit processing if we only stay in the first file
763 else if (fGlobalEntryRange->second < start) {
764 return ranges;
765 }
766
767 // The fourth case:
768 else if (fGlobalEntryRange->first >= end) {
769 fOriginalRanges.emplace_back(start, end);
770 fFirstEntry2RangeIdx[start] = i;
771 ranges.emplace_back(start, start);
772 }
773 }
774
775 else {
776 fFirstEntry2RangeIdx[start] = i;
777 fOriginalRanges.emplace_back(start, end);
778 ranges.emplace_back(start, end);
779 }
780 }
781
782 fSeenEntriesNoGlobalRange += nEntriesPerSource;
783
784 if ((fNSlots == 1) && (fCurrentRanges[0].fSource)) {
785 for (auto r : fActiveColumnReaders[0]) {
786 r->Connect(*fCurrentRanges[0].fSource, fOriginalRanges[0].first);
787 }
788 }
789
790 return ranges;
791}
792
794{
795 if (fNSlots == 1) {
796 // Ensure the connection between slot and range is valid also in single-thread mode
797 fSlotsToRangeIdxs[0] = 0;
798 return;
799 }
800
801 // The same slot ID could be picked multiple times in the same execution, thus
802 // ending up processing different page sources. Here we re-establish the
803 // connection between the slot and the correct page source by finding which
804 // range index corresponds to the first entry passed.
805 auto idxRange = fFirstEntry2RangeIdx.at(firstEntry);
806
807 // We also remember this connection so it can later be retrieved in CreateSampleInfo
808 fSlotsToRangeIdxs[slot * ROOT::Internal::RDF::CacheLineStep<std::size_t>()] = idxRange;
809
810 for (auto r : fActiveColumnReaders[slot]) {
811 r->Connect(*fCurrentRanges[idxRange].fSource,
812 fOriginalRanges[idxRange].first - fCurrentRanges[idxRange].fFirstEntry);
813 }
814}
815
817{
818 if (fNSlots == 1)
819 return;
820
821 for (auto r : fActiveColumnReaders[slot]) {
822 r->Disconnect(true /* keepValue */);
823 }
824}
825
826std::string ROOT::RDF::RNTupleDS::GetTypeName(std::string_view colName) const
827{
828 auto colNamePos = std::find(fColumnNames.begin(), fColumnNames.end(), colName);
829
830 if (colNamePos == fColumnNames.end()) {
831 auto msg = std::string("RNTupleDS: There is no column with name \"") + std::string(colName) + "\"";
832 throw std::runtime_error(msg);
833 }
834
835 const auto index = std::distance(fColumnNames.begin(), colNamePos);
836 return fColumnTypes[index];
837}
838
839bool ROOT::RDF::RNTupleDS::HasColumn(std::string_view colName) const
840{
841 return std::find(fColumnNames.begin(), fColumnNames.end(), colName) != fColumnNames.end();
842}
843
845{
846 fSeenEntriesNoGlobalRange = 0;
847 fNextFileIndex = 0;
848 fIsReadyForStaging = fHasNextSources = fStagingThreadShouldTerminate = false;
849 fThreadStaging = std::thread(&RNTupleDS::ExecStaging, this);
850 assert(fNextRanges.empty());
851
852 if (fCurrentRanges.empty() || (fFileNames.size() > fNSlots)) {
853 // First event loop or large number of files: start the staging process.
854 {
855 std::lock_guard _(fMutexStaging);
856 fIsReadyForStaging = true;
857 }
858 fCvStaging.notify_one();
859 } else {
860 // Otherwise, we will reuse fCurrentRanges. Make sure that staging and preparing next ranges will be a noop
861 // (already at the end of the list of files).
862 fNextFileIndex = std::max(fFileNames.size(), std::size_t(1));
863 }
864}
865
867{
868 for (unsigned int i = 0; i < fNSlots; ++i) {
869 for (auto r : fActiveColumnReaders[i]) {
870 r->Disconnect(false /* keepValue */);
871 }
872 }
873 {
874 std::lock_guard _(fMutexStaging);
875 fStagingThreadShouldTerminate = true;
876 }
877 fCvStaging.notify_one();
878 fThreadStaging.join();
879 // If we have a chain with more files than the number of slots, the files opened at the end of the
880 // event loop won't be reused when the event loop restarts, so we can close them.
881 if (fFileNames.size() > fNSlots) {
882 fCurrentRanges.clear();
883 fNextRanges.clear();
884 fStagingArea.clear();
885 fStagingArea.resize(fFileNames.size());
886 }
887}
888
890{
891 assert(fNSlots == 0);
892 assert(nSlots > 0);
893 fNSlots = nSlots;
894 fActiveColumnReaders.resize(fNSlots);
895 fSlotsToRangeIdxs.resize(fNSlots * ROOT::Internal::RDF::CacheLineStep<std::size_t>());
896}
897
898ROOT::RDataFrame ROOT::RDF::FromRNTuple(std::string_view ntupleName, std::string_view fileName)
899{
900 return ROOT::RDataFrame(std::make_unique<ROOT::RDF::RNTupleDS>(ntupleName, fileName));
901}
902
903ROOT::RDataFrame ROOT::RDF::FromRNTuple(std::string_view ntupleName, const std::vector<std::string> &fileNames)
904{
905 return ROOT::RDataFrame(std::make_unique<ROOT::RDF::RNTupleDS>(ntupleName, fileNames));
906}
907
908ROOT::RDF::RSampleInfo ROOT::Internal::RDF::RNTupleDS::CreateSampleInfo(
909 unsigned int slot, const std::unordered_map<std::string, ROOT::RDF::Experimental::RSample *> &sampleMap) const
910{
911 // The same slot ID could be picked multiple times in the same execution, thus
912 // ending up processing different page sources. Here we re-establish the
913 // connection between the slot and the correct page source by retrieving
914 // which range is connected currently to the slot
915
916 const auto &rangeIdx = fSlotsToRangeIdxs.at(slot * ROOT::Internal::RDF::CacheLineStep<std::size_t>());
917
918 // Missing source if a file does not exist
919 if (!fCurrentRanges[rangeIdx].fSource)
920 return ROOT::RDF::RSampleInfo{};
921
922 const auto &ntupleName = fCurrentRanges[rangeIdx].fSource->GetNTupleName();
923 const auto &ntuplePath = fCurrentRanges[rangeIdx].fFileName;
924 const auto ntupleID = std::string(ntuplePath) + '/' + ntupleName;
925
926 if (sampleMap.empty())
928 ntupleID, std::make_pair(fCurrentRanges[rangeIdx].fFirstEntry, fCurrentRanges[rangeIdx].fLastEntry), nullptr,
929 fPrincipalDescriptor.GetNEntries());
930
931 if (sampleMap.find(ntupleID) == sampleMap.end())
932 throw std::runtime_error("Full sample identifier '" + ntupleID + "' cannot be found in the available samples.");
933
935 ntupleID, std::make_pair(fCurrentRanges[rangeIdx].fFirstEntry, fCurrentRanges[rangeIdx].fLastEntry),
936 sampleMap.at(ntupleID), fPrincipalDescriptor.GetNEntries());
937}
938
941 const std::pair<ULong64_t, ULong64_t> &range)
942{
943 std::unique_ptr<ROOT::RDF::RNTupleDS> ds{new ROOT::RDF::RNTupleDS(ntupleName, fileNames, range)};
944 return ROOT::RDataFrame(std::move(ds));
945}
946
947std::pair<std::vector<ROOT::Internal::RNTupleClusterBoundaries>, ROOT::NTupleSize_t>
948ROOT::Internal::RDF::GetClustersAndEntries(std::string_view ntupleName, std::string_view location)
949{
951 source->Attach();
952 const auto descGuard = source->GetSharedDescriptorGuard();
953 return std::make_pair(ROOT::Internal::GetClusterBoundaries(descGuard.GetRef()), descGuard->GetNEntries());
954}
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
Definition RError.hxx:300
#define f(i)
Definition RSha256.hxx:104
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
long long Long64_t
Portable signed long integer 8 bytes.
Definition RtypesCore.h:83
unsigned long long ULong64_t
Portable unsigned long integer 8 bytes.
Definition RtypesCore.h:84
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#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:110
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
#define _(A, B)
Definition cfortran.h:108
void GetCollectionInfo(const ROOT::NTupleSize_t globalIndex, RNTupleLocalIndex *collectionStart, ROOT::NTupleSize_t *collectionSize)
For offset columns only, look at the two adjacent values that define a collection's coordinates.
Definition RColumn.hxx:283
An artificial field that provides the size of a fixed-size array.
void GenerateColumns(const ROOT::RNTupleDescriptor &) final
Implementations in derived classes should create the backing columns corresponding to the field type ...
void ReadGlobalImpl(ROOT::NTupleSize_t, void *to) final
RArraySizeField(const RArraySizeField &other)=delete
RArraySizeField(RArraySizeField &&other)=default
RArraySizeField & operator=(RArraySizeField &&other)=default
RArraySizeField(std::string_view name, std::size_t arrayLength)
void ReadInClusterImpl(RNTupleLocalIndex, void *to) final
void ReconcileOnDiskField(const RNTupleDescriptor &) final
For non-artificial fields, check compatibility of the in-memory field and the on-disk field.
std::size_t GetValueSize() const final
The number of bytes taken by a value of the appropriate type.
void ConstructValue(void *where) const final
Constructs value in a given location of size at least GetValueSize(). Called by the base class' Creat...
RArraySizeField & operator=(const RArraySizeField &other)=delete
std::size_t GetAlignment() const final
As a rule of thumb, the alignment is equal to the size of the type.
void GenerateColumns() final
Implementations in derived classes should create the backing columns corresponding to the field type ...
std::unique_ptr< ROOT::RFieldBase > CloneImpl(std::string_view newName) const final
Called by Clone(), which additionally copies the on-disk ID.
Every RDF column is represented by exactly one RNTuple field.
void * GetImpl(Long64_t entry) final
void Connect(RPageSource &source, Long64_t entryOffset)
Connect the field and its subfields to the page source.
RNTupleColumnReader(RNTupleDS *ds, RFieldBase *protoField)
std::unique_ptr< RFieldBase::RValue > fValue
The memory location used to read from fField.
std::unique_ptr< RFieldBase > fField
The field backing the RDF column.
Long64_t fEntryOffset
For chains, the logical entry and the physical entry in any particular file can be different.
std::shared_ptr< void > fValuePtr
Used to reuse the object created by fValue when reconnecting sources.
RNTupleDS * fDataSource
The data source that owns this column reader.
RFieldBase * fProtoField
The prototype field from which fField is cloned.
Long64_t fLastEntry
Last entry number that was read.
An artificial field that transforms an RNTuple column that contains the offset of collections into co...
Definition RNTupleDS.cxx:59
RRDFCardinalityField(RRDFCardinalityField &&other)=default
size_t GetAlignment() const final
As a rule of thumb, the alignment is equal to the size of the type.
Definition RNTupleDS.cxx:96
void GenerateColumns() final
Implementations in derived classes should create the backing columns corresponding to the field type ...
Definition RNTupleDS.cxx:89
RRDFCardinalityField(std::string_view name)
Definition RNTupleDS.cxx:71
void ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to) final
Get the number of elements of the collection identified by globalIndex.
Definition RNTupleDS.cxx:99
void ReconcileOnDiskField(const RNTupleDescriptor &) final
For non-artificial fields, check compatibility of the in-memory field and the on-disk field.
Definition RNTupleDS.cxx:68
RRDFCardinalityField & operator=(RRDFCardinalityField &&other)=default
std::unique_ptr< ROOT::RFieldBase > CloneImpl(std::string_view newName) const final
Called by Clone(), which additionally copies the on-disk ID.
Definition RNTupleDS.cxx:61
void GenerateColumns(const ROOT::RNTupleDescriptor &desc) final
Implementations in derived classes should create the backing columns corresponding to the field type ...
Definition RNTupleDS.cxx:90
const RColumnRepresentations & GetColumnRepresentations() const final
Implementations in derived classes should return a static RColumnRepresentations object.
Definition RNTupleDS.cxx:79
size_t GetValueSize() const final
The number of bytes taken by a value of the appropriate type.
Definition RNTupleDS.cxx:95
void ReadInClusterImpl(ROOT::RNTupleLocalIndex localIndex, void *to) final
Get the number of elements of the collection identified by clusterIndex.
void ConstructValue(void *where) const final
Constructs value in a given location of size at least GetValueSize(). Called by the base class' Creat...
Definition RNTupleDS.cxx:65
static void SetClusterBunchSize(RNTupleReadOptions &options, unsigned int val)
Abstract interface to read data from an ntuple.
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)
std::vector< void * > Record_t
std::optional< std::pair< ULong64_t, ULong64_t > > fGlobalEntryRange
The RDataSource implementation for RNTuple.
Definition RNTupleDS.hxx:74
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
Definition RNTupleDS.hxx:93
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.
Definition RNTupleDS.hxx:92
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:79
The list of column representations a field can have.
A field translates read and write calls from/to underlying columns to/from tree values.
ROOT::Internal::RColumn * fPrincipalColumn
All fields that have columns have a distinct main column.
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.
The container field for an ntuple model, which itself has no physical representation.
Definition RField.hxx:59
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
Addresses a column element or field item relative to a particular cluster, instead of a global NTuple...
Common user-tunable settings for reading RNTuples.
const_iterator begin() const
const_iterator end() const
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1676
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:191
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:36
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.
ENTupleStructure
The fields in the RNTuple data model tree can carry different structural information about the type s...
Tag to let data sources use the native data type when creating a column reader.
Definition Utils.hxx:347
The PrepareNextRanges() method populates the fNextRanges list with REntryRangeDS records.
Definition RNTupleDS.hxx:80