Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /include/root/ROOT/RFieldBase.hxx was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /// \file ROOT/RFieldBase.hxx
0002 /// \ingroup NTuple
0003 /// \author Jakob Blomer <jblomer@cern.ch>
0004 /// \date 2018-10-09
0005 
0006 /*************************************************************************
0007  * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
0008  * All rights reserved.                                                  *
0009  *                                                                       *
0010  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0011  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0012  *************************************************************************/
0013 
0014 #ifndef ROOT_RFieldBase
0015 #define ROOT_RFieldBase
0016 
0017 #include <ROOT/RColumn.hxx>
0018 #include <ROOT/RCreateFieldOptions.hxx>
0019 #include <ROOT/RError.hxx>
0020 #include <ROOT/RFieldUtils.hxx>
0021 #include <ROOT/RNTupleRange.hxx>
0022 #include <ROOT/RNTupleTypes.hxx>
0023 
0024 #include <atomic>
0025 #include <cstddef>
0026 #include <functional>
0027 #include <iterator>
0028 #include <memory>
0029 #include <new>
0030 #include <string>
0031 #include <string_view>
0032 #include <typeinfo>
0033 #include <type_traits>
0034 #include <vector>
0035 
0036 namespace ROOT {
0037 
0038 class REntry;
0039 class RFieldBase;
0040 class RClassField;
0041 
0042 namespace Detail {
0043 class RFieldVisitor;
0044 } // namespace Detail
0045 
0046 namespace Experimental {
0047 
0048 namespace Detail {
0049 class RRawPtrWriteEntry;
0050 } // namespace Detail
0051 
0052 } // namespace Experimental
0053 
0054 namespace Internal {
0055 
0056 class RPageSink;
0057 class RPageSource;
0058 struct RFieldCallbackInjector;
0059 struct RFieldRepresentationModifier;
0060 
0061 // TODO(jblomer): find a better way to not have these methods in the RFieldBase public API
0062 void CallFlushColumnsOnField(RFieldBase &);
0063 void CallCommitClusterOnField(RFieldBase &);
0064 void CallConnectPageSinkOnField(RFieldBase &, ROOT::Internal::RPageSink &, ROOT::NTupleSize_t firstEntry = 0);
0065 void CallConnectPageSourceOnField(RFieldBase &, ROOT::Internal::RPageSource &);
0066 ROOT::RResult<std::unique_ptr<ROOT::RFieldBase>>
0067 CallFieldBaseCreate(const std::string &fieldName, const std::string &typeName, const ROOT::RCreateFieldOptions &options,
0068                     const ROOT::RNTupleDescriptor *desc, ROOT::DescriptorId_t fieldId);
0069 
0070 } // namespace Internal
0071 
0072 // clang-format off
0073 /**
0074 \class ROOT::RFieldBase
0075 \ingroup NTuple
0076 \brief A field translates read and write calls from/to underlying columns to/from tree values
0077 
0078 A field is a serializable C++ type or a container for a collection of subfields. The RFieldBase and its
0079 type-safe descendants provide the object to column mapper. They map C++ objects to primitive columns.  The
0080 mapping is trivial for simple types such as 'double'. Complex types resolve to multiple primitive columns.
0081 The field knows based on its type and the field name the type(s) and name(s) of the columns.
0082 
0083 Note: the class hierarchy starting at RFieldBase is not meant to be extended by user-provided child classes.
0084 This is and can only be partially enforced through C++.
0085 */
0086 // clang-format on
0087 class RFieldBase {
0088    friend class RFieldZero;                                    // to reset fParent pointer in ReleaseSubfields()
0089    friend class ROOT::Experimental::Detail::RRawPtrWriteEntry; // to call Append()
0090    friend struct ROOT::Internal::RFieldCallbackInjector;       // used for unit tests
0091    friend struct ROOT::Internal::RFieldRepresentationModifier; // used for unit tests
0092    friend void Internal::CallFlushColumnsOnField(RFieldBase &);
0093    friend void Internal::CallCommitClusterOnField(RFieldBase &);
0094    friend void Internal::CallConnectPageSinkOnField(RFieldBase &, ROOT::Internal::RPageSink &, ROOT::NTupleSize_t);
0095    friend void Internal::CallConnectPageSourceOnField(RFieldBase &, ROOT::Internal::RPageSource &);
0096    friend ROOT::RResult<std::unique_ptr<ROOT::RFieldBase>>
0097    Internal::CallFieldBaseCreate(const std::string &fieldName, const std::string &typeName,
0098                                  const ROOT::RCreateFieldOptions &options, const ROOT::RNTupleDescriptor *desc,
0099                                  ROOT::DescriptorId_t fieldId);
0100 
0101    using ReadCallback_t = std::function<void(void *)>;
0102 
0103 protected:
0104    /// A functor to release the memory acquired by CreateValue() (memory and constructor).
0105    /// This implementation works for types with a trivial destructor. More complex fields implement a derived deleter.
0106    /// The deleter is operational without the field object and thus can be used to destruct/release a value after
0107    /// the field has been destructed.
0108    class RDeleter {
0109    public:
0110       virtual ~RDeleter() = default;
0111       virtual void operator()(void *objPtr, bool dtorOnly)
0112       {
0113          if (!dtorOnly)
0114             operator delete(objPtr);
0115       }
0116    };
0117 
0118    /// A deleter for templated RFieldBase descendents where the value type is known.
0119    template <typename T>
0120    class RTypedDeleter : public RDeleter {
0121    public:
0122       void operator()(void *objPtr, bool dtorOnly) final
0123       {
0124          std::destroy_at(static_cast<T *>(objPtr));
0125          RDeleter::operator()(objPtr, dtorOnly);
0126       }
0127    };
0128 
0129    // We cannot directly use RFieldBase::RDeleter as a shared pointer deleter due to splicing. We use this
0130    // wrapper class to store a polymorphic pointer to the actual deleter.
0131    struct RSharedPtrDeleter {
0132       std::unique_ptr<RFieldBase::RDeleter> fDeleter;
0133       void operator()(void *objPtr) { fDeleter->operator()(objPtr, false /* dtorOnly*/); }
0134       explicit RSharedPtrDeleter(std::unique_ptr<RFieldBase::RDeleter> deleter) : fDeleter(std::move(deleter)) {}
0135    };
0136 
0137 public:
0138    static constexpr std::uint32_t kInvalidTypeVersion = -1U;
0139    enum {
0140       /// No constructor needs to be called, i.e. any bit pattern in the allocated memory represents a valid type
0141       /// A trivially constructible field has a no-op ConstructValue() implementation
0142       kTraitTriviallyConstructible = 0x01,
0143       /// The type is cleaned up just by freeing its memory. I.e. the destructor performs a no-op.
0144       kTraitTriviallyDestructible = 0x02,
0145       /// A field of a fundamental type that can be directly mapped via RField<T>::Map(), i.e. maps as-is to a single
0146       /// column
0147       kTraitMappable = 0x04,
0148       /// The TClass checksum is set and valid
0149       kTraitTypeChecksum = 0x08,
0150       /// This field is an instance of RInvalidField and can be safely `static_cast` to it
0151       kTraitInvalidField = 0x10,
0152       /// This field is a user defined type that was missing dictionaries and was reconstructed from the on-disk
0153       /// information
0154       kTraitEmulatedField = 0x20,
0155 
0156       /// Shorthand for types that are both trivially constructible and destructible
0157       kTraitTrivialType = kTraitTriviallyConstructible | kTraitTriviallyDestructible
0158    };
0159 
0160    using ColumnRepresentation_t = std::vector<ROOT::ENTupleColumnType>;
0161 
0162    /// During its lifetime, a field undergoes the following possible state transitions:
0163    ///
0164    ///  [*] --> Unconnected --> ConnectedToSink ----
0165    ///               |      |                      |
0166    ///               |      --> ConnectedToSource ---> [*]
0167    ///               |                             |
0168    ///               -------------------------------
0169    enum class EState {
0170       kUnconnected,
0171       kConnectedToSink,
0172       kConnectedToSource
0173    };
0174 
0175    // clang-format off
0176    /**
0177    \class ROOT::RFieldBase::RColumnRepresentations
0178    \ingroup NTuple
0179    \brief The list of column representations a field can have.
0180 
0181    Some fields have multiple possible column representations, e.g. with or without split encoding.
0182    All column representations supported for writing also need to be supported for reading. In addition,
0183    fields can support extra column representations for reading only, e.g. a 64bit integer reading from a
0184    32bit column.
0185    The defined column representations must be supported by corresponding column packing/unpacking implementations,
0186    i.e. for the example above, the unpacking of 32bit ints to 64bit pages must be implemented in RColumnElement.hxx
0187    */
0188    // clang-format on
0189    class RColumnRepresentations {
0190    public:
0191       /// A list of column representations
0192       using Selection_t = std::vector<ColumnRepresentation_t>;
0193 
0194       RColumnRepresentations();
0195       RColumnRepresentations(const Selection_t &serializationTypes, const Selection_t &deserializationExtraTypes);
0196 
0197       /// The first column list from `fSerializationTypes` is the default for writing.
0198       const ColumnRepresentation_t &GetSerializationDefault() const { return fSerializationTypes[0]; }
0199       const Selection_t &GetSerializationTypes() const { return fSerializationTypes; }
0200       const Selection_t &GetDeserializationTypes() const { return fDeserializationTypes; }
0201 
0202    private:
0203       Selection_t fSerializationTypes;
0204       /// The union of the serialization types and the deserialization extra types passed during construction.
0205       /// Duplicates the serialization types list but the benefit is that GetDeserializationTypes() does not need to
0206       /// compile the list.
0207       Selection_t fDeserializationTypes;
0208    }; // class RColumnRepresentations
0209 
0210    class RValue;
0211    class RBulkValues;
0212 
0213 private:
0214    /// The field name relative to its parent field
0215    std::string fName;
0216    /// The C++ type captured by this field
0217    std::string fType;
0218    /// The role of this field in the data model structure
0219    ROOT::ENTupleStructure fStructure;
0220    /// For fixed sized arrays, the array length
0221    std::size_t fNRepetitions;
0222    /// A field qualifies as simple if it is mappable (which implies it has a single principal column),
0223    /// and it is not an artificial field and has no post-read callback
0224    bool fIsSimple;
0225    /// A field that is not backed on disk but computed, e.g. a default-constructed missing field or
0226    /// a field whose data is created by I/O customization rules. Subfields of artificial fields are
0227    /// artificial, too.
0228    bool fIsArtificial = false;
0229    /// When the columns are connected to a page source or page sink, the field represents a field id in the
0230    /// corresponding RNTuple descriptor. This on-disk ID is set in RPageSink::Create() for writing and by
0231    /// RFieldDescriptor::CreateField() when recreating a field / model from the stored descriptor.
0232    ROOT::DescriptorId_t fOnDiskId = ROOT::kInvalidDescriptorId;
0233    /// Free text set by the user
0234    std::string fDescription;
0235    /// Changed by ConnectTo[Sink,Source], reset by Clone()
0236    EState fState = EState::kUnconnected;
0237 
0238    void InvokeReadCallbacks(void *target)
0239    {
0240       for (const auto &func : fReadCallbacks)
0241          func(target);
0242    }
0243 
0244    /// Translate an entry index to a column element index of the principal column and vice versa. These functions
0245    /// take into account the role and number of repetitions on each level of the field hierarchy as follows:
0246    /// - Top level fields: element index == entry index
0247    /// - Record fields propagate their principal column index to the principal columns of direct descendant fields
0248    /// - Collection and variant fields set the principal column index of their children to 0
0249    ///
0250    /// The column element index also depends on the number of repetitions of each field in the hierarchy, e.g., given a
0251    /// field with type `std::array<std::array<float, 4>, 2>`, this function returns 8 for the innermost field.
0252    ROOT::NTupleSize_t EntryToColumnElementIndex(ROOT::NTupleSize_t globalIndex) const;
0253 
0254    /// Flushes data from active columns
0255    void FlushColumns();
0256    /// Flushes data from active columns to disk and calls CommitClusterImpl()
0257    void CommitCluster();
0258    /// Fields and their columns live in the void until connected to a physical page storage.  Only once connected, data
0259    /// can be read or written.  In order to find the field in the page storage, the field's on-disk ID has to be set.
0260    /// \param firstEntry The global index of the first entry with on-disk data for the connected field
0261    void ConnectPageSink(ROOT::Internal::RPageSink &pageSink, ROOT::NTupleSize_t firstEntry = 0);
0262    /// Connects the field and its subfield tree to the given page source. Once connected, data can be read.
0263    /// Only unconnected fields may be connected, i.e. the method is not idempotent. The field ID has to be set prior to
0264    /// calling this function. For subfields, a field ID may or may not be set. If the field ID is unset, it will be
0265    /// determined using the page source descriptor, based on the parent field ID and the subfield name.
0266    void ConnectPageSource(ROOT::Internal::RPageSource &pageSource);
0267 
0268    void SetArtificial()
0269    {
0270       fIsSimple = false;
0271       fIsArtificial = true;
0272       for (auto &field : fSubfields) {
0273          field->SetArtificial();
0274       }
0275    }
0276 
0277 protected:
0278    struct RBulkSpec;
0279 
0280    /// Bits used in CompareOnDisk()
0281    enum {
0282       /// The in-memory field and the on-disk field differ in the field version
0283       kDiffFieldVersion = 0x01,
0284       /// The in-memory field and the on-disk field differ in the type version
0285       kDiffTypeVersion = 0x02,
0286       /// The in-memory field and the on-disk field differ in their structural roles
0287       kDiffStructure = 0x04,
0288       /// The in-memory field and the on-disk field have different type names
0289       kDiffTypeName = 0x08,
0290       /// The in-memory field and the on-disk field have different repetition counts
0291       kDiffNRepetitions = 0x10
0292    };
0293 
0294    /// Collections and classes own subfields
0295    std::vector<std::unique_ptr<RFieldBase>> fSubfields;
0296    /// Subfields point to their mother field
0297    RFieldBase *fParent;
0298    /// All fields that have columns have a distinct main column. E.g., for simple fields (`float`, `int`, ...), the
0299    /// principal column corresponds to the field type. For collection fields except fixed-sized arrays,
0300    /// the main column is the offset field.  Class fields have no column of their own.
0301    /// When reading, points to any column of the column team of the active representation. Usually, this is just
0302    /// the first column.
0303    /// When writing, points to the first column index of the currently active (not suppressed) column representation.
0304    ROOT::Internal::RColumn *fPrincipalColumn = nullptr;
0305    /// Some fields have a second column in its column representation. In this case, `fAuxiliaryColumn` points into
0306    /// `fAvailableColumns` to the column that immediately follows the column `fPrincipalColumn` points to.
0307    ROOT::Internal::RColumn *fAuxiliaryColumn = nullptr;
0308    /// The columns are connected either to a sink or to a source (not to both); they are owned by the field.
0309    /// Contains all columns of all representations in order of representation and column index.
0310    std::vector<std::unique_ptr<ROOT::Internal::RColumn>> fAvailableColumns;
0311    /// Properties of the type that allow for optimizations of collections of that type
0312    std::uint32_t fTraits = 0;
0313    /// A typedef or using name that was used when creating the field
0314    std::string fTypeAlias;
0315    /// List of functions to be called after reading a value
0316    std::vector<ReadCallback_t> fReadCallbacks;
0317    /// C++ type version cached from the descriptor after a call to ConnectPageSource()
0318    std::uint32_t fOnDiskTypeVersion = kInvalidTypeVersion;
0319    /// TClass checksum cached from the descriptor after a call to ConnectPageSource(). Only set
0320    /// for classes with dictionaries.
0321    std::uint32_t fOnDiskTypeChecksum = 0;
0322    /// Pointers into the static vector returned by RColumnRepresentations::GetSerializationTypes() when
0323    /// SetColumnRepresentatives() is called. Otherwise (if empty) GetColumnRepresentatives() returns a vector
0324    /// with a single element, the default representation.  Always empty for artificial fields.
0325    std::vector<std::reference_wrapper<const ColumnRepresentation_t>> fColumnRepresentatives;
0326 
0327    /// Factory method for the field's type. The caller owns the returned pointer
0328    void *CreateObjectRawPtr() const;
0329 
0330    /// Helpers for generating columns. We use the fact that most fields have the same C++/memory types
0331    /// for all their column representations.
0332    /// Where possible, we call the helpers not from the header to reduce compilation time.
0333    template <std::uint32_t ColumnIndexT, typename HeadT, typename... TailTs>
0334    void GenerateColumnsImpl(const ColumnRepresentation_t &representation, std::uint16_t representationIndex)
0335    {
0336       assert(ColumnIndexT < representation.size());
0337       auto &column = fAvailableColumns.emplace_back(
0338          ROOT::Internal::RColumn::Create<HeadT>(representation[ColumnIndexT], ColumnIndexT, representationIndex));
0339 
0340       // Initially, the first two columns become the active column representation
0341       if (representationIndex == 0 && !fPrincipalColumn) {
0342          fPrincipalColumn = column.get();
0343       } else if (representationIndex == 0 && !fAuxiliaryColumn) {
0344          fAuxiliaryColumn = column.get();
0345       } else {
0346          // We currently have no fields with more than 2 columns in its column representation
0347          R__ASSERT(representationIndex > 0);
0348       }
0349 
0350       if constexpr (sizeof...(TailTs))
0351          GenerateColumnsImpl<ColumnIndexT + 1, TailTs...>(representation, representationIndex);
0352    }
0353 
0354    /// For writing, use the currently set column representative
0355    template <typename... ColumnCppTs>
0356    void GenerateColumnsImpl()
0357    {
0358       if (fColumnRepresentatives.empty()) {
0359          fAvailableColumns.reserve(sizeof...(ColumnCppTs));
0360          GenerateColumnsImpl<0, ColumnCppTs...>(GetColumnRepresentations().GetSerializationDefault(), 0);
0361       } else {
0362          const auto N = fColumnRepresentatives.size();
0363          fAvailableColumns.reserve(N * sizeof...(ColumnCppTs));
0364          for (unsigned i = 0; i < N; ++i) {
0365             GenerateColumnsImpl<0, ColumnCppTs...>(fColumnRepresentatives[i].get(), i);
0366          }
0367       }
0368    }
0369 
0370    /// For reading, use the on-disk column list
0371    template <typename... ColumnCppTs>
0372    void GenerateColumnsImpl(const ROOT::RNTupleDescriptor &desc)
0373    {
0374       std::uint16_t representationIndex = 0;
0375       do {
0376          const auto &onDiskTypes = EnsureCompatibleColumnTypes(desc, representationIndex);
0377          if (onDiskTypes.empty())
0378             break;
0379          GenerateColumnsImpl<0, ColumnCppTs...>(onDiskTypes, representationIndex);
0380          fColumnRepresentatives.emplace_back(onDiskTypes);
0381          if (representationIndex > 0) {
0382             for (std::size_t i = 0; i < sizeof...(ColumnCppTs); ++i) {
0383                fAvailableColumns[i]->MergeTeams(
0384                   *fAvailableColumns[representationIndex * sizeof...(ColumnCppTs) + i].get());
0385             }
0386          }
0387          representationIndex++;
0388       } while (true);
0389    }
0390 
0391    /// Implementations in derived classes should return a static RColumnRepresentations object. The default
0392    /// implementation does not attach any columns to the field.
0393    virtual const RColumnRepresentations &GetColumnRepresentations() const;
0394    /// Implementations in derived classes should create the backing columns corresponding to the field type for
0395    /// writing. The default implementation does not attach any columns to the field.
0396    virtual void GenerateColumns() {}
0397    /// Implementations in derived classes should create the backing columns corresponding to the field type for reading.
0398    /// The default implementation does not attach any columns to the field. The method should check, using the page
0399    /// source and `fOnDiskId`, if the column types match and throw if they don't.
0400    virtual void GenerateColumns(const ROOT::RNTupleDescriptor & /*desc*/) {}
0401    /// Returns the on-disk column types found in the provided descriptor for `fOnDiskId` and the given
0402    /// representation index. If there are no columns for the given representation index, return an empty
0403    /// ColumnRepresentation_t list. Otherwise, the returned reference points into the static array returned by
0404    /// GetColumnRepresentations().
0405    /// Throws an exception if the types on disk don't match any of the deserialization types from
0406    /// GetColumnRepresentations().
0407    const ColumnRepresentation_t &
0408    EnsureCompatibleColumnTypes(const ROOT::RNTupleDescriptor &desc, std::uint16_t representationIndex) const;
0409    /// When connecting a field to a page sink, the field's default column representation is subject
0410    /// to adjustment according to the write options. E.g., if compression is turned off, encoded columns
0411    /// are changed to their unencoded counterparts.
0412    void AutoAdjustColumnTypes(const ROOT::RNTupleWriteOptions &options);
0413 
0414    /// Called by Clone(), which additionally copies the on-disk ID
0415    virtual std::unique_ptr<RFieldBase> CloneImpl(std::string_view newName) const = 0;
0416 
0417    /// Constructs value in a given location of size at least GetValueSize(). Called by the base class' CreateValue().
0418    virtual void ConstructValue(void *where) const = 0;
0419    virtual std::unique_ptr<RDeleter> GetDeleter() const { return std::make_unique<RDeleter>(); }
0420    /// Allow derived classes to call ConstructValue(void *) and GetDeleter() on other (sub)fields.
0421    static void CallConstructValueOn(const RFieldBase &other, void *where) { other.ConstructValue(where); }
0422    static std::unique_ptr<RDeleter> GetDeleterOf(const RFieldBase &other) { return other.GetDeleter(); }
0423 
0424    /// Allow parents to mark their childs as artificial fields (used in class and record fields)
0425    static void CallSetArtificialOn(RFieldBase &other) { other.SetArtificial(); }
0426    /// Allow class fields to adjust the type alias of their members
0427    static void SetTypeAliasOf(RFieldBase &other, const std::string &alias) { other.fTypeAlias = alias; }
0428 
0429    /// Operations on values of complex types, e.g. ones that involve multiple columns or for which no direct
0430    /// column type exists.
0431    virtual std::size_t AppendImpl(const void *from);
0432    virtual void ReadGlobalImpl(ROOT::NTupleSize_t globalIndex, void *to);
0433    virtual void ReadInClusterImpl(RNTupleLocalIndex localIndex, void *to);
0434 
0435    /// Write the given value into columns. The value object has to be of the same type as the field.
0436    /// Returns the number of uncompressed bytes written.
0437    std::size_t Append(const void *from);
0438 
0439    /// Populate a single value with data from the field. The memory location pointed to by to needs to be of the
0440    /// fitting type. The fast path is conditioned by the field qualifying as simple, i.e. maps as-is
0441    /// to a single column and has no read callback.
0442    void Read(ROOT::NTupleSize_t globalIndex, void *to)
0443    {
0444       if (fIsSimple)
0445          return (void)fPrincipalColumn->Read(globalIndex, to);
0446 
0447       if (!fIsArtificial) {
0448          if (fTraits & kTraitMappable)
0449             fPrincipalColumn->Read(globalIndex, to);
0450          else
0451             ReadGlobalImpl(globalIndex, to);
0452       }
0453       if (R__unlikely(!fReadCallbacks.empty()))
0454          InvokeReadCallbacks(to);
0455    }
0456 
0457    /// Populate a single value with data from the field. The memory location pointed to by to needs to be of the
0458    /// fitting type. The fast path is conditioned by the field qualifying as simple, i.e. maps as-is
0459    /// to a single column and has no read callback.
0460    void Read(RNTupleLocalIndex localIndex, void *to)
0461    {
0462       if (fIsSimple)
0463          return (void)fPrincipalColumn->Read(localIndex, to);
0464 
0465       if (!fIsArtificial) {
0466          if (fTraits & kTraitMappable)
0467             fPrincipalColumn->Read(localIndex, to);
0468          else
0469             ReadInClusterImpl(localIndex, to);
0470       }
0471       if (R__unlikely(!fReadCallbacks.empty()))
0472          InvokeReadCallbacks(to);
0473    }
0474 
0475    /// General implementation of bulk read. Loop over the required range and read values that are required
0476    /// and not already present. Derived classes may implement more optimized versions of this method.
0477    /// See ReadBulk() for the return value.
0478    virtual std::size_t ReadBulkImpl(const RBulkSpec &bulkSpec);
0479 
0480    /// Returns the number of newly available values, that is the number of bools in `bulkSpec.fMaskAvail` that
0481    /// flipped from false to true. As a special return value, `kAllSet` can be used if all values are read
0482    /// independent from the masks.
0483    std::size_t ReadBulk(const RBulkSpec &bulkSpec);
0484 
0485    /// Allow derived classes to call Append() and Read() on other (sub)fields.
0486    static std::size_t CallAppendOn(RFieldBase &other, const void *from) { return other.Append(from); }
0487    static void CallReadOn(RFieldBase &other, RNTupleLocalIndex localIndex, void *to) { other.Read(localIndex, to); }
0488    static void CallReadOn(RFieldBase &other, ROOT::NTupleSize_t globalIndex, void *to) { other.Read(globalIndex, to); }
0489    static void *CallCreateObjectRawPtrOn(RFieldBase &other) { return other.CreateObjectRawPtr(); }
0490 
0491    /// Fields may need direct access to the principal column of their subfields, e.g. in RRVecField::ReadBulk()
0492    static ROOT::Internal::RColumn *GetPrincipalColumnOf(const RFieldBase &other) { return other.fPrincipalColumn; }
0493 
0494    /// Set a user-defined function to be called after reading a value, giving a chance to inspect and/or modify the
0495    /// value object.
0496    /// Returns an index that can be used to remove the callback.
0497    size_t AddReadCallback(ReadCallback_t func);
0498    void RemoveReadCallback(size_t idx);
0499 
0500    // Perform housekeeping tasks for global to cluster-local index translation
0501    virtual void CommitClusterImpl() {}
0502    // The field can indicate that it needs to register extra type information in the on-disk schema.
0503    // In this case, a callback from the page sink to the field will be registered on connect, so that the
0504    // extra type information can be collected when the dataset gets committed.
0505    virtual bool HasExtraTypeInfo() const { return false; }
0506    // The page sink's callback when the data set gets committed will call this method to get the field's extra
0507    // type information. This has to happen at the end of writing because the type information may change depending
0508    // on the data that's written, e.g. for polymorphic types in the streamer field.
0509    virtual ROOT::RExtraTypeInfoDescriptor GetExtraTypeInfo() const { return ROOT::RExtraTypeInfoDescriptor(); }
0510 
0511    /// Add a new subfield to the list of nested fields
0512    void Attach(std::unique_ptr<RFieldBase> child);
0513 
0514    /// Called by ConnectPageSource() before connecting; derived classes may override this as appropriate, e.g.
0515    /// for the application of I/O rules. In the process, the field at hand or its subfields may be marked as
0516    /// "artifical", i.e. introduced by schema evolution and not backed by on-disk information.
0517    /// May return a field substitute that fits the on-disk schema as a replacement for the field at hand.
0518    /// A field substitute must read into the same in-memory layout than the original field and field substitutions
0519    /// must not be cyclic.
0520    virtual std::unique_ptr<RFieldBase> BeforeConnectPageSource(ROOT::Internal::RPageSource & /* source */)
0521    {
0522       return nullptr;
0523    }
0524 
0525    /// For non-artificial fields, check compatibility of the in-memory field and the on-disk field. In the process,
0526    /// the field at hand may change its on-disk ID or perform other tasks related to automatic schema evolution.
0527    /// If the on-disk field is incompatible with the in-memory field at hand, an exception is thrown.
0528    virtual void ReconcileOnDiskField(const RNTupleDescriptor &desc);
0529 
0530    /// Returns a combination of kDiff... flags, indicating peroperties that are different between the field at hand
0531    /// and the given on-disk field
0532    std::uint32_t CompareOnDiskField(const RFieldDescriptor &fieldDesc, std::uint32_t ignoreBits) const;
0533    /// Compares the field to the corresponding on-disk field information in the provided descriptor.
0534    /// Throws an exception if the fields don't match.
0535    /// Optionally, a set of bits can be provided that should be ignored in the comparison.
0536    RResult<void> EnsureMatchingOnDiskField(const RNTupleDescriptor &desc, std::uint32_t ignoreBits = 0) const;
0537    /// Many fields accept a range of type prefixes for schema evolution,
0538    /// e.g. std::unique_ptr< and std::optional< for nullable fields
0539    RResult<void>
0540    EnsureMatchingTypePrefix(const RNTupleDescriptor &desc, const std::vector<std::string> &prefixes) const;
0541 
0542    /// Factory method to resurrect a field from the stored on-disk type information.  This overload takes an already
0543    /// normalized type name and type alias.
0544    /// `desc` and `fieldId` must be passed if `options.fEmulateUnknownTypes` is true, otherwise they can be left blank.
0545    static RResult<std::unique_ptr<RFieldBase>>
0546    Create(const std::string &fieldName, const std::string &typeName, const ROOT::RCreateFieldOptions &options,
0547           const ROOT::RNTupleDescriptor *desc, ROOT::DescriptorId_t fieldId);
0548 
0549 public:
0550    template <bool IsConstT>
0551    class RSchemaIteratorTemplate;
0552    using RSchemaIterator = RSchemaIteratorTemplate<false>;
0553    using RConstSchemaIterator = RSchemaIteratorTemplate<true>;
0554 
0555    // This is used in CreateObject() and is specialized for void
0556    template <typename T>
0557    struct RCreateObjectDeleter {
0558       using deleter = std::default_delete<T>;
0559    };
0560 
0561    /// Used in the return value of the Check() method
0562    struct RCheckResult {
0563       std::string fFieldName; ///< Qualified field name causing the error
0564       std::string fTypeName;  ///< Type name corresponding to the (sub)field
0565       std::string fErrMsg;    ///< Cause of the failure, e.g. unsupported type
0566    };
0567 
0568    /// The constructor creates the underlying column objects and connects them to either a sink or a source.
0569    /// If `isSimple` is `true`, the trait `kTraitMappable` is automatically set on construction. However, the
0570    /// field might be demoted to non-simple if a post-read callback is set.
0571    RFieldBase(std::string_view name, std::string_view type, ROOT::ENTupleStructure structure, bool isSimple,
0572               std::size_t nRepetitions = 0);
0573    RFieldBase(const RFieldBase &) = delete;
0574    RFieldBase(RFieldBase &&) = default;
0575    RFieldBase &operator=(const RFieldBase &) = delete;
0576    RFieldBase &operator=(RFieldBase &&) = default;
0577    virtual ~RFieldBase() = default;
0578 
0579    /// Copies the field and its subfields using a possibly new name and a new, unconnected set of columns
0580    std::unique_ptr<RFieldBase> Clone(std::string_view newName) const;
0581 
0582    /// Factory method to create a field from a certain type given as string.
0583    /// Note that the provided type name must be a valid C++ type name. Template arguments of templated types
0584    /// must be type names or integers (e.g., no expressions).
0585    static RResult<std::unique_ptr<RFieldBase>>
0586    Create(const std::string &fieldName, const std::string &typeName);
0587 
0588    /// Checks if the given type is supported by RNTuple. In case of success, the result vector is empty.
0589    /// Otherwise there is an error record for each failing subfield (subtype).
0590    static std::vector<RCheckResult> Check(const std::string &fieldName, const std::string &typeName);
0591 
0592    /// Generates an object of the field type and allocates new initialized memory according to the type.
0593    /// Implemented at the end of this header because the implementation is using RField<T>::TypeName()
0594    /// The returned object can be released with `delete`, i.e. it is valid to call:
0595    /// ~~~{.cpp}
0596    ///    auto ptr = field->CreateObject();
0597    ///    delete ptr.release();
0598    /// ~~~
0599    ///
0600    /// Note that CreateObject<void>() is supported. The returned `unique_ptr` has a custom deleter that reports an error
0601    /// if it is called. The intended use of the returned `unique_ptr<void>` is to call `release()`. In this way, the
0602    /// transfer of pointer ownership is explicit.
0603    template <typename T>
0604    std::unique_ptr<T, typename RCreateObjectDeleter<T>::deleter> CreateObject() const;
0605    /// Generates an object of the field's type, wraps it in a shared pointer and returns it as an RValue connected to
0606    /// the field.
0607    RValue CreateValue();
0608    /// Creates a new, initially empty bulk.
0609    /// RBulkValues::ReadBulk() will construct the array of values. The memory of the value array is managed by the
0610    /// RBulkValues class.
0611    RBulkValues CreateBulk();
0612    /// Creates a value from a memory location with an already constructed object
0613    RValue BindValue(std::shared_ptr<void> objPtr);
0614    /// Creates the list of direct child values given an existing value for this field. E.g. a single value for the
0615    /// correct `std::variant` or all the elements of a collection. The default implementation assumes no subvalues
0616    /// and returns an empty vector.
0617    virtual std::vector<RValue> SplitValue(const RValue &value) const;
0618    /// The number of bytes taken by a value of the appropriate type
0619    virtual size_t GetValueSize() const = 0;
0620    /// As a rule of thumb, the alignment is equal to the size of the type. There are, however, various exceptions
0621    /// to this rule depending on OS and CPU architecture. So enforce the alignment to be explicitly spelled out.
0622    virtual size_t GetAlignment() const = 0;
0623    std::uint32_t GetTraits() const { return fTraits; }
0624    bool HasReadCallbacks() const { return !fReadCallbacks.empty(); }
0625 
0626    const std::string &GetFieldName() const { return fName; }
0627    /// Returns the field name and parent field names separated by dots (`grandparent.parent.child`)
0628    std::string GetQualifiedFieldName() const;
0629    const std::string &GetTypeName() const { return fType; }
0630    const std::string &GetTypeAlias() const { return fTypeAlias; }
0631    ROOT::ENTupleStructure GetStructure() const { return fStructure; }
0632    std::size_t GetNRepetitions() const { return fNRepetitions; }
0633    const RFieldBase *GetParent() const { return fParent; }
0634    std::vector<RFieldBase *> GetMutableSubfields();
0635    std::vector<const RFieldBase *> GetConstSubfields() const;
0636    bool IsSimple() const { return fIsSimple; }
0637    bool IsArtificial() const { return fIsArtificial; }
0638    /// Get the field's description
0639    const std::string &GetDescription() const { return fDescription; }
0640    void SetDescription(std::string_view description);
0641    EState GetState() const { return fState; }
0642 
0643    ROOT::DescriptorId_t GetOnDiskId() const { return fOnDiskId; }
0644    void SetOnDiskId(ROOT::DescriptorId_t id);
0645 
0646    /// Returns the `fColumnRepresentative` pointee or, if unset (always the case for artificial fields), the field's
0647    /// default representative
0648    RColumnRepresentations::Selection_t GetColumnRepresentatives() const;
0649    /// Fixes a column representative. This can only be done _before_ connecting the field to a page sink.
0650    /// Otherwise, or if the provided representation is not in the list of GetColumnRepresentations(),
0651    /// an exception is thrown
0652    void SetColumnRepresentatives(const RColumnRepresentations::Selection_t &representatives);
0653    /// Whether or not an explicit column representative was set
0654    bool HasDefaultColumnRepresentative() const { return fColumnRepresentatives.empty(); }
0655 
0656    /// Indicates an evolution of the mapping scheme from C++ type to columns
0657    virtual std::uint32_t GetFieldVersion() const { return 0; }
0658    /// Indicates an evolution of the C++ type itself
0659    virtual std::uint32_t GetTypeVersion() const { return 0; }
0660    /// Return the current TClass reported checksum of this class. Only valid if `kTraitTypeChecksum` is set.
0661    virtual std::uint32_t GetTypeChecksum() const { return 0; }
0662    /// Return the C++ type version stored in the field descriptor; only valid after a call to ConnectPageSource()
0663    std::uint32_t GetOnDiskTypeVersion() const { return fOnDiskTypeVersion; }
0664    /// Return checksum stored in the field descriptor; only valid after a call to ConnectPageSource(),
0665    /// if the field stored a type checksum
0666    std::uint32_t GetOnDiskTypeChecksum() const { return fOnDiskTypeChecksum; }
0667 
0668    RSchemaIterator begin();
0669    RSchemaIterator end();
0670    RConstSchemaIterator begin() const;
0671    RConstSchemaIterator end() const;
0672    RConstSchemaIterator cbegin() const;
0673    RConstSchemaIterator cend() const;
0674 
0675    virtual void AcceptVisitor(ROOT::Detail::RFieldVisitor &visitor) const;
0676 }; // class RFieldBase
0677 
0678 /// Iterates over the subtree of fields in depth-first search order
0679 template <bool IsConstT>
0680 class RFieldBase::RSchemaIteratorTemplate {
0681 private:
0682    struct Position {
0683       using FieldPtr_t = std::conditional_t<IsConstT, const RFieldBase *, RFieldBase *>;
0684       Position() : fFieldPtr(nullptr), fIdxInParent(-1) {}
0685       Position(FieldPtr_t fieldPtr, int idxInParent) : fFieldPtr(fieldPtr), fIdxInParent(idxInParent) {}
0686       FieldPtr_t fFieldPtr;
0687       int fIdxInParent;
0688    };
0689    /// The stack of nodes visited when walking down the tree of fields
0690    std::vector<Position> fStack;
0691 
0692 public:
0693    using iterator = RSchemaIteratorTemplate<IsConstT>;
0694    using iterator_category = std::forward_iterator_tag;
0695    using difference_type = std::ptrdiff_t;
0696    using value_type = std::conditional_t<IsConstT, const RFieldBase, RFieldBase>;
0697    using pointer = std::conditional_t<IsConstT, const RFieldBase *, RFieldBase *>;
0698    using reference = std::conditional_t<IsConstT, const RFieldBase &, RFieldBase &>;
0699 
0700    RSchemaIteratorTemplate() { fStack.emplace_back(Position()); }
0701    RSchemaIteratorTemplate(pointer val, int idxInParent) { fStack.emplace_back(Position(val, idxInParent)); }
0702    ~RSchemaIteratorTemplate() {}
0703    /// Given that the iterator points to a valid field which is not the end iterator, go to the next field
0704    /// in depth-first search order
0705    void Advance()
0706    {
0707       auto itr = fStack.rbegin();
0708       if (!itr->fFieldPtr->fSubfields.empty()) {
0709          fStack.emplace_back(Position(itr->fFieldPtr->fSubfields[0].get(), 0));
0710          return;
0711       }
0712 
0713       unsigned int nextIdxInParent = ++(itr->fIdxInParent);
0714       while (nextIdxInParent >= itr->fFieldPtr->fParent->fSubfields.size()) {
0715          if (fStack.size() == 1) {
0716             itr->fFieldPtr = itr->fFieldPtr->fParent;
0717             itr->fIdxInParent = -1;
0718             return;
0719          }
0720          fStack.pop_back();
0721          itr = fStack.rbegin();
0722          nextIdxInParent = ++(itr->fIdxInParent);
0723       }
0724       itr->fFieldPtr = itr->fFieldPtr->fParent->fSubfields[nextIdxInParent].get();
0725    }
0726 
0727    iterator operator++(int) /* postfix */
0728    {
0729       auto r = *this;
0730       Advance();
0731       return r;
0732    }
0733    iterator &operator++() /* prefix */
0734    {
0735       Advance();
0736       return *this;
0737    }
0738    reference operator*() const { return *fStack.back().fFieldPtr; }
0739    pointer operator->() const { return fStack.back().fFieldPtr; }
0740    bool operator==(const iterator &rh) const { return fStack.back().fFieldPtr == rh.fStack.back().fFieldPtr; }
0741    bool operator!=(const iterator &rh) const { return fStack.back().fFieldPtr != rh.fStack.back().fFieldPtr; }
0742 };
0743 
0744 /// Points to an object with RNTuple I/O support and keeps a pointer to the corresponding field.
0745 /// Fields can create RValue objects through RFieldBase::CreateValue(), RFieldBase::BindValue()) or
0746 /// RFieldBase::SplitValue().
0747 class RFieldBase::RValue final {
0748    friend class RFieldBase;
0749    friend class ROOT::REntry;
0750 
0751 private:
0752    RFieldBase *fField = nullptr;  ///< The field that created the RValue
0753    /// Set by Bind() or by RFieldBase::CreateValue(), RFieldBase::SplitValue() or RFieldBase::BindValue()
0754    std::shared_ptr<void> fObjPtr;
0755    mutable std::atomic<const std::type_info *> fTypeInfo = nullptr;
0756 
0757    RValue(RFieldBase *field, std::shared_ptr<void> objPtr) : fField(field), fObjPtr(objPtr) {}
0758 
0759 public:
0760    RValue(const RValue &other) : fField(other.fField), fObjPtr(other.fObjPtr) {}
0761    RValue &operator=(const RValue &other)
0762    {
0763       fField = other.fField;
0764       fObjPtr = other.fObjPtr;
0765       // We could copy over the cached type info, or just start with a fresh state...
0766       fTypeInfo = nullptr;
0767       return *this;
0768    }
0769    RValue(RValue &&other) : fField(other.fField), fObjPtr(other.fObjPtr) {}
0770    RValue &operator=(RValue &&other)
0771    {
0772       fField = other.fField;
0773       fObjPtr = other.fObjPtr;
0774       // We could copy over the cached type info, or just start with a fresh state...
0775       fTypeInfo = nullptr;
0776       return *this;
0777    }
0778    ~RValue() = default;
0779 
0780 private:
0781    template <typename T>
0782    void EnsureMatchingType() const
0783    {
0784       if constexpr (!std::is_void_v<T>) {
0785          const std::type_info &ti = typeid(T);
0786          // Fast path: if we had a matching type before, try comparing the type_info's. This may still fail in case the
0787          // type has a suppressed template argument that may change the typeid.
0788          auto *cachedTypeInfo = fTypeInfo.load();
0789          if (cachedTypeInfo != nullptr && *cachedTypeInfo == ti) {
0790             return;
0791          }
0792          std::string renormalizedTypeName = Internal::GetRenormalizedTypeName(ti);
0793          if (Internal::IsMatchingFieldType(fField->GetTypeName(), renormalizedTypeName, ti)) {
0794             fTypeInfo.store(&ti);
0795             return;
0796          }
0797          throw RException(R__FAIL("type mismatch for field \"" + fField->GetFieldName() + "\": expected " +
0798                                   fField->GetTypeName() + ", got " + renormalizedTypeName));
0799       }
0800    }
0801 
0802    std::size_t Append() { return fField->Append(fObjPtr.get()); }
0803 
0804 public:
0805    void Read(ROOT::NTupleSize_t globalIndex) { fField->Read(globalIndex, fObjPtr.get()); }
0806    void Read(RNTupleLocalIndex localIndex) { fField->Read(localIndex, fObjPtr.get()); }
0807 
0808    void Bind(std::shared_ptr<void> objPtr) { fObjPtr = objPtr; }
0809    void BindRawPtr(void *rawPtr);
0810    /// Replace the current object pointer by a pointer to a new object constructed by the field
0811    void EmplaceNew() { fObjPtr = fField->CreateValue().GetPtr<void>(); }
0812 
0813    template <typename T>
0814    std::shared_ptr<T> GetPtr() const
0815    {
0816       EnsureMatchingType<T>();
0817       return std::static_pointer_cast<T>(fObjPtr);
0818    }
0819 
0820    template <typename T>
0821    const T &GetRef() const
0822    {
0823       EnsureMatchingType<T>();
0824       return *static_cast<T *>(fObjPtr.get());
0825    }
0826 
0827    const RFieldBase &GetField() const { return *fField; }
0828 };
0829 
0830 /// Input parameter to RFieldBase::ReadBulk() and RFieldBase::ReadBulkImpl().
0831 //  See the RBulkValues class documentation for more information.
0832 struct RFieldBase::RBulkSpec {
0833    /// Possible return value of ReadBulk() and ReadBulkImpl(), which indicates that the full bulk range was read
0834    /// independently of the provided masks.
0835    static const std::size_t kAllSet = std::size_t(-1);
0836 
0837    RNTupleLocalIndex fFirstIndex; ///< Start of the bulk range
0838    std::size_t fCount = 0;        ///< Size of the bulk range
0839    /// A bool array of size fCount, indicating the required values in the requested range
0840    const bool *fMaskReq = nullptr;
0841    bool *fMaskAvail = nullptr; ///< A bool array of size `fCount`, indicating the valid values in fValues
0842    /// The destination area, which has to be an array of valid objects of the correct type large enough to hold the bulk
0843    /// range.
0844    void *fValues = nullptr;
0845    /// Reference to memory owned by the RBulkValues class. The field implementing BulkReadImpl() may use `fAuxData` as
0846    /// memory that stays persistent between calls.
0847    std::vector<unsigned char> *fAuxData = nullptr;
0848 };
0849 
0850 // clang-format off
0851 /**
0852 \class ROOT::RFieldBase::RBulkValues
0853 \ingroup NTuple
0854 \brief Points to an array of objects with RNTuple I/O support, used for bulk reading.
0855 
0856 Similar to RValue, but manages an array of consecutive values. Bulks have to come from the same cluster.
0857 Bulk I/O works with two bit masks: the mask of all the available entries in the current bulk and the mask
0858 of the required entries in a bulk read. The idea is that a single bulk may serve multiple read operations
0859 on the same range, where in each read operation a different subset of values is required.
0860 The memory of the value array is managed by the RBulkValues class.
0861 */
0862 // clang-format on
0863 class RFieldBase::RBulkValues final {
0864 private:
0865    friend class RFieldBase;
0866 
0867    RFieldBase *fField = nullptr;                   ///< The field that created the array of values
0868    std::unique_ptr<RFieldBase::RDeleter> fDeleter; /// Cached deleter of fField
0869    void *fValues = nullptr;                        ///< Pointer to the start of the array
0870    std::size_t fValueSize = 0;                     ///< Cached copy of RFieldBase::GetValueSize()
0871    std::size_t fCapacity = 0;                      ///< The size of the array memory block in number of values
0872    std::size_t fSize = 0;              ///< The number of available values in the array (provided their mask is set)
0873    bool fIsAdopted = false;            ///< True if the user provides the memory buffer for fValues
0874    std::unique_ptr<bool[]> fMaskAvail; ///< Masks invalid values in the array
0875    std::size_t fNValidValues = 0;      ///< The sum of non-zero elements in the fMask
0876    RNTupleLocalIndex fFirstIndex;      ///< Index of the first value of the array
0877    /// Reading arrays of complex values may require additional memory, for instance for the elements of
0878    /// arrays of vectors. A pointer to the `fAuxData` array is passed to the field's BulkRead method.
0879    /// The RBulkValues class does not modify the array in-between calls to the field's BulkRead method.
0880    std::vector<unsigned char> fAuxData;
0881 
0882    void ReleaseValues();
0883    /// Sets a new range for the bulk. If there is enough capacity, the `fValues` array will be reused.
0884    /// Otherwise a new array is allocated. After reset, fMaskAvail is false for all values.
0885    void Reset(RNTupleLocalIndex firstIndex, std::size_t size);
0886 
0887    bool ContainsRange(RNTupleLocalIndex firstIndex, std::size_t size) const
0888    {
0889       if (firstIndex.GetClusterId() != fFirstIndex.GetClusterId())
0890          return false;
0891       return (firstIndex.GetIndexInCluster() >= fFirstIndex.GetIndexInCluster()) &&
0892              ((firstIndex.GetIndexInCluster() + size) <= (fFirstIndex.GetIndexInCluster() + fSize));
0893    }
0894 
0895    void *GetValuePtrAt(std::size_t idx) const { return reinterpret_cast<unsigned char *>(fValues) + idx * fValueSize; }
0896 
0897    explicit RBulkValues(RFieldBase *field)
0898       : fField(field), fDeleter(field->GetDeleter()), fValueSize(field->GetValueSize())
0899    {
0900    }
0901 
0902 public:
0903    ~RBulkValues();
0904    RBulkValues(const RBulkValues &) = delete;
0905    RBulkValues &operator=(const RBulkValues &) = delete;
0906    RBulkValues(RBulkValues &&other);
0907    RBulkValues &operator=(RBulkValues &&other);
0908 
0909    // Sets `fValues` and `fSize`/`fCapacity` to the given values. The capacity is specified in number of values.
0910    // Once a buffer is adopted, an attempt to read more values then available throws an exception.
0911    void AdoptBuffer(void *buf, std::size_t capacity);
0912 
0913    /// Reads `size` values from the associated field, starting from `firstIndex`. Note that the index is given
0914    /// relative to a certain cluster. The return value points to the array of read objects.
0915    /// The `maskReq` parameter is a bool array of at least `size` elements. Only objects for which the mask is
0916    /// true are guaranteed to be read in the returned value array. A `nullptr` means to read all elements.
0917    void *ReadBulk(RNTupleLocalIndex firstIndex, const bool *maskReq, std::size_t size)
0918    {
0919       if (!ContainsRange(firstIndex, size))
0920          Reset(firstIndex, size);
0921 
0922       // We may read a subrange of the currently available range
0923       auto offset = firstIndex.GetIndexInCluster() - fFirstIndex.GetIndexInCluster();
0924 
0925       if (fNValidValues == fSize)
0926          return GetValuePtrAt(offset);
0927 
0928       RBulkSpec bulkSpec;
0929       bulkSpec.fFirstIndex = firstIndex;
0930       bulkSpec.fCount = size;
0931       bulkSpec.fMaskReq = maskReq;
0932       bulkSpec.fMaskAvail = &fMaskAvail[offset];
0933       bulkSpec.fValues = GetValuePtrAt(offset);
0934       bulkSpec.fAuxData = &fAuxData;
0935       auto nRead = fField->ReadBulk(bulkSpec);
0936       if (nRead == RBulkSpec::kAllSet) {
0937          // We expect that field implementations consistently return kAllSet either in all cases or never. This avoids
0938          // the following case where we would have to manually count how many valid values we actually have:
0939          // 1. A partial ReadBulk, according to maskReq, with values potentially missing in the middle.
0940          // 2. A second ReadBulk that reads a complete subrange. If this returned kAllSet, we don't know how to update
0941          // fNValidValues, other than counting. The field should return a concrete number of how many new values it read
0942          // in addition to those already present.
0943          R__ASSERT((offset == 0) && (size == fSize));
0944          fNValidValues = fSize;
0945       } else {
0946          fNValidValues += nRead;
0947       }
0948       return GetValuePtrAt(offset);
0949    }
0950 
0951    /// Overload to read all elements in the given cluster range.
0952    void *ReadBulk(ROOT::RNTupleLocalRange range) { return ReadBulk(*range.begin(), nullptr, range.size()); }
0953 };
0954 
0955 namespace Internal {
0956 // At some point, RFieldBase::OnClusterCommit() may allow for a user-defined callback to change the
0957 // column representation. For now, we inject this for testing and internal use only.
0958 struct RFieldRepresentationModifier {
0959    static void SetPrimaryColumnRepresentation(RFieldBase &field, std::uint16_t newRepresentationIdx)
0960    {
0961       R__ASSERT(newRepresentationIdx < field.fColumnRepresentatives.size());
0962       const auto N = field.fColumnRepresentatives[0].get().size();
0963       R__ASSERT(N >= 1 && N <= 2);
0964       R__ASSERT(field.fPrincipalColumn);
0965       field.fPrincipalColumn = field.fAvailableColumns[newRepresentationIdx * N].get();
0966       if (field.fAuxiliaryColumn) {
0967          R__ASSERT(N == 2);
0968          field.fAuxiliaryColumn = field.fAvailableColumns[newRepresentationIdx * N + 1].get();
0969       }
0970    }
0971 };
0972 } // namespace Internal
0973 } // namespace ROOT
0974 
0975 #endif