Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleProcessor.cxx
Go to the documentation of this file.
1/// \file RNTupleProcessor.cxx
2/// \author Florine de Geus <florine.de.geus@cern.ch>
3/// \date 2024-03-26
4/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
5/// is welcome!
6
7/*************************************************************************
8 * Copyright (C) 1995-2024, Rene Brun and Fons Rademakers. *
9 * All rights reserved. *
10 * *
11 * For the licensing terms see $ROOTSYS/LICENSE. *
12 * For the list of contributors see $ROOTSYS/README/CREDITS. *
13 *************************************************************************/
14
16
17#include <ROOT/RFieldBase.hxx>
18#include <ROOT/RNTuple.hxx>
20#include <ROOT/StringUtils.hxx>
21
22#include <TDirectory.h>
23
24#include <iomanip>
25
28{
29 processor.AddAllFieldsToEntry(RNTupleProcessorProvenance(), includeSubfields);
30 return processor.fEntry.get();
31}
32
33//------------------------------------------------------------------------------
34
35std::unique_ptr<ROOT::RNTupleProcessor>
37{
38 return std::unique_ptr<RNTupleSingleProcessor>(new RNTupleSingleProcessor(std::move(ntuple), options));
39}
40
41std::unique_ptr<ROOT::RNTupleProcessor>
42ROOT::RNTupleProcessor::CreateChain(std::vector<RNTupleInputSpec> ntuples, const RNTupleProcessorOptions &options)
43{
44 if (ntuples.empty())
45 throw RException(R__FAIL("at least one RNTuple must be provided"));
46
47 std::vector<std::unique_ptr<RNTupleProcessor>> innerProcessors;
48 innerProcessors.reserve(ntuples.size());
49
50 for (auto &ntuple : ntuples) {
51 innerProcessors.emplace_back(Create(std::move(ntuple)));
52 }
53
54 return CreateChain(std::move(innerProcessors), options);
55}
56
57std::unique_ptr<ROOT::RNTupleProcessor>
58ROOT::RNTupleProcessor::CreateChain(std::vector<std::unique_ptr<RNTupleProcessor>> innerProcessors,
59 const RNTupleProcessorOptions &options)
60{
61 if (innerProcessors.empty())
62 throw RException(R__FAIL("at least one inner processor must be provided"));
63
64 return std::unique_ptr<RNTupleChainProcessor>(new RNTupleChainProcessor(std::move(innerProcessors), options));
65}
66
67std::unique_ptr<ROOT::RNTupleProcessor>
69 const std::vector<std::string> &joinFields,
70 const RNTupleProcessorOptions &options)
71{
72 if (joinFields.size() > 4) {
73 throw RException(R__FAIL("a maximum of four join fields is allowed"));
74 }
75
76 if (std::unordered_set(joinFields.begin(), joinFields.end()).size() < joinFields.size()) {
77 throw RException(R__FAIL("join fields must be unique"));
78 }
79
80 std::unique_ptr<RNTupleProcessor> primaryProcessor = RNTupleProcessor::Create(std::move(primaryNTuple), options);
81
82 std::unique_ptr<RNTupleProcessor> auxProcessor = RNTupleProcessor::Create(std::move(auxNTuple));
83
84 return CreateRNTupleJoinProcessor(std::move(primaryProcessor), std::move(auxProcessor), joinFields, options);
85}
86
87std::unique_ptr<ROOT::RNTupleProcessor>
88ROOT::Experimental::CreateRNTupleJoinProcessor(std::unique_ptr<RNTupleProcessor> primaryProcessor,
89 std::unique_ptr<RNTupleProcessor> auxProcessor,
90 const std::vector<std::string> &joinFields,
91 const RNTupleProcessorOptions &options)
92{
93 if (joinFields.size() > 4) {
94 throw RException(R__FAIL("a maximum of four join fields is allowed"));
95 }
96
97 if (std::unordered_set(joinFields.begin(), joinFields.end()).size() < joinFields.size()) {
98 throw RException(R__FAIL("join fields must be unique"));
99 }
100
101 return std::unique_ptr<RNTupleJoinProcessor>(
102 new RNTupleJoinProcessor(std::move(primaryProcessor), std::move(auxProcessor), joinFields, options));
103}
104
105//------------------------------------------------------------------------------
106
108 : RNTupleProcessor(options), fNTupleSpec(std::move(ntuple))
109{
110 if (fOptions.GetProcessorName().empty()) {
112 }
113}
114
115namespace {
116std::unique_ptr<ROOT::Internal::RPageSource> CreatePageSource(const ROOT::RNTupleInputSpec &inputSpec)
117{
118 if (const std::string *storagePath = std::get_if<std::string>(&inputSpec.GetStorage()))
119 return ROOT::Internal::RPageSource::Create(inputSpec.GetNTupleName(), *storagePath);
120
121 auto dir = std::get<TDirectory *>(inputSpec.GetStorage());
122 auto ntuple = std::unique_ptr<ROOT::RNTuple>(dir->Get<ROOT::RNTuple>(inputSpec.GetNTupleName().c_str()));
124}
125} // anonymous namespace
126
128{
129 // We do not cache the page source to avoid side-effects e.g. keeping too many files opened in the case of a chain.
130 // We could still cache it, then invalidate it elsewhere, but the invalidation would need to be propagated to any
131 // RNTupleProcessorEntry objects in flight, which may require more complexity than necessary.
132 if (fNEntries == ROOT::kInvalidNTupleIndex) {
133 auto source = CreatePageSource(fNTupleSpec);
134 source->Attach();
135 fNEntries = source->GetNEntries();
136 }
137 return fNEntries;
138}
139
140void ROOT::RNTupleSingleProcessor::Initialize(std::shared_ptr<Internal::RNTupleProcessorEntry> entry)
141{
142 // The processor has already been initialized.
143 if (IsInitialized() && fPageSource)
144 return;
145
146 if (!entry)
147 fEntry = std::make_shared<Internal::RNTupleProcessorEntry>();
148 else
149 fEntry = std::move(entry);
150
151 fPageSource = CreatePageSource(fNTupleSpec);
152 fPageSource->Attach();
153
154 fNEntries = fPageSource->GetNEntries();
155}
156
158{
159 Initialize();
160 auto desc = fPageSource->GetSharedDescriptorGuard();
161 auto fieldZeroId = desc->GetFieldZeroId();
162
163 // TODO handle subfields
164 return desc->FindFieldId(fieldName, fieldZeroId) != ROOT::kInvalidDescriptorId;
165}
166
167std::unique_ptr<ROOT::RFieldBase>
168ROOT::RNTupleSingleProcessor::CreateAndConnectField(const std::string &qualifiedFieldName, const std::string &typeName)
169{
170 assert(fPageSource);
171
172 // Strip the "R_rntproc_join_" prefix (for join fields) from the field name, if present.
173 const std::string onDiskFieldName =
174 qualifiedFieldName.find("R_rntproc_join_") == 0 ? qualifiedFieldName.substr(15) : qualifiedFieldName;
175
176 auto descGuard = fPageSource->GetSharedDescriptorGuard();
177 const auto &desc = descGuard.GetConstRef();
178
179 const auto onDiskFieldId = desc.FindFieldId(onDiskFieldName);
180
181 if (onDiskFieldId == kInvalidDescriptorId) {
182 return nullptr;
183 }
184
185 std::unique_ptr<ROOT::RFieldBase> field;
186 if (typeName.empty()) {
187 const auto &fieldDesc = desc.GetFieldDescriptor(onDiskFieldId);
188 field = fieldDesc.CreateField(desc);
189 } else {
190 // Strip the parent field name prefix(es), if present.
191 std::string subfieldName = onDiskFieldName;
192 auto posDot = onDiskFieldName.find_last_of('.');
193 if (posDot != std::string::npos)
194 subfieldName = onDiskFieldName.substr(posDot + 1);
195
196 field = ROOT::RFieldBase::Create(subfieldName, typeName).Unwrap();
197 }
198
199 field->SetOnDiskId(onDiskFieldId);
200 return ConnectField(std::move(field));
201}
202
203std::unique_ptr<ROOT::RFieldBase> ROOT::RNTupleSingleProcessor::ConnectField(std::unique_ptr<ROOT::RFieldBase> field)
204{
205 ROOT::RFieldZero fieldZero;
207 fieldZero.Attach(std::move(field));
208 ROOT::Internal::CallConnectPageSourceOnField(fieldZero, *fPageSource);
209 return std::move(fieldZero.ReleaseSubfields()[0]);
210}
211
213ROOT::RNTupleSingleProcessor::AddFieldToEntry(const std::string &canonicalFieldName, const std::string &typeName,
214 void *valuePtr, const Internal::RNTupleProcessorProvenance &provenance)
215{
216 auto fieldIdx = fEntry->FindFieldIndex(canonicalFieldName, typeName);
217 if (!fieldIdx) {
218 // Strip the processor name prefix(es), if present.
219 std::string qualifiedFieldName = canonicalFieldName;
220 if (provenance.IsPresentInFieldName(qualifiedFieldName)) {
221 qualifiedFieldName = qualifiedFieldName.substr(provenance.Get().size() + 1);
222 }
223
224 auto field = CreateAndConnectField(qualifiedFieldName, typeName);
225
226 if (!field) {
227 throw RException(R__FAIL("cannot register field with name \"" + qualifiedFieldName +
228 "\" because it is not present in the on-disk information of the RNTuple(s) this "
229 "processor is created from"));
230 }
231
232 fieldIdx = fEntry->AddField(qualifiedFieldName, std::move(field), valuePtr, provenance);
233 }
234
235 return *fieldIdx;
236}
237
239ROOT::RNTupleSingleProcessor::AddFieldToEntry(std::unique_ptr<ROOT::RFieldBase> field,
240 const std::string &canonicalFieldName, void *valuePtr,
241 const Internal::RNTupleProcessorProvenance &provenance)
242{
243 auto fieldIdx = fEntry->FindFieldIndex(canonicalFieldName, field->GetTypeName());
244 if (!fieldIdx) {
245 // Strip the processor name prefix(es), if present.
246 std::string qualifiedFieldName = canonicalFieldName;
247 if (provenance.IsPresentInFieldName(qualifiedFieldName)) {
248 qualifiedFieldName = qualifiedFieldName.substr(provenance.Get().size() + 1);
249 }
250
251 field = ConnectField(std::move(field));
252
253 if (!field) {
254 throw RException(R__FAIL("cannot register field with name \"" + qualifiedFieldName +
255 "\" because it is not present in the on-disk information of the RNTuple(s) this "
256 "processor is created from"));
257 }
258
259 fieldIdx = fEntry->AddField(qualifiedFieldName, std::move(field), valuePtr, provenance);
260 }
261
262 return *fieldIdx;
263}
264
266 bool includeSubfields)
267{
268 Initialize();
269 auto descGuard = fPageSource->GetSharedDescriptorGuard();
270 const auto &desc = descGuard.GetConstRef();
271 auto fnAddSubfields = [this, &desc, &provenance](const ROOT::RFieldDescriptor &field, auto &fn) -> void {
272 std::string fieldName = desc.GetQualifiedFieldName(field.GetId());
273 if (auto prefix = provenance.Get(); !prefix.empty())
274 fieldName = prefix + "." + fieldName;
275
276 AddFieldToEntry(fieldName, field.GetTypeName(), nullptr, provenance);
277 for (const auto &subfield : desc.GetFieldIterable(field.GetId())) {
278 fn(subfield, fn);
279 }
280 };
281
282 for (const auto &field : desc.GetTopLevelFields()) {
283 if (includeSubfields) {
284 fnAddSubfields(field, fnAddSubfields);
285 } else {
286 std::string fieldName = desc.GetQualifiedFieldName(field.GetId());
287 if (auto prefix = provenance.Get(); !prefix.empty())
288 fieldName = prefix + "." + fieldName;
289
290 AddFieldToEntry(fieldName, field.GetTypeName(), nullptr, provenance);
291 }
292 }
293}
294
296{
297 if (globalEntryNumber >= fNEntries || !fEntry)
299 else
300 mapping.SetEntryNumber(globalEntryNumber);
301}
302
304{
305 assert(mapping.GetEntryNumber() != kInvalidNTupleIndex);
306
307 for (auto fieldIdx : fFieldIdxs) {
308 fEntry->ReadValue(fieldIdx, mapping.GetEntryNumber());
309 }
310
311 fNEntriesProcessed++;
312}
313
314namespace {
315void ResetColumnRepresentative(ROOT::RFieldBase &field)
316{
318 for (auto &subfield : field.GetMutableSubfields()) {
319 ResetColumnRepresentative(*subfield);
320 }
321}
322
323void SetOnDiskId(ROOT::RFieldBase &field, ROOT::DescriptorId_t onDiskId, const ROOT::RNTupleDescriptor &desc)
324{
325 field.SetOnDiskId(onDiskId);
326
327 for (auto &subfield : field.GetMutableSubfields()) {
328 onDiskId = desc.FindFieldId(subfield->GetQualifiedFieldName());
329 SetOnDiskId(*subfield, onDiskId, desc);
330 }
331}
332} // anonymous namespace
333
335 const std::unordered_set<Internal::RNTupleProcessorEntry::FieldIndex_t> &fieldIdxs,
336 const Internal::RNTupleProcessorProvenance & /* provenance */, bool updateFields)
337{
338 Initialize(fEntry);
339
340 fFieldIdxs = fieldIdxs;
341
342 if (updateFields) {
343 auto descGuard = fPageSource->GetSharedDescriptorGuard();
344 const auto &desc = descGuard.GetConstRef();
345
346 for (const auto &fieldIdx : fFieldIdxs) {
347 const auto &currField = fEntry->GetValue(fieldIdx).GetField();
348
349 auto onDiskId = desc.FindFieldId(currField.GetQualifiedFieldName());
350 bool needsClone = onDiskId != ROOT::kInvalidDescriptorId &&
351 currField.GetTypeName() == desc.GetFieldDescriptor(onDiskId).GetTypeName();
352 if (const auto fieldOriginIdx = fEntry->GetFieldOriginIdx(fieldIdx);
353 fieldOriginIdx != ROOT::kInvalidDescriptorId) {
354 const auto &originField = fEntry->GetField(fieldOriginIdx);
355 onDiskId = desc.FindFieldId(originField.GetQualifiedFieldName());
356 needsClone = true;
357 }
358
359 std::unique_ptr<ROOT::RFieldBase> newField;
360 if (needsClone) {
361 // We have enough information to clone the existing field.
362 newField = currField.Clone(currField.GetFieldName());
363 // The clone's column representatives (+ its subfields') need to be reset before it can be connected to the
364 // new page source.
365 ResetColumnRepresentative(*newField);
366 SetOnDiskId(*newField, onDiskId, desc);
367 newField = ConnectField(std::move(newField));
368 } else {
369 // We need to create the new field from scratch (e.g. because it is a subfield without a parent field
370 // attached).
371 newField = CreateAndConnectField(fEntry->GetQualifiedFieldName(fieldIdx), currField.GetTypeName());
372 }
373
374 fEntry->UpdateField(fieldIdx, std::move(newField));
375 }
376 }
377}
378
380{
381 if (fPageSource) {
382 fEntry->ResetFields(fFieldIdxs);
383 fPageSource.reset();
384 }
385}
386
393
395{
396 static constexpr int width = 32;
397
398 std::string ntupleNameTrunc = fNTupleSpec.fNTupleName.substr(0, width - 4);
399 if (ntupleNameTrunc.size() < fNTupleSpec.fNTupleName.size())
400 ntupleNameTrunc = fNTupleSpec.fNTupleName.substr(0, width - 6) + "..";
401
402 output << "+" << std::setfill('-') << std::setw(width - 1) << "+\n";
403 output << std::setfill(' ') << "| " << ntupleNameTrunc << std::setw(width - 2 - ntupleNameTrunc.size()) << " |\n";
404
405 if (const std::string *storage = std::get_if<std::string>(&fNTupleSpec.fStorage)) {
406 std::string storageTrunc = storage->substr(0, width - 5);
407 if (storageTrunc.size() < storage->size())
408 storageTrunc = storage->substr(0, width - 8) + "...";
409
410 output << std::setfill(' ') << "| " << storageTrunc << std::setw(width - 2 - storageTrunc.size()) << " |\n";
411 } else {
412 output << "| " << std::setw(width - 2) << " |\n";
413 }
414
415 output << "+" << std::setfill('-') << std::setw(width - 1) << "+\n";
416}
417
418//------------------------------------------------------------------------------
419
420ROOT::RNTupleChainProcessor::RNTupleChainProcessor(std::vector<std::unique_ptr<RNTupleProcessor>> processors,
421 const RNTupleProcessorOptions &options)
422 : RNTupleProcessor(options), fInnerProcessors(std::move(processors))
423{
424 if (fOptions.GetProcessorName().empty()) {
425 // `CreateChain` ensures there is at least one inner processor.
427 }
428
430}
431
432void ROOT::RNTupleChainProcessor::Initialize(std::shared_ptr<Internal::RNTupleProcessorEntry> entry)
433{
434 if (IsInitialized())
435 return;
436
437 if (!entry)
438 fEntry = std::make_shared<Internal::RNTupleProcessorEntry>();
439 else
440 fEntry = std::move(entry);
441
442 fInnerProcessors[0]->Initialize(fEntry);
443}
444
446{
447 if (fNEntries == kInvalidNTupleIndex) {
448 fNEntries = 0;
449
450 for (unsigned i = 0; i < fInnerProcessors.size(); ++i) {
451 if (fInnerNEntries[i] == kInvalidNTupleIndex) {
452 fInnerNEntries[i] = fInnerProcessors[i]->GetNEntries();
453 }
454
455 fNEntries += fInnerNEntries[i];
456 }
457 }
458
459 return fNEntries;
460}
461
463 const std::unordered_set<Internal::RNTupleProcessorEntry::FieldIndex_t> &fieldIdxs,
464 const Internal::RNTupleProcessorProvenance &provenance, bool /* updateFields */)
465{
466 Initialize();
467 fFieldIdxs = fieldIdxs;
468 fProvenance = provenance;
469 ConnectInnerProcessor(fCurrentProcessorNumber);
470}
471
473{
474 for (const auto &innerProc : fInnerProcessors) {
475 innerProc->Disconnect();
476 }
477}
478
480{
481 if (fCurrentProcessorNumber != processorNumber) {
482 fInnerProcessors[fCurrentProcessorNumber]->Disconnect();
483 fCurrentProcessorNumber = processorNumber;
484 }
485
486 auto &innerProc = fInnerProcessors[processorNumber];
487 innerProc->Initialize(fEntry);
488 innerProc->Connect(fFieldIdxs, fProvenance, /*updateFields=*/true);
489}
490
492ROOT::RNTupleChainProcessor::AddFieldToEntry(const std::string &fieldName, const std::string &typeName, void *valuePtr,
493 const Internal::RNTupleProcessorProvenance &provenance)
494{
495 return fInnerProcessors[fCurrentProcessorNumber]->AddFieldToEntry(fieldName, typeName, valuePtr, provenance);
496}
497
499ROOT::RNTupleChainProcessor::AddFieldToEntry(std::unique_ptr<ROOT::RFieldBase> field, const std::string &fieldName,
500 void *valuePtr, const Internal::RNTupleProcessorProvenance &provenance)
501{
502 return fInnerProcessors[fCurrentProcessorNumber]->AddFieldToEntry(std::move(field), fieldName, valuePtr, provenance);
503}
504
506 bool includeSubfields)
507{
508 Initialize();
509 fInnerProcessors[0]->AddAllFieldsToEntry(provenance, includeSubfields);
510}
511
513{
514 std::size_t currProcessorIdx = mapping.GetInnerProcessorIdx();
515
516 // If the requested entry number is lower than the current entry number, we have to again localise the correct local
517 // entry number starting from the first processor in the chain. Otherwise, we can continue looking from the inner
518 // processor that is currently connected, which is much faster when the chain consists of many inner processors.
519 if (fLastLoadedEntry != ROOT::kInvalidNTupleIndex && globalEntryNumber < fLastLoadedEntry) {
520 currProcessorIdx = 0;
521 ConnectInnerProcessor(currProcessorIdx);
522 }
523
524 ROOT::NTupleSize_t entriesSeen = 0;
525 for (unsigned i = 0; i < currProcessorIdx; ++i) {
526 if (fInnerNEntries[i] == kInvalidNTupleIndex) {
527 fInnerNEntries[i] = fInnerProcessors[i]->GetNEntries();
528 }
529
530 if (fInnerNEntries[i] >= globalEntryNumber) {
531 currProcessorIdx = 0;
532 ConnectInnerProcessor(currProcessorIdx);
533 break;
534 }
535 entriesSeen += fInnerNEntries[i];
536 }
537
538 ROOT::NTupleSize_t localEntryNumber = globalEntryNumber - entriesSeen;
539 fInnerProcessors[currProcessorIdx]->SetEntryMapping(localEntryNumber, mapping.GetInnerProcessorMapping());
540
541 // As long as the entry fails to load from the current processor, we decrement the local entry number with the number
542 // of entries in this processor and try with the next processor until we find the correct local entry number.
544 if (fInnerNEntries[currProcessorIdx] == kInvalidNTupleIndex) {
545 fInnerNEntries[currProcessorIdx] = fInnerProcessors[currProcessorIdx]->GetNEntries();
546 }
547
548 localEntryNumber -= fInnerNEntries[currProcessorIdx];
549
550 // The provided global entry number is larger than the number of available entries.
551 if (++currProcessorIdx >= fInnerProcessors.size()) {
553 return;
554 }
555
556 ConnectInnerProcessor(currProcessorIdx);
557 fInnerProcessors[currProcessorIdx]->SetEntryMapping(localEntryNumber, mapping.GetInnerProcessorMapping());
558 }
559
560 mapping.SetEntryNumber(globalEntryNumber);
561 mapping.SetInnerProcessorIdx(currProcessorIdx);
562}
563
565{
566 assert(mapping.GetEntryNumber() != kInvalidNTupleIndex);
567
568 if (fCurrentProcessorNumber != mapping.GetInnerProcessorIdx())
569 ConnectInnerProcessor(mapping.GetInnerProcessorIdx());
570 fInnerProcessors[mapping.GetInnerProcessorIdx()]->LoadEntry(mapping.GetInnerProcessorMapping());
571 fLastLoadedEntry = mapping.GetEntryNumber();
572 fNEntriesProcessed++;
573}
574
576 ROOT::NTupleSize_t entryOffset)
577{
578 for (unsigned i = 0; i < fInnerProcessors.size(); ++i) {
579 const auto &innerProc = fInnerProcessors[i];
580 // TODO can this be done (more) lazily? I.e. only when a match cannot be found in the current inner proc?
581 innerProc->Initialize(fEntry);
582 innerProc->AddEntriesToJoinTable(joinTable, entryOffset);
583 entryOffset += innerProc->GetNEntries();
584 }
585}
586
588{
589 for (const auto &innerProc : fInnerProcessors) {
590 innerProc->PrintStructure(output);
591 }
592}
593
594//------------------------------------------------------------------------------
595
596ROOT::Experimental::RNTupleJoinProcessor::RNTupleJoinProcessor(std::unique_ptr<RNTupleProcessor> primaryProcessor,
597 std::unique_ptr<RNTupleProcessor> auxProcessor,
598 const std::vector<std::string> &joinFields,
599 const RNTupleProcessorOptions &options)
600 : RNTupleProcessor(options),
601 fPrimaryProcessor(std::move(primaryProcessor)),
602 fAuxiliaryProcessor(std::move(auxProcessor)),
603 fJoinFieldNames(joinFields)
604{
605 if (fOptions.GetProcessorName().empty()) {
606 fOptions.SetProcessorName(fPrimaryProcessor->fOptions.GetProcessorName());
607 }
608}
609
610void ROOT::Experimental::RNTupleJoinProcessor::Initialize(std::shared_ptr<ROOT::Internal::RNTupleProcessorEntry> entry)
611{
612 if (IsInitialized())
613 return;
614
615 if (!entry)
616 fEntry = std::make_shared<ROOT::Internal::RNTupleProcessorEntry>();
617 else
618 fEntry = std::move(entry);
619
620 fPrimaryProcessor->Initialize(fEntry);
621 fAuxiliaryProcessor->Initialize(fEntry);
622
623 if (!fJoinFieldNames.empty()) {
624 for (const auto &joinField : fJoinFieldNames) {
625 if (!fPrimaryProcessor->CanReadFieldFromDisk(joinField)) {
626 throw RException(R__FAIL("could not find join field \"" + joinField + "\" in primary processor \"" +
627 fPrimaryProcessor->fOptions.GetProcessorName() + "\""));
628 }
629 if (!fAuxiliaryProcessor->CanReadFieldFromDisk(joinField)) {
630 throw RException(R__FAIL("could not find join field \"" + joinField + "\" in auxiliary processor \"" +
631 fAuxiliaryProcessor->fOptions.GetProcessorName() + "\""));
632 }
633
634 // We prepend the name of the primary processor in this case to prevent reading from the wrong join field in
635 // composed join operations.
636 auto fieldIdx =
637 AddFieldToEntry(fOptions.GetProcessorName() + ".R_rntproc_join_" + joinField, "std::uint64_t", nullptr,
638 ROOT::Internal::RNTupleProcessorProvenance(fOptions.GetProcessorName()));
639 fJoinFieldIdxs.insert(fieldIdx);
640 }
641
642 fJoinTable = Experimental::Internal::RNTupleJoinTable::Create(fJoinFieldNames);
643 }
644}
645
647 const std::unordered_set<ROOT::Internal::RNTupleProcessorEntry::FieldIndex_t> &fieldIdxs,
648 const ROOT::Internal::RNTupleProcessorProvenance &provenance, bool updateFields)
649{
650 Initialize();
651
652 auto auxProvenance = provenance.Evolve(fAuxiliaryProcessor->fOptions.GetProcessorName());
653 for (const auto &fieldIdx : fieldIdxs) {
654 const auto &fieldProvenance = fEntry->GetFieldProvenance(fieldIdx);
655 if (fieldProvenance.Contains(auxProvenance))
656 fAuxiliaryFieldIdxs.insert(fieldIdx);
657 else
658 fFieldIdxs.insert(fieldIdx);
659 }
660
661 fPrimaryProcessor->Connect(fFieldIdxs, provenance, updateFields);
662 fAuxiliaryProcessor->Connect(fAuxiliaryFieldIdxs, auxProvenance, updateFields);
663}
664
666{
667 fPrimaryProcessor->Disconnect();
668 fAuxiliaryProcessor->Disconnect();
669}
670
672ROOT::Experimental::RNTupleJoinProcessor::AddFieldToEntry(const std::string &fieldName, const std::string &typeName,
673 void *valuePtr,
675{
676 auto auxProvenance = provenance.Evolve(fAuxiliaryProcessor->fOptions.GetProcessorName());
677 if (auxProvenance.IsPresentInFieldName(fieldName)) {
678 // If the primaryProcessor has a field with the name of the auxProcessor (either as a "proper" field or because
679 // the primary processor itself is a join where its auxProcessor bears the same name as the current auxProcessor),
680 // there will be name conflicts, so error out.
681 if (fPrimaryProcessor->CanReadFieldFromDisk(fieldName)) {
682 throw RException(R__FAIL("ambiguous field name: \"" + fieldName +
683 "\" is present in the primary RNTupleProcessor \"" +
684 fPrimaryProcessor->fOptions.GetProcessorName() +
685 "\", but may also refer to a field in the auxiliary RNTupleProcessor named \"" +
686 fAuxiliaryProcessor->fOptions.GetProcessorName() +
687 "\". To avoid this ambiguity, rename the auxiliary RNTupleProcessor."));
688 }
689
690 auto fieldIdx = fAuxiliaryProcessor->AddFieldToEntry(fieldName, typeName, valuePtr, auxProvenance);
691 if (fieldIdx)
692 fAuxiliaryFieldIdxs.insert(fieldIdx);
693 return fieldIdx;
694 } else {
695 auto fieldIdx = fPrimaryProcessor->AddFieldToEntry(fieldName, typeName, valuePtr, provenance);
696 if (fieldIdx)
697 fFieldIdxs.insert(fieldIdx);
698 return fieldIdx;
699 }
700}
701
703ROOT::Experimental::RNTupleJoinProcessor::AddFieldToEntry(std::unique_ptr<ROOT::RFieldBase> field,
704 const std::string &fieldName, void *valuePtr,
706{
707 auto auxProvenance = provenance.Evolve(fAuxiliaryProcessor->GetOptions().GetProcessorName());
708 if (auxProvenance.IsPresentInFieldName(fieldName)) {
709 // If the primaryProcessor has a field with the name of the auxProcessor (either as a "proper" field or because
710 // the primary processor itself is a join where its auxProcessor bears the same name as the current auxProcessor),
711 // there will be name conflicts, so error out.
712 if (fPrimaryProcessor->CanReadFieldFromDisk(fieldName)) {
713 throw RException(R__FAIL("ambiguous field name: \"" + fieldName +
714 "\" is present in the primary RNTupleProcessor \"" +
715 fPrimaryProcessor->GetOptions().GetProcessorName() +
716 "\", but may also refer to a field in the auxiliary RNTupleProcessor named \"" +
717 fAuxiliaryProcessor->GetOptions().GetProcessorName() +
718 "\". To avoid this ambiguity, rename the auxiliary RNTupleProcessor."));
719 }
720
721 auto fieldIdx = fAuxiliaryProcessor->AddFieldToEntry(std::move(field), fieldName, valuePtr, auxProvenance);
722 fAuxiliaryFieldIdxs.insert(fieldIdx);
723 return fieldIdx;
724 } else {
725 auto fieldIdx = fPrimaryProcessor->AddFieldToEntry(std::move(field), fieldName, valuePtr, provenance);
726 if (fieldIdx)
727 fFieldIdxs.insert(fieldIdx);
728 return fieldIdx;
729 }
730}
731
733 const ROOT::Internal::RNTupleProcessorProvenance &provenance, bool includeSubfields)
734{
735 Initialize();
736 fPrimaryProcessor->AddAllFieldsToEntry(provenance, includeSubfields);
737 auto auxProvenance = provenance.Evolve(fAuxiliaryProcessor->GetOptions().GetProcessorName());
738 fAuxiliaryProcessor->AddAllFieldsToEntry(auxProvenance, includeSubfields);
739}
740
742{
743 for (const auto &fieldIdx : fAuxiliaryFieldIdxs) {
744 fEntry->SetFieldValidity(fieldIdx, isValid);
745 }
746}
747
749 REntryMapping &mapping)
750{
751 fPrimaryProcessor->SetEntryMapping(globalEntryNumber, mapping.GetPrimaryProcessorMapping());
754 for (auto fieldIdx : fFieldIdxs) {
755 fEntry->SetFieldValidity(fieldIdx, false);
756 }
757 SetAuxiliaryFieldValidity(false);
758 return;
759 }
760 // We only set the entry mapping of the auxiliary ntuple here if the join is aligned. For unaligne joins, we need to
761 // first get the correct entry number from the join table, which requires reading join field data from disk. Because
762 // we want to do this only when the entry is actually loaded to avoid unnecessary reads, we postpone this to the
763 // actual `LoadEntry` call.
764 if (!fJoinTable) {
765 fAuxiliaryProcessor->SetEntryMapping(globalEntryNumber, mapping.GetAuxiliaryProcessorMapping());
766 }
767 mapping.SetEntryNumber(globalEntryNumber);
768}
769
771{
772 assert(mapping.GetEntryNumber() != kInvalidNTupleIndex);
773
774 fPrimaryProcessor->LoadEntry(mapping.GetPrimaryProcessorMapping());
775 fNEntriesProcessed++;
776
777 if (!fJoinTable) {
778 fAuxiliaryProcessor->LoadEntry(mapping.GetAuxiliaryProcessorMapping());
779 return;
780 }
781
782 if (!fJoinTableIsBuilt) {
783 fAuxiliaryProcessor->AddEntriesToJoinTable(*fJoinTable);
784 fJoinTableIsBuilt = true;
785 }
786
787 // Collect the values of the join fields for this entry.
788 std::vector<ROOT::Experimental::Internal::RNTupleJoinTable::JoinValue_t> values;
789 values.reserve(fJoinFieldIdxs.size());
790 for (const auto &fieldIdx : fJoinFieldIdxs) {
791 auto val = fEntry->GetValue(fieldIdx).GetRef<ROOT::Experimental::Internal::RNTupleJoinTable::JoinValue_t>();
792 values.push_back(val);
793 }
794
795 // Find the entry index corresponding to the join field values for each auxiliary processor and load the
796 // corresponding entry.
797 const auto entryIdx = fJoinTable->GetEntryIndex(values);
798 REntryMapping auxMapping;
799 fAuxiliaryProcessor->SetEntryMapping(entryIdx, auxMapping);
800
801 if (entryIdx == kInvalidNTupleIndex) {
802 SetAuxiliaryFieldValidity(false);
803 } else {
804 SetAuxiliaryFieldValidity(true);
805 fAuxiliaryProcessor->LoadEntry(auxMapping);
806 }
807}
808
810{
811 if (fNEntries == kInvalidNTupleIndex)
812 fNEntries = fPrimaryProcessor->GetNEntries();
813 return fNEntries;
814}
815
818{
819 fPrimaryProcessor->AddEntriesToJoinTable(joinTable, entryOffset);
820}
821
823{
824 std::ostringstream primaryStructureStr;
825 fPrimaryProcessor->PrintStructure(primaryStructureStr);
826 const auto primaryStructure = ROOT::Split(primaryStructureStr.str(), "\n", /*skipEmpty=*/true);
827 const auto primaryStructureWidth = primaryStructure.front().size();
828
829 std::ostringstream auxStructureStr;
830 fAuxiliaryProcessor->PrintStructure(auxStructureStr);
831 const auto auxStructure = ROOT::Split(auxStructureStr.str(), "\n", /*skipEmpty=*/true);
832
833 const auto maxLength = std::max(primaryStructure.size(), auxStructure.size());
834 for (unsigned i = 0; i < maxLength; i++) {
835 if (i < primaryStructure.size())
836 output << primaryStructure[i];
837 else
838 output << std::setw(primaryStructureWidth) << "";
839
840 if (i < auxStructure.size())
841 output << " " << auxStructure[i];
842
843 output << "\n";
844 }
845}
#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:322
Option_t Option_t width
Builds a join table on one or several fields of an RNTuple so it can be joined onto other RNTuples.
static std::unique_ptr< RNTupleJoinTable > Create(const std::vector< std::string > &joinFieldNames)
Create an RNTupleJoinTable from an existing RNTuple.
RNTupleJoinTable & Add(ROOT::Internal::RPageSource &pageSource, PartitionKey_t partitionKey=kDefaultPartitionKey, ROOT::NTupleSize_t entryOffset=0)
Add an entry mapping to the join table.
static constexpr PartitionKey_t kDefaultPartitionKey
Processor specialization for horizontally combined (joined) RNTupleProcessors.
void PrintStructureImpl(std::ostream &output) const final
Processor-specific implementation for printing its structure, called by PrintStructure().
void SetEntryMapping(ROOT::NTupleSize_t globalEntryNumber, REntryMapping &mapping) final
Set the local entry mapping (for nested processors), according to a global entry number.
void AddAllFieldsToEntry(const ROOT::Internal::RNTupleProcessorProvenance &provenance, bool includeSubfields) final
Add all known fields (from on-disk information) to the entry.
void AddEntriesToJoinTable(Internal::RNTupleJoinTable &joinTable, ROOT::NTupleSize_t entryOffset=0) final
Add the entry mappings for this processor to the provided join table.
void Disconnect() final
Disconnect the processor from associated physical storage.
ROOT::NTupleSize_t GetNEntries() final
Get the total number of entries in this processor.
void SetAuxiliaryFieldValidity(bool validity)
Set the validity for all fields in the auxiliary processor at once.
void Initialize(std::shared_ptr< ROOT::Internal::RNTupleProcessorEntry > entry=nullptr) final
Initialize the processor by creating an (initially empty) fEntry, or setting an existing one.
void Connect(const std::unordered_set< ROOT::Internal::RNTupleProcessorEntry::FieldIndex_t > &fieldIdxs, const ROOT::Internal::RNTupleProcessorProvenance &provenance=ROOT::Internal::RNTupleProcessorProvenance(), bool updateFields=false) final
Connect the provided fields indices in the entry to their on-disk fields.
std::unique_ptr< RNTupleProcessor > fPrimaryProcessor
void LoadEntryImpl(const REntryMapping &mapping) final
Load the entry identified by the entry mapping.
ROOT::Internal::RNTupleProcessorEntry::FieldIndex_t AddFieldToEntry(const std::string &fieldName, const std::string &typeName, void *valuePtr=nullptr, const ROOT::Internal::RNTupleProcessorProvenance &provenance=ROOT::Internal::RNTupleProcessorProvenance()) final
Add a field to the entry.
Collection of values in an RNTupleProcessor, analogous to REntry, with checks and support for missing...
std::string Get() const
Get the full processor provenance, in the form of "x.y.z".
RNTupleProcessorProvenance Evolve(const std::string &processorName) const
Add a new processor to the provenance.
bool IsPresentInFieldName(std::string_view fieldName) const
Check whether the provided field name contains this provenance.
static std::unique_ptr< RPageSourceFile > CreateFromAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Used from the RNTuple class to build a datasource if the anchor is already available.
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)
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
std::vector< ColumnRepresentation_t > Selection_t
A list of column representations.
A field translates read and write calls from/to underlying columns to/from tree values.
void SetColumnRepresentatives(const RColumnRepresentations::Selection_t &representatives)
Fixes a column representative.
void SetOnDiskId(ROOT::DescriptorId_t id)
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.
Metadata stored for every field of an RNTuple.
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
Processor specialization for vertically combined (chained) RNTupleProcessors.
std::vector< std::unique_ptr< RNTupleProcessor > > fInnerProcessors
void AddAllFieldsToEntry(const Internal::RNTupleProcessorProvenance &provenance, bool includeSubfields) final
Add all known fields (from on-disk information) to the entry.
void ConnectInnerProcessor(std::size_t processorNumber)
Update the entry to reflect any missing fields in the current inner processor.
void Initialize(std::shared_ptr< ROOT::Internal::RNTupleProcessorEntry > entry=nullptr) final
Initialize the processor by creating an (initially empty) fEntry, or setting an existing one.
std::vector< ROOT::NTupleSize_t > fInnerNEntries
void Connect(const std::unordered_set< Internal::RNTupleProcessorEntry::FieldIndex_t > &fieldIdxs, const Internal::RNTupleProcessorProvenance &provenance=Internal::RNTupleProcessorProvenance(), bool updateFields=false) final
Connect the provided fields indices in the entry to their on-disk fields.
Internal::RNTupleProcessorEntry::FieldIndex_t AddFieldToEntry(const std::string &fieldName, const std::string &typeName, void *valuePtr=nullptr, const Internal::RNTupleProcessorProvenance &provenance=Internal::RNTupleProcessorProvenance()) final
Add a field to the entry.
void LoadEntryImpl(const REntryMapping &mapping) final
Load the entry identified by the entry mapping.
void SetEntryMapping(ROOT::NTupleSize_t globalEntryNumber, REntryMapping &mapping) final
Set the local entry mapping (for nested processors), according to a global entry number.
void AddEntriesToJoinTable(Experimental::Internal::RNTupleJoinTable &joinTable, ROOT::NTupleSize_t entryOffset=0) final
Add the entry mappings for this processor to the provided join table.
void PrintStructureImpl(std::ostream &output) const final
Processor-specific implementation for printing its structure, called by PrintStructure().
void Disconnect() final
Disconnect the processor from associated physical storage.
ROOT::NTupleSize_t GetNEntries() final
Get the total number of entries in this processor.
The on-storage metadata of an RNTuple.
const RFieldDescriptor & GetFieldDescriptor(ROOT::DescriptorId_t fieldId) const
ROOT::DescriptorId_t FindFieldId(std::string_view fieldName, ROOT::DescriptorId_t parentId) const
const std::string & GetNTupleName() const
const Storage_t & GetStorage() const
const std::string & GetProcessorName() const
Get the processor's name.
void SetProcessorName(std::string_view name)
Set the processor's name.
Maps a processor's global (outer) entry index to its inner ones.
void SetEntryNumber(ROOT::NTupleSize_t entryNumber)
ROOT::NTupleSize_t GetEntryNumber() const
Interface for iterating over entries of vertically ("chained") and/or horizontally ("joined") combine...
virtual void AddAllFieldsToEntry(const Internal::RNTupleProcessorProvenance &provenance, bool includeSubfields)=0
Add all known fields (from on-disk information) to the entry.
static std::unique_ptr< RNTupleProcessor > CreateChain(std::vector< RNTupleInputSpec > ntuples, const RNTupleProcessorOptions &opts=RNTupleProcessorOptions())
Create an RNTupleProcessor for a chain (i.e., a vertical combination) of RNTuples.
static std::unique_ptr< RNTupleProcessor > Create(RNTupleInputSpec ntuple, const RNTupleProcessorOptions &opts=RNTupleProcessorOptions())
Create an RNTupleProcessor for a single RNTuple.
RNTupleProcessorOptions fOptions
friend class Experimental::RNTupleJoinProcessor
std::shared_ptr< ROOT::Internal::RNTupleProcessorEntry > fEntry
void SetEntryMapping(ROOT::NTupleSize_t globalEntryNumber, REntryMapping &mapping) final
Set the local entry mapping (for nested processors), according to a global entry number.
void PrintStructureImpl(std::ostream &output) const final
Processor-specific implementation for printing its structure, called by PrintStructure().
void LoadEntryImpl(const REntryMapping &mapping) final
Load the entry identified by the entry mapping.
std::unique_ptr< ROOT::RFieldBase > ConnectField(std::unique_ptr< ROOT::RFieldBase > field)
Connect an existing field to the processor's page source.
bool CanReadFieldFromDisk(std::string_view fieldName) final
Check if a field exists on-disk and can be read by the processor.
void Initialize(std::shared_ptr< ROOT::Internal::RNTupleProcessorEntry > entry=nullptr) final
Initialize the processor by creating an (initially empty) fEntry, or setting an existing one.
Internal::RNTupleProcessorEntry::FieldIndex_t AddFieldToEntry(const std::string &fieldName, const std::string &typeName, void *valuePtr=nullptr, const Internal::RNTupleProcessorProvenance &provenance=Internal::RNTupleProcessorProvenance()) final
Add a field to the entry.
void AddAllFieldsToEntry(const Internal::RNTupleProcessorProvenance &provenance, bool includeSubfields) final
Add all known fields (from on-disk information) to the entry.
void AddEntriesToJoinTable(Experimental::Internal::RNTupleJoinTable &joinTable, ROOT::NTupleSize_t entryOffset=0) final
Add the entry mappings for this processor to the provided join table.
std::unique_ptr< ROOT::RFieldBase > CreateAndConnectField(const std::string &qualifiedFieldName, const std::string &typeName)
Create a new field and connect it to the processor's page source.
ROOT::NTupleSize_t GetNEntries() final
Get the total number of entries in this processor.
void Connect(const std::unordered_set< Internal::RNTupleProcessorEntry::FieldIndex_t > &fieldIdxs, const Internal::RNTupleProcessorProvenance &provenance=Internal::RNTupleProcessorProvenance(), bool updateFields=false) final
Connect the provided fields indices in the entry to their on-disk fields.
void Disconnect() final
Disconnect the processor from associated physical storage.
Representation of an RNTuple data set in a ROOT file.
Definition RNTuple.hxx:67
std::unique_ptr< RNTupleProcessor > CreateRNTupleJoinProcessor(RNTupleInputSpec primaryNTuple, RNTupleInputSpec auxNTuple, const std::vector< std::string > &joinFields, const RNTupleProcessorOptions &opts=RNTupleProcessorOptions())
Create an RNTupleProcessor for a join (i.e., a horizontal combination) of RNTuples.
void SetAllowFieldSubstitutions(RFieldZero &fieldZero, bool val)
Definition RField.cxx:35
void CallConnectPageSourceOnField(RFieldBase &, ROOT::Internal::RPageSource &)
const RNTupleProcessorEntry * LoadFullRNTupleProcessorEntry(ROOT::RNTupleProcessor &processor, bool includeSubfields)
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
constexpr NTupleSize_t kInvalidNTupleIndex
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
constexpr DescriptorId_t kInvalidDescriptorId