Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleDescriptor.cxx
Go to the documentation of this file.
1/// \file RNTupleDescriptor.cxx
2/// \ingroup NTuple ROOT7
3/// \author Jakob Blomer <jblomer@cern.ch>
4/// \author Javier Lopez-Gomez <javier.lopez.gomez@cern.ch>
5/// \date 2018-10-04
6/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
7/// is welcome!
8
9/*************************************************************************
10 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
11 * All rights reserved. *
12 * *
13 * For the licensing terms see $ROOTSYS/LICENSE. *
14 * For the list of contributors see $ROOTSYS/README/CREDITS. *
15 *************************************************************************/
16
17#include <ROOT/RError.hxx>
18#include <ROOT/RField.hxx>
20#include <ROOT/RNTupleModel.hxx>
21#include <ROOT/RNTupleUtil.hxx>
22#include <ROOT/RPage.hxx>
23#include <string_view>
24
25#include <RZip.h>
26#include <TError.h>
27
28#include <algorithm>
29#include <cstdint>
30#include <deque>
31#include <iostream>
32#include <set>
33#include <utility>
34
35
37{
38 return fFieldId == other.fFieldId && fFieldVersion == other.fFieldVersion && fTypeVersion == other.fTypeVersion &&
40 fTypeName == other.fTypeName && fTypeAlias == other.fTypeAlias && fNRepetitions == other.fNRepetitions &&
41 fStructure == other.fStructure && fParentId == other.fParentId && fLinkIds == other.fLinkIds &&
43}
44
47{
49 clone.fFieldId = fFieldId;
50 clone.fFieldVersion = fFieldVersion;
51 clone.fTypeVersion = fTypeVersion;
52 clone.fFieldName = fFieldName;
53 clone.fFieldDescription = fFieldDescription;
54 clone.fTypeName = fTypeName;
55 clone.fTypeAlias = fTypeAlias;
56 clone.fNRepetitions = fNRepetitions;
57 clone.fStructure = fStructure;
58 clone.fParentId = fParentId;
59 clone.fLinkIds = fLinkIds;
60 clone.fLogicalColumnIds = fLogicalColumnIds;
61 return clone;
62}
63
64std::unique_ptr<ROOT::Experimental::RFieldBase>
66{
67 if (GetStructure() == ENTupleStructure::kUnsplit) {
68 return std::make_unique<RUnsplitField>(GetFieldName(), GetTypeName());
69 }
70
71 if (GetTypeName().empty()) {
72 // For untyped records or collections, we have no class available to collect all the sub fields.
73 // Therefore, we create an untyped record field as an artificial binder for the record itself, and in the case of
74 // collections, its items.
75 std::vector<std::unique_ptr<RFieldBase>> memberFields;
76 for (auto id : fLinkIds) {
77 const auto &memberDesc = ntplDesc.GetFieldDescriptor(id);
78 memberFields.emplace_back(memberDesc.CreateField(ntplDesc));
79 }
80 if (GetStructure() == ENTupleStructure::kRecord) {
81 auto recordField = std::make_unique<RRecordField>(GetFieldName(), memberFields);
82 recordField->SetOnDiskId(fFieldId);
83 return recordField;
84 } else if (GetStructure() == ENTupleStructure::kCollection) {
85 auto recordField = std::make_unique<RRecordField>("_0", memberFields);
86 auto collectionField = std::make_unique<RVectorField>(GetFieldName(), std::move(recordField));
87 collectionField->SetOnDiskId(fFieldId);
88 return collectionField;
89 } else {
90 throw RException(R__FAIL("unknown field type for field \"" + GetFieldName() + "\""));
91 }
92 }
93
94 auto field = RFieldBase::Create(GetFieldName(), GetTypeAlias().empty() ? GetTypeName() : GetTypeAlias()).Unwrap();
95 field->SetOnDiskId(fFieldId);
96 for (auto &f : *field)
97 f.SetOnDiskId(ntplDesc.FindFieldId(f.GetFieldName(), f.GetParent()->GetOnDiskId()));
98 return field;
99}
100
101
102////////////////////////////////////////////////////////////////////////////////
103
104
106{
107 return fLogicalColumnId == other.fLogicalColumnId && fPhysicalColumnId == other.fPhysicalColumnId &&
108 fModel == other.fModel && fFieldId == other.fFieldId && fIndex == other.fIndex;
109}
110
111
114{
116 clone.fLogicalColumnId = fLogicalColumnId;
117 clone.fPhysicalColumnId = fPhysicalColumnId;
118 clone.fModel = fModel;
119 clone.fFieldId = fFieldId;
120 clone.fIndex = fIndex;
121 clone.fFirstElementIndex = fFirstElementIndex;
122 return clone;
123}
124
125
126////////////////////////////////////////////////////////////////////////////////
127
130{
131 // TODO(jblomer): binary search
132 RPageInfo pageInfo;
133 decltype(idxInCluster) firstInPage = 0;
134 NTupleSize_t pageNo = 0;
135 for (const auto &pi : fPageInfos) {
136 if (firstInPage + pi.fNElements > idxInCluster) {
137 pageInfo = pi;
138 break;
139 }
140 firstInPage += pi.fNElements;
141 ++pageNo;
142 }
143 R__ASSERT(firstInPage <= idxInCluster);
144 R__ASSERT((firstInPage + pageInfo.fNElements) > idxInCluster);
145 return RPageInfoExtended{pageInfo, firstInPage, pageNo};
146}
147
148std::size_t
150 const Internal::RColumnElementBase &element,
151 std::size_t pageSize)
152{
153 R__ASSERT(fPhysicalColumnId == columnRange.fPhysicalColumnId);
154
155 const auto nElements = std::accumulate(fPageInfos.begin(), fPageInfos.end(), 0U,
156 [](std::size_t n, const auto &PI) { return n + PI.fNElements; });
157 const auto nElementsRequired = static_cast<std::uint64_t>(columnRange.fNElements);
158
159 if (nElementsRequired == nElements)
160 return 0U;
161 R__ASSERT((nElementsRequired > nElements) && "invalid attempt to shrink RPageRange");
162
163 std::vector<RPageInfo> pageInfos;
164 // Synthesize new `RPageInfo`s as needed
165 const std::uint64_t nElementsPerPage = pageSize / element.GetSize();
166 R__ASSERT(nElementsPerPage > 0);
167 for (auto nRemainingElements = nElementsRequired - nElements; nRemainingElements > 0;) {
169 PI.fNElements = std::min(nElementsPerPage, nRemainingElements);
170 PI.fLocator.fType = RNTupleLocator::kTypePageZero;
171 PI.fLocator.fBytesOnStorage = element.GetPackedSize(PI.fNElements);
172 pageInfos.emplace_back(PI);
173 nRemainingElements -= PI.fNElements;
174 }
175
176 pageInfos.insert(pageInfos.end(), std::make_move_iterator(fPageInfos.begin()),
177 std::make_move_iterator(fPageInfos.end()));
178 std::swap(fPageInfos, pageInfos);
179 return nElementsRequired - nElements;
180}
181
183{
184 return fClusterId == other.fClusterId && fFirstEntryIndex == other.fFirstEntryIndex &&
185 fNEntries == other.fNEntries && fColumnRanges == other.fColumnRanges && fPageRanges == other.fPageRanges;
186}
187
188
189std::unordered_set<ROOT::Experimental::DescriptorId_t> ROOT::Experimental::RClusterDescriptor::GetColumnIds() const
190{
191 std::unordered_set<DescriptorId_t> result;
192 for (const auto &x : fColumnRanges)
193 result.emplace(x.first);
194 return result;
195}
196
198{
199 std::uint64_t nbytes = 0;
200 for (const auto &pr : fPageRanges) {
201 for (const auto &pi : pr.second.fPageInfos) {
202 nbytes += pi.fLocator.fBytesOnStorage;
203 }
204 }
205 return nbytes;
206}
207
209{
211 clone.fClusterId = fClusterId;
212 clone.fFirstEntryIndex = fFirstEntryIndex;
213 clone.fNEntries = fNEntries;
214 clone.fColumnRanges = fColumnRanges;
215 for (const auto &d : fPageRanges)
216 clone.fPageRanges.emplace(d.first, d.second.Clone());
217 return clone;
218}
219
220////////////////////////////////////////////////////////////////////////////////
221
223{
224 return fContentId == other.fContentId && fTypeName == other.fTypeName &&
225 fTypeVersionFrom == other.fTypeVersionFrom && fTypeVersionTo == other.fTypeVersionTo;
226}
227
229{
231 clone.fContentId = fContentId;
232 clone.fTypeVersionFrom = fTypeVersionFrom;
233 clone.fTypeVersionTo = fTypeVersionTo;
234 clone.fTypeName = fTypeName;
235 clone.fContent = fContent;
236 return clone;
237}
238
239////////////////////////////////////////////////////////////////////////////////
240
242{
243 // clang-format off
244 return fName == other.fName &&
245 fDescription == other.fDescription &&
246 fNEntries == other.fNEntries &&
247 fGeneration == other.fGeneration &&
248 fFieldZeroId == other.fFieldZeroId &&
249 fFieldDescriptors == other.fFieldDescriptors &&
250 fColumnDescriptors == other.fColumnDescriptors &&
251 fClusterGroupDescriptors == other.fClusterGroupDescriptors &&
252 fClusterDescriptors == other.fClusterDescriptors;
253 // clang-format on
254}
255
258{
260 for (const auto &cd : fClusterDescriptors) {
261 if (!cd.second.ContainsColumn(physicalColumnId))
262 continue;
263 auto columnRange = cd.second.GetColumnRange(physicalColumnId);
264 result = std::max(result, columnRange.fFirstElementIndex + columnRange.fNElements);
265 }
266 return result;
267}
268
269
272{
273 std::string leafName(fieldName);
274 auto posDot = leafName.find_last_of('.');
275 if (posDot != std::string::npos) {
276 auto parentName = leafName.substr(0, posDot);
277 leafName = leafName.substr(posDot + 1);
278 parentId = FindFieldId(parentName, parentId);
279 }
280 auto itrFieldDesc = fFieldDescriptors.find(parentId);
281 if (itrFieldDesc == fFieldDescriptors.end())
283 for (const auto linkId : itrFieldDesc->second.GetLinkIds()) {
284 if (fFieldDescriptors.at(linkId).GetFieldName() == leafName)
285 return linkId;
286 }
288}
289
290
292{
293 if (fieldId == kInvalidDescriptorId)
294 return "";
295
296 const auto &fieldDescriptor = fFieldDescriptors.at(fieldId);
297 auto prefix = GetQualifiedFieldName(fieldDescriptor.GetParentId());
298 if (prefix.empty())
299 return fieldDescriptor.GetFieldName();
300 return prefix + "." + fieldDescriptor.GetFieldName();
301}
302
305{
306 return FindFieldId(fieldName, GetFieldZeroId());
307}
308
311{
312 auto itr = fFieldDescriptors.find(fieldId);
313 if (itr == fFieldDescriptors.cend())
315 if (itr->second.GetLogicalColumnIds().size() <= columnIndex)
317 return itr->second.GetLogicalColumnIds().at(columnIndex);
318}
319
322{
323 auto logicalId = FindLogicalColumnId(fieldId, columnIndex);
324 if (logicalId == kInvalidDescriptorId)
326 return GetColumnDescriptor(logicalId).GetPhysicalId();
327}
328
331{
332 // TODO(jblomer): binary search?
333 for (const auto &cd : fClusterDescriptors) {
334 if (!cd.second.ContainsColumn(physicalColumnId))
335 continue;
336 auto columnRange = cd.second.GetColumnRange(physicalColumnId);
337 if (columnRange.Contains(index))
338 return cd.second.GetId();
339 }
341}
342
343
344// TODO(jblomer): fix for cases of sharded clasters
347{
348 const auto &clusterDesc = GetClusterDescriptor(clusterId);
349 auto firstEntryInNextCluster = clusterDesc.GetFirstEntryIndex() + clusterDesc.GetNEntries();
350 // TODO(jblomer): binary search?
351 for (const auto &cd : fClusterDescriptors) {
352 if (cd.second.GetFirstEntryIndex() == firstEntryInNextCluster)
353 return cd.second.GetId();
354 }
356}
357
358
359// TODO(jblomer): fix for cases of sharded clasters
362{
363 const auto &clusterDesc = GetClusterDescriptor(clusterId);
364 // TODO(jblomer): binary search?
365 for (const auto &cd : fClusterDescriptors) {
366 if (cd.second.GetFirstEntryIndex() + cd.second.GetNEntries() == clusterDesc.GetFirstEntryIndex())
367 return cd.second.GetId();
368 }
370}
371
372std::vector<ROOT::Experimental::DescriptorId_t>
374{
375 auto fieldZeroId = desc.GetFieldZeroId();
376
377 std::vector<DescriptorId_t> fields;
378 for (const DescriptorId_t fieldId : fFields) {
379 if (desc.GetFieldDescriptor(fieldId).GetParentId() == fieldZeroId)
380 fields.emplace_back(fieldId);
381 }
382 return fields;
383}
384
386 for (unsigned int i = 0; true; ++i) {
387 auto logicalId = fNTuple.FindLogicalColumnId(fieldId, i);
388 if (logicalId == kInvalidDescriptorId)
389 break;
390 fColumns.emplace_back(logicalId);
391 }
392}
393
395 const RNTupleDescriptor &ntuple, const RFieldDescriptor &field)
396 : fNTuple(ntuple)
397{
398 CollectColumnIds(field.GetId());
399}
400
402 const RNTupleDescriptor &ntuple)
403 : fNTuple(ntuple)
404{
405 std::deque<DescriptorId_t> fieldIdQueue{ntuple.GetFieldZeroId()};
406
407 while (!fieldIdQueue.empty()) {
408 auto currFieldId = fieldIdQueue.front();
409 fieldIdQueue.pop_front();
410
411 CollectColumnIds(currFieldId);
412
413 for (const auto &field : ntuple.GetFieldIterable(currFieldId)) {
414 auto fieldId = field.GetId();
415 fieldIdQueue.push_back(fieldId);
416 }
417 }
418}
419
421{
422 std::vector<std::uint64_t> result;
423 unsigned int base = 0;
424 std::uint64_t flags = 0;
425 for (auto f : fFeatureFlags) {
426 if ((f > 0) && ((f % 64) == 0))
427 throw RException(R__FAIL("invalid feature flag: " + std::to_string(f)));
428 while (f > base + 64) {
429 result.emplace_back(flags);
430 flags = 0;
431 base += 64;
432 }
433 f -= base;
434 flags |= 1 << f;
435 }
436 result.emplace_back(flags);
437 return result;
438}
439
442 std::vector<RClusterDescriptor> &clusterDescs)
443{
444 auto iter = fClusterGroupDescriptors.find(clusterGroupId);
445 if (iter == fClusterGroupDescriptors.end())
446 return R__FAIL("invalid attempt to add details of unknown cluster group");
447 if (iter->second.HasClusterDetails())
448 return R__FAIL("invalid attempt to re-populate cluster group details");
449 if (iter->second.GetNClusters() != clusterDescs.size())
450 return R__FAIL("mismatch of number of clusters");
451
452 std::vector<DescriptorId_t> clusterIds;
453 for (unsigned i = 0; i < clusterDescs.size(); ++i) {
454 clusterIds.emplace_back(clusterDescs[i].GetId());
455 auto [_, success] = fClusterDescriptors.emplace(clusterIds.back(), std::move(clusterDescs[i]));
456 if (!success) {
457 return R__FAIL("invalid attempt to re-populate existing cluster");
458 }
459 }
460 auto cgBuilder = Internal::RClusterGroupDescriptorBuilder::FromSummary(iter->second);
461 cgBuilder.AddClusters(clusterIds);
462 iter->second = cgBuilder.MoveDescriptor().Unwrap();
463 return RResult<void>::Success();
464}
465
468{
469 auto iter = fClusterGroupDescriptors.find(clusterGroupId);
470 if (iter == fClusterGroupDescriptors.end())
471 return R__FAIL("invalid attempt to drop cluster details of unknown cluster group");
472 if (!iter->second.HasClusterDetails())
473 return R__FAIL("invalid attempt to drop details of cluster group summary");
474
475 for (auto clusterId : iter->second.GetClusterIds())
476 fClusterDescriptors.erase(clusterId);
477 iter->second = iter->second.CloneSummary();
478 return RResult<void>::Success();
479}
480
481std::unique_ptr<ROOT::Experimental::RNTupleModel> ROOT::Experimental::RNTupleDescriptor::CreateModel() const
482{
483 auto fieldZero = std::make_unique<RFieldZero>();
484 fieldZero->SetOnDiskId(GetFieldZeroId());
485 auto model = RNTupleModel::Create(std::move(fieldZero));
486 for (const auto &topDesc : GetTopLevelFields())
487 model->AddField(topDesc.CreateField(*this));
488 model->Freeze();
489 return model;
490}
491
492std::unique_ptr<ROOT::Experimental::RNTupleDescriptor> ROOT::Experimental::RNTupleDescriptor::Clone() const
493{
494 auto clone = std::make_unique<RNTupleDescriptor>();
495 clone->fName = fName;
496 clone->fDescription = fDescription;
497 clone->fOnDiskHeaderXxHash3 = fOnDiskHeaderXxHash3;
498 clone->fOnDiskHeaderSize = fOnDiskHeaderSize;
499 clone->fOnDiskFooterSize = fOnDiskFooterSize;
500 clone->fNEntries = fNEntries;
501 clone->fNClusters = fNClusters;
502 clone->fNPhysicalColumns = fNPhysicalColumns;
503 clone->fFieldZeroId = fFieldZeroId;
504 clone->fGeneration = fGeneration;
505 for (const auto &d : fFieldDescriptors)
506 clone->fFieldDescriptors.emplace(d.first, d.second.Clone());
507 for (const auto &d : fColumnDescriptors)
508 clone->fColumnDescriptors.emplace(d.first, d.second.Clone());
509 for (const auto &d : fClusterGroupDescriptors)
510 clone->fClusterGroupDescriptors.emplace(d.first, d.second.Clone());
511 for (const auto &d : fClusterDescriptors)
512 clone->fClusterDescriptors.emplace(d.first, d.second.Clone());
513 for (const auto &d : fExtraTypeInfoDescriptors)
514 clone->fExtraTypeInfoDescriptors.emplace_back(d.Clone());
516 clone->fHeaderExtension = std::make_unique<RHeaderExtension>(*fHeaderExtension);
517 return clone;
518}
519
520////////////////////////////////////////////////////////////////////////////////
521
523{
524 return fColumnGroupId == other.fColumnGroupId && fPhysicalColumnIds == other.fPhysicalColumnIds;
525}
526
527////////////////////////////////////////////////////////////////////////////////
528
530{
531 return fClusterGroupId == other.fClusterGroupId && fClusterIds == other.fClusterIds &&
532 fMinEntry == other.fMinEntry && fEntrySpan == other.fEntrySpan && fNClusters == other.fNClusters;
533}
534
536{
538 clone.fClusterGroupId = fClusterGroupId;
539 clone.fPageListLocator = fPageListLocator;
540 clone.fPageListLength = fPageListLength;
541 clone.fMinEntry = fMinEntry;
542 clone.fEntrySpan = fEntrySpan;
543 clone.fNClusters = fNClusters;
544 return clone;
545}
546
548{
549 RClusterGroupDescriptor clone = CloneSummary();
550 clone.fClusterIds = fClusterIds;
551 return clone;
552}
553
554////////////////////////////////////////////////////////////////////////////////
555
557 DescriptorId_t physicalId, std::uint64_t firstElementIndex, std::uint32_t compressionSettings,
558 const RClusterDescriptor::RPageRange &pageRange)
559{
560 if (physicalId != pageRange.fPhysicalColumnId)
561 return R__FAIL("column ID mismatch");
562 if (fCluster.fPageRanges.count(physicalId) > 0)
563 return R__FAIL("column ID conflict");
564 RClusterDescriptor::RColumnRange columnRange{physicalId, firstElementIndex, ClusterSize_t{0}};
565 columnRange.fCompressionSettings = compressionSettings;
566 for (const auto &pi : pageRange.fPageInfos) {
567 columnRange.fNElements += pi.fNElements;
568 }
569 fCluster.fPageRanges[physicalId] = pageRange.Clone();
570 fCluster.fColumnRanges[physicalId] = columnRange;
571 return RResult<void>::Success();
572}
573
576{
577 /// Carries out a depth-first traversal of a field subtree rooted at `rootFieldId`. For each field, `visitField` is
578 /// called passing the field ID and the number of overall repetitions, taking into account the repetitions of each
579 /// parent field in the hierarchy.
580 auto fnTraverseSubtree = [&](DescriptorId_t rootFieldId, std::uint64_t nRepetitionsAtThisLevel,
581 const auto &visitField, const auto &enterSubtree) -> void {
582 visitField(rootFieldId, nRepetitionsAtThisLevel);
583 for (const auto &f : desc.GetFieldIterable(rootFieldId)) {
584 const std::uint64_t nRepetitions = std::max(f.GetNRepetitions(), std::uint64_t{1U}) * nRepetitionsAtThisLevel;
585 enterSubtree(f.GetId(), nRepetitions, visitField, enterSubtree);
586 }
587 };
588
589 // Extended columns can only be part of the header extension
590 auto xHeader = desc.GetHeaderExtension();
591 if (!xHeader)
592 return *this;
593
594 // Ensure that all columns in the header extension have their associated `R(Column|Page)Range`
595 for (const auto &topLevelFieldId : xHeader->GetTopLevelFields(desc)) {
596 fnTraverseSubtree(
597 topLevelFieldId, std::max(desc.GetFieldDescriptor(topLevelFieldId).GetNRepetitions(), std::uint64_t{1U}),
598 [&](DescriptorId_t fieldId, std::uint64_t nRepetitions) {
599 for (const auto &c : desc.GetColumnIterable(fieldId)) {
600 const DescriptorId_t physicalId = c.GetPhysicalId();
601 auto &columnRange = fCluster.fColumnRanges[physicalId];
602 auto &pageRange = fCluster.fPageRanges[physicalId];
603 // Initialize a RColumnRange for `physicalId` if it was not there. Columns that were created during model
604 // extension won't have on-disk metadata for the clusters that were already committed before the model
605 // was extended. Therefore, these need to be synthetically initialized upon reading.
606 if (columnRange.fPhysicalColumnId == kInvalidDescriptorId) {
607 columnRange.fPhysicalColumnId = physicalId;
608 columnRange.fFirstElementIndex = 0;
609 columnRange.fNElements = 0;
610
611 pageRange.fPhysicalColumnId = physicalId;
612 }
613 // Fixup the RColumnRange and RPageRange in deferred columns. We know what the first element index and
614 // number of elements should have been if the column was not deferred; fix those and let
615 // `ExtendToFitColumnRange()` synthesize RPageInfos accordingly.
616 // Note that a deferred column (i.e, whose first element index is > 0) already met the criteria of
617 // `RFieldBase::EntryToColumnElementIndex()`, i.e. it is a principal column reachable from the field zero
618 // excluding subfields of collection and variant fields.
619 if (c.IsDeferredColumn()) {
620 columnRange.fFirstElementIndex = fCluster.GetFirstEntryIndex() * nRepetitions;
621 columnRange.fNElements = fCluster.GetNEntries() * nRepetitions;
622 const auto element = Internal::RColumnElementBase::Generate<void>(c.GetModel().GetType());
623 pageRange.ExtendToFitColumnRange(columnRange, *element, Internal::RPage::kPageZeroSize);
624 }
625 }
626 },
627 fnTraverseSubtree);
628 }
629 return *this;
630}
631
634{
635 if (fCluster.fClusterId == kInvalidDescriptorId)
636 return R__FAIL("unset cluster ID");
637 if (fCluster.fNEntries == 0)
638 return R__FAIL("empty cluster");
639 for (const auto &pr : fCluster.fPageRanges) {
640 if (fCluster.fColumnRanges.count(pr.first) == 0) {
641 return R__FAIL("missing column range");
642 }
643 }
645 std::swap(result, fCluster);
646 return result;
647}
648
649////////////////////////////////////////////////////////////////////////////////
650
653 const RClusterGroupDescriptor &clusterGroupDesc)
654{
656 builder.ClusterGroupId(clusterGroupDesc.GetId())
657 .PageListLocator(clusterGroupDesc.GetPageListLocator())
658 .PageListLength(clusterGroupDesc.GetPageListLength())
659 .MinEntry(clusterGroupDesc.GetMinEntry())
660 .EntrySpan(clusterGroupDesc.GetEntrySpan())
661 .NClusters(clusterGroupDesc.GetNClusters());
662 return builder;
663}
664
667{
668 if (fClusterGroup.fClusterGroupId == kInvalidDescriptorId)
669 return R__FAIL("unset cluster group ID");
671 std::swap(result, fClusterGroup);
672 return result;
673}
674
675////////////////////////////////////////////////////////////////////////////////
676
679{
680 if (fColumnGroup.fColumnGroupId == kInvalidDescriptorId)
681 return R__FAIL("unset column group ID");
683 std::swap(result, fColumnGroup);
684 return result;
685}
686
687////////////////////////////////////////////////////////////////////////////////
688
691{
692 if (fExtraTypeInfo.fContentId == EExtraTypeInfoIds::kInvalid)
693 throw RException(R__FAIL("invalid extra type info content id"));
695 std::swap(result, fExtraTypeInfo);
696 return result;
697}
698
699////////////////////////////////////////////////////////////////////////////////
700
703{
704 if (fDescriptor.fFieldDescriptors.count(fieldId) == 0)
705 return R__FAIL("field with id '" + std::to_string(fieldId) + "' doesn't exist");
706 return RResult<void>::Success();
707}
708
710{
711 // Reuse field name validity check
712 auto validName = RFieldBase::EnsureValidFieldName(fDescriptor.GetName());
713 if (!validName) {
714 return R__FORWARD_ERROR(validName);
715 }
716 // open-ended list of invariant checks
717 for (const auto& key_val: fDescriptor.fFieldDescriptors) {
718 const auto& id = key_val.first;
719 const auto& desc = key_val.second;
720 // parent not properly set
721 if (id != DescriptorId_t(0) && desc.GetParentId() == kInvalidDescriptorId) {
722 return R__FAIL("field with id '" + std::to_string(id) + "' has an invalid parent id");
723 }
724 }
725 return RResult<void>::Success();
726}
727
729{
731 std::swap(result, fDescriptor);
732 return result;
733}
734
736 const std::string_view description)
737{
738 fDescriptor.fName = std::string(name);
739 fDescriptor.fDescription = std::string(description);
740}
741
743{
744 if (flag % 64 == 0)
745 throw RException(R__FAIL("invalid feature flag: " + std::to_string(flag)));
746 fDescriptor.fFeatureFlags.insert(flag);
747}
748
751{
752 if (fColumn.GetLogicalId() == kInvalidDescriptorId)
753 return R__FAIL("invalid logical column id");
754 if (fColumn.GetPhysicalId() == kInvalidDescriptorId)
755 return R__FAIL("invalid physical column id");
756 if (fColumn.GetModel().GetType() == EColumnType::kUnknown)
757 return R__FAIL("invalid column model");
758 if (fColumn.GetFieldId() == kInvalidDescriptorId)
759 return R__FAIL("invalid field id, dangling column");
760 return fColumn.Clone();
761}
762
764 : fField(fieldDesc.Clone())
765{
767 fField.fLinkIds = {};
768}
769
772{
773 RFieldDescriptorBuilder fieldDesc;
774 fieldDesc.FieldVersion(field.GetFieldVersion())
776 .FieldName(field.GetFieldName())
778 .TypeName(field.GetTypeName())
779 .TypeAlias(field.GetTypeAlias())
780 .Structure(field.GetStructure())
782 return fieldDesc;
783}
784
787{
788 if (fField.GetId() == kInvalidDescriptorId) {
789 return R__FAIL("invalid field id");
790 }
791 if (fField.GetStructure() == ENTupleStructure::kInvalid) {
792 return R__FAIL("invalid field structure");
793 }
794 // FieldZero is usually named "" and would be a false positive here
795 if (fField.GetParentId() != kInvalidDescriptorId) {
796 auto validName = RFieldBase::EnsureValidFieldName(fField.GetFieldName());
797 if (!validName) {
798 return R__FORWARD_ERROR(validName);
799 }
800 }
801 return fField.Clone();
802}
803
805{
806 fDescriptor.fFieldDescriptors.emplace(fieldDesc.GetId(), fieldDesc.Clone());
807 if (fDescriptor.fHeaderExtension)
808 fDescriptor.fHeaderExtension->AddFieldId(fieldDesc.GetId());
809 if (fieldDesc.GetFieldName().empty() && fieldDesc.GetParentId() == kInvalidDescriptorId) {
810 fDescriptor.fFieldZeroId = fieldDesc.GetId();
811 }
812}
813
816{
817 auto fieldExists = RResult<void>::Success();
818 if (!(fieldExists = EnsureFieldExists(fieldId)))
819 return R__FORWARD_ERROR(fieldExists);
820 if (!(fieldExists = EnsureFieldExists(linkId)))
821 return R__FAIL("child field with id '" + std::to_string(linkId) + "' doesn't exist in NTuple");
822
823 if (linkId == fDescriptor.GetFieldZeroId()) {
824 return R__FAIL("cannot make FieldZero a child field");
825 }
826 // fail if field already has another valid parent
827 auto parentId = fDescriptor.fFieldDescriptors.at(linkId).GetParentId();
828 if ((parentId != kInvalidDescriptorId) && (parentId != fieldId)) {
829 return R__FAIL("field '" + std::to_string(linkId) + "' already has a parent ('" +
830 std::to_string(parentId) + ")");
831 }
832 if (fieldId == linkId) {
833 return R__FAIL("cannot make field '" + std::to_string(fieldId) + "' a child of itself");
834 }
835 fDescriptor.fFieldDescriptors.at(linkId).fParentId = fieldId;
836 fDescriptor.fFieldDescriptors.at(fieldId).fLinkIds.push_back(linkId);
837 return RResult<void>::Success();
838}
839
842 DescriptorId_t fieldId, const RColumnModel &model,
843 std::uint32_t index, std::uint64_t firstElementIdx)
844{
846 c.fLogicalColumnId = logicalId;
847 c.fPhysicalColumnId = physicalId;
848 c.fFieldId = fieldId;
849 c.fModel = model;
850 c.fIndex = index;
851 c.fFirstElementIndex = firstElementIdx;
852
853 auto res = AttachColumn(fieldId, c);
854 if (!res)
855 R__FORWARD_ERROR(res);
856
857 if (!c.IsAliasColumn())
858 fDescriptor.fNPhysicalColumns++;
859 if (fDescriptor.fHeaderExtension)
860 fDescriptor.fHeaderExtension->AddColumn(/*isAliasColumn=*/c.IsAliasColumn());
861 fDescriptor.fColumnDescriptors.emplace(logicalId, std::move(c));
862
863 return RResult<void>::Success();
864}
865
868{
869 const auto fieldId = columnDesc.GetFieldId();
870 const auto index = columnDesc.GetIndex();
871
872 auto fieldExists = EnsureFieldExists(fieldId);
873 if (!fieldExists)
874 return R__FORWARD_ERROR(fieldExists);
875 if (fDescriptor.FindLogicalColumnId(fieldId, index) != kInvalidDescriptorId) {
876 return R__FAIL("column index clash");
877 }
878 if (index > 0) {
879 if (fDescriptor.FindLogicalColumnId(fieldId, index - 1) == kInvalidDescriptorId)
880 return R__FAIL("out of bounds column index");
881 }
882 if (columnDesc.IsAliasColumn()) {
883 if (columnDesc.GetModel() != fDescriptor.GetColumnDescriptor(columnDesc.GetPhysicalId()).GetModel())
884 return R__FAIL("alias column type mismatch");
885 }
886 auto res = AttachColumn(fieldId, columnDesc);
887 if (!res)
888 R__FORWARD_ERROR(res);
889
890 auto logicalId = columnDesc.GetLogicalId();
891 if (!columnDesc.IsAliasColumn())
892 fDescriptor.fNPhysicalColumns++;
893 fDescriptor.fColumnDescriptors.emplace(logicalId, std::move(columnDesc));
894 if (fDescriptor.fHeaderExtension)
895 fDescriptor.fHeaderExtension->AddColumn(/*isAliasColumn=*/columnDesc.IsAliasColumn());
896
897 return RResult<void>::Success();
898}
899
902 const RColumnDescriptor &columnDesc)
903{
904 auto itrFieldDesc = fDescriptor.fFieldDescriptors.find(fieldId);
905 if (itrFieldDesc == fDescriptor.fFieldDescriptors.end()) {
906 return R__FAIL("AttachColumn: invalid field ID");
907 }
908 auto &logicalColumnIds = itrFieldDesc->second.fLogicalColumnIds;
909 for (std::size_t i = logicalColumnIds.size(); i <= columnDesc.GetIndex(); ++i) {
910 logicalColumnIds.emplace_back(kInvalidDescriptorId);
911 }
912 logicalColumnIds.at(columnDesc.GetIndex()) = columnDesc.GetLogicalId();
913
914 return RResult<void>::Success();
915}
916
919{
920 const auto id = clusterGroup.GetId();
921 if (fDescriptor.fClusterGroupDescriptors.count(id) > 0)
922 return R__FAIL("cluster group id clash");
923 fDescriptor.fNEntries = std::max(fDescriptor.fNEntries, clusterGroup.GetMinEntry() + clusterGroup.GetEntrySpan());
924 fDescriptor.fNClusters += clusterGroup.GetNClusters();
925 fDescriptor.fClusterGroupDescriptors.emplace(id, std::move(clusterGroup));
926 return RResult<void>::Success();
927}
928
930{
931 fDescriptor.fName = "";
932 fDescriptor.fDescription = "";
933 fDescriptor.fFieldDescriptors.clear();
934 fDescriptor.fColumnDescriptors.clear();
935 fDescriptor.fClusterDescriptors.clear();
936 fDescriptor.fClusterGroupDescriptors.clear();
937 fDescriptor.fHeaderExtension.reset();
938}
939
941{
942 if (!fDescriptor.fHeaderExtension)
943 fDescriptor.fHeaderExtension = std::make_unique<RNTupleDescriptor::RHeaderExtension>();
944}
945
948{
949 auto clusterId = clusterDesc.GetId();
950 if (fDescriptor.fClusterDescriptors.count(clusterId) > 0)
951 return R__FAIL("cluster id clash");
952 fDescriptor.fClusterDescriptors.emplace(clusterId, std::move(clusterDesc));
953 return RResult<void>::Success();
954}
955
958{
959 // Make sure we have no duplicates
960 if (std::find(fDescriptor.fExtraTypeInfoDescriptors.begin(), fDescriptor.fExtraTypeInfoDescriptors.end(),
961 extraTypeInfoDesc) != fDescriptor.fExtraTypeInfoDescriptors.end()) {
962 return R__FAIL("extra type info duplicates");
963 }
964 fDescriptor.fExtraTypeInfoDescriptors.emplace_back(std::move(extraTypeInfoDesc));
965 return RResult<void>::Success();
966}
#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__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 d(i)
Definition RSha256.hxx:102
#define f(i)
Definition RSha256.hxx:104
#define c(i)
Definition RSha256.hxx:101
TObject * clone(const char *newname) const override
Definition RooChi2Var.h:9
#define PI
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
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
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
#define _(A, B)
Definition cfortran.h:108
A helper class for piece-wise construction of an RClusterDescriptor.
RResult< RClusterDescriptor > MoveDescriptor()
Move out the full cluster descriptor including page locations.
RClusterDescriptorBuilder & AddExtendedColumnRanges(const RNTupleDescriptor &desc)
Add column and page ranges for columns created during late model extension missing in this cluster.
RResult< void > CommitColumnRange(DescriptorId_t physicalId, std::uint64_t firstElementIndex, std::uint32_t compressionSettings, const RClusterDescriptor::RPageRange &pageRange)
A helper class for piece-wise construction of an RClusterGroupDescriptor.
RClusterGroupDescriptorBuilder & PageListLocator(const RNTupleLocator &pageListLocator)
RClusterGroupDescriptorBuilder & MinEntry(std::uint64_t minEntry)
RClusterGroupDescriptorBuilder & ClusterGroupId(DescriptorId_t clusterGroupId)
RClusterGroupDescriptorBuilder & EntrySpan(std::uint64_t entrySpan)
RClusterGroupDescriptorBuilder & NClusters(std::uint32_t nClusters)
RClusterGroupDescriptorBuilder & PageListLength(std::uint64_t pageListLength)
static RClusterGroupDescriptorBuilder FromSummary(const RClusterGroupDescriptor &clusterGroupDesc)
RResult< RColumnDescriptor > MakeDescriptor() const
Attempt to make a column descriptor.
A column element encapsulates the translation between basic C++ types and their column representation...
std::size_t GetPackedSize(std::size_t nElements=1U) const
A helper class for piece-wise construction of an RFieldDescriptor.
RFieldDescriptorBuilder & TypeVersion(std::uint32_t typeVersion)
RFieldDescriptorBuilder & NRepetitions(std::uint64_t nRepetitions)
RFieldDescriptorBuilder & FieldVersion(std::uint32_t fieldVersion)
RFieldDescriptorBuilder & Structure(const ENTupleStructure &structure)
RFieldDescriptorBuilder & TypeName(const std::string &typeName)
static RFieldDescriptorBuilder FromField(const RFieldBase &field)
Make a new RFieldDescriptorBuilder based off a live NTuple field.
RResult< RFieldDescriptor > MakeDescriptor() const
Attempt to make a field descriptor.
RFieldDescriptorBuilder & FieldName(const std::string &fieldName)
RFieldDescriptorBuilder()=default
Make an empty dangling field descriptor.
RFieldDescriptorBuilder & TypeAlias(const std::string &typeAlias)
RFieldDescriptorBuilder & FieldDescription(const std::string &fieldDescription)
RResult< void > AttachColumn(DescriptorId_t fieldId, const RColumnDescriptor &columnDesc)
void BeginHeaderExtension()
Mark the beginning of the header extension; any fields and columns added after a call to this functio...
RResult< void > EnsureFieldExists(DescriptorId_t fieldId) const
RResult< void > AddFieldLink(DescriptorId_t fieldId, DescriptorId_t linkId)
RResult< void > EnsureValidDescriptor() const
Checks whether invariants hold:
RResult< void > AddCluster(RClusterDescriptor &&clusterDesc)
void SetNTuple(const std::string_view name, const std::string_view description)
RResult< void > AddClusterGroup(RClusterGroupDescriptor &&clusterGroup)
RResult< void > AddColumn(DescriptorId_t logicalId, DescriptorId_t physicalId, DescriptorId_t fieldId, const RColumnModel &model, std::uint32_t index, std::uint64_t firstElementIdx=0U)
void Reset()
Clears so-far stored clusters, fields, and columns and return to a pristine ntuple descriptor.
RResult< void > AddExtraTypeInfo(RExtraTypeInfoDescriptor &&extraTypeInfoDesc)
Records the parition of data into pages for a particular column in a particular cluster.
std::size_t ExtendToFitColumnRange(const RColumnRange &columnRange, const Internal::RColumnElementBase &element, std::size_t pageSize)
Extend this RPageRange to fit the given RColumnRange, i.e.
RPageInfoExtended Find(ClusterSize_t::ValueType idxInCluster) const
Find the page in the RPageRange that contains the given element. The element must exist.
Meta-data for a set of ntuple clusters.
std::unordered_map< DescriptorId_t, RPageRange > fPageRanges
NTupleSize_t fFirstEntryIndex
Clusters can be swapped by adjusting the entry offsets.
std::unordered_set< DescriptorId_t > GetColumnIds() const
std::unordered_map< DescriptorId_t, RColumnRange > fColumnRanges
bool operator==(const RClusterDescriptor &other) const
Clusters are bundled in cluster groups.
std::uint64_t fMinEntry
The minimum first entry number of the clusters in the cluster group.
std::uint64_t fEntrySpan
Number of entries that are (partially for sharded clusters) covered by this cluster group.
RClusterGroupDescriptor CloneSummary() const
std::uint32_t fNClusters
Number of clusters is always known even if the cluster IDs are not (yet) populated.
bool operator==(const RClusterGroupDescriptor &other) const
std::vector< DescriptorId_t > fClusterIds
The cluster IDs can be empty if the corresponding page list is not loaded.
Meta-data stored for every column of an ntuple.
DescriptorId_t fPhysicalColumnId
Usually identical to the logical column ID, except for alias columns where it references the shadowed...
DescriptorId_t fLogicalColumnId
The actual column identifier, which is the link to the corresponding field.
RColumnDescriptor Clone() const
Get a copy of the descriptor.
DescriptorId_t fFieldId
Every column belongs to one and only one field.
RColumnModel fModel
Contains the column type and whether it is sorted.
std::uint32_t fIndex
A field can be serialized into several columns, which are numbered from zero to $n$.
bool operator==(const RColumnDescriptor &other) const
Meta-data for a sets of columns; non-trivial column groups are used for sharded clusters.
std::unordered_set< DescriptorId_t > fPhysicalColumnIds
bool operator==(const RColumnGroupDescriptor &other) const
Holds the static meta-data of an RNTuple column.
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Field specific extra type information from the header / extenstion header.
bool operator==(const RExtraTypeInfoDescriptor &other) const
std::uint32_t fTypeVersionFrom
Extra type information restricted to a certain version range of the type.
EExtraTypeInfoIds fContentId
Specifies the meaning of the extra information.
std::string fTypeName
The type name the extra information refers to; empty for RNTuple-wide extra information.
A field translates read and write calls from/to underlying columns to/from tree values.
Definition RField.hxx:99
std::string GetFieldName() const
Definition RField.hxx:677
ENTupleStructure GetStructure() const
Definition RField.hxx:682
std::string GetTypeName() const
Definition RField.hxx:680
virtual std::uint32_t GetTypeVersion() const
Indicates an evolution of the C++ type itself.
Definition RField.hxx:709
virtual std::uint32_t GetFieldVersion() const
Indicates an evolution of the mapping scheme from C++ type to columns.
Definition RField.hxx:707
static RResult< std::unique_ptr< RFieldBase > > Create(const std::string &fieldName, const std::string &canonicalType, const std::string &typeAlias, bool fContinueOnError=false)
Factory method to resurrect a field from the stored on-disk type information.
Definition RField.cxx:626
std::string GetDescription() const
Get the field's description.
Definition RField.hxx:690
std::size_t GetNRepetitions() const
Definition RField.hxx:683
std::string GetTypeAlias() const
Definition RField.hxx:681
static RResult< void > EnsureValidFieldName(std::string_view fieldName)
Check whether a given string is a valid field name.
Definition RField.cxx:846
Meta-data stored for every field of an ntuple.
std::vector< DescriptorId_t > fLinkIds
The pointers in the other direction from parent to children.
std::unique_ptr< RFieldBase > CreateField(const RNTupleDescriptor &ntplDesc) const
In general, we create a field simply from the C++ type name.
std::uint32_t fTypeVersion
The version of the C++ type itself.
std::string fFieldDescription
Free text set by the user.
std::string fFieldName
The leaf name, not including parent fields.
std::uint32_t fFieldVersion
The version of the C++-type-to-column translation mechanics.
std::vector< DescriptorId_t > fLogicalColumnIds
The ordered list of columns attached to this field.
DescriptorId_t fParentId
Establishes sub field relationships, such as classes and collections.
RFieldDescriptor Clone() const
Get a copy of the descriptor.
bool operator==(const RFieldDescriptor &other) const
std::string fTypeAlias
A typedef or using directive that resolved to the type name during field creation.
ENTupleStructure fStructure
The structural information carried by this field in the data model tree.
std::string fTypeName
The C++ type that was used when writing the field.
std::uint64_t fNRepetitions
The number of elements per entry for fixed-size arrays.
RColumnDescriptorIterable(const RNTupleDescriptor &ntuple, const RFieldDescriptor &field)
std::vector< DescriptorId_t > GetTopLevelFields(const RNTupleDescriptor &desc) const
Return a vector containing the IDs of the top-level fields defined in the extension header.
The on-storage meta-data of an ntuple.
std::uint64_t fNPhysicalColumns
Updated by the descriptor builder when columns are added.
std::unordered_map< DescriptorId_t, RClusterDescriptor > fClusterDescriptors
May contain only a subset of all the available clusters, e.g.
std::uint64_t fGeneration
Once constructed by an RNTupleDescriptorBuilder, the descriptor is mostly immutable except for set of...
std::uint64_t fOnDiskFooterSize
Like fOnDiskHeaderSize, contains both cluster summaries and page locations.
std::uint64_t fNEntries
Updated by the descriptor builder when the cluster groups are added.
DescriptorId_t FindPhysicalColumnId(DescriptorId_t fieldId, std::uint32_t columnIndex) const
std::vector< RExtraTypeInfoDescriptor > fExtraTypeInfoDescriptors
NTupleSize_t GetNElements(DescriptorId_t physicalColumnId) const
DescriptorId_t FindLogicalColumnId(DescriptorId_t fieldId, std::uint32_t columnIndex) const
std::unordered_map< DescriptorId_t, RClusterGroupDescriptor > fClusterGroupDescriptors
DescriptorId_t FindNextClusterId(DescriptorId_t clusterId) const
DescriptorId_t FindPrevClusterId(DescriptorId_t clusterId) const
std::unordered_map< DescriptorId_t, RColumnDescriptor > fColumnDescriptors
std::unique_ptr< RNTupleDescriptor > Clone() const
DescriptorId_t FindClusterId(DescriptorId_t physicalColumnId, NTupleSize_t index) const
std::uint64_t fNClusters
Updated by the descriptor builder when the cluster groups are added.
std::string fName
The ntuple name needs to be unique in a given storage location (file)
RFieldDescriptorIterable GetTopLevelFields() const
std::unordered_map< DescriptorId_t, RFieldDescriptor > fFieldDescriptors
RFieldDescriptorIterable GetFieldIterable(const RFieldDescriptor &fieldDesc) const
DescriptorId_t GetFieldZeroId() const
Returns the logical parent of all top-level NTuple data fields.
std::uint64_t fOnDiskHeaderXxHash3
Set by the descriptor builder when deserialized.
bool operator==(const RNTupleDescriptor &other) const
std::string GetQualifiedFieldName(DescriptorId_t fieldId) const
Walks up the parents of the field ID and returns a field name of the form a.b.c.d In case of invalid ...
RResult< void > AddClusterGroupDetails(DescriptorId_t clusterGroupId, std::vector< RClusterDescriptor > &clusterDescs)
Methods to load and drop cluster group details (cluster IDs and page locations)
DescriptorId_t FindFieldId(std::string_view fieldName, DescriptorId_t parentId) const
const RFieldDescriptor & GetFieldDescriptor(DescriptorId_t fieldId) const
std::unique_ptr< RNTupleModel > CreateModel() const
Re-create the C++ model from the stored meta-data.
RResult< void > DropClusterGroupDetails(DescriptorId_t clusterGroupId)
std::unique_ptr< RHeaderExtension > fHeaderExtension
std::string fDescription
Free text from the user.
DescriptorId_t fFieldZeroId
Set by the descriptor builder.
const RHeaderExtension * GetHeaderExtension() const
Return header extension information; if the descriptor does not have a header extension,...
std::uint64_t fOnDiskHeaderSize
Set by the descriptor builder when deserialized.
std::vector< std::uint64_t > GetFeatureFlags() const
static std::unique_ptr< RNTupleModel > Create()
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
struct void * fTypeName
Definition cppyy.h:9
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
constexpr DescriptorId_t kInvalidDescriptorId
The window of element indexes of a particular column in a particular cluster.
ClusterSize_t fNElements
The number of column elements in the cluster.
We do not need to store the element size / uncompressed page size because we know to which column the...
std::uint32_t fNElements
The sum of the elements of all the pages must match the corresponding fNElements field in fColumnRang...
Wrap the integer in a struct in order to avoid template specialization clash with std::uint64_t.