@@ -264,3 +264,154 @@ TEST(RNTuple, SoASimpleSwapped)
264264 EXPECT_FLOAT_EQ (1.0 , v (0 ).at (0 ).fX );
265265 EXPECT_FLOAT_EQ (2.0 , v (0 ).at (0 ).fY );
266266}
267+
268+ TEST (RNTuple, SoABasicWriteRead)
269+ {
270+ ROOT ::TestSupport::FileRaii fileGuard (" test_rntuple_soa_write_read.root" );
271+
272+ {
273+ auto model = ROOT::RNTupleModel::Create ();
274+ model->AddField (std::make_unique<RSoAField>(" simple" , " SoASimple" ));
275+ model->AddField (std::make_unique<RSoAField>(" empty" , " SoA" ));
276+
277+ auto writer = ROOT::RNTupleWriter::Recreate (std::move (model), " ntpl" , fileGuard.GetPath ());
278+ auto simpleSoA = writer->GetModel ().GetDefaultEntry ().GetPtr <SoASimple>(" simple" );
279+ auto emptySoA = writer->GetModel ().GetDefaultEntry ().GetPtr <SoA>(" empty" );
280+
281+ simpleSoA->fX .push_back (1.0 );
282+ simpleSoA->fY .push_back (2.0 );
283+ writer->Fill ();
284+ writer->CommitCluster ();
285+
286+ simpleSoA->fX .clear ();
287+ simpleSoA->fY .clear ();
288+ writer->Fill ();
289+
290+ simpleSoA->fX .push_back (3.0 );
291+ simpleSoA->fY .push_back (4.0 );
292+ simpleSoA->fX .push_back (5.0 );
293+ simpleSoA->fY .push_back (6.0 );
294+ writer->Fill ();
295+ }
296+
297+ auto reader = ROOT::RNTupleReader::Open (" ntpl" , fileGuard.GetPath ());
298+ EXPECT_EQ (3u , reader->GetNEntries ());
299+ auto simpleSoA = reader->GetModel ().GetDefaultEntry ().GetPtr <SoASimple>(" simple" );
300+
301+ reader->LoadEntry (0 );
302+ EXPECT_EQ (1U , simpleSoA->fX .size ());
303+ EXPECT_EQ (1U , simpleSoA->fY .size ());
304+ EXPECT_FLOAT_EQ (1.0 , simpleSoA->fX [0 ]);
305+ EXPECT_FLOAT_EQ (2.0 , simpleSoA->fY [0 ]);
306+
307+ reader->LoadEntry (1 );
308+ EXPECT_TRUE (simpleSoA->fX .empty ());
309+ EXPECT_TRUE (simpleSoA->fY .empty ());
310+
311+ reader->LoadEntry (2 );
312+ EXPECT_EQ (2U , simpleSoA->fX .size ());
313+ EXPECT_EQ (2U , simpleSoA->fY .size ());
314+ EXPECT_FLOAT_EQ (3.0 , simpleSoA->fX [0 ]);
315+ EXPECT_FLOAT_EQ (4.0 , simpleSoA->fY [0 ]);
316+ EXPECT_FLOAT_EQ (5.0 , simpleSoA->fX [1 ]);
317+ EXPECT_FLOAT_EQ (6.0 , simpleSoA->fY [1 ]);
318+ }
319+
320+ TEST (RNTuple, SoAReadAdopted)
321+ {
322+ ROOT ::TestSupport::FileRaii fileGuard (" test_rntuple_soa_read_adopted.root" );
323+
324+ {
325+ auto model = ROOT::RNTupleModel::Create ();
326+ model->AddField (std::make_unique<RSoAField>(" simple" , " SoASimple" ));
327+
328+ auto writer = ROOT::RNTupleWriter::Recreate (std::move (model), " ntpl" , fileGuard.GetPath ());
329+ auto simpleSoA = writer->GetModel ().GetDefaultEntry ().GetPtr <SoASimple>(" simple" );
330+
331+ simpleSoA->fX .push_back (1.0 );
332+ simpleSoA->fY .push_back (2.0 );
333+ simpleSoA->fX .push_back (3.0 );
334+ simpleSoA->fY .push_back (4.0 );
335+ writer->Fill ();
336+ }
337+
338+ auto reader = ROOT::RNTupleReader::Open (" ntpl" , fileGuard.GetPath ());
339+ EXPECT_EQ (1u , reader->GetNEntries ());
340+
341+ auto viewSize = reader->GetView <ROOT ::RNTupleCardinality<std::uint64_t >>(" simple" );
342+ EXPECT_EQ (2u , viewSize (0 ));
343+
344+ float x[2 ] = {.0 , .0 };
345+ float y[2 ] = {.0 , .0 };
346+ SoASimple soa;
347+ soa.fX = ROOT ::RVec<float >(x, 2 );
348+ soa.fY = ROOT ::RVec<float >(y, 2 );
349+ auto viewSoA = reader->GetView (" simple" , &soa, " SoASimple" );
350+ viewSoA (0 );
351+ EXPECT_FLOAT_EQ (1.0 , x[0 ]);
352+ EXPECT_FLOAT_EQ (2.0 , y[0 ]);
353+ EXPECT_FLOAT_EQ (3.0 , x[1 ]);
354+ EXPECT_FLOAT_EQ (4.0 , y[1 ]);
355+ }
356+
357+ TEST (RNTuple, SoAReadComplex)
358+ {
359+ ROOT ::TestSupport::FileRaii fileGuard (" test_rntuple_soa_read_complex.root" );
360+
361+ {
362+ auto model = ROOT::RNTupleModel::Create ();
363+ model->AddField (std::make_unique<RSoAField>(" complex" , " SoAComplex" ));
364+
365+ auto writer = ROOT::RNTupleWriter::Recreate (std::move (model), " ntpl" , fileGuard.GetPath ());
366+ auto complexSoA = writer->GetModel ().GetDefaultEntry ().GetPtr <SoAComplex>(" complex" );
367+
368+ complexSoA->fA .resize (2 );
369+ writer->Fill ();
370+ complexSoA->fA .clear ();
371+ writer->Fill ();
372+ }
373+
374+ auto reader = ROOT::RNTupleReader::Open (" ntpl" , fileGuard.GetPath ());
375+ EXPECT_EQ (2u , reader->GetNEntries ());
376+
377+ auto complexSoA = reader->GetModel ().GetDefaultEntry ().GetPtr <SoAComplex>(" complex" );
378+ ComplexStruct::gNCallConstructor = 0 ;
379+ ComplexStruct::gNCallDestructor = 0 ;
380+
381+ reader->LoadEntry (0 );
382+ EXPECT_EQ (2U , complexSoA->fA .size ());
383+ EXPECT_EQ (2 , ComplexStruct::gNCallConstructor );
384+ EXPECT_EQ (0 , ComplexStruct::gNCallDestructor );
385+
386+ reader->LoadEntry (1 );
387+ EXPECT_TRUE (complexSoA->fA .empty ());
388+ EXPECT_EQ (2 , ComplexStruct::gNCallConstructor );
389+ EXPECT_EQ (2 , ComplexStruct::gNCallDestructor );
390+ }
391+
392+ TEST (RNTuple, SoAFromVector)
393+ {
394+ ROOT ::TestSupport::FileRaii fileGuard (" test_rntuple_soa_from_vector.root" );
395+
396+ {
397+ auto model = ROOT::RNTupleModel::Create ();
398+ auto v = model->MakeField <std::vector<RecordSimple>>(" simple" );
399+
400+ auto writer = ROOT::RNTupleWriter::Recreate (std::move (model), " ntpl" , fileGuard.GetPath ());
401+ v->emplace_back (RecordSimple{1.0 , 2.0 });
402+
403+ writer->Fill ();
404+ }
405+
406+ auto reader = ROOT::RNTupleReader::Open (" ntpl" , fileGuard.GetPath ());
407+ SoASimple soa;
408+
409+ // Until SoA schema evolution is implemented, the reading the vector as SoA will
410+ try {
411+ reader->GetView (" simple" , &soa, " SoASimple" );
412+ FAIL () << " reading a vector with a SoA field should fail" ;
413+ } catch (const ROOT ::RException &e) {
414+ EXPECT_THAT (e.what (), testing::HasSubstr (
415+ " in-memory field simple of type SoASimple is incompatible with on-disk field simple" ));
416+ }
417+ }
0 commit comments