Skip to content

Commit a78d828

Browse files
committed
[ntuple] Add RNTupleLocatorMulti class for kTypeMulti locator
1 parent a776301 commit a78d828

5 files changed

Lines changed: 168 additions & 8 deletions

File tree

tree/ntuple/inc/ROOT/RNTupleTypes.hxx

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,28 @@ public:
201201
std::uint64_t GetLocation() const { return fLocation; }
202202
};
203203

204+
/// RNTupleLocator payload for the kTypeMulti locator (type 0x03). Used by storage
205+
/// backends that pack multiple pages into shared objects (e.g., S3 Mode A). The two
206+
/// 32-bit fields are written to disk as separate integers, so the wire format is
207+
/// directly interpretable without knowledge of any internal bit packing.
208+
class RNTupleLocatorMulti {
209+
private:
210+
std::uint32_t fObjectId = 0;
211+
std::uint32_t fOffset = 0;
212+
213+
public:
214+
RNTupleLocatorMulti() = default;
215+
RNTupleLocatorMulti(std::uint32_t objectId, std::uint32_t offset) : fObjectId(objectId), fOffset(offset) {}
216+
217+
bool operator==(const RNTupleLocatorMulti &other) const
218+
{
219+
return fObjectId == other.fObjectId && fOffset == other.fOffset;
220+
}
221+
222+
std::uint32_t GetObjectId() const { return fObjectId; }
223+
std::uint32_t GetOffset() const { return fOffset; }
224+
};
225+
204226
// Workaround missing return type overloading
205227
class RNTupleLocator;
206228
namespace Internal {
@@ -216,6 +238,11 @@ template <>
216238
struct RNTupleLocatorHelper<RNTupleLocatorObject64> {
217239
static RNTupleLocatorObject64 Get(const RNTupleLocator &loc);
218240
};
241+
242+
template <>
243+
struct RNTupleLocatorHelper<RNTupleLocatorMulti> {
244+
static RNTupleLocatorMulti Get(const RNTupleLocator &loc);
245+
};
219246
} // namespace Internal
220247

221248
/// Generic information about the physical location of data. Values depend on the concrete storage type. E.g.,
@@ -226,6 +253,7 @@ struct RNTupleLocatorHelper<RNTupleLocatorObject64> {
226253
class RNTupleLocator {
227254
friend struct Internal::RNTupleLocatorHelper<std::uint64_t>;
228255
friend struct Internal::RNTupleLocatorHelper<RNTupleLocatorObject64>;
256+
friend struct Internal::RNTupleLocatorHelper<RNTupleLocatorMulti>;
229257

230258
public:
231259
/// Values for the _Type_ field in non-disk locators. Serializable types must have the MSb == 0; see
@@ -277,7 +305,8 @@ public:
277305
void SetType(ELocatorType type);
278306
void SetReserved(std::uint8_t reserved);
279307

280-
/// Note that for GetPosition() / SetPosition(), the locator type must correspond (kTypeFile, kTypeObject64).
308+
/// Note that for GetPosition() / SetPosition(), the locator type must correspond
309+
/// (kTypeFile, kTypeObject64, kTypeMulti).
281310

282311
template <typename T>
283312
T GetPosition() const
@@ -287,6 +316,7 @@ public:
287316

288317
void SetPosition(std::uint64_t position);
289318
void SetPosition(RNTupleLocatorObject64 position);
319+
void SetPosition(RNTupleLocatorMulti position);
290320
};
291321

292322
namespace Internal {

tree/ntuple/src/RNTupleSerialize.cxx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,50 @@ ROOT::RResult<void> DeserializeLocatorPayloadObject64(const unsigned char *buffe
511511
return ROOT::RResult<void>::Success();
512512
}
513513

514+
std::uint32_t SerializeLocatorPayloadMulti(const ROOT::RNTupleLocator &locator, unsigned char *buffer)
515+
{
516+
const auto &data = locator.GetPosition<ROOT::RNTupleLocatorMulti>();
517+
const uint32_t sizeofNBytesOnStorage = (locator.GetNBytesOnStorage() > std::numeric_limits<std::uint32_t>::max())
518+
? sizeof(std::uint64_t)
519+
: sizeof(std::uint32_t);
520+
if (buffer) {
521+
if (sizeofNBytesOnStorage == sizeof(std::uint32_t)) {
522+
RNTupleSerializer::SerializeUInt32(locator.GetNBytesOnStorage(), buffer);
523+
} else {
524+
RNTupleSerializer::SerializeUInt64(locator.GetNBytesOnStorage(), buffer);
525+
}
526+
RNTupleSerializer::SerializeUInt32(data.GetObjectId(), buffer + sizeofNBytesOnStorage);
527+
RNTupleSerializer::SerializeUInt32(data.GetOffset(),
528+
buffer + sizeofNBytesOnStorage + sizeof(std::uint32_t));
529+
}
530+
return sizeofNBytesOnStorage + 2 * sizeof(std::uint32_t);
531+
}
532+
533+
ROOT::RResult<void> DeserializeLocatorPayloadMulti(const unsigned char *buffer, std::uint32_t sizeofLocatorPayload,
534+
ROOT::RNTupleLocator &locator)
535+
{
536+
std::uint32_t sizeofNBytesOnStorage;
537+
if (sizeofLocatorPayload == 12) {
538+
std::uint32_t nBytesOnStorage;
539+
RNTupleSerializer::DeserializeUInt32(buffer, nBytesOnStorage);
540+
locator.SetNBytesOnStorage(nBytesOnStorage);
541+
sizeofNBytesOnStorage = sizeof(std::uint32_t);
542+
} else if (sizeofLocatorPayload == 16) {
543+
std::uint64_t nBytesOnStorage;
544+
RNTupleSerializer::DeserializeUInt64(buffer, nBytesOnStorage);
545+
locator.SetNBytesOnStorage(nBytesOnStorage);
546+
sizeofNBytesOnStorage = sizeof(std::uint64_t);
547+
} else {
548+
return R__FAIL("invalid Multi locator payload size: " + std::to_string(sizeofLocatorPayload));
549+
}
550+
std::uint32_t objectId;
551+
std::uint32_t offset;
552+
RNTupleSerializer::DeserializeUInt32(buffer + sizeofNBytesOnStorage, objectId);
553+
RNTupleSerializer::DeserializeUInt32(buffer + sizeofNBytesOnStorage + sizeof(std::uint32_t), offset);
554+
locator.SetPosition(ROOT::RNTupleLocatorMulti(objectId, offset));
555+
return ROOT::RResult<void>::Success();
556+
}
557+
514558
std::uint32_t SerializeAliasColumn(const ROOT::RColumnDescriptor &columnDesc,
515559
const ROOT::Internal::RNTupleSerializer::RContext &context, void *buffer)
516560
{
@@ -1091,7 +1135,7 @@ ROOT::Internal::RNTupleSerializer::SerializeLocator(const RNTupleLocator &locato
10911135
locatorType = 0x02;
10921136
break;
10931137
case RNTupleLocator::kTypeMulti:
1094-
size += SerializeLocatorPayloadObject64(locator, payloadp);
1138+
size += SerializeLocatorPayloadMulti(locator, payloadp);
10951139
locatorType = 0x03;
10961140
break;
10971141
default:
@@ -1144,7 +1188,7 @@ ROOT::RResult<std::uint32_t> ROOT::Internal::RNTupleSerializer::DeserializeLocat
11441188
break;
11451189
case 0x03:
11461190
locator.SetType(RNTupleLocator::kTypeMulti);
1147-
DeserializeLocatorPayloadObject64(bytes, payloadSize, locator);
1191+
DeserializeLocatorPayloadMulti(bytes, payloadSize, locator);
11481192
break;
11491193
default: locator.SetType(RNTupleLocator::kTypeUnknown);
11501194
}

tree/ntuple/src/RNTupleTypes.cxx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,18 @@ void ROOT::RNTupleLocator::SetPosition(std::uint64_t position)
8080

8181
void ROOT::RNTupleLocator::SetPosition(RNTupleLocatorObject64 position)
8282
{
83-
if (GetType() != kTypeObject64 && GetType() != kTypeMulti)
83+
if (GetType() != kTypeObject64)
8484
throw RException(R__FAIL("cannot set position as 64bit object for type " + std::to_string(GetType())));
8585
fPosition = position.GetLocation();
8686
}
8787

88+
void ROOT::RNTupleLocator::SetPosition(RNTupleLocatorMulti position)
89+
{
90+
if (GetType() != kTypeMulti)
91+
throw RException(R__FAIL("cannot set position as Multi locator for type " + std::to_string(GetType())));
92+
fPosition = (static_cast<std::uint64_t>(position.GetObjectId()) << 32) | position.GetOffset();
93+
}
94+
8895
std::uint64_t ROOT::Internal::RNTupleLocatorHelper<std::uint64_t>::Get(const RNTupleLocator &loc)
8996
{
9097
if (loc.GetType() != ROOT::RNTupleLocator::kTypeFile)
@@ -95,7 +102,17 @@ std::uint64_t ROOT::Internal::RNTupleLocatorHelper<std::uint64_t>::Get(const RNT
95102
ROOT::RNTupleLocatorObject64
96103
ROOT::Internal::RNTupleLocatorHelper<ROOT::RNTupleLocatorObject64>::Get(const RNTupleLocator &loc)
97104
{
98-
if (loc.GetType() != ROOT::RNTupleLocator::kTypeObject64 && loc.GetType() != ROOT::RNTupleLocator::kTypeMulti)
105+
if (loc.GetType() != ROOT::RNTupleLocator::kTypeObject64)
99106
throw RException(R__FAIL("cannot retrieve position as 64bit object for type " + std::to_string(loc.GetType())));
100107
return RNTupleLocatorObject64{loc.fPosition};
101108
}
109+
110+
ROOT::RNTupleLocatorMulti
111+
ROOT::Internal::RNTupleLocatorHelper<ROOT::RNTupleLocatorMulti>::Get(const RNTupleLocator &loc)
112+
{
113+
if (loc.GetType() != ROOT::RNTupleLocator::kTypeMulti)
114+
throw RException(
115+
R__FAIL("cannot retrieve position as Multi locator for type " + std::to_string(loc.GetType())));
116+
return RNTupleLocatorMulti(static_cast<std::uint32_t>(loc.fPosition >> 32),
117+
static_cast<std::uint32_t>(loc.fPosition));
118+
}

tree/ntuple/test/ntuple_serialize.cxx

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ TEST(RNTuple, SerializeLocator)
402402
// Multi locator round-trip with 32-bit nBytesOnStorage
403403
locator = RNTupleLocator{};
404404
locator.SetType(RNTupleLocator::kTypeMulti);
405-
locator.SetPosition(RNTupleLocatorObject64{42U});
405+
locator.SetPosition(RNTupleLocatorMulti{7, 1024});
406406
locator.SetNBytesOnStorage(1024U);
407407
locator.SetReserved(0);
408408
EXPECT_EQ(16u, RNTupleSerializer::SerializeLocator(locator, buffer).Unwrap());
@@ -411,7 +411,9 @@ TEST(RNTuple, SerializeLocator)
411411
EXPECT_EQ(locator.GetType(), RNTupleLocator::kTypeMulti);
412412
EXPECT_EQ(locator.GetNBytesOnStorage(), 1024U);
413413
EXPECT_EQ(locator.GetReserved(), 0);
414-
EXPECT_EQ(42U, locator.GetPosition<RNTupleLocatorObject64>().GetLocation());
414+
auto multi = locator.GetPosition<RNTupleLocatorMulti>();
415+
EXPECT_EQ(7U, multi.GetObjectId());
416+
EXPECT_EQ(1024U, multi.GetOffset());
415417

416418
// Multi locator round-trip with 64-bit nBytesOnStorage and reserved bit
417419
locator.SetNBytesOnStorage(static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()) + 1);
@@ -422,7 +424,9 @@ TEST(RNTuple, SerializeLocator)
422424
EXPECT_EQ(locator.GetType(), RNTupleLocator::kTypeMulti);
423425
EXPECT_EQ(locator.GetNBytesOnStorage(), static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()) + 1);
424426
EXPECT_EQ(locator.GetReserved(), 1);
425-
EXPECT_EQ(42U, locator.GetPosition<RNTupleLocatorObject64>().GetLocation());
427+
multi = locator.GetPosition<RNTupleLocatorMulti>();
428+
EXPECT_EQ(7U, multi.GetObjectId());
429+
EXPECT_EQ(1024U, multi.GetOffset());
426430

427431
std::int32_t *head = reinterpret_cast<std::int32_t *>(buffer);
428432
#ifndef R__BYTESWAP
@@ -435,6 +439,70 @@ TEST(RNTuple, SerializeLocator)
435439
EXPECT_EQ(locator.GetType(), RNTupleLocator::kTypeUnknown);
436440
}
437441

442+
TEST(RNTuple, RNTupleLocatorMultiFields)
443+
{
444+
// Default-constructed: both fields zero
445+
RNTupleLocatorMulti zero;
446+
EXPECT_EQ(0U, zero.GetObjectId());
447+
EXPECT_EQ(0U, zero.GetOffset());
448+
449+
// Non-trivial values
450+
RNTupleLocatorMulti m(0x12345, 0xABCDE);
451+
EXPECT_EQ(0x12345U, m.GetObjectId());
452+
EXPECT_EQ(0xABCDEU, m.GetOffset());
453+
454+
// Max 32-bit values: both fields use the full uint32 range
455+
RNTupleLocatorMulti maxVals(0xFFFFFFFFU, 0xFFFFFFFFU);
456+
EXPECT_EQ(0xFFFFFFFFU, maxVals.GetObjectId());
457+
EXPECT_EQ(0xFFFFFFFFU, maxVals.GetOffset());
458+
459+
// Object id only: no leak into offset
460+
RNTupleLocatorMulti idOnly(0xFFFFFFFFU, 0U);
461+
EXPECT_EQ(0xFFFFFFFFU, idOnly.GetObjectId());
462+
EXPECT_EQ(0U, idOnly.GetOffset());
463+
464+
// Offset only: no leak into object id
465+
RNTupleLocatorMulti offsetOnly(0U, 0xFFFFFFFFU);
466+
EXPECT_EQ(0U, offsetOnly.GetObjectId());
467+
EXPECT_EQ(0xFFFFFFFFU, offsetOnly.GetOffset());
468+
469+
// Equality semantics
470+
EXPECT_EQ(RNTupleLocatorMulti(1, 2), RNTupleLocatorMulti(1, 2));
471+
EXPECT_FALSE(RNTupleLocatorMulti(1, 2) == RNTupleLocatorMulti(1, 3));
472+
EXPECT_FALSE(RNTupleLocatorMulti(1, 2) == RNTupleLocatorMulti(2, 2));
473+
}
474+
475+
TEST(RNTuple, RNTupleLocatorMultiTypeEnforcement)
476+
{
477+
RNTupleLocator locator;
478+
479+
// SetPosition(Multi) requires kTypeMulti.
480+
locator.SetType(RNTupleLocator::kTypeObject64);
481+
EXPECT_THROW(locator.SetPosition(RNTupleLocatorMulti(1, 2)), ROOT::RException);
482+
locator.SetType(RNTupleLocator::kTypeFile);
483+
EXPECT_THROW(locator.SetPosition(RNTupleLocatorMulti(1, 2)), ROOT::RException);
484+
485+
// SetPosition(Object64) no longer accepts kTypeMulti after the split.
486+
locator = RNTupleLocator{};
487+
locator.SetType(RNTupleLocator::kTypeMulti);
488+
EXPECT_THROW(locator.SetPosition(RNTupleLocatorObject64{1}), ROOT::RException);
489+
490+
// Correct usage round-trip.
491+
locator.SetPosition(RNTupleLocatorMulti(1, 2));
492+
auto m = locator.GetPosition<RNTupleLocatorMulti>();
493+
EXPECT_EQ(1U, m.GetObjectId());
494+
EXPECT_EQ(2U, m.GetOffset());
495+
496+
// GetPosition<Object64>() no longer accepts kTypeMulti after the split.
497+
EXPECT_THROW(locator.GetPosition<RNTupleLocatorObject64>(), ROOT::RException);
498+
499+
// GetPosition<Multi>() rejects non-Multi types.
500+
locator = RNTupleLocator{};
501+
locator.SetType(RNTupleLocator::kTypeObject64);
502+
locator.SetPosition(RNTupleLocatorObject64{1});
503+
EXPECT_THROW(locator.GetPosition<RNTupleLocatorMulti>(), ROOT::RException);
504+
}
505+
438506
TEST(RNTuple, SerializeEnvelopeLink)
439507
{
440508
RNTupleSerializer::REnvelopeLink link;

tree/ntuple/test/ntuple_test.hxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
using ROOT::EExtraTypeInfoIds;
5858
using ROOT::RNTupleLocalIndex;
5959
using ROOT::RNTupleLocator;
60+
using ROOT::RNTupleLocatorMulti;
6061
using ROOT::RNTupleLocatorObject64;
6162
using ROOT::Internal::RColumnIndex;
6263
using RClusterDescriptor = ROOT::RClusterDescriptor;

0 commit comments

Comments
 (0)