Skip to content

Commit 2c021b8

Browse files
committed
[ntuple] fix automatic schema evolution of pair/tuple
For std::pair and std::tuple, check that the number of subfields in memory and on disk is identical. For 2 subfields, std::pair and std::tuple are interchangible. The pair/tuple elements don't need to be an exact match but are themselves subject to automatic schema evolution.
1 parent 816001b commit 2c021b8

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

tree/ntuple/inc/ROOT/RField/RFieldRecord.hxx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ protected:
136136
RPairField(std::string_view fieldName, std::array<std::unique_ptr<RFieldBase>, 2> itemFields,
137137
const std::array<std::size_t, 2> &offsets);
138138

139+
void ReconcileOnDiskField(const RNTupleDescriptor &desc) final;
140+
139141
public:
140142
RPairField(std::string_view fieldName, std::array<std::unique_ptr<RFieldBase>, 2> itemFields);
141143
RPairField(RPairField &&other) = default;
@@ -186,6 +188,8 @@ protected:
186188
RTupleField(std::string_view fieldName, std::vector<std::unique_ptr<RFieldBase>> itemFields,
187189
const std::vector<std::size_t> &offsets);
188190

191+
void ReconcileOnDiskField(const RNTupleDescriptor &desc) final;
192+
189193
public:
190194
RTupleField(std::string_view fieldName, std::vector<std::unique_ptr<RFieldBase>> itemFields);
191195
RTupleField(RTupleField &&other) = default;

tree/ntuple/src/RFieldMeta.cxx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,21 @@ ROOT::RPairField::RPairField(std::string_view fieldName, std::array<std::unique_
624624
fOffsets.push_back(secondElem->GetThisOffset());
625625
}
626626

627+
void ROOT::RPairField::ReconcileOnDiskField(const RNTupleDescriptor &desc)
628+
{
629+
static const std::vector<std::string> prefixes = {"std::pair<", "std::tuple<"};
630+
631+
const auto &fieldDesc = desc.GetFieldDescriptor(GetOnDiskId());
632+
EnsureMatchingOnDiskField(fieldDesc, kDiffTypeName);
633+
EnsureMatchingTypePrefix(fieldDesc, prefixes);
634+
635+
const auto nOnDiskSubfields = fieldDesc.GetLinkIds().size();
636+
if (nOnDiskSubfields != 2) {
637+
throw ROOT::RException(
638+
R__FAIL("invalid number of on-disk subfields for std::pair " + std::to_string(nOnDiskSubfields)));
639+
}
640+
}
641+
627642
//------------------------------------------------------------------------------
628643

629644
ROOT::RProxiedCollectionField::RCollectionIterableOnce::RIteratorFuncs
@@ -1151,6 +1166,22 @@ ROOT::RTupleField::RTupleField(std::string_view fieldName, std::vector<std::uniq
11511166
}
11521167
}
11531168

1169+
void ROOT::RTupleField::ReconcileOnDiskField(const RNTupleDescriptor &desc)
1170+
{
1171+
static const std::vector<std::string> prefixes = {"std::pair<", "std::tuple<"};
1172+
1173+
const auto &fieldDesc = desc.GetFieldDescriptor(GetOnDiskId());
1174+
EnsureMatchingOnDiskField(fieldDesc, kDiffTypeName);
1175+
EnsureMatchingTypePrefix(fieldDesc, prefixes);
1176+
1177+
const auto nOnDiskSubfields = fieldDesc.GetLinkIds().size();
1178+
const auto nSubfields = fSubfields.size();
1179+
if (nOnDiskSubfields != nSubfields) {
1180+
throw ROOT::RException(R__FAIL("invalid number of on-disk subfields for std::tuple " +
1181+
std::to_string(nOnDiskSubfields) + " vs. " + std::to_string(nSubfields)));
1182+
}
1183+
}
1184+
11541185
//------------------------------------------------------------------------------
11551186

11561187
namespace {

tree/ntuple/test/ntuple_evolution_type.cxx

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <new>
55
#include <string>
66
#include <string_view>
7+
#include <tuple>
78
#include <utility>
89

910
class RNewStringField final : public ROOT::RFieldBase {
@@ -122,3 +123,49 @@ TEST(RNTupleEvolution, CheckRepetitionCount)
122123
EXPECT_THAT(err.what(), testing::HasSubstr("repetition count 3 vs. 2"));
123124
}
124125
}
126+
127+
TEST(RNTupleEvolution, CheckPairTuple)
128+
{
129+
FileRaii fileGuard("test_ntuple_evolution_check_pair_tuple.root");
130+
{
131+
auto model = ROOT::RNTupleModel::Create();
132+
auto p = model->MakeField<std::pair<char, float>>("p");
133+
auto t2 = model->MakeField<std::tuple<char, float>>("t2");
134+
model->MakeField<std::tuple<char, float, float>>("t3");
135+
auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "ntpl", fileGuard.GetPath());
136+
137+
*p = {1, 2.0};
138+
*t2 = {3, 4.0};
139+
writer->Fill();
140+
}
141+
142+
auto reader = RNTupleReader::Open("ntpl", fileGuard.GetPath());
143+
144+
try {
145+
reader->GetView<std::tuple<char>>("p");
146+
FAIL() << "non-matching number of subfields for pair/tuple should throw";
147+
} catch (const ROOT::RException &err) {
148+
EXPECT_THAT(err.what(), testing::HasSubstr("invalid number of on-disk subfields"));
149+
}
150+
151+
try {
152+
reader->GetView<std::tuple<char, float, float>>("p");
153+
FAIL() << "non-matching number of subfields for pair/tuple should throw";
154+
} catch (const ROOT::RException &err) {
155+
EXPECT_THAT(err.what(), testing::HasSubstr("invalid number of on-disk subfields"));
156+
}
157+
158+
try {
159+
reader->GetView<std::pair<char, float>>("t3");
160+
FAIL() << "non-matching number of subfields for pair/tuple should throw";
161+
} catch (const ROOT::RException &err) {
162+
EXPECT_THAT(err.what(), testing::HasSubstr("invalid number of on-disk subfields"));
163+
}
164+
165+
auto t2 = reader->GetView<std::pair<char, double>>("t2");
166+
auto p = reader->GetView<std::tuple<int, double>>("p");
167+
EXPECT_EQ(3, t2(0).first);
168+
EXPECT_DOUBLE_EQ(4.0, t2(0).second);
169+
EXPECT_EQ(1, std::get<0>(p(0)));
170+
EXPECT_DOUBLE_EQ(2.0, std::get<1>(p(0)));
171+
}

0 commit comments

Comments
 (0)