16#include <ROOT/RCurlConnection.hxx>
26#include <nlohmann/json.hpp>
60 nlohmann::json jsonAnchor;
61 jsonAnchor[
"anchorVersion"] = fVersionAnchor;
62 jsonAnchor[
"formatVersionEpoch"] = fVersionEpoch;
63 jsonAnchor[
"formatVersionMajor"] = fVersionMajor;
64 jsonAnchor[
"formatVersionMinor"] = fVersionMinor;
65 jsonAnchor[
"formatVersionPatch"] = fVersionPatch;
66 jsonAnchor[
"urlTemplate"] = fUrlTemplate;
67 jsonAnchor[
"cloneTemplate"] = fCloneTemplate;
68 jsonAnchor[
"headerObjId"] = fHeaderObjId;
69 jsonAnchor[
"headerOffset"] = fHeaderOffset;
70 jsonAnchor[
"nBytesHeader"] = fNBytesHeader;
71 jsonAnchor[
"lenHeader"] = fLenHeader;
72 jsonAnchor[
"footerObjId"] = fFooterObjId;
73 jsonAnchor[
"footerOffset"] = fFooterOffset;
74 jsonAnchor[
"nBytesFooter"] = fNBytesFooter;
75 jsonAnchor[
"lenFooter"] = fLenFooter;
77 auto canonical = jsonAnchor.dump(-1);
78 jsonAnchor[
"checksum"] = XXH3_64bits(canonical.data(), canonical.size());
79 return jsonAnchor.dump(2);
89 nlohmann::json jsonAnchor;
91 jsonAnchor = nlohmann::json::parse(
json);
92 }
catch (
const nlohmann::json::parse_error &
e) {
93 return R__FAIL(
"cannot parse S3 anchor JSON: " + std::string(
e.what()));
99 anchor.
fVersionAnchor = jsonAnchor.at(
"anchorVersion").get<std::uint32_t>();
100 }
catch (
const nlohmann::json::exception &
e) {
101 return R__FAIL(
"missing or invalid 'anchorVersion' in S3 anchor: " + std::string(
e.what()));
108 anchor.
fVersionEpoch = jsonAnchor.at(
"formatVersionEpoch").get<std::uint16_t>();
109 anchor.
fVersionMajor = jsonAnchor.at(
"formatVersionMajor").get<std::uint16_t>();
110 anchor.
fVersionMinor = jsonAnchor.at(
"formatVersionMinor").get<std::uint16_t>();
111 anchor.
fVersionPatch = jsonAnchor.at(
"formatVersionPatch").get<std::uint16_t>();
112 anchor.
fUrlTemplate = jsonAnchor.at(
"urlTemplate").get<std::string>();
113 anchor.
fCloneTemplate = jsonAnchor.at(
"cloneTemplate").get<std::string>();
114 anchor.
fHeaderObjId = jsonAnchor.at(
"headerObjId").get<std::uint64_t>();
115 anchor.
fHeaderOffset = jsonAnchor.at(
"headerOffset").get<std::uint64_t>();
116 anchor.
fNBytesHeader = jsonAnchor.at(
"nBytesHeader").get<std::uint64_t>();
117 anchor.
fLenHeader = jsonAnchor.at(
"lenHeader").get<std::uint64_t>();
118 anchor.
fFooterObjId = jsonAnchor.at(
"footerObjId").get<std::uint64_t>();
119 anchor.
fFooterOffset = jsonAnchor.at(
"footerOffset").get<std::uint64_t>();
120 anchor.
fNBytesFooter = jsonAnchor.at(
"nBytesFooter").get<std::uint64_t>();
121 anchor.
fLenFooter = jsonAnchor.at(
"lenFooter").get<std::uint64_t>();
122 }
catch (
const nlohmann::json::exception &
e) {
123 return R__FAIL(
"missing or invalid field in S3 anchor: " + std::string(
e.what()));
126 if (!jsonAnchor.contains(
"checksum"))
127 return R__FAIL(
"missing 'checksum' field in S3 anchor");
129 std::uint64_t storedChecksum;
131 storedChecksum = jsonAnchor.at(
"checksum").get<std::uint64_t>();
132 }
catch (
const nlohmann::json::exception &
e) {
133 return R__FAIL(
"invalid 'checksum' field in S3 anchor: " + std::string(
e.what()));
136 jsonAnchor.erase(
"checksum");
137 auto canonical = jsonAnchor.dump(-1);
138 auto computedChecksum = XXH3_64bits(canonical.data(), canonical.size());
140 if (storedChecksum != computedChecksum)
141 return R__FAIL(
"S3 anchor checksum mismatch");
150 const std::string uriStr(uri);
155 if (uriStr.find_first_of(
"@?#") != std::string::npos)
156 return R__FAIL(
"S3 URI must not contain userinfo ('@'), a query ('?') or a fragment ('#'): " + uriStr);
163 std::string schemeLower;
164 for (std::size_t i = 0; i < uriStr.size() && i < std::strlen(
"ntpl+s3+https://"); ++i)
165 schemeLower.push_back(
static_cast<char>(std::tolower(
static_cast<unsigned char>(uriStr[i]))));
167 std::string httpScheme;
168 std::size_t schemeLen = 0;
170 httpScheme =
"https";
171 schemeLen = std::strlen(
"ntpl+s3+https://");
174 schemeLen = std::strlen(
"ntpl+s3+http://");
176 return R__FAIL(
"invalid S3 URI (expected ntpl+s3+http:// or ntpl+s3+https://): " + uriStr);
179 std::string hostAndPath = uriStr.substr(schemeLen);
182 while (!hostAndPath.empty() && hostAndPath.back() ==
'/')
183 hostAndPath.pop_back();
187 if (hostAndPath.empty())
188 return R__FAIL(
"S3 URI has no host: " + uriStr);
190 return httpScheme +
"://" + hostAndPath;
205 static std::once_flag once;
206 std::call_once(once, []() {
208 <<
"Do not store real data with this version of RNTuple!";
218 return fBaseUrl +
"/" + std::to_string(objId);
226 fConnection.SetUrl(url).ThrowOnError();
227 auto status = fConnection.SendPutReq(
data,
size);
236 auto zipBuffer = MakeUninitArray<unsigned char>(
length);
240 const auto headerObjId = fObjectId++;
243 PutObject(MakeObjectUrl(headerObjId), zipBuffer.get(), szZipHeader);
246 fAnchor.fHeaderObjId = headerObjId;
247 fAnchor.fHeaderOffset = 0;
248 fAnchor.fNBytesHeader = szZipHeader;
249 fAnchor.fLenHeader =
length;
257 const auto pageObjId = fObjectId++;
260 PutObject(MakeObjectUrl(pageObjId),
reinterpret_cast<const unsigned char *
>(sealedPage.
GetBuffer()),
268 fCounters->fNPageCommitted.Inc();
276 return std::exchange(fNBytesCurrentCluster, 0);
283 auto bufPageListZip = MakeUninitArray<unsigned char>(
length);
287 const auto objId = fObjectId++;
290 PutObject(MakeObjectUrl(objId), bufPageListZip.get(), szPageListZip);
295 result.SetNBytesOnStorage(szPageListZip);
297 fCounters->fSzWritePayload.Add(
static_cast<std::int64_t
>(szPageListZip));
304 auto bufFooterZip = MakeUninitArray<unsigned char>(
length);
308 const auto footerObjId = fObjectId++;
311 PutObject(MakeObjectUrl(footerObjId), bufFooterZip.get(), szFooterZip);
314 fAnchor.fFooterObjId = footerObjId;
315 fAnchor.fFooterOffset = 0;
316 fAnchor.fNBytesFooter = szFooterZip;
317 fAnchor.fLenFooter =
length;
321 const auto anchorJson = fAnchor.ToJSON();
322 PutObject(fBaseUrl,
reinterpret_cast<const unsigned char *
>(anchorJson.data()), anchorJson.size());
329std::unique_ptr<ROOT::Internal::RPageSink>
335 std::string cloneBaseUrl = fAnchor.fCloneTemplate;
337 auto pos = cloneBaseUrl.find(
"${baseurl}");
338 if (pos != std::string::npos)
339 cloneBaseUrl.replace(pos, std::strlen(
"${baseurl}"), fBaseUrl);
341 pos = cloneBaseUrl.find(
"${name}");
342 if (pos != std::string::npos)
343 cloneBaseUrl.replace(pos, std::strlen(
"${name}"),
name);
354constexpr std::uint64_t kAnchorReadSize = 8 * 1024;
358constexpr std::uint64_t kMaxAnchorSize = 1024 * 1024;
363constexpr std::uint64_t kFormatMaxEnvelopeSize = 1ULL << 48;
374 :
RPageSource(ntupleName, options), fBaseUrl(baseUrl), fMainConnection(fBaseUrl), fClusterConnection(fBaseUrl)
386 StopClusterPoolBackgroundThread();
393 std::string url = fResolvedUrlTemplate;
395 const auto pos = url.find(
"${objid}");
396 if (pos != std::string::npos)
397 url.replace(pos, std::strlen(
"${objid}"), std::to_string(objId));
403 const std::string &url,
unsigned char *buffer,
408 connection.SetUrl(url).ThrowOnError();
412 ROOT::Internal::RCurlConnection::RUserRange range;
413 range.fDestination = buffer;
415 range.fLength =
size;
417 auto status = connection.SendRangesReq(1, &range);
420 if (range.fNBytesRecv !=
size) {
422 " bytes, expected " + std::to_string(
size)));
427 const std::string &url,
unsigned char *buffer,
430 connection.SetUrl(url).ThrowOnError();
435 ROOT::Internal::RCurlConnection::RUserRange range;
436 range.fDestination = buffer;
438 range.fLength =
size;
440 auto status = connection.SendRangesReq(1, &range);
443 return range.fNBytesRecv;
451 auto anchorBuffer = MakeUninitArray<unsigned char>(kAnchorReadSize);
452 auto anchorSize = GetShortObject(fMainConnection, fBaseUrl, anchorBuffer.get(), kAnchorReadSize);
454 if (anchorSize == kAnchorReadSize) {
457 anchorBuffer = MakeUninitArray<unsigned char>(kMaxAnchorSize);
458 anchorSize = GetShortObject(fMainConnection, fBaseUrl, anchorBuffer.get(), kMaxAnchorSize);
459 if (anchorSize == kMaxAnchorSize) {
461 std::to_string(kMaxAnchorSize) +
" bytes); refusing to read it"));
465 const std::string anchorJson(
reinterpret_cast<const char *
>(anchorBuffer.get()), anchorSize);
469 fAnchor = anchorResult.Inspect();
473 fResolvedUrlTemplate = fAnchor.GetUrlTemplate();
474 if (
const auto pos = fResolvedUrlTemplate.find(
"${baseurl}"); pos != std::string::npos)
475 fResolvedUrlTemplate.replace(pos, std::strlen(
"${baseurl}"), fBaseUrl);
481 const auto maxEnvelopeSize =
483 if (fAnchor.GetNBytesHeader() > maxEnvelopeSize || fAnchor.GetNBytesFooter() > maxEnvelopeSize ||
484 fAnchor.GetLenHeader() > maxEnvelopeSize || fAnchor.GetLenFooter() > maxEnvelopeSize) {
486 std::to_string(maxEnvelopeSize) +
" bytes"));
489 fDescriptorBuilder.SetVersion(fAnchor.GetVersionEpoch(), fAnchor.GetVersionMajor(), fAnchor.GetVersionMinor(),
490 fAnchor.GetVersionPatch());
491 fDescriptorBuilder.SetOnDiskHeaderSize(fAnchor.GetNBytesHeader());
492 fDescriptorBuilder.AddToOnDiskFooterSize(fAnchor.GetNBytesFooter());
496 fAnchor.GetNBytesHeader() + fAnchor.GetNBytesFooter() + std::max(fAnchor.GetLenHeader(), fAnchor.GetLenFooter());
497 fStructureBuffer.fBuffer = MakeUninitArray<unsigned char>(bufSize);
498 fStructureBuffer.fPtrHeader = fStructureBuffer.fBuffer.get();
499 fStructureBuffer.fPtrFooter = fStructureBuffer.fBuffer.get() + fAnchor.GetNBytesHeader();
503 GetObject(fMainConnection, MakeObjectUrl(fAnchor.GetHeaderObjId()),
504 reinterpret_cast<unsigned char *
>(fStructureBuffer.fPtrHeader), fAnchor.GetNBytesHeader());
505 GetObject(fMainConnection, MakeObjectUrl(fAnchor.GetFooterObjId()),
506 reinterpret_cast<unsigned char *
>(fStructureBuffer.fPtrFooter), fAnchor.GetNBytesFooter());
508 fCounters->fNRead.Add(3);
514 auto unzipBuf =
reinterpret_cast<unsigned char *
>(fStructureBuffer.fPtrFooter) + fAnchor.GetNBytesFooter();
521 const auto &storedName = fDescriptorBuilder.GetDescriptor().GetName();
522 if (!fNTupleName.empty() && storedName != fNTupleName) {
524 R__FAIL(
"the S3 ntuple at " + fBaseUrl +
" is named '" + storedName +
"', not '" + fNTupleName +
"'"));
530 return fDescriptorBuilder.MoveDescriptor();
536 GetObject(fMainConnection, MakeObjectUrl(objId), buffer, locator.
GetNBytesOnStorage());
545 auto buffer =
static_cast<unsigned char *
>(
const_cast<void *
>(sealedPage.
GetBuffer()));
546 GetObject(fMainConnection, MakeObjectUrl(objId), buffer, sealedPage.
GetBufferSize());
549std::vector<std::unique_ptr<ROOT::Internal::RCluster>>
553 struct RS3SealedPageLocator {
556 std::uint64_t fObjId = 0;
557 std::uint64_t fBufferSize = 0;
560 fCounters->fNClusterLoaded.Add(clusterKeys.size());
562 std::vector<std::unique_ptr<ROOT::Internal::RCluster>> clusters;
564 for (
const auto &clusterKey : clusterKeys) {
565 std::vector<RS3SealedPageLocator> onDiskPages;
566 std::uint64_t clusterBufSize = 0;
568 auto pageZeroMap = std::make_unique<ROOT::Internal::ROnDiskPageMap>();
570 clusterKey, *pageZeroMap,
573 const auto &pageLocator = pageInfo.
GetLocator();
575 const auto pageBufferSize = pageLocator.GetNBytesOnStorage() + pageInfo.
HasChecksum() * kNBytesPageChecksum;
576 onDiskPages.emplace_back(RS3SealedPageLocator{physicalColumnId, pageNo, objId, pageBufferSize});
577 clusterBufSize += pageBufferSize;
580 auto clusterBuffer =
new unsigned char[clusterBufSize];
582 std::make_unique<ROOT::Internal::ROnDiskPageMapHeap>(std::unique_ptr<
unsigned char[]>(clusterBuffer));
588 auto pageBuffer = clusterBuffer;
589 for (
const auto &sealedLoc : onDiskPages) {
592 GetObject(fClusterConnection, MakeObjectUrl(sealedLoc.fObjId), pageBuffer, sealedLoc.fBufferSize);
593 pageBuffer += sealedLoc.fBufferSize;
597 fCounters->fNPageRead.Add(onDiskPages.size());
598 fCounters->fSzReadPayload.Add(clusterBufSize);
599 fCounters->fNRead.Add(onDiskPages.size());
601 auto cluster = std::make_unique<ROOT::Internal::RCluster>(clusterKey.fClusterId);
602 cluster->Adopt(std::move(pageMap));
603 cluster->Adopt(std::move(pageZeroMap));
604 for (
auto colId : clusterKey.fPhysicalColumnSet)
605 cluster->SetColumnAvailable(colId);
606 clusters.emplace_back(std::move(cluster));
618 clone->fAnchor = fAnchor;
619 clone->fResolvedUrlTemplate = fResolvedUrlTemplate;
623std::unique_ptr<ROOT::Internal::RPageSource>
#define R__FORWARD_ERROR(res)
Short-hand to return an RResult<T> in an error state (i.e. after checking)
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
#define R__LOG_WARNING(...)
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
Record wall time and CPU time between construction and destruction.
Entry point for an RNTuple stored in S3-compatible object storage.
std::uint16_t fVersionMinor
bool operator==(const RNTupleAnchorS3 &other) const
Field-by-field equality check across all data members.
std::uint64_t fHeaderObjId
Object ID and byte offset of the compressed header within the S3 object.
std::string fUrlTemplate
Pattern for resolving object IDs to full S3 URLs.
std::uint32_t fVersionAnchor
Allows evolving the anchor JSON schema in future versions.
std::uint16_t fVersionPatch
std::uint16_t fVersionEpoch
Version of the RNTuple binary format supported by the writer.
std::string fCloneTemplate
Pattern for resolving clone (attribute-set) names to base URLs.
std::uint64_t fNBytesHeader
Compressed and uncompressed sizes of the header envelope.
std::string ToJSON() const
Serialize the anchor to a JSON string suitable for storage at the base URL.
std::uint64_t fHeaderOffset
std::uint64_t fFooterOffset
std::uint64_t fNBytesFooter
Compressed and uncompressed sizes of the footer envelope.
static RResult< RNTupleAnchorS3 > CreateFromJSON(const std::string &json)
Deserialize the anchor from a JSON string. Returns an error on malformed or incompatible input.
std::uint64_t fFooterObjId
Object ID and byte offset of the compressed footer within the S3 object.
std::uint16_t fVersionMajor
Storage provider that writes ntuple pages into S3-compatible object storage.
ROOT::Internal::RCurlConnection fConnection
One HTTP connection reused for every upload, so curl keeps it alive across objects on the same host i...
void InitImpl(unsigned char *serializedHeader, std::uint32_t length) final
std::uint64_t StageClusterImpl() final
Returns the number of bytes written to storage (excluding metadata)
RNTupleLocator CommitClusterGroupImpl(unsigned char *serializedPageList, std::uint32_t length) final
Returns the locator of the page list envelope of the given buffer that contains the serialized page l...
std::unique_ptr< ROOT::Internal::RPageSink > CloneAsHidden(std::string_view name, const ROOT::RNTupleWriteOptions &opts) const final
Creates a new sink with the same underlying storage as this but writing to a different RNTuple named ...
std::string MakeObjectUrl(std::uint64_t objId) const
Resolve a numeric object ID to its full HTTP URL.
RNTupleLocator CommitSealedPageImpl(ROOT::DescriptorId_t physicalColumnId, const RPageStorage::RSealedPage &sealedPage) final
RPageSinkS3(std::string_view ntupleName, std::string_view baseUrl, const ROOT::RNTupleWriteOptions &options, RFromBaseUrl)
Internal constructor used by CloneAsHidden: the public constructor derives the base URL by parsing an...
RNTupleLink CommitDatasetImpl() final
void PutObject(const std::string &url, const unsigned char *data, std::size_t size)
Upload raw bytes to the given S3 URL via an HTTP PUT request.
Storage provider that reads ntuple pages from S3-compatible object storage.
ROOT::RNTupleDescriptor AttachImpl() final
LoadStructureImpl() has been called before AttachImpl() is called
~RPageSourceS3() override
void LoadSealedPageImpl(const RNTupleLocator &locator, RSealedPage &sealedPage) final
RPageSourceS3(std::string_view ntupleName, std::string_view baseUrl, const ROOT::RNTupleReadOptions &options, RFromBaseUrl)
Internal constructor used by CloneImpl: the public constructor derives the base URL by parsing an s3 ...
ROOT::Internal::RCurlConnection fClusterConnection
Connection used exclusively by LoadClusters(), which the cluster pool calls on its own I/O thread.
void LoadStructureImpl() final
Fills fStructureBuffer with the compressed header and footer.
void LoadPageListImpl(const RNTupleLocator &locator, unsigned char *buffer) final
std::unique_ptr< RPageSource > OpenWithDifferentAnchor(const ROOT::Internal::RNTupleLink &anchorLink, const ROOT::RNTupleReadOptions &options={}) final
Creates a new PageSource using the same underlying file as this but referring to a different RNTuple,...
void GetObject(ROOT::Internal::RCurlConnection &connection, const std::string &url, unsigned char *buffer, std::size_t size)
Download size bytes from url into the caller-provided buffer via an HTTP GET request.
std::string MakeObjectUrl(std::uint64_t objId) const
Resolve a numeric object ID to its full HTTP URL through the anchor's URL template.
std::size_t GetShortObject(ROOT::Internal::RCurlConnection &connection, const std::string &url, unsigned char *buffer, std::size_t size)
Download at most size bytes of url, returning how many arrived.
ROOT::Internal::RCurlConnection fMainConnection
Connection used by everything that runs on the calling thread: the anchor, header and footer in LoadS...
std::vector< std::unique_ptr< ROOT::Internal::RCluster > > LoadClusters(std::span< ROOT::Internal::RCluster::RKey > clusterKeys) final
Populates all the pages of the given cluster ids and columns; it is possible that some columns do not...
std::unique_ptr< RPageSource > CloneImpl() const final
The cloned page source opens its own pair of HTTP connections to the same base URL.
Helper class to compress data blocks in the ROOT compression frame format.
static std::size_t Zip(const void *from, std::size_t nbytes, int compression, void *to)
Returns the size of the compressed data, written into the provided output buffer.
Helper class to uncompress data blocks in the ROOT compression frame format.
static void Unzip(const void *from, size_t nbytes, size_t dataLen, void *to)
The nbytes parameter provides the size ls of the from buffer.
static std::uint64_t GetMaxEnvelopeSize(const RNTupleReadOptions &options)
A helper class for serializing and deserialization of the RNTuple binary format.
static RResult< void > DeserializeFooter(const void *buffer, std::uint64_t bufSize, ROOT::Internal::RNTupleDescriptorBuilder &descBuilder)
static RResult< void > DeserializeHeader(const void *buffer, std::uint64_t bufSize, ROOT::Internal::RNTupleDescriptorBuilder &descBuilder)
A page as being stored on disk, that is packed and compressed.
Base class for a sink with a physical storage backend.
void EnableDefaultMetrics(const std::string &prefix)
Enables the default set of metrics provided by RPageSink.
Abstract interface to read data from an ntuple.
void EnableDefaultMetrics(const std::string &prefix)
Enables the default set of metrics provided by RPageSource.
Base class for all ROOT issued exceptions.
The on-storage metadata of an RNTuple.
RNTupleLocator payload that is common for object stores using 64bit location information.
Generic information about the physical location of data.
std::uint64_t GetNBytesOnStorage() const
T GetPosition() const
Note that for GetPosition() / SetPosition(), the locator type must correspond (kTypeFile,...
Common user-tunable settings for reading RNTuples.
Common user-tunable settings for storing RNTuples.
The class is used as a return type for operations that can fail; wraps a value of type T or an RError...
RResult< std::string > ParseS3Url(std::string_view uri)
Translate an ntpl+s3 URI into its plain HTTP(S) equivalent.
ROOT::RLogChannel & NTupleLog()
Log channel for RNTuple diagnostics.
std::unique_ptr< T[]> MakeUninitArray(std::size_t size)
Make an array of default-initialized elements.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
bool StartsWith(std::string_view string, std::string_view prefix)
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
Tag to select the internal constructor that takes an already-resolved base URL.
Tag to select the internal constructor that takes an already-resolved base URL.
On-disk pages within a page source are identified by the column and page number.
A sealed page contains the bytes of a page as written to storage (packed & compressed).
const void * GetBuffer() const
std::size_t GetBufferSize() const
std::size_t GetDataSize() const
Information about a single page in the context of a cluster's page range.
const RNTupleLocator & GetLocator() const