diff --git a/tree/ntuple/inc/ROOT/RField.hxx b/tree/ntuple/inc/ROOT/RField.hxx index f47cf9c4aca20..28699049683da 100644 --- a/tree/ntuple/inc/ROOT/RField.hxx +++ b/tree/ntuple/inc/ROOT/RField.hxx @@ -355,17 +355,24 @@ public: template class RSimpleField : public RFieldBase { + void ReconcileIntegralField(const RNTupleDescriptor &desc); + void ReconcileFloatingPointField(const RNTupleDescriptor &desc); + protected: void GenerateColumns() override { GenerateColumnsImpl(); } void GenerateColumns(const ROOT::RNTupleDescriptor &desc) override { GenerateColumnsImpl(desc); } void ConstructValue(void *where) const final { new (where) T{0}; } - void ReconcileOnDiskField(const RNTupleDescriptor &desc) final + void ReconcileOnDiskField(const RNTupleDescriptor &desc) override { - // Differences in the type name don't matter for simple fields; the valid column representations take - // care of (allowed) schema differences. - EnsureMatchingOnDiskField(desc.GetFieldDescriptor(GetOnDiskId()), kDiffTypeName); + if constexpr (std::is_integral_v) { + ReconcileIntegralField(desc); + } else if constexpr (std::is_floating_point_v) { + ReconcileFloatingPointField(desc); + } else { + RFieldBase::ReconcileOnDiskField(desc); + } } RSimpleField(std::string_view name, std::string_view type) diff --git a/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx b/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx index b70cc90e2eaa0..a24af5fc9342e 100644 --- a/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx +++ b/tree/ntuple/inc/ROOT/RNTupleDescriptor.hxx @@ -149,6 +149,10 @@ public: /// natively supported stdlib classes. /// The dictionary does not need to be available for this method. bool IsCustomClass() const; + /// Tells if the field describes a user-defined enum type. + /// The dictionary does not need to be available for this method. + /// Needs the full descriptor to look up sub fields. + bool IsCustomEnum(const RNTupleDescriptor &desc) const; }; // clang-format off diff --git a/tree/ntuple/src/RField.cxx b/tree/ntuple/src/RField.cxx index c1aad7d3943c3..c59d63e03e7cc 100644 --- a/tree/ntuple/src/RField.cxx +++ b/tree/ntuple/src/RField.cxx @@ -92,6 +92,41 @@ const ROOT::RField> *ROOT::RCardinalityF //------------------------------------------------------------------------------ +template +void ROOT::RSimpleField::ReconcileIntegralField(const RNTupleDescriptor &desc) +{ + const RFieldDescriptor &fieldDesc = desc.GetFieldDescriptor(GetOnDiskId()); + EnsureMatchingOnDiskField(fieldDesc, kDiffTypeName); + + if (fieldDesc.IsCustomEnum(desc)) { + SetOnDiskId(desc.FindFieldId("_0", GetOnDiskId())); + return; + } + + static const std::string gIntegralTypeNames[] = {"bool", "char", "std::int8_t", "std::uint8_t", + "std::int16_t", "std::uint16_t", "std::int32_t", "std::uint32_t", + "std::int64_t", "std::uint64_t"}; + if (std::find(std::begin(gIntegralTypeNames), std::end(gIntegralTypeNames), fieldDesc.GetTypeName()) == + std::end(gIntegralTypeNames)) { + throw RException(R__FAIL("unexpected on-disk type name '" + fieldDesc.GetTypeName() + "' for field of type '" + + GetTypeName() + "'")); + } +} + +template +void ROOT::RSimpleField::ReconcileFloatingPointField(const RNTupleDescriptor &desc) +{ + const RFieldDescriptor &fieldDesc = desc.GetFieldDescriptor(GetOnDiskId()); + EnsureMatchingOnDiskField(fieldDesc, kDiffTypeName); + + if (!(fieldDesc.GetTypeName() == "float" || fieldDesc.GetTypeName() == "double")) { + throw RException(R__FAIL("unexpected on-disk type name '" + fieldDesc.GetTypeName() + "' for field of type '" + + GetTypeName() + "'")); + } +} + +//------------------------------------------------------------------------------ + template class ROOT::RSimpleField; const ROOT::RFieldBase::RColumnRepresentations &ROOT::RField::GetColumnRepresentations() const diff --git a/tree/ntuple/src/RNTupleDescriptor.cxx b/tree/ntuple/src/RNTupleDescriptor.cxx index 66201b13d486f..a0976d324a23f 100644 --- a/tree/ntuple/src/RNTupleDescriptor.cxx +++ b/tree/ntuple/src/RNTupleDescriptor.cxx @@ -169,6 +169,24 @@ bool ROOT::RFieldDescriptor::IsCustomClass() const return true; } +bool ROOT::RFieldDescriptor::IsCustomEnum(const RNTupleDescriptor &desc) const +{ + if (fStructure != ROOT::ENTupleStructure::kPlain) + return false; + if (fTypeName.rfind("std::", 0) == 0) + return false; + + auto subFieldId = desc.FindFieldId("_0", fFieldId); + if (subFieldId == kInvalidDescriptorId) + return false; + + static const std::string gIntTypeNames[] = {"bool", "char", "std::int8_t", "std::uint8_t", + "std::int16_t", "std::uint16_t", "std::int32_t", "std::uint32_t", + "std::int64_t", "std::uint64_t"}; + return std::find(std::begin(gIntTypeNames), std::end(gIntTypeNames), + desc.GetFieldDescriptor(subFieldId).GetTypeName()) != std::end(gIntTypeNames); +} + //////////////////////////////////////////////////////////////////////////////// bool ROOT::RColumnDescriptor::operator==(const RColumnDescriptor &other) const diff --git a/tree/ntuple/test/ntuple_evolution_type.cxx b/tree/ntuple/test/ntuple_evolution_type.cxx index 950672f254225..c7700f6b0e2c5 100644 --- a/tree/ntuple/test/ntuple_evolution_type.cxx +++ b/tree/ntuple/test/ntuple_evolution_type.cxx @@ -207,3 +207,25 @@ TEST(RNTupleEvolution, CheckPairTuple) EXPECT_EQ(1, std::get<0>(p(0))); EXPECT_DOUBLE_EQ(2.0, std::get<1>(p(0))); } + +TEST(RNTupleEvolution, Enum) +{ + FileRaii fileGuard("test_ntuple_evolution_check_pair_tuple.root"); + { + auto model = ROOT::RNTupleModel::Create(); + auto e1 = model->MakeField("e1"); + auto e2 = model->MakeField("e2"); + auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath()); + + *e1 = static_cast(42); + *e2 = static_cast(137); + + writer->Fill(); + } + + auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath()); + auto ve1 = reader->GetView("e1"); + auto ve2 = reader->GetView("e2"); + EXPECT_EQ(42, ve1(0)); + EXPECT_EQ(137, ve2(0)); +} diff --git a/tree/ntuple/test/ntuple_types.cxx b/tree/ntuple/test/ntuple_types.cxx index 6de5bff86ec2f..b47b33131fbbd 100644 --- a/tree/ntuple/test/ntuple_types.cxx +++ b/tree/ntuple/test/ntuple_types.cxx @@ -1669,8 +1669,7 @@ TEST(RNTuple, Casting) auto reader = RNTupleReader::Open(std::move(modelB), "ntuple", fileGuard.GetPath()); FAIL() << "should not be able to cast int to float"; } catch (const ROOT::RException &err) { - EXPECT_THAT(err.what(), testing::HasSubstr("On-disk column types")); - EXPECT_THAT(err.what(), testing::HasSubstr("cannot be matched")); + EXPECT_THAT(err.what(), testing::HasSubstr("unexpected on-disk type name")); } auto modelC = RNTupleModel::Create(); diff --git a/tree/ntuple/test/ntuple_view.cxx b/tree/ntuple/test/ntuple_view.cxx index 0af0c959555c5..80a2ca7134ce6 100644 --- a/tree/ntuple/test/ntuple_view.cxx +++ b/tree/ntuple/test/ntuple_view.cxx @@ -609,7 +609,6 @@ TEST(RNTuple, VoidWithExternalAddressAndTypeName) void *bazAsIntsPtr = &intVec; reader->GetView("baz", bazAsIntsPtr, "std::vector"); } catch (const ROOT::RException &err) { - EXPECT_THAT(err.what(), testing::HasSubstr("On-disk column types {`SplitReal32`} for field `baz._0` cannot be " - "matched to its in-memory type `std::int32_t`")); + EXPECT_THAT(err.what(), testing::HasSubstr("unexpected on-disk type")); } }