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>
18#include <ROOT/RNTupleModel.hxx>
19#include <ROOT/RNTuple.hxx>
20#include <ROOT/StringUtils.hxx>
21
22#include <atomic>
23#include <cstdlib>
24#include <memory>
25#include <utility>
26
27namespace {
28std::uint64_t GetNewModelId()
29{
30 static std::atomic<std::uint64_t> gLastModelId = 0;
31 return ++gLastModelId;
32}
33} // anonymous namespace
34
37 const FieldMap_t &fieldMap)
38{
39 auto source = fieldMap.at(target);
40 const bool hasCompatibleStructure =
41 (source->GetStructure() == target->GetStructure()) ||
42 ((source->GetStructure() == ENTupleStructure::kCollection) && dynamic_cast<const RCardinalityField *>(target));
43 if (!hasCompatibleStructure)
44 return R__FAIL("field mapping structural mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
45 if (source->GetStructure() == ENTupleStructure::kLeaf) {
46 if (target->GetTypeName() != source->GetTypeName())
47 return R__FAIL("field mapping type mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
48 }
49
50 // We support projections only across records and collections. In the following, we check that the projected
51 // field is on the same path of collection fields in the field tree than the source field.
52
53 // Finds the first non-record parent field of the input field
54 auto fnBreakPoint = [](const RFieldBase *f) -> const RFieldBase * {
55 auto parent = f->GetParent();
56 while (parent) {
57 if (parent->GetStructure() != ENTupleStructure::kRecord)
58 return parent;
59 parent = parent->GetParent();
60 }
61 // We reached the zero field
62 return nullptr;
63 };
64
65 // If source or target has a variant or reference as a parent, error out
66 auto *sourceBreakPoint = fnBreakPoint(source);
67 if (sourceBreakPoint && sourceBreakPoint->GetStructure() != ENTupleStructure::kCollection)
68 return R__FAIL("unsupported field mapping (source structure)");
69 auto *targetBreakPoint = fnBreakPoint(target);
70 if (targetBreakPoint && sourceBreakPoint->GetStructure() != ENTupleStructure::kCollection)
71 return R__FAIL("unsupported field mapping (target structure)");
72
73 if (!sourceBreakPoint && !targetBreakPoint) {
74 // Source and target have no collections as parent
76 }
77 if (sourceBreakPoint && targetBreakPoint) {
78 if (sourceBreakPoint == targetBreakPoint) {
79 // Source and target are children of the same collection
81 }
82 if (auto it = fieldMap.find(targetBreakPoint); it != fieldMap.end() && it->second == sourceBreakPoint) {
83 // The parent collection of parent is mapped to the parent collection of the source
85 }
86 // Source and target are children of different collections
87 return R__FAIL("field mapping structure mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
88 }
89
90 // Either source or target have no collection as a parent, but the other one has; that doesn't fit
91 return R__FAIL("field mapping structure mismatch: " + source->GetFieldName() + " --> " + target->GetFieldName());
92}
93
95ROOT::Experimental::RNTupleModel::RProjectedFields::Add(std::unique_ptr<RFieldBase> field, const FieldMap_t &fieldMap)
96{
97 auto result = EnsureValidMapping(field.get(), fieldMap);
98 if (!result)
100 for (const auto &f : *field) {
101 result = EnsureValidMapping(&f, fieldMap);
102 if (!result)
103 return R__FORWARD_ERROR(result);
104 }
105
106 fFieldMap.insert(fieldMap.begin(), fieldMap.end());
107 fFieldZero->Attach(std::move(field));
108 return RResult<void>::Success();
109}
110
113{
114 if (auto it = fFieldMap.find(target); it != fFieldMap.end())
115 return it->second;
116 return nullptr;
117}
118
119std::unique_ptr<ROOT::Experimental::RNTupleModel::RProjectedFields>
121{
122 auto cloneFieldZero = std::unique_ptr<RFieldZero>(static_cast<RFieldZero *>(fFieldZero->Clone("").release()));
123 auto clone = std::unique_ptr<RProjectedFields>(new RProjectedFields(std::move(cloneFieldZero)));
124 clone->fModel = newModel;
125 // TODO(jblomer): improve quadratic search to re-wire the field mappings given the new model and the cloned
126 // projected fields. Not too critical as we generally expect a limited number of projected fields
127 for (const auto &[k, v] : fFieldMap) {
128 for (const auto &f : *clone->GetFieldZero()) {
129 if (f.GetQualifiedFieldName() == k->GetQualifiedFieldName()) {
130 clone->fFieldMap[&f] = clone->fModel->FindField(v->GetQualifiedFieldName());
131 break;
132 }
133 }
134 }
135 return clone;
136}
137
139 : fWriter(writer), fOpenChangeset(fWriter.GetUpdatableModel())
140{
141}
142
144{
145 fOpenChangeset.fModel.Unfreeze();
146 // We set the model ID to zero until CommitUpdate(). That prevents calls to RNTupleWriter::Fill() in the middle
147 // of updates
148 std::swap(fOpenChangeset.fModel.fModelId, fNewModelId);
149}
150
152{
153 fOpenChangeset.fModel.Freeze();
154 std::swap(fOpenChangeset.fModel.fModelId, fNewModelId);
155 if (fOpenChangeset.IsEmpty())
156 return;
157 Internal::RNTupleModelChangeset toCommit{fOpenChangeset.fModel};
158 std::swap(fOpenChangeset.fAddedFields, toCommit.fAddedFields);
159 std::swap(fOpenChangeset.fAddedProjectedFields, toCommit.fAddedProjectedFields);
160 fWriter.GetSink().UpdateSchema(toCommit, fWriter.GetNEntries());
161}
162
163void ROOT::Experimental::RNTupleModel::RUpdater::AddField(std::unique_ptr<RFieldBase> field)
164{
165 auto fieldp = field.get();
166 fOpenChangeset.fModel.AddField(std::move(field));
167 fOpenChangeset.fAddedFields.emplace_back(fieldp);
168}
169
172 std::function<std::string(const std::string &)> mapping)
173{
174 auto fieldp = field.get();
175 auto result = fOpenChangeset.fModel.AddProjectedField(std::move(field), mapping);
176 if (result)
177 fOpenChangeset.fAddedProjectedFields.emplace_back(fieldp);
179}
180
182{
183 RResult<void> nameValid = RFieldBase::EnsureValidFieldName(fieldName);
184 if (!nameValid) {
185 nameValid.Throw();
186 }
187 auto fieldNameStr = std::string(fieldName);
188 if (fFieldNames.insert(fieldNameStr).second == false) {
189 throw RException(R__FAIL("field name '" + fieldNameStr + "' already exists in NTuple model"));
190 }
191}
192
194{
195 if (IsFrozen())
196 throw RException(R__FAIL("invalid attempt to modify frozen model"));
197}
198
200{
201 if (!fDefaultEntry)
202 throw RException(R__FAIL("invalid attempt to use default entry of bare model"));
203}
204
205ROOT::Experimental::RNTupleModel::RNTupleModel(std::unique_ptr<RFieldZero> fieldZero)
206 : fFieldZero(std::move(fieldZero)), fModelId(GetNewModelId())
207{}
208
209std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleModel::CreateBare()
210{
211 return CreateBare(std::make_unique<RFieldZero>());
212}
213
214std::unique_ptr<ROOT::Experimental::RNTupleModel>
215ROOT::Experimental::RNTupleModel::CreateBare(std::unique_ptr<RFieldZero> fieldZero)
216{
217 auto model = std::unique_ptr<RNTupleModel>(new RNTupleModel(std::move(fieldZero)));
218 model->fProjectedFields = std::make_unique<RProjectedFields>(model.get());
219 return model;
220}
221
222std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleModel::Create()
223{
224 return Create(std::make_unique<RFieldZero>());
225}
226
227std::unique_ptr<ROOT::Experimental::RNTupleModel>
228ROOT::Experimental::RNTupleModel::Create(std::unique_ptr<RFieldZero> fieldZero)
229{
230 auto model = CreateBare(std::move(fieldZero));
231 model->fDefaultEntry = std::unique_ptr<REntry>(new REntry(model->fModelId));
232 return model;
233}
234
235std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleModel::Clone() const
236{
237 auto cloneModel = std::unique_ptr<RNTupleModel>(
238 new RNTupleModel(std::unique_ptr<RFieldZero>(static_cast<RFieldZero *>(fFieldZero->Clone("").release()))));
239 cloneModel->fModelId = GetNewModelId();
240 cloneModel->fIsFrozen = fIsFrozen;
241 cloneModel->fFieldNames = fFieldNames;
242 cloneModel->fDescription = fDescription;
243 cloneModel->fProjectedFields = fProjectedFields->Clone(cloneModel.get());
244 if (fDefaultEntry) {
245 cloneModel->fDefaultEntry = std::unique_ptr<REntry>(new REntry(cloneModel->fModelId));
246 for (const auto &f : cloneModel->fFieldZero->GetSubFields()) {
247 cloneModel->fDefaultEntry->AddValue(f->CreateValue());
248 }
249 }
250 return cloneModel;
251}
252
254{
255 if (fieldName.empty())
256 return nullptr;
257
258 auto *field = static_cast<ROOT::Experimental::RFieldBase *>(fFieldZero.get());
259 for (auto subfieldName : ROOT::Split(fieldName, ".")) {
260 const auto subfields = field->GetSubFields();
261 auto it = std::find_if(subfields.begin(), subfields.end(),
262 [&](const auto *f) { return f->GetFieldName() == subfieldName; });
263 if (it != subfields.end()) {
264 field = *it;
265 } else {
266 field = nullptr;
267 break;
268 }
269 }
270
271 return field;
272}
273
274void ROOT::Experimental::RNTupleModel::AddField(std::unique_ptr<RFieldBase> field)
275{
276 EnsureNotFrozen();
277 if (!field)
278 throw RException(R__FAIL("null field"));
279 EnsureValidFieldName(field->GetFieldName());
280
281 if (fDefaultEntry)
282 fDefaultEntry->AddValue(field->CreateValue());
283 fFieldZero->Attach(std::move(field));
284}
285
288 std::function<std::string(const std::string &)> mapping)
289{
290 EnsureNotFrozen();
291 if (!field)
292 return R__FAIL("null field");
293 auto fieldName = field->GetFieldName();
294
296 auto sourceField = FindField(mapping(fieldName));
297 if (!sourceField)
298 return R__FAIL("no such field: " + mapping(fieldName));
299 fieldMap[field.get()] = sourceField;
300 for (const auto &subField : *field) {
301 sourceField = FindField(mapping(subField.GetQualifiedFieldName()));
302 if (!sourceField)
303 return R__FAIL("no such field: " + mapping(fieldName));
304 fieldMap[&subField] = sourceField;
305 }
306
307 EnsureValidFieldName(fieldName);
308 auto result = fProjectedFields->Add(std::move(field), fieldMap);
309 if (!result) {
310 fFieldNames.erase(fieldName);
311 return R__FORWARD_ERROR(result);
312 }
313 return RResult<void>::Success();
314}
315
316std::shared_ptr<ROOT::Experimental::RCollectionNTupleWriter> ROOT::Experimental::RNTupleModel::MakeCollection(
317 std::string_view fieldName, std::unique_ptr<RNTupleModel> collectionModel)
318{
319 EnsureNotFrozen();
320 EnsureValidFieldName(fieldName);
321 if (!collectionModel) {
322 throw RException(R__FAIL("null collectionModel"));
323 }
324
325 auto collectionWriter = std::make_shared<RCollectionNTupleWriter>(std::move(collectionModel->fDefaultEntry));
326
327 auto field = std::make_unique<RCollectionField>(fieldName, collectionWriter, std::move(collectionModel->fFieldZero));
328 field->SetDescription(collectionModel->GetDescription());
329
330 if (fDefaultEntry)
331 fDefaultEntry->AddValue(field->BindValue(std::shared_ptr<void>(collectionWriter->GetOffsetPtr(), [](void *) {})));
332
333 fFieldZero->Attach(std::move(field));
334 return collectionWriter;
335}
336
338{
339 if (!IsFrozen())
340 throw RException(R__FAIL("invalid attempt to get mutable zero field of unfrozen model"));
341 return *fFieldZero;
342}
343
345{
346 auto f = FindField(fieldName);
347 if (!f)
348 throw RException(R__FAIL("invalid field: " + std::string(fieldName)));
349
350 return *f;
351}
352
354{
355 EnsureNotBare();
356 return *fDefaultEntry;
357}
358
360{
361 if (!IsFrozen())
362 throw RException(R__FAIL("invalid attempt to get default entry of unfrozen model"));
363 EnsureNotBare();
364 return *fDefaultEntry;
365}
366
367std::unique_ptr<ROOT::Experimental::REntry> ROOT::Experimental::RNTupleModel::CreateEntry() const
368{
369 if (!IsFrozen())
370 throw RException(R__FAIL("invalid attempt to create entry of unfrozen model"));
371
372 auto entry = std::unique_ptr<REntry>(new REntry(fModelId));
373 for (const auto &f : fFieldZero->GetSubFields()) {
374 entry->AddValue(f->CreateValue());
375 }
376 return entry;
377}
378
379std::unique_ptr<ROOT::Experimental::REntry> ROOT::Experimental::RNTupleModel::CreateBareEntry() const
380{
381 if (!IsFrozen())
382 throw RException(R__FAIL("invalid attempt to create entry of unfrozen model"));
383
384 auto entry = std::unique_ptr<REntry>(new REntry(fModelId));
385 for (const auto &f : fFieldZero->GetSubFields()) {
386 entry->AddValue(f->BindValue(nullptr));
387 }
388 return entry;
389}
390
392{
393 if (!IsFrozen())
394 throw RException(R__FAIL("invalid attempt to create bulk of unfrozen model"));
395
396 auto f = FindField(fieldName);
397 if (!f)
398 throw RException(R__FAIL("no such field: " + std::string(fieldName)));
399 return f->CreateBulk();
400}
401
403{
404 if (!IsFrozen())
405 return;
406
407 fModelId = GetNewModelId();
408 if (fDefaultEntry)
409 fDefaultEntry->fModelId = fModelId;
410 fIsFrozen = false;
411}
412
414{
415 fIsFrozen = true;
416}
417
418void ROOT::Experimental::RNTupleModel::SetDescription(std::string_view description)
419{
420 EnsureNotFrozen();
421 fDescription = std::string(description);
422}
#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:1676
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:225
A field translates read and write calls from/to underlying columns to/from tree values.
Definition RField.hxx:90
const RFieldBase * GetParent() const
Definition RField.hxx:636
std::vector< RFieldBase * > GetSubFields()
Definition RField.cxx:748
static RResult< void > EnsureValidFieldName(std::string_view fieldName)
Check whether a given string is a valid field name.
Definition RField.cxx:652
The container field for an ntuple model, which itself has no physical representation.
Definition RField.hxx:680
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.
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.
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)
std::shared_ptr< RCollectionNTupleWriter > MakeCollection(std::string_view fieldName, std::unique_ptr< RNTupleModel > collectionModel)
Ingests a model for a sub collection and attaches it to the current model.
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.
Definition RNTuple.hxx:443
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::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