Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleModel.cxx
Go to the documentation of this file.
1/// \file RNTupleModel.cxx
2/// \ingroup NTuple ROOT7
3/// \author Jakob Blomer <jblomer@cern.ch>
4/// \date 2018-10-15
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
16#include <ROOT/RError.hxx>
17#include <ROOT/RField.hxx>
19#include <ROOT/RNTupleModel.hxx>
21#include <ROOT/StringUtils.hxx>
22
23#include <atomic>
24#include <cstdlib>
25#include <memory>
26#include <utility>
27
28namespace {
29std::uint64_t GetNewModelId()
30{
31 static std::atomic<std::uint64_t> gLastModelId = 0;
32 return ++gLastModelId;
33}
34} // anonymous namespace
35
36std::unique_ptr<ROOT::Experimental::RNTupleModel>
38 std::string_view rightFieldPrefix)
39{
40 if (!left.IsFrozen() || !right.IsFrozen())
41 throw RException(R__FAIL("invalid attempt to merge unfrozen models"));
42
43 auto newModel = left.Clone();
44 newModel->Unfreeze();
45
46 if (!rightFieldPrefix.empty()) {
47 newModel->MakeCollection(std::string(rightFieldPrefix), right.Clone());
48 } else {
49 for (const auto &f : right.GetFieldZero().GetSubFields()) {
50 newModel->AddField(f->Clone(f->GetFieldName()));
51 }
52 }
53
54 newModel->Freeze();
55 return newModel;
56}
57
58//------------------------------------------------------------------------------
59
62 const FieldMap_t &fieldMap)
63{
64 auto source = fieldMap.at(target);
65 const bool hasCompatibleStructure =
66 (source->GetStructure() == target->GetStructure()) ||
67 ((source->GetStructure() == ENTupleStructure::kCollection) && dynamic_cast<const RCardinalityField *>(target));
68 if (!hasCompatibleStructure)
69 return R__FAIL("field mapping structural mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
70 if ((source->GetStructure() == ENTupleStructure::kLeaf) || (source->GetStructure() == ENTupleStructure::kUnsplit)) {
71 if (target->GetTypeName() != source->GetTypeName())
72 return R__FAIL("field mapping type mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
73 }
74
75 // We support projections only across records and collections. In the following, we check that the projected
76 // field is on the same path of collection fields in the field tree than the source field.
77
78 // Finds the first non-record parent field of the input field
79 auto fnBreakPoint = [](const RFieldBase *f) -> const RFieldBase * {
80 auto parent = f->GetParent();
81 while (parent) {
82 if (parent->GetStructure() != ENTupleStructure::kRecord)
83 return parent;
84 parent = parent->GetParent();
85 }
86 // We reached the zero field
87 return nullptr;
88 };
89
90 // If source or target has a variant or reference as a parent, error out
91 auto *sourceBreakPoint = fnBreakPoint(source);
92 if (sourceBreakPoint && sourceBreakPoint->GetStructure() != ENTupleStructure::kCollection)
93 return R__FAIL("unsupported field mapping (source structure)");
94 auto *targetBreakPoint = fnBreakPoint(target);
95 if (targetBreakPoint && sourceBreakPoint->GetStructure() != ENTupleStructure::kCollection)
96 return R__FAIL("unsupported field mapping (target structure)");
97
98 if (!sourceBreakPoint && !targetBreakPoint) {
99 // Source and target have no collections as parent
100 return RResult<void>::Success();
101 }
102 if (sourceBreakPoint && targetBreakPoint) {
103 if (sourceBreakPoint == targetBreakPoint) {
104 // Source and target are children of the same collection
105 return RResult<void>::Success();
106 }
107 if (auto it = fieldMap.find(targetBreakPoint); it != fieldMap.end() && it->second == sourceBreakPoint) {
108 // The parent collection of parent is mapped to the parent collection of the source
109 return RResult<void>::Success();
110 }
111 // Source and target are children of different collections
112 return R__FAIL("field mapping structure mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
113 }
114
115 // Either source or target have no collection as a parent, but the other one has; that doesn't fit
116 return R__FAIL("field mapping structure mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
117}
118
120ROOT::Experimental::RNTupleModel::RProjectedFields::Add(std::unique_ptr<RFieldBase> field, const FieldMap_t &fieldMap)
121{
122 auto result = EnsureValidMapping(field.get(), fieldMap);
123 if (!result)
124 return R__FORWARD_ERROR(result);
125 for (const auto &f : *field) {
126 result = EnsureValidMapping(&f, fieldMap);
127 if (!result)
128 return R__FORWARD_ERROR(result);
129 }
130
131 fFieldMap.insert(fieldMap.begin(), fieldMap.end());
132 fFieldZero->Attach(std::move(field));
133 return RResult<void>::Success();
134}
135
138{
139 if (auto it = fFieldMap.find(target); it != fFieldMap.end())
140 return it->second;
141 return nullptr;
142}
143
144std::unique_ptr<ROOT::Experimental::RNTupleModel::RProjectedFields>
146{
147 auto cloneFieldZero = std::unique_ptr<RFieldZero>(static_cast<RFieldZero *>(fFieldZero->Clone("").release()));
148 auto clone = std::unique_ptr<RProjectedFields>(new RProjectedFields(std::move(cloneFieldZero)));
149 clone->fModel = newModel;
150 // TODO(jblomer): improve quadratic search to re-wire the field mappings given the new model and the cloned
151 // projected fields. Not too critical as we generally expect a limited number of projected fields
152 for (const auto &[k, v] : fFieldMap) {
153 for (const auto &f : *clone->GetFieldZero()) {
154 if (f.GetQualifiedFieldName() == k->GetQualifiedFieldName()) {
155 clone->fFieldMap[&f] = clone->fModel->FindField(v->GetQualifiedFieldName());
156 break;
157 }
158 }
159 }
160 return clone;
161}
162
164 : fWriter(writer), fOpenChangeset(fWriter.GetUpdatableModel())
165{
166}
167
169{
170 fOpenChangeset.fModel.Unfreeze();
171 // We set the model ID to zero until CommitUpdate(). That prevents calls to RNTupleWriter::Fill() in the middle
172 // of updates
173 std::swap(fOpenChangeset.fModel.fModelId, fNewModelId);
174}
175
177{
178 fOpenChangeset.fModel.Freeze();
179 std::swap(fOpenChangeset.fModel.fModelId, fNewModelId);
180 if (fOpenChangeset.IsEmpty())
181 return;
182 Internal::RNTupleModelChangeset toCommit{fOpenChangeset.fModel};
183 std::swap(fOpenChangeset.fAddedFields, toCommit.fAddedFields);
184 std::swap(fOpenChangeset.fAddedProjectedFields, toCommit.fAddedProjectedFields);
185 fWriter.GetSink().UpdateSchema(toCommit, fWriter.GetNEntries());
186}
187
188void ROOT::Experimental::RNTupleModel::RUpdater::AddField(std::unique_ptr<RFieldBase> field)
189{
190 auto fieldp = field.get();
191 fOpenChangeset.fModel.AddField(std::move(field));
192 fOpenChangeset.fAddedFields.emplace_back(fieldp);
193}
194
197 std::function<std::string(const std::string &)> mapping)
198{
199 auto fieldp = field.get();
200 auto result = fOpenChangeset.fModel.AddProjectedField(std::move(field), mapping);
201 if (result)
202 fOpenChangeset.fAddedProjectedFields.emplace_back(fieldp);
204}
205
207{
208 RResult<void> nameValid = RFieldBase::EnsureValidFieldName(fieldName);
209 if (!nameValid) {
210 nameValid.Throw();
211 }
212 auto fieldNameStr = std::string(fieldName);
213 if (fFieldNames.count(fieldNameStr) > 0)
214 throw RException(R__FAIL("field name '" + fieldNameStr + "' already exists in NTuple model"));
215}
216
218{
219 if (IsFrozen())
220 throw RException(R__FAIL("invalid attempt to modify frozen model"));
221}
222
224{
225 if (!fDefaultEntry)
226 throw RException(R__FAIL("invalid attempt to use default entry of bare model"));
227}
228
229ROOT::Experimental::RNTupleModel::RNTupleModel(std::unique_ptr<RFieldZero> fieldZero)
230 : fFieldZero(std::move(fieldZero)), fModelId(GetNewModelId())
231{}
232
233std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleModel::CreateBare()
234{
235 return CreateBare(std::make_unique<RFieldZero>());
236}
237
238std::unique_ptr<ROOT::Experimental::RNTupleModel>
239ROOT::Experimental::RNTupleModel::CreateBare(std::unique_ptr<RFieldZero> fieldZero)
240{
241 auto model = std::unique_ptr<RNTupleModel>(new RNTupleModel(std::move(fieldZero)));
242 model->fProjectedFields = std::make_unique<RProjectedFields>(model.get());
243 return model;
244}
245
246std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleModel::Create()
247{
248 return Create(std::make_unique<RFieldZero>());
249}
250
251std::unique_ptr<ROOT::Experimental::RNTupleModel>
252ROOT::Experimental::RNTupleModel::Create(std::unique_ptr<RFieldZero> fieldZero)
253{
254 auto model = CreateBare(std::move(fieldZero));
255 model->fDefaultEntry = std::unique_ptr<REntry>(new REntry(model->fModelId));
256 return model;
257}
258
259std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleModel::Clone() const
260{
261 auto cloneModel = std::unique_ptr<RNTupleModel>(
262 new RNTupleModel(std::unique_ptr<RFieldZero>(static_cast<RFieldZero *>(fFieldZero->Clone("").release()))));
263 cloneModel->fModelId = GetNewModelId();
264 cloneModel->fIsFrozen = fIsFrozen;
265 cloneModel->fFieldNames = fFieldNames;
266 cloneModel->fDescription = fDescription;
267 cloneModel->fProjectedFields = fProjectedFields->Clone(cloneModel.get());
268 if (fDefaultEntry) {
269 cloneModel->fDefaultEntry = std::unique_ptr<REntry>(new REntry(cloneModel->fModelId));
270 for (const auto &f : cloneModel->fFieldZero->GetSubFields()) {
271 cloneModel->fDefaultEntry->AddValue(f->CreateValue());
272 }
273 }
274 return cloneModel;
275}
276
278{
279 if (fieldName.empty())
280 return nullptr;
281
282 auto *field = static_cast<ROOT::Experimental::RFieldBase *>(fFieldZero.get());
283 for (auto subfieldName : ROOT::Split(fieldName, ".")) {
284 const auto subfields = field->GetSubFields();
285 auto it = std::find_if(subfields.begin(), subfields.end(),
286 [&](const auto *f) { return f->GetFieldName() == subfieldName; });
287 if (it != subfields.end()) {
288 field = *it;
289 } else {
290 field = nullptr;
291 break;
292 }
293 }
294
295 return field;
296}
297
298void ROOT::Experimental::RNTupleModel::AddField(std::unique_ptr<RFieldBase> field)
299{
300 EnsureNotFrozen();
301 if (!field)
302 throw RException(R__FAIL("null field"));
303 EnsureValidFieldName(field->GetFieldName());
304
305 if (fDefaultEntry)
306 fDefaultEntry->AddValue(field->CreateValue());
307 fFieldNames.insert(field->GetFieldName());
308 fFieldZero->Attach(std::move(field));
309}
310
313 std::function<std::string(const std::string &)> mapping)
314{
315 EnsureNotFrozen();
316 if (!field)
317 return R__FAIL("null field");
318 auto fieldName = field->GetFieldName();
319
321 auto sourceField = FindField(mapping(fieldName));
322 if (!sourceField)
323 return R__FAIL("no such field: " + mapping(fieldName));
324 fieldMap[field.get()] = sourceField;
325 for (const auto &subField : *field) {
326 sourceField = FindField(mapping(subField.GetQualifiedFieldName()));
327 if (!sourceField)
328 return R__FAIL("no such field: " + mapping(fieldName));
329 fieldMap[&subField] = sourceField;
330 }
331
332 EnsureValidFieldName(fieldName);
333 auto result = fProjectedFields->Add(std::move(field), fieldMap);
334 if (!result) {
335 return R__FORWARD_ERROR(result);
336 }
337 fFieldNames.insert(fieldName);
338 return RResult<void>::Success();
339}
340
341std::shared_ptr<ROOT::Experimental::RNTupleCollectionWriter>
343 std::unique_ptr<RNTupleModel> collectionModel)
344{
345 EnsureNotFrozen();
346 EnsureValidFieldName(fieldName);
347 if (!collectionModel) {
348 throw RException(R__FAIL("null collectionModel"));
349 }
350
351 auto collectionWriter = std::make_shared<RNTupleCollectionWriter>(std::move(collectionModel->fDefaultEntry));
352
353 auto field = std::make_unique<RCollectionField>(fieldName, collectionWriter, std::move(collectionModel->fFieldZero));
354 field->SetDescription(collectionModel->GetDescription());
355
356 if (fDefaultEntry)
357 fDefaultEntry->AddValue(field->BindValue(std::shared_ptr<void>(collectionWriter->GetOffsetPtr(), [](void *) {})));
358
359 fFieldNames.insert(field->GetFieldName());
360 fFieldZero->Attach(std::move(field));
361 return collectionWriter;
362}
363
365{
366 if (!IsFrozen())
367 throw RException(R__FAIL("invalid attempt to get mutable zero field of unfrozen model"));
368 return *fFieldZero;
369}
370
372{
373 auto f = FindField(fieldName);
374 if (!f)
375 throw RException(R__FAIL("invalid field: " + std::string(fieldName)));
376
377 return *f;
378}
379
381{
382 EnsureNotBare();
383 return *fDefaultEntry;
384}
385
387{
388 if (!IsFrozen())
389 throw RException(R__FAIL("invalid attempt to get default entry of unfrozen model"));
390 EnsureNotBare();
391 return *fDefaultEntry;
392}
393
394std::unique_ptr<ROOT::Experimental::REntry> ROOT::Experimental::RNTupleModel::CreateEntry() const
395{
396 if (!IsFrozen())
397 throw RException(R__FAIL("invalid attempt to create entry of unfrozen model"));
398
399 auto entry = std::unique_ptr<REntry>(new REntry(fModelId));
400 for (const auto &f : fFieldZero->GetSubFields()) {
401 entry->AddValue(f->CreateValue());
402 }
403 return entry;
404}
405
406std::unique_ptr<ROOT::Experimental::REntry> ROOT::Experimental::RNTupleModel::CreateBareEntry() const
407{
408 if (!IsFrozen())
409 throw RException(R__FAIL("invalid attempt to create entry of unfrozen model"));
410
411 auto entry = std::unique_ptr<REntry>(new REntry(fModelId));
412 for (const auto &f : fFieldZero->GetSubFields()) {
413 entry->AddValue(f->BindValue(nullptr));
414 }
415 return entry;
416}
417
419{
420 if (!IsFrozen())
421 throw RException(R__FAIL("invalid attempt to get field token of unfrozen model"));
422
423 const auto &topLevelFields = fFieldZero->GetSubFields();
424 auto it = std::find_if(topLevelFields.begin(), topLevelFields.end(),
425 [&fieldName](const RFieldBase *f) { return f->GetFieldName() == fieldName; });
426
427 if (it == topLevelFields.end()) {
428 throw RException(R__FAIL("invalid field name: " + std::string(fieldName)));
429 }
430 return REntry::RFieldToken(std::distance(topLevelFields.begin(), it), fModelId);
431}
432
434{
435 if (!IsFrozen())
436 throw RException(R__FAIL("invalid attempt to create bulk of unfrozen model"));
437
438 auto f = FindField(fieldName);
439 if (!f)
440 throw RException(R__FAIL("no such field: " + std::string(fieldName)));
441 return f->CreateBulk();
442}
443
445{
446 if (!IsFrozen())
447 return;
448
449 fModelId = GetNewModelId();
450 if (fDefaultEntry)
451 fDefaultEntry->fModelId = fModelId;
452 fIsFrozen = false;
453}
454
456{
457 fIsFrozen = true;
458}
459
460void ROOT::Experimental::RNTupleModel::SetDescription(std::string_view description)
461{
462 EnsureNotFrozen();
463 fDescription = std::string(description);
464}
#define R__FORWARD_ERROR(res)
Short-hand to return an RResult<T> in an error state (i.e. after checking)
Definition RError.hxx:294
#define R__FORWARD_RESULT(res)
Short-hand to return an RResult<T> value from a subroutine to the calling stack frame.
Definition RError.hxx:292
#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:290
#define f(i)
Definition RSha256.hxx:104
TObject * clone(const char *newname) const override
Definition RooChi2Var.h:9
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 Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t target
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 Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
An artificial field that transforms an RNTuple column that contains the offset of collections into co...
Definition RField.hxx:1801
The field token identifies a top-level field in this entry.
Definition REntry.hxx:54
The REntry is a collection of values in an ntuple corresponding to a complete row in the data set.
Definition REntry.hxx:45
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Similar to RValue but manages an array of consecutive values.
Definition RField.hxx:232
A field translates read and write calls from/to underlying columns to/from tree values.
Definition RField.hxx:96
const RFieldBase * GetParent() const
Definition RField.hxx:674
std::vector< RFieldBase * > GetSubFields()
Definition RField.cxx:910
static RResult< void > EnsureValidFieldName(std::string_view fieldName)
Check whether a given string is a valid field name.
Definition RField.cxx:808
The container field for an ntuple model, which itself has no physical representation.
Definition RField.hxx:718
Projected fields are fields whose columns are reused from existing fields.
RResult< void > EnsureValidMapping(const RFieldBase *target, const FieldMap_t &fieldMap)
Asserts that the passed field is a valid target of the source field provided in the field map.
std::unordered_map< const RFieldBase *, const RFieldBase * > FieldMap_t
The map keys are the projected target fields, the map values are the backing source fields Note that ...
const RFieldBase * GetSourceField(const RFieldBase *target) const
RResult< void > Add(std::unique_ptr< RFieldBase > field, const FieldMap_t &fieldMap)
Adds a new projected field.
std::unique_ptr< RProjectedFields > Clone(const RNTupleModel *newModel) const
The new model needs to be a clone of fModel.
void CommitUpdate()
Commit changes since the last call to BeginUpdate().
void BeginUpdate()
Begin a new set of alterations to the underlying model.
RResult< void > AddProjectedField(std::unique_ptr< RFieldBase > field, std::function< std::string(const std::string &)> mapping)
void AddField(std::unique_ptr< RFieldBase > field)
The RNTupleModel encapulates the schema of an ntuple.
std::unordered_set< std::string > fFieldNames
Keeps track of which field names are taken, including projected field names.
void EnsureValidFieldName(std::string_view fieldName)
Checks that user-provided field names are valid in the context of this NTuple model.
std::uint64_t fModelId
Every model has a unique ID to distinguish it from other models.
REntry::RFieldToken GetToken(std::string_view fieldName) const
Creates a token to be used in REntry methods to address a top-level field.
RResult< void > AddProjectedField(std::unique_ptr< RFieldBase > field, std::function< std::string(const std::string &)> mapping)
Adds a top-level field based on existing fields.
void EnsureNotBare() const
Throws an RException if fDefaultEntry is nullptr.
std::unique_ptr< RNTupleModel > Clone() const
void EnsureNotFrozen() const
Throws an RException if fFrozen is true.
std::shared_ptr< RNTupleCollectionWriter > MakeCollection(std::string_view fieldName, std::unique_ptr< RNTupleModel > collectionModel)
Ingests a model for a sub collection and attaches it to the current model.
const RFieldBase & GetField(std::string_view fieldName) const
std::unique_ptr< REntry > CreateBareEntry() const
In a bare entry, all values point to nullptr.
std::unique_ptr< REntry > CreateEntry() const
RFieldBase::RBulk CreateBulk(std::string_view fieldName) const
Calls the given field's CreateBulk() method. Throws an exception if no field with the given name exis...
static std::unique_ptr< RNTupleModel > Create()
void SetDescription(std::string_view description)
std::unique_ptr< REntry > fDefaultEntry
Contains field values corresponding to the created top-level fields.
RFieldBase * FindField(std::string_view fieldName) const
The field name can be a top-level field or a nested field. Returns nullptr if the field is not in the...
RNTupleModel(std::unique_ptr< RFieldZero > fieldZero)
static std::unique_ptr< RNTupleModel > CreateBare()
A bare model has no default entry.
void AddField(std::unique_ptr< RFieldBase > field)
Adds a field whose type is not known at compile time.
RFieldZero & GetFieldZero()
Non-const access to the root field is used to commit clusters during writing and to set the on-disk f...
std::unique_ptr< RFieldZero > fFieldZero
Hierarchy of fields consisting of simple types and collections (sub trees)
An RNTuple that gets filled with entries (data) and writes them to storage.
void Throw()
Throws an RException with fError.
Definition RError.cxx:67
The class is used as a return type for operations that can fail; wraps a value of type T or an RError...
Definition RError.hxx:194
std::unique_ptr< RNTupleModel > MergeModels(const RNTupleModel &left, const RNTupleModel &right, std::string_view rightFieldPrefix="")
Merge two RNTuple models.
std::vector< std::string > Split(std::string_view str, std::string_view delims, bool skipEmpty=false)
Splits a string at each character in delims.
The incremental changes to a RNTupleModel