@@ -686,42 +686,110 @@ ROOT::Experimental::RSoAField::RSoAField(std::string_view fieldName, std::string
686686{
687687}
688688
689+ void ROOT::Experimental::RSoAField::GraftNestedMemberFields (
690+ const RSoAField &nestedSoA, std::size_t offsetInParent,
691+ std::function<RFieldBase *(const std::string &)> fnRecordFieldFinder)
692+ {
693+ const std::size_t nNestedRecordMemberFields = nestedSoA.fRecordMemberFields .size ();
694+
695+ // The qualified field name of fields in nestedSoA->fRecordMemberFields will have a "<field name>._0."
696+ // prefix because these fields are rooted in a collection named after the nested SoA field.
697+ //
698+ // E.g., in the following example:
699+ //
700+ // struct SoA_A { struct Record_A {
701+ // SoA_B fB; Record_B fB;
702+ // }; };
703+ //
704+ // struct SoA_B { struct Record_B {
705+ // ROOT::RVec<float> fX; float fX;
706+ // }; };
707+ //
708+ // The on-disk schema of SoA_A is "collection of Record_A", and the on-disk schema of SoA_B is
709+ // "collection of Record_B".
710+ // The qualified field name of fX in the subfield hiararchy of SoA_A is <field name>._0.fB.fX.
711+ // The qualified field name of fX in the subfield hiararchy of SoA_B is <field name>._0.fX.
712+ const auto lenPrefix = nestedSoA.GetFieldName ().length () + strlen (" ._0." );
713+
714+ for (std::size_t i = 0 ; i < nNestedRecordMemberFields; ++i) {
715+ const auto fieldNameForMatching =
716+ nestedSoA.GetFieldName () + " ." + nestedSoA.fRecordMemberFields [i]->GetQualifiedFieldName ().substr (lenPrefix);
717+
718+ fRecordMemberFields .emplace_back (fnRecordFieldFinder (fieldNameForMatching));
719+ fRecordMemberDeleters .emplace_back (GetDeleterOf (*nestedSoA.fRecordMemberFields [i]));
720+ fSoAMemberOffsets .emplace_back (offsetInParent + nestedSoA.fSoAMemberOffsets [i]);
721+ }
722+ }
723+
689724void ROOT::Experimental::RSoAField::CollectRecordMemberFields ()
690725{
691- std::vector<RFieldBase *> realRecordMemberFields;
726+ // Build a map of all subfields (nested) of the underlying record type. Map the fully qualified name of the
727+ // subfields to their field pointer, so that we can later match the subfields of the SoA class to their corresponding
728+ // fields in the underlying record type. Note that the members of the SoA class and the underlying record type
729+ // can have different ordering. However, the base classes of the SoA class and the underlying record type must match
730+ // in order.
731+
732+ std::vector<RFieldBase *> realRecordMemberFields; // Contains all subfields of the underlying record type
733+ // Qualified field name --> index in realRecordMemberFields
734+ std::unordered_map<std::string, std::size_t > recordFieldNameToIdx;
735+
736+ // Count the top-level subfields of the underlying record type for cross-check with the SoA type
692737 unsigned int nDirectRecordSubfields = 0 ;
738+ unsigned int nDirectRecordBases = 0 ;
739+
693740 for (auto itr = fSubfields [0 ]->begin (), iEnd = fSubfields [0 ]->end (); itr != iEnd; ++itr) {
694- if (itr->GetParent () == fSubfields [0 ].get ())
695- nDirectRecordSubfields++;
741+ if (itr->GetParent () == fSubfields [0 ].get ()) {
742+ (itr->GetFieldName ()[0 ] == ' :' ) ? nDirectRecordBases++ : nDirectRecordSubfields++;
743+ }
744+
745+ // Build the qualified field name for matching. We root the qualified field name at the underlying record type.
746+ auto qualifiedName = itr->GetFieldName ();
747+ auto parent = itr->GetParent ();
748+ while (parent != fSubfields [0 ].get ()) {
749+ qualifiedName = parent->GetFieldName () + " ." + qualifiedName;
750+ parent = parent->GetParent ();
751+ }
752+ recordFieldNameToIdx[qualifiedName] = realRecordMemberFields.size ();
753+
696754 realRecordMemberFields.emplace_back (&(*itr));
697755 }
698756
699- for (const auto f : realRecordMemberFields) {
700- if (f->GetFieldName ()[0 ] == ' :' ) {
701- throw RException (R__FAIL (" SoA fields with inheritance are currently unsupported" ));
702- }
757+ // Base classes are treated as unrolled nested SoA classes
758+ const auto *soaBases = fSoAClass ->GetListOfBases ();
759+ if (soaBases->GetSize () != static_cast <Int_t>(nDirectRecordBases)) {
760+ throw RException (R__FAIL (std::string (" number of base classes don't match between SoA class " ) + GetFieldName () +
761+ " and its underlying record type" ));
703762 }
763+ for (unsigned int i = 0 ; i < static_cast <unsigned int >(soaBases->GetSize ()); ++i) {
764+ auto base = static_cast <TBaseClass *>(soaBases->At (i));
765+ if (base->GetDelta () < 0 ) {
766+ throw RException (R__FAIL (std::string (" virtual inheritance is not supported: " ) + GetTypeName () +
767+ " virtually inherits from " + base->GetName ()));
768+ }
769+ TClass *cl = base->GetClassPointer ();
704770
705- // Build map name --> index in realRecordMemberFields vector. Cut the name prefix up to the collection subfields
706- // so that it matches the data member name
707- std::unordered_map<std::string, std::size_t > recordFieldNameToIdx;
708- {
709- recordFieldNameToIdx.reserve (realRecordMemberFields.size ());
710- const auto lenFieldNamePrefix = GetFieldName ().length () + strlen (" ._0." );
711- for (std::size_t i = 0 ; i < realRecordMemberFields.size (); ++i) {
712- recordFieldNameToIdx[realRecordMemberFields[i]->GetQualifiedFieldName ().substr (lenFieldNamePrefix)] = i;
771+ const auto baseFieldName = std::string (" :_" ) + std::to_string (i);
772+
773+ // SoA class `A` inherits from a SoA class `B` whose underlying record type is `X` if and only if the underlying
774+ // record type of `A` inherits from a type `X`.
775+ const auto underlyingBaseTypeName = ROOT::Internal::GetRNTupleSoARecord (cl);
776+ auto recordBaseField = realRecordMemberFields[recordFieldNameToIdx[baseFieldName]];
777+ if (underlyingBaseTypeName != recordBaseField->GetTypeName ()) {
778+ throw RException (R__FAIL (std::string (" inheritance of SoA class " ) + GetFieldName () +
779+ " does not match its underlying record type" ));
713780 }
714- }
715781
716- const auto *bases = fSoAClass -> GetListOfBases () ;
717- assert (bases);
718- for ( auto baseClass : ROOT ::Detail::TRangeStaticCast<TBaseClass>(*bases)) {
719- if (baseClass-> GetDelta () < 0 ) {
720- throw RException (R__FAIL (std::string (" virtual inheritance is not supported : " ) + GetTypeName () +
721- " virtually inherits from " + baseClass-> GetName () ));
782+ std::unique_ptr<RSoAField> soaBaseField ;
783+ try {
784+ soaBaseField = std::make_unique<RSoAField>(baseFieldName, cl-> GetName ());
785+ } catch ( const RException &e ) {
786+ throw RException (R__FAIL (std::string (" invalid field type in base class : " ) + cl-> GetName () + " of SoA field " +
787+ GetFieldName () + " ( " + e. what () + " ) " ));
722788 }
723- // At a later point, we will support inheritance
724- throw RException (R__FAIL (" SoA fields with inheritance are currently unsupported" ));
789+
790+ GraftNestedMemberFields (*soaBaseField, base->GetDelta (), [&](const std::string &name) {
791+ return realRecordMemberFields[recordFieldNameToIdx[name]];
792+ });
725793 }
726794
727795 unsigned int nMembers = 0 ;
@@ -753,17 +821,9 @@ void ROOT::Experimental::RSoAField::CollectRecordMemberFields()
753821 underlyingField->GetTypeName ()));
754822 }
755823
756- const auto lenFieldNamePrefix = soaField->GetFieldName ().length () + strlen (" ._0." );
757-
758- for (std::size_t i = 0 ; i < soaField->fRecordMemberFields .size (); ++i) {
759- const auto f = soaField->fRecordMemberFields [i];
760- const auto fieldNameForMatching =
761- dmField->GetFieldName () + " ." + f->GetQualifiedFieldName ().substr (lenFieldNamePrefix);
762-
763- fRecordMemberFields .emplace_back (realRecordMemberFields[recordFieldNameToIdx[fieldNameForMatching]]);
764- fRecordMemberDeleters .emplace_back (GetDeleterOf (*soaField->fRecordMemberFields [i]));
765- fSoAMemberOffsets .emplace_back (dataMember->GetOffset () + soaField->fSoAMemberOffsets [i]);
766- }
824+ GraftNestedMemberFields (*soaField, dataMember->GetOffset (), [&](const std::string &name) {
825+ return realRecordMemberFields[recordFieldNameToIdx[name]];
826+ });
767827 } else if (auto vecField = dynamic_cast <RRVecField *>(dmField.get ())) {
768828 if (vecField->begin ()->GetTypeName () != underlyingField->GetTypeName () ||
769829 vecField->begin ()->GetTypeAlias () != underlyingField->GetTypeAlias ()) {
@@ -945,6 +1005,15 @@ void ROOT::Experimental::RSoAField::ReconstructSplitFields() const
9451005 fSplitFields = std::make_unique<std::vector<std::unique_ptr<ROOT ::RFieldBase>>>();
9461006 fSplitOffsets = std::make_unique<std::vector<std::size_t >>();
9471007
1008+ unsigned int iBase = 0 ;
1009+ for (auto base : ROOT ::Detail::TRangeStaticCast<TBaseClass>(*fSoAClass ->GetListOfBases ())) {
1010+ TClass *cl = base->GetClassPointer ();
1011+ auto baseField = RFieldBase::Create (std::string (" :_" + std::to_string (iBase)), cl->GetName ()).Unwrap ();
1012+ fSplitFields ->emplace_back (std::move (baseField));
1013+ fSplitOffsets ->emplace_back (base->GetDelta ());
1014+ iBase++;
1015+ }
1016+
9481017 for (auto dataMember : ROOT ::Detail::TRangeStaticCast<TDataMember>(*fSoAClass ->GetListOfDataMembers ())) {
9491018 if ((dataMember->Property () & kIsStatic ) || !dataMember->IsPersistent ())
9501019 continue ;
0 commit comments