Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-07 09:25:37

0001 /// \file ROOT/RNTupleDescriptor.hxx
0002 /// \ingroup NTuple
0003 /// \author Jakob Blomer <jblomer@cern.ch>
0004 /// \author Javier Lopez-Gomez <javier.lopez.gomez@cern.ch>
0005 /// \date 2018-07-19
0006 
0007 /*************************************************************************
0008  * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
0009  * All rights reserved.                                                  *
0010  *                                                                       *
0011  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0012  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0013  *************************************************************************/
0014 
0015 #ifndef ROOT_RNTupleDescriptor
0016 #define ROOT_RNTupleDescriptor
0017 
0018 #include <ROOT/RCreateFieldOptions.hxx>
0019 #include <ROOT/RError.hxx>
0020 #include <ROOT/RNTupleSerialize.hxx>
0021 #include <ROOT/RNTupleTypes.hxx>
0022 #include <ROOT/RSpan.hxx>
0023 
0024 #include <TError.h>
0025 
0026 #include <algorithm>
0027 #include <chrono>
0028 #include <cmath>
0029 #include <functional>
0030 #include <iterator>
0031 #include <map>
0032 #include <memory>
0033 #include <optional>
0034 #include <ostream>
0035 #include <vector>
0036 #include <set>
0037 #include <string>
0038 #include <string_view>
0039 #include <unordered_map>
0040 #include <unordered_set>
0041 
0042 namespace ROOT {
0043 
0044 class RFieldBase;
0045 class RNTupleModel;
0046 
0047 namespace Internal {
0048 class RColumnElementBase;
0049 }
0050 
0051 class RNTupleDescriptor;
0052 
0053 namespace Internal {
0054 class RColumnDescriptorBuilder;
0055 class RClusterDescriptorBuilder;
0056 class RClusterGroupDescriptorBuilder;
0057 class RExtraTypeInfoDescriptorBuilder;
0058 class RFieldDescriptorBuilder;
0059 class RNTupleDescriptorBuilder;
0060 
0061 RNTupleDescriptor CloneDescriptorSchema(const RNTupleDescriptor &desc);
0062 struct RNTupleClusterBoundaries {
0063    ROOT::NTupleSize_t fFirstEntry = kInvalidNTupleIndex;
0064    ROOT::NTupleSize_t fLastEntryPlusOne = kInvalidNTupleIndex;
0065 };
0066 
0067 std::vector<ROOT::Internal::RNTupleClusterBoundaries> GetClusterBoundaries(const RNTupleDescriptor &desc);
0068 } // namespace Internal
0069 
0070 namespace Experimental {
0071 
0072 // clang-format off
0073 /**
0074 \class ROOT::Experimental::RNTupleAttrSetDescriptor
0075 \ingroup NTuple
0076 \brief Metadata stored for every Attribute Set linked to an RNTuple.
0077 */
0078 // clang-format on
0079 class RNTupleAttrSetDescriptor final {
0080    friend class Experimental::Internal::RNTupleAttrSetDescriptorBuilder;
0081 
0082    std::uint16_t fSchemaVersionMajor = 0;
0083    std::uint16_t fSchemaVersionMinor = 0;
0084    std::uint32_t fAnchorLength = 0; ///< uncompressed size of the linked anchor
0085    // The locator of the AttributeSet anchor.
0086    // In case of kTypeFile, it points to the beginning of the Anchor's payload.
0087    // NOTE: Only kTypeFile is supported at the moment.
0088    RNTupleLocator fAnchorLocator;
0089    std::string fName;
0090 
0091 public:
0092    RNTupleAttrSetDescriptor() = default;
0093    RNTupleAttrSetDescriptor(const RNTupleAttrSetDescriptor &other) = delete;
0094    RNTupleAttrSetDescriptor &operator=(const RNTupleAttrSetDescriptor &other) = delete;
0095    RNTupleAttrSetDescriptor(RNTupleAttrSetDescriptor &&other) = default;
0096    RNTupleAttrSetDescriptor &operator=(RNTupleAttrSetDescriptor &&other) = default;
0097 
0098    bool operator==(const RNTupleAttrSetDescriptor &other) const;
0099    bool operator!=(const RNTupleAttrSetDescriptor &other) const { return !(*this == other); }
0100 
0101    const std::string &GetName() const { return fName; }
0102    std::uint16_t GetSchemaVersionMajor() const { return fSchemaVersionMajor; }
0103    std::uint16_t GetSchemaVersionMinor() const { return fSchemaVersionMinor; }
0104    std::uint32_t GetAnchorLength() const { return fAnchorLength; }
0105    const RNTupleLocator &GetAnchorLocator() const { return fAnchorLocator; }
0106 
0107    RNTupleAttrSetDescriptor Clone() const;
0108 };
0109 
0110 class RNTupleAttrSetDescriptorIterable;
0111 
0112 } // namespace Experimental
0113 
0114 // clang-format off
0115 /**
0116 \class ROOT::RFieldDescriptor
0117 \ingroup NTuple
0118 \brief Metadata stored for every field of an RNTuple
0119 */
0120 // clang-format on
0121 class RFieldDescriptor final {
0122    friend class Internal::RNTupleDescriptorBuilder;
0123    friend class Internal::RFieldDescriptorBuilder;
0124 
0125 private:
0126    ROOT::DescriptorId_t fFieldId = ROOT::kInvalidDescriptorId;
0127    /// The version of the C++-type-to-column translation mechanics
0128    std::uint32_t fFieldVersion = 0;
0129    /// The version of the C++ type itself
0130    std::uint32_t fTypeVersion = 0;
0131    /// The leaf name, not including parent fields
0132    std::string fFieldName;
0133    /// Free text set by the user
0134    std::string fFieldDescription;
0135    /// The C++ type that was used when writing the field
0136    std::string fTypeName;
0137    /// A typedef or using directive that resolved to the type name during field creation
0138    std::string fTypeAlias;
0139    /// The number of elements per entry for fixed-size arrays
0140    std::uint64_t fNRepetitions = 0;
0141    /// The structural information carried by this field in the data model tree
0142    ROOT::ENTupleStructure fStructure = ROOT::ENTupleStructure::kInvalid;
0143    /// Establishes sub field relationships, such as classes and collections
0144    ROOT::DescriptorId_t fParentId = ROOT::kInvalidDescriptorId;
0145    /// For projected fields, the source field ID
0146    ROOT::DescriptorId_t fProjectionSourceId = ROOT::kInvalidDescriptorId;
0147    /// The pointers in the other direction from parent to children. They are serialized, too, to keep the
0148    /// order of sub fields.
0149    std::vector<ROOT::DescriptorId_t> fLinkIds;
0150    /// The number of columns in the column representations of the field. The column cardinality helps to navigate the
0151    /// list of logical column ids. For example, the second column of the third column representation is
0152    /// fLogicalColumnIds[2 * fColumnCardinality + 1]
0153    std::uint32_t fColumnCardinality = 0;
0154    /// The ordered list of columns attached to this field: first by representation index then by column index.
0155    std::vector<ROOT::DescriptorId_t> fLogicalColumnIds;
0156    /// For custom classes, we store the ROOT TClass reported checksum to facilitate the use of I/O rules that
0157    /// identify types by their checksum
0158    std::optional<std::uint32_t> fTypeChecksum;
0159    /// Indicates if this is a collection that should be represented in memory by a SoA layout.
0160    bool fIsSoACollection = false;
0161 
0162 public:
0163    RFieldDescriptor() = default;
0164    RFieldDescriptor(const RFieldDescriptor &other) = delete;
0165    RFieldDescriptor &operator=(const RFieldDescriptor &other) = delete;
0166    RFieldDescriptor(RFieldDescriptor &&other) = default;
0167    RFieldDescriptor &operator=(RFieldDescriptor &&other) = default;
0168 
0169    bool operator==(const RFieldDescriptor &other) const;
0170    /// Get a copy of the descriptor
0171    RFieldDescriptor Clone() const;
0172 
0173    /// In general, we create a field simply from the C++ type name. For untyped fields, however, we potentially need
0174    /// access to sub fields, which is provided by the RNTupleDescriptor argument.
0175    std::unique_ptr<ROOT::RFieldBase>
0176    CreateField(const RNTupleDescriptor &ntplDesc, const ROOT::RCreateFieldOptions &options = {}) const;
0177 
0178    ROOT::DescriptorId_t GetId() const { return fFieldId; }
0179    std::uint32_t GetFieldVersion() const { return fFieldVersion; }
0180    std::uint32_t GetTypeVersion() const { return fTypeVersion; }
0181    const std::string &GetFieldName() const { return fFieldName; }
0182    const std::string &GetFieldDescription() const { return fFieldDescription; }
0183    const std::string &GetTypeName() const { return fTypeName; }
0184    const std::string &GetTypeAlias() const { return fTypeAlias; }
0185    std::uint64_t GetNRepetitions() const { return fNRepetitions; }
0186    ROOT::ENTupleStructure GetStructure() const { return fStructure; }
0187    ROOT::DescriptorId_t GetParentId() const { return fParentId; }
0188    ROOT::DescriptorId_t GetProjectionSourceId() const { return fProjectionSourceId; }
0189    const std::vector<ROOT::DescriptorId_t> &GetLinkIds() const { return fLinkIds; }
0190    const std::vector<ROOT::DescriptorId_t> &GetLogicalColumnIds() const { return fLogicalColumnIds; }
0191    std::uint32_t GetColumnCardinality() const { return fColumnCardinality; }
0192    std::optional<std::uint32_t> GetTypeChecksum() const { return fTypeChecksum; }
0193    bool IsProjectedField() const { return fProjectionSourceId != ROOT::kInvalidDescriptorId; }
0194    bool IsSoACollection() const { return fIsSoACollection; }
0195 
0196    bool IsCustomClass() const R__DEPRECATED(6, 42, "removed from public interface");
0197    bool IsCustomEnum(const RNTupleDescriptor &desc) const R__DEPRECATED(6, 42, "removed from public interface");
0198    bool IsStdAtomic() const R__DEPRECATED(6, 42, "removed from public interface");
0199 };
0200 
0201 // clang-format off
0202 /**
0203 \class ROOT::RColumnDescriptor
0204 \ingroup NTuple
0205 \brief Metadata stored for every column of an RNTuple
0206 */
0207 // clang-format on
0208 class RColumnDescriptor final {
0209    friend class Internal::RColumnDescriptorBuilder;
0210    friend class Internal::RNTupleDescriptorBuilder;
0211 
0212 public:
0213    struct RValueRange {
0214       double fMin = 0, fMax = 0;
0215 
0216       RValueRange() = default;
0217       RValueRange(double min, double max) : fMin(min), fMax(max) {}
0218       RValueRange(std::pair<double, double> range) : fMin(range.first), fMax(range.second) {}
0219 
0220       bool operator==(RValueRange other) const { return fMin == other.fMin && fMax == other.fMax; }
0221       bool operator!=(RValueRange other) const { return !(*this == other); }
0222    };
0223 
0224 private:
0225    /// The actual column identifier, which is the link to the corresponding field
0226    ROOT::DescriptorId_t fLogicalColumnId = ROOT::kInvalidDescriptorId;
0227    /// Usually identical to the logical column ID, except for alias columns where it references the shadowed column
0228    ROOT::DescriptorId_t fPhysicalColumnId = ROOT::kInvalidDescriptorId;
0229    /// Every column belongs to one and only one field
0230    ROOT::DescriptorId_t fFieldId = ROOT::kInvalidDescriptorId;
0231    /// The absolute value specifies the index for the first stored element for this column.
0232    /// For deferred columns the absolute value is larger than zero.
0233    /// Negative values specify a suppressed and deferred column.
0234    std::int64_t fFirstElementIndex = 0U;
0235    /// A field can be serialized into several columns, which are numbered from zero to $n$
0236    std::uint32_t fIndex = 0;
0237    /// A field may use multiple column representations, which are numbered from zero to $m$.
0238    /// Every representation has the same number of columns.
0239    std::uint16_t fRepresentationIndex = 0;
0240    /// The size in bits of elements of this column. Most columns have the size fixed by their type
0241    /// but low-precision float columns have variable bit widths.
0242    std::uint16_t fBitsOnStorage = 0;
0243    /// The on-disk column type
0244    ROOT::ENTupleColumnType fType = ROOT::ENTupleColumnType::kUnknown;
0245    /// Optional value range (used e.g. by quantized real fields)
0246    std::optional<RValueRange> fValueRange;
0247 
0248 public:
0249    RColumnDescriptor() = default;
0250    RColumnDescriptor(const RColumnDescriptor &other) = delete;
0251    RColumnDescriptor &operator=(const RColumnDescriptor &other) = delete;
0252    RColumnDescriptor(RColumnDescriptor &&other) = default;
0253    RColumnDescriptor &operator=(RColumnDescriptor &&other) = default;
0254 
0255    bool operator==(const RColumnDescriptor &other) const;
0256    /// Get a copy of the descriptor
0257    RColumnDescriptor Clone() const;
0258 
0259    ROOT::DescriptorId_t GetLogicalId() const { return fLogicalColumnId; }
0260    ROOT::DescriptorId_t GetPhysicalId() const { return fPhysicalColumnId; }
0261    ROOT::DescriptorId_t GetFieldId() const { return fFieldId; }
0262    std::uint32_t GetIndex() const { return fIndex; }
0263    std::uint16_t GetRepresentationIndex() const { return fRepresentationIndex; }
0264    std::uint64_t GetFirstElementIndex() const { return std::abs(fFirstElementIndex); }
0265    std::uint16_t GetBitsOnStorage() const { return fBitsOnStorage; }
0266    ROOT::ENTupleColumnType GetType() const { return fType; }
0267    std::optional<RValueRange> GetValueRange() const { return fValueRange; }
0268    bool IsAliasColumn() const { return fPhysicalColumnId != fLogicalColumnId; }
0269    bool IsDeferredColumn() const { return fFirstElementIndex != 0; }
0270    bool IsSuppressedDeferredColumn() const { return fFirstElementIndex < 0; }
0271 };
0272 
0273 // clang-format off
0274 /**
0275 \class ROOT::RClusterDescriptor
0276 \ingroup NTuple
0277 \brief Metadata for RNTuple clusters
0278 
0279 The cluster descriptor is built in two phases.  In a first phase, the descriptor has only an ID.
0280 In a second phase, the event range, column group, page locations and column ranges are added.
0281 Both phases are populated by the RClusterDescriptorBuilder.
0282 Clusters span across all available columns in the RNTuple.
0283 */
0284 // clang-format on
0285 class RClusterDescriptor final {
0286    friend class Internal::RClusterDescriptorBuilder;
0287 
0288 public:
0289    // clang-format off
0290    /**
0291    \class ROOT::RClusterDescriptor::RColumnRange
0292    \ingroup NTuple
0293    \brief The window of element indexes of a particular column in a particular cluster
0294    */
0295    // clang-format on
0296    class RColumnRange final {
0297       ROOT::DescriptorId_t fPhysicalColumnId = ROOT::kInvalidDescriptorId;
0298       /// The global index of the first column element in the cluster
0299       ROOT::NTupleSize_t fFirstElementIndex = ROOT::kInvalidNTupleIndex;
0300       /// The number of column elements in the cluster
0301       ROOT::NTupleSize_t fNElements = ROOT::kInvalidNTupleIndex;
0302       /// The usual format for ROOT compression settings (see Compression.h).
0303       /// The pages of a particular column in a particular cluster are all compressed with the same settings.
0304       /// If unset, the compression settings are undefined (deferred columns, suppressed columns).
0305       std::optional<std::uint32_t> fCompressionSettings;
0306       /// Suppressed columns have an empty page range and unknown compression settings.
0307       /// Their element index range, however, is aligned with the corresponding column of the
0308       /// primary column representation (see Section "Suppressed Columns" in the specification)
0309       bool fIsSuppressed = false;
0310 
0311       // TODO(jblomer): we perhaps want to store summary information, such as average, min/max, etc.
0312       // Should this be done on the field level?
0313 
0314    public:
0315       RColumnRange() = default;
0316 
0317       RColumnRange(ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t firstElementIndex,
0318                    ROOT::NTupleSize_t nElements, std::optional<std::uint32_t> compressionSettings,
0319                    bool suppressed = false)
0320          : fPhysicalColumnId(physicalColumnId),
0321            fFirstElementIndex(firstElementIndex),
0322            fNElements(nElements),
0323            fCompressionSettings(compressionSettings),
0324            fIsSuppressed(suppressed)
0325       {
0326       }
0327 
0328       ROOT::DescriptorId_t GetPhysicalColumnId() const { return fPhysicalColumnId; }
0329       void SetPhysicalColumnId(ROOT::DescriptorId_t id) { fPhysicalColumnId = id; }
0330 
0331       ROOT::NTupleSize_t GetFirstElementIndex() const { return fFirstElementIndex; }
0332       void SetFirstElementIndex(ROOT::NTupleSize_t idx) { fFirstElementIndex = idx; }
0333       void IncrementFirstElementIndex(ROOT::NTupleSize_t by) { fFirstElementIndex += by; }
0334 
0335       ROOT::NTupleSize_t GetNElements() const { return fNElements; }
0336       void SetNElements(ROOT::NTupleSize_t n) { fNElements = n; }
0337       void IncrementNElements(ROOT::NTupleSize_t by) { fNElements += by; }
0338 
0339       std::optional<std::uint32_t> GetCompressionSettings() const { return fCompressionSettings; }
0340       void SetCompressionSettings(std::optional<std::uint32_t> comp) { fCompressionSettings = comp; }
0341 
0342       bool IsSuppressed() const { return fIsSuppressed; }
0343       void SetIsSuppressed(bool suppressed) { fIsSuppressed = suppressed; }
0344 
0345       bool operator==(const RColumnRange &other) const
0346       {
0347          return fPhysicalColumnId == other.fPhysicalColumnId && fFirstElementIndex == other.fFirstElementIndex &&
0348                 fNElements == other.fNElements && fCompressionSettings == other.fCompressionSettings &&
0349                 fIsSuppressed == other.fIsSuppressed;
0350       }
0351 
0352       bool Contains(ROOT::NTupleSize_t index) const
0353       {
0354          return (fFirstElementIndex <= index && (fFirstElementIndex + fNElements) > index);
0355       }
0356    };
0357 
0358    // clang-format off
0359    /**
0360    \class ROOT::RClusterDescriptor::RPageInfo
0361    \ingroup NTuple
0362    \brief Information about a single page in the context of a cluster's page range.
0363    */
0364    // clang-format on
0365    // NOTE: We do not need to store the element size / uncompressed page size because we know to which column
0366    // the page belongs
0367    struct RPageInfo {
0368    private:
0369       /// The meaning of `fLocator` depends on the storage backend.
0370       RNTupleLocator fLocator;
0371       /// The sum of the elements of all the pages must match the corresponding `fNElements` field in `fColumnRanges`
0372       std::uint32_t fNElements = std::uint32_t(-1);
0373       /// If true, the 8 bytes following the serialized page are an xxhash of the on-disk page data
0374       bool fHasChecksum = false;
0375 
0376    public:
0377       RPageInfo() = default;
0378       RPageInfo(std::uint32_t nElements, const RNTupleLocator &locator, bool hasChecksum)
0379          : fLocator(locator), fNElements(nElements), fHasChecksum(hasChecksum)
0380       {
0381       }
0382 
0383       bool operator==(const RPageInfo &other) const
0384       {
0385          return fLocator == other.fLocator && fNElements == other.fNElements;
0386       }
0387 
0388       const RNTupleLocator &GetLocator() const { return fLocator; }
0389       RNTupleLocator &GetLocator() { return fLocator; }
0390       void SetLocator(const RNTupleLocator &locator) { fLocator = locator; }
0391 
0392       std::uint32_t GetNElements() const { return fNElements; }
0393       void SetNElements(std::uint32_t n) { fNElements = n; }
0394 
0395       bool HasChecksum() const { return fHasChecksum; }
0396       void SetHasChecksum(bool hasChecksum) { fHasChecksum = hasChecksum; }
0397    };
0398 
0399    // clang-format off
0400    /**
0401    \class ROOT::RClusterDescriptor::RPageInfoExtended
0402    \ingroup NTuple
0403    \brief Additional information about a page in an in-memory RPageRange.
0404 
0405    Used by RPageRange::Find() to return information relative to the RPageRange.  This information is not stored on disk
0406    and we don't need to keep it in memory because it can be easily recomputed.
0407    */
0408    // clang-format on
0409    struct RPageInfoExtended final : RPageInfo {
0410    private:
0411       /// Index (in cluster) of the first element in page.
0412       ROOT::NTupleSize_t fFirstElementIndex = 0;
0413       /// Page number in the corresponding RPageRange.
0414       ROOT::NTupleSize_t fPageNumber = 0;
0415 
0416    public:
0417       RPageInfoExtended() = default;
0418       RPageInfoExtended(const RPageInfo &pageInfo, ROOT::NTupleSize_t firstElementIndex, ROOT::NTupleSize_t pageNumber)
0419          : RPageInfo(pageInfo), fFirstElementIndex(firstElementIndex), fPageNumber(pageNumber)
0420       {
0421       }
0422 
0423       ROOT::NTupleSize_t GetFirstElementIndex() const { return fFirstElementIndex; }
0424       void SetFirstElementIndex(ROOT::NTupleSize_t firstInPage) { fFirstElementIndex = firstInPage; }
0425 
0426       ROOT::NTupleSize_t GetPageNumber() const { return fPageNumber; }
0427       void SetPageNumber(ROOT::NTupleSize_t pageNumber) { fPageNumber = pageNumber; }
0428    };
0429 
0430    // clang-format off
0431    /**
0432    \class ROOT::RClusterDescriptor::RPageRange
0433    \ingroup NTuple
0434    \brief Records the partition of data into pages for a particular column in a particular cluster
0435    */
0436    // clang-format on
0437    class RPageRange final {
0438       friend class Internal::RClusterDescriptorBuilder;
0439 
0440    private:
0441       /// \brief Extend this RPageRange to fit the given RColumnRange.
0442       ///
0443       /// To do so, prepend as many synthetic RPageInfos as needed to cover the range in `columnRange`.
0444       /// RPageInfos are constructed to contain as many elements of type `element` given a page size
0445       /// limit of `pageSize` (in bytes); the locator for the referenced pages is `kTypePageZero`.
0446       /// This function is used to make up RPageRanges for clusters that contain deferred columns.
0447       /// \return The number of column elements covered by the synthesized RPageInfos
0448       std::size_t ExtendToFitColumnRange(const RColumnRange &columnRange,
0449                                          const ROOT::Internal::RColumnElementBase &element, std::size_t pageSize);
0450 
0451       std::vector<RPageInfo> fPageInfos;
0452 
0453       /// Has the same length than fPageInfos and stores the sum of the number of elements of all the pages
0454       /// up to and including a given index. Used for binary search in Find().
0455       /// This vector is only created if fPageInfos has at least kLargeRangeThreshold elements.
0456       std::unique_ptr<std::vector<ROOT::NTupleSize_t>> fCumulativeNElements;
0457 
0458       ROOT::DescriptorId_t fPhysicalColumnId = ROOT::kInvalidDescriptorId;
0459 
0460    public:
0461       /// Create the fCumulativeNElements only when its needed, i.e. when there are many pages to search through.
0462       static constexpr std::size_t kLargeRangeThreshold = 10;
0463 
0464       RPageRange() = default;
0465       RPageRange(const RPageRange &other) = delete;
0466       RPageRange &operator=(const RPageRange &other) = delete;
0467       RPageRange(RPageRange &&other) = default;
0468       RPageRange &operator=(RPageRange &&other) = default;
0469 
0470       RPageRange Clone() const
0471       {
0472          RPageRange clone;
0473          clone.fPhysicalColumnId = fPhysicalColumnId;
0474          clone.fPageInfos = fPageInfos;
0475          if (fCumulativeNElements) {
0476             clone.fCumulativeNElements = std::make_unique<std::vector<ROOT::NTupleSize_t>>(*fCumulativeNElements);
0477          }
0478          return clone;
0479       }
0480 
0481       /// Find the page in the RPageRange that contains the given element. The element must exist.
0482       RPageInfoExtended Find(ROOT::NTupleSize_t idxInCluster) const;
0483 
0484       ROOT::DescriptorId_t GetPhysicalColumnId() const { return fPhysicalColumnId; }
0485       void SetPhysicalColumnId(ROOT::DescriptorId_t id) { fPhysicalColumnId = id; }
0486 
0487       const std::vector<RPageInfo> &GetPageInfos() const { return fPageInfos; }
0488       std::vector<RPageInfo> &GetPageInfos() { return fPageInfos; }
0489 
0490       bool operator==(const RPageRange &other) const
0491       {
0492          return fPhysicalColumnId == other.fPhysicalColumnId && fPageInfos == other.fPageInfos;
0493       }
0494    };
0495 
0496 private:
0497    ROOT::DescriptorId_t fClusterId = ROOT::kInvalidDescriptorId;
0498    /// Clusters can be swapped by adjusting the entry offsets of the cluster and all ranges
0499    ROOT::NTupleSize_t fFirstEntryIndex = ROOT::kInvalidNTupleIndex;
0500    ROOT::NTupleSize_t fNEntries = ROOT::kInvalidNTupleIndex;
0501 
0502    std::unordered_map<ROOT::DescriptorId_t, RColumnRange> fColumnRanges;
0503    std::unordered_map<ROOT::DescriptorId_t, RPageRange> fPageRanges;
0504 
0505 public:
0506    class RColumnRangeIterable;
0507 
0508    RClusterDescriptor() = default;
0509    RClusterDescriptor(const RClusterDescriptor &other) = delete;
0510    RClusterDescriptor &operator=(const RClusterDescriptor &other) = delete;
0511    RClusterDescriptor(RClusterDescriptor &&other) = default;
0512    RClusterDescriptor &operator=(RClusterDescriptor &&other) = default;
0513 
0514    RClusterDescriptor Clone() const;
0515 
0516    bool operator==(const RClusterDescriptor &other) const;
0517 
0518    ROOT::DescriptorId_t GetId() const { return fClusterId; }
0519    ROOT::NTupleSize_t GetFirstEntryIndex() const { return fFirstEntryIndex; }
0520    ROOT::NTupleSize_t GetNEntries() const { return fNEntries; }
0521    const RColumnRange &GetColumnRange(ROOT::DescriptorId_t physicalId) const { return fColumnRanges.at(physicalId); }
0522    const RPageRange &GetPageRange(ROOT::DescriptorId_t physicalId) const { return fPageRanges.at(physicalId); }
0523    /// Returns an iterator over pairs { columnId, columnRange }. The iteration order is unspecified.
0524    RColumnRangeIterable GetColumnRangeIterable() const;
0525    bool ContainsColumn(ROOT::DescriptorId_t physicalId) const
0526    {
0527       return fColumnRanges.find(physicalId) != fColumnRanges.end();
0528    }
0529    std::uint64_t GetNBytesOnStorage() const;
0530 };
0531 
0532 class RClusterDescriptor::RColumnRangeIterable final {
0533 private:
0534    const RClusterDescriptor &fDesc;
0535 
0536 public:
0537    class RIterator final {
0538    private:
0539       using Iter_t = std::unordered_map<ROOT::DescriptorId_t, RColumnRange>::const_iterator;
0540       /// The wrapped map iterator
0541       Iter_t fIter;
0542 
0543    public:
0544       using iterator_category = std::forward_iterator_tag;
0545       using iterator = RIterator;
0546       using value_type = RColumnRange;
0547       using difference_type = std::ptrdiff_t;
0548       using pointer = const RColumnRange *;
0549       using reference = const RColumnRange &;
0550 
0551       RIterator(Iter_t iter) : fIter(iter) {}
0552       iterator &operator++() /* prefix */
0553       {
0554          ++fIter;
0555          return *this;
0556       }
0557       iterator operator++(int) /* postfix */
0558       {
0559          auto old = *this;
0560          operator++();
0561          return old;
0562       }
0563       reference operator*() const { return fIter->second; }
0564       pointer operator->() const { return &fIter->second; }
0565       bool operator!=(const iterator &rh) const { return fIter != rh.fIter; }
0566       bool operator==(const iterator &rh) const { return fIter == rh.fIter; }
0567    };
0568 
0569    explicit RColumnRangeIterable(const RClusterDescriptor &desc) : fDesc(desc) {}
0570 
0571    RIterator begin() { return RIterator{fDesc.fColumnRanges.cbegin()}; }
0572    RIterator end() { return RIterator{fDesc.fColumnRanges.cend()}; }
0573    size_t size() { return fDesc.fColumnRanges.size(); }
0574 };
0575 
0576 // clang-format off
0577 /**
0578 \class ROOT::RClusterGroupDescriptor
0579 \ingroup NTuple
0580 \brief Clusters are bundled in cluster groups.
0581 
0582 Very large RNTuples can contain multiple cluster groups to organize cluster metadata.
0583 Every RNTuple has at least one cluster group.  The clusters in a cluster group are ordered
0584 corresponding to their first entry number.
0585 */
0586 // clang-format on
0587 class RClusterGroupDescriptor final {
0588    friend class Internal::RClusterGroupDescriptorBuilder;
0589 
0590 private:
0591    ROOT::DescriptorId_t fClusterGroupId = ROOT::kInvalidDescriptorId;
0592    /// The cluster IDs can be empty if the corresponding page list is not loaded.
0593    /// Otherwise, cluster ids are sorted by first entry number.
0594    std::vector<ROOT::DescriptorId_t> fClusterIds;
0595    /// The page list that corresponds to the cluster group
0596    RNTupleLocator fPageListLocator;
0597    /// Uncompressed size of the page list
0598    std::uint64_t fPageListLength = 0;
0599    /// The minimum first entry number of the clusters in the cluster group
0600    std::uint64_t fMinEntry = 0;
0601    /// Number of entries that are (partially for sharded clusters) covered by this cluster group.
0602    std::uint64_t fEntrySpan = 0;
0603    /// Number of clusters is always known even if the cluster IDs are not (yet) populated
0604    std::uint32_t fNClusters = 0;
0605 
0606 public:
0607    RClusterGroupDescriptor() = default;
0608    RClusterGroupDescriptor(const RClusterGroupDescriptor &other) = delete;
0609    RClusterGroupDescriptor &operator=(const RClusterGroupDescriptor &other) = delete;
0610    RClusterGroupDescriptor(RClusterGroupDescriptor &&other) = default;
0611    RClusterGroupDescriptor &operator=(RClusterGroupDescriptor &&other) = default;
0612 
0613    RClusterGroupDescriptor Clone() const;
0614    /// Creates a clone without the cluster IDs
0615    RClusterGroupDescriptor CloneSummary() const;
0616 
0617    bool operator==(const RClusterGroupDescriptor &other) const;
0618 
0619    ROOT::DescriptorId_t GetId() const { return fClusterGroupId; }
0620    std::uint32_t GetNClusters() const { return fNClusters; }
0621    RNTupleLocator GetPageListLocator() const { return fPageListLocator; }
0622    std::uint64_t GetPageListLength() const { return fPageListLength; }
0623    const std::vector<ROOT::DescriptorId_t> &GetClusterIds() const { return fClusterIds; }
0624    std::uint64_t GetMinEntry() const { return fMinEntry; }
0625    std::uint64_t GetEntrySpan() const { return fEntrySpan; }
0626    /// A cluster group is loaded in two stages. Stage one loads only the summary information.
0627    /// Stage two loads the list of cluster IDs.
0628    bool HasClusterDetails() const { return !fClusterIds.empty(); }
0629 };
0630 
0631 /// Used in RExtraTypeInfoDescriptor
0632 enum class EExtraTypeInfoIds {
0633    kInvalid,
0634    kStreamerInfo
0635 };
0636 
0637 // clang-format off
0638 /**
0639 \class ROOT::RExtraTypeInfoDescriptor
0640 \ingroup NTuple
0641 \brief Field specific extra type information from the header / extenstion header
0642 
0643 Currently only used by streamer fields to store RNTuple-wide list of streamer info records.
0644 */
0645 // clang-format on
0646 class RExtraTypeInfoDescriptor final {
0647    friend class Internal::RExtraTypeInfoDescriptorBuilder;
0648 
0649 private:
0650    /// Specifies the meaning of the extra information
0651    EExtraTypeInfoIds fContentId = EExtraTypeInfoIds::kInvalid;
0652    /// Type version the extra type information is bound to
0653    std::uint32_t fTypeVersion = 0;
0654    /// The type name the extra information refers to; empty for RNTuple-wide extra information
0655    std::string fTypeName;
0656    /// The content format depends on the content ID and may be binary
0657    std::string fContent;
0658 
0659 public:
0660    RExtraTypeInfoDescriptor() = default;
0661    RExtraTypeInfoDescriptor(const RExtraTypeInfoDescriptor &other) = delete;
0662    RExtraTypeInfoDescriptor &operator=(const RExtraTypeInfoDescriptor &other) = delete;
0663    RExtraTypeInfoDescriptor(RExtraTypeInfoDescriptor &&other) = default;
0664    RExtraTypeInfoDescriptor &operator=(RExtraTypeInfoDescriptor &&other) = default;
0665 
0666    bool operator==(const RExtraTypeInfoDescriptor &other) const;
0667 
0668    RExtraTypeInfoDescriptor Clone() const;
0669 
0670    EExtraTypeInfoIds GetContentId() const { return fContentId; }
0671    std::uint32_t GetTypeVersion() const { return fTypeVersion; }
0672    const std::string &GetTypeName() const { return fTypeName; }
0673    const std::string &GetContent() const { return fContent; }
0674 };
0675 
0676 namespace Internal {
0677 // Used by the RNTupleReader to activate/deactivate entries. Needs to adapt when we have sharded clusters.
0678 ROOT::DescriptorId_t CallFindClusterIdOn(const ROOT::RNTupleDescriptor &desc, ROOT::NTupleSize_t entryIdx);
0679 } // namespace Internal
0680 
0681 // clang-format off
0682 /**
0683 \class ROOT::RNTupleDescriptor
0684 \ingroup NTuple
0685 \brief The on-storage metadata of an RNTuple
0686 
0687 Represents the on-disk (on storage) information about an RNTuple. The metadata consists of a header, a footer, and
0688 potentially multiple page lists.
0689 The header carries the RNTuple schema, i.e. the fields and the associated columns and their relationships.
0690 The footer carries information about one or several cluster groups and links to their page lists.
0691 For every cluster group, a page list envelope stores cluster summaries and page locations.
0692 For every cluster, it stores for every column the range of element indexes as well as a list of pages and page
0693 locations.
0694 
0695 The descriptor provides machine-independent (de-)serialization of headers and footers, and it provides lookup routines
0696 for RNTuple objects (pages, clusters, ...).  It is supposed to be usable by all RPageStorage implementations.
0697 
0698 The serialization does not use standard ROOT streamers in order to not let it depend on libCore. The serialization uses
0699 the concept of envelopes and frames: header, footer, and page list envelopes have a preamble with a type ID and length.
0700 Substructures are serialized in frames and have a size and number of items (for list frames). This allows for forward
0701 and backward compatibility when the metadata evolves.
0702 */
0703 // clang-format on
0704 class RNTupleDescriptor final {
0705    friend class Internal::RNTupleDescriptorBuilder;
0706    friend RNTupleDescriptor Internal::CloneDescriptorSchema(const RNTupleDescriptor &desc);
0707    friend DescriptorId_t Internal::CallFindClusterIdOn(const RNTupleDescriptor &desc, NTupleSize_t entryIdx);
0708 
0709 public:
0710    class RHeaderExtension;
0711 
0712 private:
0713    /// The RNTuple name needs to be unique in a given storage location (file)
0714    std::string fName;
0715    /// Free text from the user
0716    std::string fDescription;
0717 
0718    ROOT::DescriptorId_t fFieldZeroId = ROOT::kInvalidDescriptorId; ///< Set by the descriptor builder
0719 
0720    std::uint64_t fNPhysicalColumns = 0; ///< Updated by the descriptor builder when columns are added
0721 
0722    std::set<unsigned int> fFeatureFlags;
0723    std::unordered_map<ROOT::DescriptorId_t, RFieldDescriptor> fFieldDescriptors;
0724    std::unordered_map<ROOT::DescriptorId_t, RColumnDescriptor> fColumnDescriptors;
0725 
0726    std::vector<RExtraTypeInfoDescriptor> fExtraTypeInfoDescriptors;
0727    std::unique_ptr<RHeaderExtension> fHeaderExtension;
0728 
0729    //// All fields above are part of the schema and are cloned when creating a new descriptor from a given one
0730    //// (see CloneSchema())
0731 
0732    std::uint16_t fVersionEpoch = 0; ///< Set by the descriptor builder when deserialized
0733    std::uint16_t fVersionMajor = 0; ///< Set by the descriptor builder when deserialized
0734    std::uint16_t fVersionMinor = 0; ///< Set by the descriptor builder when deserialized
0735    std::uint16_t fVersionPatch = 0; ///< Set by the descriptor builder when deserialized
0736 
0737    std::uint64_t fOnDiskHeaderSize = 0;    ///< Set by the descriptor builder when deserialized
0738    std::uint64_t fOnDiskHeaderXxHash3 = 0; ///< Set by the descriptor builder when deserialized
0739    std::uint64_t fOnDiskFooterSize = 0; ///< Like fOnDiskHeaderSize, contains both cluster summaries and page locations
0740 
0741    std::uint64_t fNEntries = 0;  ///< Updated by the descriptor builder when the cluster groups are added
0742    std::uint64_t fNClusters = 0; ///< Updated by the descriptor builder when the cluster groups are added
0743 
0744    /// \brief The generation of the descriptor
0745    ///
0746    /// Once constructed by an RNTupleDescriptorBuilder, the descriptor is mostly immutable except for the set of
0747    /// active page locations.  During the lifetime of the descriptor, page location information for clusters
0748    /// can be added or removed.  When this happens, the generation should be increased, so that users of the
0749    /// descriptor know that the information changed.  The generation is increased, e.g., by the page source's
0750    /// exclusive lock guard around the descriptor.  It is used, e.g., by the descriptor cache in RNTupleReader.
0751    std::uint64_t fGeneration = 0;
0752 
0753    std::unordered_map<ROOT::DescriptorId_t, RClusterGroupDescriptor> fClusterGroupDescriptors;
0754    /// References cluster groups sorted by entry range and thus allows for binary search.
0755    /// Note that this list is empty during the descriptor building process and will only be
0756    /// created when the final descriptor is extracted from the builder.
0757    std::vector<ROOT::DescriptorId_t> fSortedClusterGroupIds;
0758    /// Potentially a subset of all the available clusters
0759    std::unordered_map<ROOT::DescriptorId_t, RClusterDescriptor> fClusterDescriptors;
0760    /// List of AttributeSets linked to this RNTuple
0761    std::vector<Experimental::RNTupleAttrSetDescriptor> fAttributeSets;
0762 
0763    // We don't expose this publicly because when we add sharded clusters, this interface does not make sense anymore
0764    ROOT::DescriptorId_t FindClusterId(ROOT::NTupleSize_t entryIdx) const;
0765 
0766    /// Creates a descriptor containing only the schema information about this RNTuple, i.e. all the information needed
0767    /// to create a new RNTuple with the same schema as this one but not necessarily the same clustering. This is used
0768    /// when merging two RNTuples.
0769    RNTupleDescriptor CloneSchema() const;
0770 
0771 public:
0772    /// All known feature flags.
0773    /// Note that the flag values represent the bit _index_, not the already-bitshifted integer.
0774    enum EFeatureFlags {
0775       /// Signals that the RNTuple contains at least one deferred column that is part of a collection and was extended
0776       /// (i.e. it appears in the footer). This can happen when merging two RNTuples that have the same collection field
0777       /// backed by columns with different encoding, e.g. a vector<float> whose elements are represented by SplitReal32
0778       /// in the first ntuple and by Real32 in the second.
0779       /// Added in version 1.1.0.0 of the binary format.
0780       kFeatureFlag_NestedDeferredColumns = 0,
0781       // Insert new feature flags here, with contiguous values. If at any point a "hole" appears in the valid feature
0782       // flags values, the check in RNTupleSerialize must be updated.
0783 
0784       // End of regular feature flags
0785       kFeatureFlag_COUNT,
0786 
0787       /// Reserved for forward-compatibility testing
0788       kFeatureFlag_Test = 137
0789    };
0790 
0791    class RColumnDescriptorIterable;
0792    class RFieldDescriptorIterable;
0793    class RClusterGroupDescriptorIterable;
0794    class RClusterDescriptorIterable;
0795    class RExtraTypeInfoDescriptorIterable;
0796    friend class Experimental::RNTupleAttrSetDescriptorIterable;
0797 
0798    /// Modifiers passed to CreateModel()
0799    struct RCreateModelOptions {
0800    private:
0801       /// If set to true, projected fields will be reconstructed as such. This will prevent the model to be used
0802       /// with an RNTupleReader, but it is useful, e.g., to accurately merge data.
0803       bool fReconstructProjections = false;
0804       /// By default, creating a model will fail if any of the reconstructed fields contains an unknown column type
0805       /// or an unknown field structural role.
0806       /// If this option is enabled, the model will be created and all fields containing unknown data (directly
0807       /// or indirectly) will be skipped instead.
0808       bool fForwardCompatible = false;
0809       /// If true, the model will be created without a default entry (bare model).
0810       bool fCreateBare = false;
0811       /// If true, fields with a user defined type that have no available dictionaries will be reconstructed
0812       /// as record fields from the on-disk information; otherwise, they will cause an error.
0813       bool fEmulateUnknownTypes = false;
0814 
0815    public:
0816       RCreateModelOptions() {} // Work around compiler bug, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88165
0817 
0818       void SetReconstructProjections(bool v) { fReconstructProjections = v; }
0819       bool GetReconstructProjections() const { return fReconstructProjections; }
0820 
0821       void SetForwardCompatible(bool v) { fForwardCompatible = v; }
0822       bool GetForwardCompatible() const { return fForwardCompatible; }
0823 
0824       void SetCreateBare(bool v) { fCreateBare = v; }
0825       bool GetCreateBare() const { return fCreateBare; }
0826 
0827       void SetEmulateUnknownTypes(bool v) { fEmulateUnknownTypes = v; }
0828       bool GetEmulateUnknownTypes() const { return fEmulateUnknownTypes; }
0829    };
0830 
0831    RNTupleDescriptor() = default;
0832    RNTupleDescriptor(const RNTupleDescriptor &other) = delete;
0833    RNTupleDescriptor &operator=(const RNTupleDescriptor &other) = delete;
0834    RNTupleDescriptor(RNTupleDescriptor &&other) = default;
0835    RNTupleDescriptor &operator=(RNTupleDescriptor &&other) = default;
0836 
0837    RNTupleDescriptor Clone() const;
0838 
0839    bool operator==(const RNTupleDescriptor &other) const;
0840 
0841    std::uint64_t GetOnDiskHeaderXxHash3() const { return fOnDiskHeaderXxHash3; }
0842    std::uint64_t GetOnDiskHeaderSize() const { return fOnDiskHeaderSize; }
0843    std::uint64_t GetOnDiskFooterSize() const { return fOnDiskFooterSize; }
0844    /// \see ROOT::RNTuple::GetCurrentVersion()
0845    std::uint64_t GetVersion() const
0846    {
0847       return (static_cast<std::uint64_t>(fVersionEpoch) << 48) | (static_cast<std::uint64_t>(fVersionMajor) << 32) |
0848              (static_cast<std::uint64_t>(fVersionMinor) << 16) | (static_cast<std::uint64_t>(fVersionPatch));
0849    }
0850 
0851    const RFieldDescriptor &GetFieldDescriptor(ROOT::DescriptorId_t fieldId) const
0852    {
0853       return fFieldDescriptors.at(fieldId);
0854    }
0855    const RColumnDescriptor &GetColumnDescriptor(ROOT::DescriptorId_t columnId) const
0856    {
0857       return fColumnDescriptors.at(columnId);
0858    }
0859    const RClusterGroupDescriptor &GetClusterGroupDescriptor(ROOT::DescriptorId_t clusterGroupId) const
0860    {
0861       return fClusterGroupDescriptors.at(clusterGroupId);
0862    }
0863    const RClusterDescriptor &GetClusterDescriptor(ROOT::DescriptorId_t clusterId) const
0864    {
0865       return fClusterDescriptors.at(clusterId);
0866    }
0867 
0868    RFieldDescriptorIterable GetFieldIterable(const RFieldDescriptor &fieldDesc) const;
0869    RFieldDescriptorIterable
0870    GetFieldIterable(const RFieldDescriptor &fieldDesc,
0871                     const std::function<bool(ROOT::DescriptorId_t, ROOT::DescriptorId_t)> &comparator) const;
0872    RFieldDescriptorIterable GetFieldIterable(ROOT::DescriptorId_t fieldId) const;
0873    RFieldDescriptorIterable
0874    GetFieldIterable(ROOT::DescriptorId_t fieldId,
0875                     const std::function<bool(ROOT::DescriptorId_t, ROOT::DescriptorId_t)> &comparator) const;
0876 
0877    RFieldDescriptorIterable GetTopLevelFields() const;
0878    RFieldDescriptorIterable
0879    GetTopLevelFields(const std::function<bool(ROOT::DescriptorId_t, ROOT::DescriptorId_t)> &comparator) const;
0880 
0881    RColumnDescriptorIterable GetColumnIterable() const;
0882    RColumnDescriptorIterable GetColumnIterable(const RFieldDescriptor &fieldDesc) const;
0883    RColumnDescriptorIterable GetColumnIterable(ROOT::DescriptorId_t fieldId) const;
0884 
0885    RClusterGroupDescriptorIterable GetClusterGroupIterable() const;
0886 
0887    RClusterDescriptorIterable GetClusterIterable() const;
0888 
0889    RExtraTypeInfoDescriptorIterable GetExtraTypeInfoIterable() const;
0890 
0891    ROOT::Experimental::RNTupleAttrSetDescriptorIterable GetAttrSetIterable() const;
0892 
0893    const std::string &GetName() const { return fName; }
0894    const std::string &GetDescription() const { return fDescription; }
0895 
0896    std::size_t GetNFields() const { return fFieldDescriptors.size(); }
0897    std::size_t GetNLogicalColumns() const { return fColumnDescriptors.size(); }
0898    std::size_t GetNPhysicalColumns() const { return fNPhysicalColumns; }
0899    std::size_t GetNClusterGroups() const { return fClusterGroupDescriptors.size(); }
0900    std::size_t GetNClusters() const { return fNClusters; }
0901    std::size_t GetNActiveClusters() const { return fClusterDescriptors.size(); }
0902    std::size_t GetNExtraTypeInfos() const { return fExtraTypeInfoDescriptors.size(); }
0903    std::size_t GetNAttributeSets() const { return fAttributeSets.size(); }
0904 
0905    /// We know the number of entries from adding the cluster summaries
0906    ROOT::NTupleSize_t GetNEntries() const { return fNEntries; }
0907    ROOT::NTupleSize_t GetNElements(ROOT::DescriptorId_t physicalColumnId) const;
0908 
0909    /// Returns the logical parent of all top-level RNTuple data fields.
0910    ROOT::DescriptorId_t GetFieldZeroId() const { return fFieldZeroId; }
0911    const RFieldDescriptor &GetFieldZero() const { return GetFieldDescriptor(GetFieldZeroId()); }
0912    ROOT::DescriptorId_t FindFieldId(std::string_view fieldName, ROOT::DescriptorId_t parentId) const;
0913    /// Searches for a top-level field
0914    ROOT::DescriptorId_t FindFieldId(std::string_view fieldName) const;
0915    ROOT::DescriptorId_t FindLogicalColumnId(ROOT::DescriptorId_t fieldId, std::uint32_t columnIndex,
0916                                             std::uint16_t representationIndex) const;
0917    ROOT::DescriptorId_t FindPhysicalColumnId(ROOT::DescriptorId_t fieldId, std::uint32_t columnIndex,
0918                                              std::uint16_t representationIndex) const;
0919    ROOT::DescriptorId_t FindClusterId(ROOT::DescriptorId_t physicalColumnId, ROOT::NTupleSize_t index) const;
0920    ROOT::DescriptorId_t FindNextClusterId(ROOT::DescriptorId_t clusterId) const;
0921    ROOT::DescriptorId_t FindPrevClusterId(ROOT::DescriptorId_t clusterId) const;
0922 
0923    /// Walks up the parents of the field ID and returns a field name of the form a.b.c.d
0924    /// In case of invalid field ID, an empty string is returned.
0925    std::string GetQualifiedFieldName(ROOT::DescriptorId_t fieldId) const;
0926 
0927    /// Adjust the type name of the passed RFieldDescriptor for comparison with another renormalized type name.
0928    std::string GetTypeNameForComparison(const RFieldDescriptor &fieldDesc) const;
0929 
0930    bool HasFeature(unsigned int flag) const { return fFeatureFlags.count(flag) > 0; }
0931    std::vector<std::uint64_t> GetFeatureFlags() const;
0932 
0933    /// Return header extension information; if the descriptor does not have a header extension, return `nullptr`
0934    const RHeaderExtension *GetHeaderExtension() const { return fHeaderExtension.get(); }
0935 
0936    /// Methods to load and drop cluster group details (cluster IDs and page locations)
0937    RResult<void>
0938    AddClusterGroupDetails(ROOT::DescriptorId_t clusterGroupId, std::vector<RClusterDescriptor> &clusterDescs);
0939    RResult<void> DropClusterGroupDetails(ROOT::DescriptorId_t clusterGroupId);
0940 
0941    std::uint64_t GetGeneration() const { return fGeneration; }
0942    void IncGeneration() { fGeneration++; }
0943 
0944    /// Re-create the C++ model from the stored metadata
0945    std::unique_ptr<ROOT::RNTupleModel> CreateModel(const RCreateModelOptions &options = RCreateModelOptions()) const;
0946    void PrintInfo(std::ostream &output) const;
0947 };
0948 
0949 // clang-format off
0950 /**
0951 \class ROOT::RNTupleDescriptor::RColumnDescriptorIterable
0952 \ingroup NTuple
0953 \brief Used to loop over a field's associated columns
0954 */
0955 // clang-format on
0956 class RNTupleDescriptor::RColumnDescriptorIterable final {
0957 private:
0958    /// The associated RNTuple for this range.
0959    const RNTupleDescriptor &fNTuple;
0960    /// The descriptor ids of the columns ordered by field, representation, and column index
0961    std::vector<ROOT::DescriptorId_t> fColumns = {};
0962 
0963 public:
0964    class RIterator final {
0965    private:
0966       /// The enclosing range's RNTuple.
0967       const RNTupleDescriptor &fNTuple;
0968       /// The enclosing range's descriptor id list.
0969       const std::vector<ROOT::DescriptorId_t> &fColumns;
0970       std::size_t fIndex = 0;
0971 
0972    public:
0973       using iterator_category = std::forward_iterator_tag;
0974       using iterator = RIterator;
0975       using value_type = RFieldDescriptor;
0976       using difference_type = std::ptrdiff_t;
0977       using pointer = const RColumnDescriptor *;
0978       using reference = const RColumnDescriptor &;
0979 
0980       RIterator(const RNTupleDescriptor &ntuple, const std::vector<ROOT::DescriptorId_t> &columns, std::size_t index)
0981          : fNTuple(ntuple), fColumns(columns), fIndex(index)
0982       {
0983       }
0984       iterator &operator++() /* prefix */
0985       {
0986          ++fIndex;
0987          return *this;
0988       }
0989       iterator operator++(int) /* postfix */
0990       {
0991          auto old = *this;
0992          operator++();
0993          return old;
0994       }
0995       reference operator*() const { return fNTuple.GetColumnDescriptor(fColumns.at(fIndex)); }
0996       pointer operator->() const { return &fNTuple.GetColumnDescriptor(fColumns.at(fIndex)); }
0997       bool operator!=(const iterator &rh) const { return fIndex != rh.fIndex; }
0998       bool operator==(const iterator &rh) const { return fIndex == rh.fIndex; }
0999    };
1000 
1001    RColumnDescriptorIterable(const RNTupleDescriptor &ntuple, const RFieldDescriptor &fieldDesc);
1002    RColumnDescriptorIterable(const RNTupleDescriptor &ntuple);
1003 
1004    RIterator begin() { return RIterator(fNTuple, fColumns, 0); }
1005    RIterator end() { return RIterator(fNTuple, fColumns, fColumns.size()); }
1006    size_t size() { return fColumns.size(); }
1007 };
1008 
1009 // clang-format off
1010 /**
1011 \class ROOT::RNTupleDescriptor::RFieldDescriptorIterable
1012 \ingroup NTuple
1013 \brief Used to loop over a field's child fields
1014 */
1015 // clang-format on
1016 class RNTupleDescriptor::RFieldDescriptorIterable final {
1017 private:
1018    /// The associated RNTuple for this range.
1019    const RNTupleDescriptor &fNTuple;
1020    /// The descriptor IDs of the child fields. These may be sorted using
1021    /// a comparison function.
1022    std::vector<ROOT::DescriptorId_t> fFieldChildren = {};
1023 
1024 public:
1025    class RIterator final {
1026    private:
1027       /// The enclosing range's RNTuple.
1028       const RNTupleDescriptor &fNTuple;
1029       /// The enclosing range's descriptor id list.
1030       const std::vector<ROOT::DescriptorId_t> &fFieldChildren;
1031       std::size_t fIndex = 0;
1032 
1033    public:
1034       using iterator_category = std::forward_iterator_tag;
1035       using iterator = RIterator;
1036       using value_type = RFieldDescriptor;
1037       using difference_type = std::ptrdiff_t;
1038       using pointer = const RFieldDescriptor *;
1039       using reference = const RFieldDescriptor &;
1040 
1041       RIterator(const RNTupleDescriptor &ntuple, const std::vector<ROOT::DescriptorId_t> &fieldChildren,
1042                 std::size_t index)
1043          : fNTuple(ntuple), fFieldChildren(fieldChildren), fIndex(index)
1044       {
1045       }
1046       iterator &operator++() /* prefix */
1047       {
1048          ++fIndex;
1049          return *this;
1050       }
1051       iterator operator++(int) /* postfix */
1052       {
1053          auto old = *this;
1054          operator++();
1055          return old;
1056       }
1057       reference operator*() const { return fNTuple.GetFieldDescriptor(fFieldChildren.at(fIndex)); }
1058       pointer operator->() const { return &fNTuple.GetFieldDescriptor(fFieldChildren.at(fIndex)); }
1059       bool operator!=(const iterator &rh) const { return fIndex != rh.fIndex; }
1060       bool operator==(const iterator &rh) const { return fIndex == rh.fIndex; }
1061    };
1062    RFieldDescriptorIterable(const RNTupleDescriptor &ntuple, const RFieldDescriptor &field)
1063       : fNTuple(ntuple), fFieldChildren(field.GetLinkIds())
1064    {
1065    }
1066    /// Sort the range using an arbitrary comparison function.
1067    RFieldDescriptorIterable(const RNTupleDescriptor &ntuple, const RFieldDescriptor &field,
1068                             const std::function<bool(ROOT::DescriptorId_t, ROOT::DescriptorId_t)> &comparator)
1069       : fNTuple(ntuple), fFieldChildren(field.GetLinkIds())
1070    {
1071       std::sort(fFieldChildren.begin(), fFieldChildren.end(), comparator);
1072    }
1073    RIterator begin() { return RIterator(fNTuple, fFieldChildren, 0); }
1074    RIterator end() { return RIterator(fNTuple, fFieldChildren, fFieldChildren.size()); }
1075 };
1076 
1077 // clang-format off
1078 /**
1079 \class ROOT::RNTupleDescriptor::RClusterGroupDescriptorIterable
1080 \ingroup NTuple
1081 \brief Used to loop over all the cluster groups of an RNTuple (in unspecified order)
1082 
1083 Enumerate all cluster group IDs from the descriptor.  No specific order can be assumed.
1084 */
1085 // clang-format on
1086 class RNTupleDescriptor::RClusterGroupDescriptorIterable final {
1087 private:
1088    /// The associated RNTuple for this range.
1089    const RNTupleDescriptor &fNTuple;
1090 
1091 public:
1092    class RIterator final {
1093    private:
1094       using Iter_t = std::unordered_map<ROOT::DescriptorId_t, RClusterGroupDescriptor>::const_iterator;
1095       /// The wrapped map iterator
1096       Iter_t fIter;
1097 
1098    public:
1099       using iterator_category = std::forward_iterator_tag;
1100       using iterator = RIterator;
1101       using value_type = RClusterGroupDescriptor;
1102       using difference_type = std::ptrdiff_t;
1103       using pointer = const RClusterGroupDescriptor *;
1104       using reference = const RClusterGroupDescriptor &;
1105 
1106       RIterator(Iter_t iter) : fIter(iter) {}
1107       iterator &operator++() /* prefix */
1108       {
1109          ++fIter;
1110          return *this;
1111       }
1112       iterator operator++(int) /* postfix */
1113       {
1114          auto old = *this;
1115          operator++();
1116          return old;
1117       }
1118       reference operator*() const { return fIter->second; }
1119       pointer operator->() const { return &fIter->second; }
1120       bool operator!=(const iterator &rh) const { return fIter != rh.fIter; }
1121       bool operator==(const iterator &rh) const { return fIter == rh.fIter; }
1122    };
1123 
1124    RClusterGroupDescriptorIterable(const RNTupleDescriptor &ntuple) : fNTuple(ntuple) {}
1125    RIterator begin() { return RIterator(fNTuple.fClusterGroupDescriptors.cbegin()); }
1126    RIterator end() { return RIterator(fNTuple.fClusterGroupDescriptors.cend()); }
1127 };
1128 
1129 // clang-format off
1130 /**
1131 \class ROOT::RNTupleDescriptor::RClusterDescriptorIterable
1132 \ingroup NTuple
1133 \brief Used to loop over all the clusters of an RNTuple (in unspecified order)
1134 
1135 Enumerate all cluster IDs from all cluster descriptors.  No specific order can be assumed, use
1136 RNTupleDescriptor::FindNextClusterId() and RNTupleDescriptor::FindPrevClusterId() to traverse
1137 clusters by entry number.
1138 */
1139 // clang-format on
1140 class RNTupleDescriptor::RClusterDescriptorIterable final {
1141 private:
1142    /// The associated RNTuple for this range.
1143    const RNTupleDescriptor &fNTuple;
1144 
1145 public:
1146    class RIterator final {
1147    private:
1148       using Iter_t = std::unordered_map<ROOT::DescriptorId_t, RClusterDescriptor>::const_iterator;
1149       /// The wrapped map iterator
1150       Iter_t fIter;
1151 
1152    public:
1153       using iterator_category = std::forward_iterator_tag;
1154       using iterator = RIterator;
1155       using value_type = RClusterDescriptor;
1156       using difference_type = std::ptrdiff_t;
1157       using pointer = const RClusterDescriptor *;
1158       using reference = const RClusterDescriptor &;
1159 
1160       RIterator(Iter_t iter) : fIter(iter) {}
1161       iterator &operator++() /* prefix */
1162       {
1163          ++fIter;
1164          return *this;
1165       }
1166       iterator operator++(int) /* postfix */
1167       {
1168          auto old = *this;
1169          operator++();
1170          return old;
1171       }
1172       reference operator*() const { return fIter->second; }
1173       pointer operator->() const { return &fIter->second; }
1174       bool operator!=(const iterator &rh) const { return fIter != rh.fIter; }
1175       bool operator==(const iterator &rh) const { return fIter == rh.fIter; }
1176    };
1177 
1178    RClusterDescriptorIterable(const RNTupleDescriptor &ntuple) : fNTuple(ntuple) {}
1179    RIterator begin() { return RIterator(fNTuple.fClusterDescriptors.cbegin()); }
1180    RIterator end() { return RIterator(fNTuple.fClusterDescriptors.cend()); }
1181 };
1182 
1183 // clang-format off
1184 /**
1185 \class ROOT::RNTupleDescriptor::RExtraTypeInfoDescriptorIterable
1186 \ingroup NTuple
1187 \brief Used to loop over all the extra type info record of an RNTuple (in unspecified order)
1188 */
1189 // clang-format on
1190 class RNTupleDescriptor::RExtraTypeInfoDescriptorIterable final {
1191 private:
1192    /// The associated RNTuple for this range.
1193    const RNTupleDescriptor &fNTuple;
1194 
1195 public:
1196    class RIterator final {
1197    private:
1198       using Iter_t = std::vector<RExtraTypeInfoDescriptor>::const_iterator;
1199       /// The wrapped vector iterator
1200       Iter_t fIter;
1201 
1202    public:
1203       using iterator_category = std::forward_iterator_tag;
1204       using iterator = RIterator;
1205       using value_type = RExtraTypeInfoDescriptor;
1206       using difference_type = std::ptrdiff_t;
1207       using pointer = const RExtraTypeInfoDescriptor *;
1208       using reference = const RExtraTypeInfoDescriptor &;
1209 
1210       RIterator(Iter_t iter) : fIter(iter) {}
1211       iterator &operator++() /* prefix */
1212       {
1213          ++fIter;
1214          return *this;
1215       }
1216       iterator operator++(int) /* postfix */
1217       {
1218          auto old = *this;
1219          operator++();
1220          return old;
1221       }
1222       reference operator*() const { return *fIter; }
1223       pointer operator->() const { return &*fIter; }
1224       bool operator!=(const iterator &rh) const { return fIter != rh.fIter; }
1225       bool operator==(const iterator &rh) const { return fIter == rh.fIter; }
1226    };
1227 
1228    RExtraTypeInfoDescriptorIterable(const RNTupleDescriptor &ntuple) : fNTuple(ntuple) {}
1229    RIterator begin() { return RIterator(fNTuple.fExtraTypeInfoDescriptors.cbegin()); }
1230    RIterator end() { return RIterator(fNTuple.fExtraTypeInfoDescriptors.cend()); }
1231 };
1232 
1233 namespace Experimental {
1234 // clang-format off
1235 /**
1236 \class ROOT::Experimental::RNTupleAttrSetDescriptorIterable
1237 \ingroup NTuple
1238 \brief Used to loop over all the Attribute Sets linked to an RNTuple
1239 */
1240 // clang-format on
1241 // TODO: move this to RNTupleDescriptor::RNTupleAttrSetDescriptorIterable when it moves out of Experimental.
1242 class RNTupleAttrSetDescriptorIterable final {
1243 private:
1244    /// The associated RNTuple for this range.
1245    const RNTupleDescriptor &fNTuple;
1246 
1247 public:
1248    class RIterator final {
1249    private:
1250       using Iter_t = std::vector<RNTupleAttrSetDescriptor>::const_iterator;
1251       /// The wrapped vector iterator
1252       Iter_t fIter;
1253 
1254    public:
1255       using iterator_category = std::forward_iterator_tag;
1256       using iterator = RIterator;
1257       using value_type = RNTupleAttrSetDescriptor;
1258       using difference_type = std::ptrdiff_t;
1259       using pointer = const value_type *;
1260       using reference = const value_type &;
1261 
1262       RIterator(Iter_t iter) : fIter(iter) {}
1263       iterator &operator++() /* prefix */
1264       {
1265          ++fIter;
1266          return *this;
1267       }
1268       iterator operator++(int) /* postfix */
1269       {
1270          auto old = *this;
1271          operator++();
1272          return old;
1273       }
1274       reference operator*() const { return *fIter; }
1275       pointer operator->() const { return &*fIter; }
1276       bool operator!=(const iterator &rh) const { return fIter != rh.fIter; }
1277       bool operator==(const iterator &rh) const { return fIter == rh.fIter; }
1278    };
1279 
1280    RNTupleAttrSetDescriptorIterable(const RNTupleDescriptor &ntuple) : fNTuple(ntuple) {}
1281    RIterator begin() { return RIterator(fNTuple.fAttributeSets.cbegin()); }
1282    RIterator end() { return RIterator(fNTuple.fAttributeSets.cend()); }
1283 };
1284 } // namespace Experimental
1285 
1286 // clang-format off
1287 /**
1288 \class ROOT::RNTupleDescriptor::RHeaderExtension
1289 \ingroup NTuple
1290 \brief Summarizes information about fields and the corresponding columns that were added after the header has been serialized
1291 */
1292 // clang-format on
1293 class RNTupleDescriptor::RHeaderExtension final {
1294    friend class Internal::RNTupleDescriptorBuilder;
1295 
1296 private:
1297    /// All field IDs of late model extensions, in the order of field addition. This is necessary to serialize the
1298    /// the fields in that order.
1299    std::vector<ROOT::DescriptorId_t> fFieldIdsOrder;
1300    /// All field IDs of late model extensions for efficient lookup. When a column gets added to the extension
1301    /// header, this enables us to determine if the column belongs to a field of the header extension of if it
1302    /// belongs to a field of the regular header that gets extended by additional column representations.
1303    std::unordered_set<ROOT::DescriptorId_t> fFieldIdsLookup;
1304    /// All logical column IDs of columns that extend, with additional column representations, fields of the regular
1305    /// header. During serialization, these columns are not picked up as columns of `fFieldIdsOrder`. But instead
1306    /// these columns need to be serialized in the extension header without re-serializing the field.
1307    std::vector<ROOT::DescriptorId_t> fExtendedColumnRepresentations;
1308    /// Number of logical and physical columns; updated by the descriptor builder when columns are added
1309    std::uint32_t fNLogicalColumns = 0;
1310    std::uint32_t fNPhysicalColumns = 0;
1311 
1312    /// Marks `fieldDesc` as an extended field, i.e. a field that appears in the Header Extension (e.g. having been added
1313    /// through late model extension). Note that the field descriptor should also have been added to the RNTuple
1314    /// Descriptor alongside non-extended fields.
1315    void MarkExtendedField(const RFieldDescriptor &fieldDesc)
1316    {
1317       fFieldIdsOrder.emplace_back(fieldDesc.GetId());
1318       fFieldIdsLookup.insert(fieldDesc.GetId());
1319    }
1320 
1321    /// Marks `columnDesc` as an extended column, i.e. a column that appears in the Header Extension (e.g. having been
1322    /// added through late model extension as an additional representation of an existing column). Note that the column
1323    /// descriptor should also have been added to the RNTuple Descriptor alongside non-extended columns.
1324    void MarkExtendedColumn(const RColumnDescriptor &columnDesc)
1325    {
1326       fNLogicalColumns++;
1327       if (!columnDesc.IsAliasColumn())
1328          fNPhysicalColumns++;
1329       if (fFieldIdsLookup.count(columnDesc.GetFieldId()) == 0) {
1330          fExtendedColumnRepresentations.emplace_back(columnDesc.GetLogicalId());
1331       }
1332    }
1333 
1334 public:
1335    std::size_t GetNFields() const { return fFieldIdsOrder.size(); }
1336    std::size_t GetNLogicalColumns() const { return fNLogicalColumns; }
1337    std::size_t GetNPhysicalColumns() const { return fNPhysicalColumns; }
1338    const std::vector<ROOT::DescriptorId_t> &GetExtendedColumnRepresentations() const
1339    {
1340       return fExtendedColumnRepresentations;
1341    }
1342    /// Return a vector containing the IDs of the top-level fields defined in the extension header, in the order
1343    /// of their addition. Note that these fields are not necessarily top-level fields in the overall schema.
1344    /// If a nested field is extended, it will return the top-most field of the extended subtree.
1345    /// We cannot create this vector when building the fFields because at the time when AddExtendedField is called,
1346    /// the field is not yet linked into the schema tree.
1347    std::vector<ROOT::DescriptorId_t> GetTopMostFields(const RNTupleDescriptor &desc) const;
1348 
1349    bool ContainsField(ROOT::DescriptorId_t fieldId) const
1350    {
1351       return fFieldIdsLookup.find(fieldId) != fFieldIdsLookup.end();
1352    }
1353    bool ContainsExtendedColumnRepresentation(ROOT::DescriptorId_t columnId) const
1354    {
1355       return std::find(fExtendedColumnRepresentations.begin(), fExtendedColumnRepresentations.end(), columnId) !=
1356              fExtendedColumnRepresentations.end();
1357    }
1358 };
1359 
1360 namespace Experimental::Internal {
1361 class RNTupleAttrSetDescriptorBuilder final {
1362    ROOT::Experimental::RNTupleAttrSetDescriptor fDesc;
1363 
1364 public:
1365    RNTupleAttrSetDescriptorBuilder &Name(std::string_view name)
1366    {
1367       fDesc.fName = name;
1368       return *this;
1369    }
1370    RNTupleAttrSetDescriptorBuilder &SchemaVersion(std::uint16_t major, std::uint16_t minor)
1371    {
1372       fDesc.fSchemaVersionMajor = major;
1373       fDesc.fSchemaVersionMinor = minor;
1374       return *this;
1375    }
1376    RNTupleAttrSetDescriptorBuilder &AnchorLocator(const RNTupleLocator &loc)
1377    {
1378       fDesc.fAnchorLocator = loc;
1379       return *this;
1380    }
1381    RNTupleAttrSetDescriptorBuilder &AnchorLength(std::uint32_t length)
1382    {
1383       fDesc.fAnchorLength = length;
1384       return *this;
1385    }
1386 
1387    /// Attempt to make an AttributeSet descriptor. This may fail if the builder
1388    /// was not given enough information to make a proper descriptor.
1389    RResult<ROOT::Experimental::RNTupleAttrSetDescriptor> MoveDescriptor();
1390 };
1391 } // namespace Experimental::Internal
1392 
1393 namespace Internal {
1394 
1395 // clang-format off
1396 /**
1397 \class ROOT::Internal::RColumnDescriptorBuilder
1398 \ingroup NTuple
1399 \brief A helper class for piece-wise construction of an RColumnDescriptor
1400 
1401 Dangling column descriptors can become actual descriptors when added to an
1402 RNTupleDescriptorBuilder instance and then linked to their fields.
1403 */
1404 // clang-format on
1405 class RColumnDescriptorBuilder final {
1406 private:
1407    RColumnDescriptor fColumn = RColumnDescriptor();
1408 
1409 public:
1410    /// Make an empty column descriptor builder.
1411    RColumnDescriptorBuilder() = default;
1412 
1413    RColumnDescriptorBuilder &LogicalColumnId(ROOT::DescriptorId_t logicalColumnId)
1414    {
1415       fColumn.fLogicalColumnId = logicalColumnId;
1416       return *this;
1417    }
1418    RColumnDescriptorBuilder &PhysicalColumnId(ROOT::DescriptorId_t physicalColumnId)
1419    {
1420       fColumn.fPhysicalColumnId = physicalColumnId;
1421       return *this;
1422    }
1423    RColumnDescriptorBuilder &BitsOnStorage(std::uint16_t bitsOnStorage)
1424    {
1425       fColumn.fBitsOnStorage = bitsOnStorage;
1426       return *this;
1427    }
1428    RColumnDescriptorBuilder &Type(ROOT::ENTupleColumnType type)
1429    {
1430       fColumn.fType = type;
1431       return *this;
1432    }
1433    RColumnDescriptorBuilder &FieldId(ROOT::DescriptorId_t fieldId)
1434    {
1435       fColumn.fFieldId = fieldId;
1436       return *this;
1437    }
1438    RColumnDescriptorBuilder &Index(std::uint32_t index)
1439    {
1440       fColumn.fIndex = index;
1441       return *this;
1442    }
1443    RColumnDescriptorBuilder &FirstElementIndex(std::uint64_t firstElementIdx)
1444    {
1445       fColumn.fFirstElementIndex = firstElementIdx;
1446       return *this;
1447    }
1448    RColumnDescriptorBuilder &SetSuppressedDeferred()
1449    {
1450       R__ASSERT(fColumn.fFirstElementIndex != 0);
1451       if (fColumn.fFirstElementIndex > 0)
1452          fColumn.fFirstElementIndex = -fColumn.fFirstElementIndex;
1453       return *this;
1454    }
1455    RColumnDescriptorBuilder &RepresentationIndex(std::uint16_t representationIndex)
1456    {
1457       fColumn.fRepresentationIndex = representationIndex;
1458       return *this;
1459    }
1460    RColumnDescriptorBuilder &ValueRange(double min, double max)
1461    {
1462       fColumn.fValueRange = {min, max};
1463       return *this;
1464    }
1465    RColumnDescriptorBuilder &ValueRange(std::optional<RColumnDescriptor::RValueRange> valueRange)
1466    {
1467       fColumn.fValueRange = valueRange;
1468       return *this;
1469    }
1470    ROOT::DescriptorId_t GetFieldId() const { return fColumn.fFieldId; }
1471    ROOT::DescriptorId_t GetRepresentationIndex() const { return fColumn.fRepresentationIndex; }
1472    /// Attempt to make a column descriptor. This may fail if the column
1473    /// was not given enough information to make a proper descriptor.
1474    RResult<RColumnDescriptor> MakeDescriptor() const;
1475 };
1476 
1477 // clang-format off
1478 /**
1479 \class ROOT::Internal::RFieldDescriptorBuilder
1480 \ingroup NTuple
1481 \brief A helper class for piece-wise construction of an RFieldDescriptor
1482 
1483 Dangling field descriptors describe a single field in isolation. They are
1484 missing the necessary relationship information (parent field, any child fields)
1485 required to describe a real RNTuple field.
1486 
1487 Dangling field descriptors can only become actual descriptors when added to an
1488 RNTupleDescriptorBuilder instance and then linked to other fields.
1489 */
1490 // clang-format on
1491 class RFieldDescriptorBuilder final {
1492 private:
1493    RFieldDescriptor fField = RFieldDescriptor();
1494 
1495 public:
1496    /// Make an empty dangling field descriptor.
1497    RFieldDescriptorBuilder() = default;
1498 
1499    /// Make a new RFieldDescriptorBuilder based off a live RNTuple field.
1500    static RFieldDescriptorBuilder FromField(const ROOT::RFieldBase &field);
1501 
1502    RFieldDescriptorBuilder &FieldId(ROOT::DescriptorId_t fieldId)
1503    {
1504       fField.fFieldId = fieldId;
1505       return *this;
1506    }
1507    RFieldDescriptorBuilder &FieldVersion(std::uint32_t fieldVersion)
1508    {
1509       fField.fFieldVersion = fieldVersion;
1510       return *this;
1511    }
1512    RFieldDescriptorBuilder &TypeVersion(std::uint32_t typeVersion)
1513    {
1514       fField.fTypeVersion = typeVersion;
1515       return *this;
1516    }
1517    RFieldDescriptorBuilder &ParentId(ROOT::DescriptorId_t id)
1518    {
1519       fField.fParentId = id;
1520       return *this;
1521    }
1522    RFieldDescriptorBuilder &ProjectionSourceId(ROOT::DescriptorId_t id)
1523    {
1524       fField.fProjectionSourceId = id;
1525       return *this;
1526    }
1527    RFieldDescriptorBuilder &FieldName(const std::string &fieldName)
1528    {
1529       fField.fFieldName = fieldName;
1530       return *this;
1531    }
1532    RFieldDescriptorBuilder &FieldDescription(const std::string &fieldDescription)
1533    {
1534       fField.fFieldDescription = fieldDescription;
1535       return *this;
1536    }
1537    RFieldDescriptorBuilder &TypeName(const std::string &typeName)
1538    {
1539       fField.fTypeName = typeName;
1540       return *this;
1541    }
1542    RFieldDescriptorBuilder &TypeAlias(const std::string &typeAlias)
1543    {
1544       fField.fTypeAlias = typeAlias;
1545       return *this;
1546    }
1547    RFieldDescriptorBuilder &NRepetitions(std::uint64_t nRepetitions)
1548    {
1549       fField.fNRepetitions = nRepetitions;
1550       return *this;
1551    }
1552    RFieldDescriptorBuilder &Structure(const ROOT::ENTupleStructure &structure)
1553    {
1554       fField.fStructure = structure;
1555       return *this;
1556    }
1557    RFieldDescriptorBuilder &TypeChecksum(const std::optional<std::uint32_t> typeChecksum)
1558    {
1559       fField.fTypeChecksum = typeChecksum;
1560       return *this;
1561    }
1562    RFieldDescriptorBuilder &IsSoACollection(bool val)
1563    {
1564       fField.fIsSoACollection = val;
1565       return *this;
1566    }
1567    ROOT::DescriptorId_t GetParentId() const { return fField.fParentId; }
1568    /// Attempt to make a field descriptor. This may fail if the dangling field
1569    /// was not given enough information to make a proper descriptor.
1570    RResult<RFieldDescriptor> MakeDescriptor() const;
1571 };
1572 
1573 // clang-format off
1574 /**
1575 \class ROOT::Internal::RClusterDescriptorBuilder
1576 \ingroup NTuple
1577 \brief A helper class for piece-wise construction of an RClusterDescriptor
1578 
1579 The cluster descriptor builder starts from a summary-only cluster descriptor and allows for the
1580 piecewise addition of page locations.
1581 */
1582 // clang-format on
1583 class RClusterDescriptorBuilder final {
1584 private:
1585    RClusterDescriptor fCluster;
1586 
1587 public:
1588    RClusterDescriptorBuilder &ClusterId(ROOT::DescriptorId_t clusterId)
1589    {
1590       fCluster.fClusterId = clusterId;
1591       return *this;
1592    }
1593 
1594    RClusterDescriptorBuilder &FirstEntryIndex(std::uint64_t firstEntryIndex)
1595    {
1596       fCluster.fFirstEntryIndex = firstEntryIndex;
1597       return *this;
1598    }
1599 
1600    RClusterDescriptorBuilder &NEntries(std::uint64_t nEntries)
1601    {
1602       fCluster.fNEntries = nEntries;
1603       return *this;
1604    }
1605 
1606    RResult<void> CommitColumnRange(ROOT::DescriptorId_t physicalId, std::uint64_t firstElementIndex,
1607                                    std::uint32_t compressionSettings, const RClusterDescriptor::RPageRange &pageRange);
1608 
1609    /// Books the given column ID as being suppressed in this cluster. The correct first element index and number of
1610    /// elements need to be set by CommitSuppressedColumnRanges() once all the calls to CommitColumnRange() and
1611    /// MarkSuppressedColumnRange() took place.
1612    RResult<void> MarkSuppressedColumnRange(ROOT::DescriptorId_t physicalId);
1613 
1614    /// Sets the first element index and number of elements for all the suppressed column ranges.
1615    /// The information is taken from the corresponding columns from the primary representation.
1616    /// Needs to be called when all the columns (suppressed and regular) where added.
1617    RResult<void> CommitSuppressedColumnRanges(const RNTupleDescriptor &desc);
1618 
1619    /// Add column and page ranges for columns created during late model extension missing in this cluster.  The locator
1620    /// type for the synthesized page ranges is `kTypePageZero`.  All the page sources must be able to populate the
1621    /// 'zero' page from such locator. Any call to CommitColumnRange() and CommitSuppressedColumnRanges()
1622    /// should happen before calling this function.
1623    RClusterDescriptorBuilder &AddExtendedColumnRanges(const RNTupleDescriptor &desc);
1624 
1625    const RClusterDescriptor::RColumnRange &GetColumnRange(ROOT::DescriptorId_t physicalId)
1626    {
1627       return fCluster.GetColumnRange(physicalId);
1628    }
1629 
1630    /// Move out the full cluster descriptor including page locations
1631    RResult<RClusterDescriptor> MoveDescriptor();
1632 };
1633 
1634 // clang-format off
1635 /**
1636 \class ROOT::Internal::RClusterGroupDescriptorBuilder
1637 \ingroup NTuple
1638 \brief A helper class for piece-wise construction of an RClusterGroupDescriptor
1639 */
1640 // clang-format on
1641 class RClusterGroupDescriptorBuilder final {
1642 private:
1643    RClusterGroupDescriptor fClusterGroup;
1644 
1645 public:
1646    RClusterGroupDescriptorBuilder() = default;
1647    static RClusterGroupDescriptorBuilder FromSummary(const RClusterGroupDescriptor &clusterGroupDesc);
1648 
1649    RClusterGroupDescriptorBuilder &ClusterGroupId(ROOT::DescriptorId_t clusterGroupId)
1650    {
1651       fClusterGroup.fClusterGroupId = clusterGroupId;
1652       return *this;
1653    }
1654    RClusterGroupDescriptorBuilder &PageListLocator(const RNTupleLocator &pageListLocator)
1655    {
1656       fClusterGroup.fPageListLocator = pageListLocator;
1657       return *this;
1658    }
1659    RClusterGroupDescriptorBuilder &PageListLength(std::uint64_t pageListLength)
1660    {
1661       fClusterGroup.fPageListLength = pageListLength;
1662       return *this;
1663    }
1664    RClusterGroupDescriptorBuilder &MinEntry(std::uint64_t minEntry)
1665    {
1666       fClusterGroup.fMinEntry = minEntry;
1667       return *this;
1668    }
1669    RClusterGroupDescriptorBuilder &EntrySpan(std::uint64_t entrySpan)
1670    {
1671       fClusterGroup.fEntrySpan = entrySpan;
1672       return *this;
1673    }
1674    RClusterGroupDescriptorBuilder &NClusters(std::uint32_t nClusters)
1675    {
1676       fClusterGroup.fNClusters = nClusters;
1677       return *this;
1678    }
1679    void AddSortedClusters(const std::vector<ROOT::DescriptorId_t> &clusterIds)
1680    {
1681       if (clusterIds.size() != fClusterGroup.GetNClusters())
1682          throw RException(R__FAIL("mismatch of number of clusters"));
1683       fClusterGroup.fClusterIds = clusterIds;
1684    }
1685 
1686    RResult<RClusterGroupDescriptor> MoveDescriptor();
1687 };
1688 
1689 // clang-format off
1690 /**
1691 \class ROOT::Internal::RExtraTypeInfoDescriptorBuilder
1692 \ingroup NTuple
1693 \brief A helper class for piece-wise construction of an RExtraTypeInfoDescriptor
1694 */
1695 // clang-format on
1696 class RExtraTypeInfoDescriptorBuilder final {
1697 private:
1698    RExtraTypeInfoDescriptor fExtraTypeInfo;
1699 
1700 public:
1701    RExtraTypeInfoDescriptorBuilder() = default;
1702 
1703    RExtraTypeInfoDescriptorBuilder &ContentId(EExtraTypeInfoIds contentId)
1704    {
1705       fExtraTypeInfo.fContentId = contentId;
1706       return *this;
1707    }
1708    RExtraTypeInfoDescriptorBuilder &TypeVersion(std::uint32_t typeVersion)
1709    {
1710       fExtraTypeInfo.fTypeVersion = typeVersion;
1711       return *this;
1712    }
1713    RExtraTypeInfoDescriptorBuilder &TypeName(const std::string &typeName)
1714    {
1715       fExtraTypeInfo.fTypeName = typeName;
1716       return *this;
1717    }
1718    RExtraTypeInfoDescriptorBuilder &Content(const std::string &content)
1719    {
1720       fExtraTypeInfo.fContent = content;
1721       return *this;
1722    }
1723 
1724    RResult<RExtraTypeInfoDescriptor> MoveDescriptor();
1725 };
1726 
1727 // clang-format off
1728 /**
1729 \class ROOT::Internal::RNTupleDescriptorBuilder
1730 \ingroup NTuple
1731 \brief A helper class for piece-wise construction of an RNTupleDescriptor
1732 
1733 Used by RPageStorage implementations in order to construct the RNTupleDescriptor from the various header parts.
1734 */
1735 // clang-format on
1736 class RNTupleDescriptorBuilder final {
1737 private:
1738    RNTupleDescriptor fDescriptor;
1739    RResult<void> EnsureFieldExists(ROOT::DescriptorId_t fieldId) const;
1740 
1741 public:
1742    /// Checks whether invariants hold:
1743    /// * RNTuple epoch is valid
1744    /// * RNTuple name is valid
1745    /// * Fields have valid parents
1746    /// * Number of columns is constant across column representations
1747    RResult<void> EnsureValidDescriptor() const;
1748    const RNTupleDescriptor &GetDescriptor() const { return fDescriptor; }
1749    RNTupleDescriptor MoveDescriptor();
1750 
1751    /// Copies the "schema" part of `descriptor` into the builder's descriptor.
1752    /// This resets the builder's descriptor.
1753    void SetSchemaFromExisting(const RNTupleDescriptor &descriptor);
1754 
1755    void SetVersion(std::uint16_t versionEpoch, std::uint16_t versionMajor, std::uint16_t versionMinor,
1756                    std::uint16_t versionPatch);
1757    void SetVersionForWriting();
1758 
1759    void SetNTuple(const std::string_view name, const std::string_view description);
1760    void SetFeature(unsigned int flag);
1761 
1762    void SetOnDiskHeaderXxHash3(std::uint64_t xxhash3) { fDescriptor.fOnDiskHeaderXxHash3 = xxhash3; }
1763    void SetOnDiskHeaderSize(std::uint64_t size) { fDescriptor.fOnDiskHeaderSize = size; }
1764    /// The real footer size also include the page list envelopes
1765    void AddToOnDiskFooterSize(std::uint64_t size) { fDescriptor.fOnDiskFooterSize += size; }
1766 
1767    void AddField(const RFieldDescriptor &fieldDesc);
1768    RResult<void> AddFieldLink(ROOT::DescriptorId_t fieldId, ROOT::DescriptorId_t linkId);
1769    RResult<void> AddFieldProjection(ROOT::DescriptorId_t sourceId, ROOT::DescriptorId_t targetId);
1770 
1771    // The field that the column belongs to has to be already available. For fields with multiple columns,
1772    // the columns need to be added in order of the column index
1773    RResult<void> AddColumn(RColumnDescriptor &&columnDesc);
1774 
1775    RResult<void> AddClusterGroup(RClusterGroupDescriptor &&clusterGroup);
1776    RResult<void> AddCluster(RClusterDescriptor &&clusterDesc);
1777 
1778    RResult<void> AddExtraTypeInfo(RExtraTypeInfoDescriptor &&extraTypeInfoDesc);
1779    void ReplaceExtraTypeInfo(RExtraTypeInfoDescriptor &&extraTypeInfoDesc);
1780 
1781    RResult<void> AddAttributeSet(Experimental::RNTupleAttrSetDescriptor &&attrSetDesc);
1782 
1783    /// Mark the beginning of the header extension; any fields and columns added after a call to this function are
1784    /// annotated as begin part of the header extension.
1785    void BeginHeaderExtension();
1786 
1787    /// \brief Shift column IDs of alias columns by `offset`
1788    ///
1789    /// If the descriptor is constructed in pieces consisting of physical and alias columns
1790    /// (regular and projected fields), the natural column order would be
1791    ///   - Physical and alias columns of piece one
1792    ///   - Physical and alias columns of piece two
1793    ///   - etc.
1794    /// What we want, however, are first all physical column IDs and then all alias column IDs.
1795    /// This method adds `offset` to the logical column IDs of all alias columns and fixes up the corresponding
1796    /// column IDs in the projected field descriptors.  In this way, a new piece of physical and alias columns can
1797    /// first shift the existing alias columns by the number of new physical columns, resulting in the following order
1798    ///   - Physical columns of piece one
1799    ///   - Physical columns of piece two
1800    ///   - ...
1801    //    - Logical columns of piece one
1802    ///   - Logical columns of piece two
1803    ///   - ...
1804    void ShiftAliasColumns(std::uint32_t offset);
1805 };
1806 
1807 inline RNTupleDescriptor CloneDescriptorSchema(const RNTupleDescriptor &desc)
1808 {
1809    return desc.CloneSchema();
1810 }
1811 
1812 /// Tells if the field describes a user-defined enum type.
1813 /// The dictionary does not need to be available for this method.
1814 /// Needs the full descriptor to look up sub fields.
1815 bool IsCustomEnumFieldDesc(const RNTupleDescriptor &desc, const RFieldDescriptor &fieldDesc);
1816 
1817 /// Tells if the field describes a std::atomic<T> type
1818 bool IsStdAtomicFieldDesc(const RFieldDescriptor &fieldDesc);
1819 
1820 } // namespace Internal
1821 
1822 } // namespace ROOT
1823 
1824 #endif // ROOT_RNTupleDescriptor