37#include <initializer_list>
38#include <unordered_map>
74 std::initializer_list<std::pair<const char *, T>>
validValues)
94 {
"Filter", ENTupleMergingMode::kFilter},
95 {
"Union", ENTupleMergingMode::kUnion},
96 {
"Strict", ENTupleMergingMode::kStrict},
104 {
"Abort", ENTupleMergeErrBehavior::kAbort},
105 {
"Skip", ENTupleMergeErrBehavior::kSkip},
112 opts,
"rntuple.VersionBehavior=",
114 {
"WarnOnHigherVersion", ENTupleMergeVersionBehavior::kWarnOnHigherVersion},
115 {
"AbortOnHigherVersion", ENTupleMergeVersionBehavior::kAbortOnHigherVersion},
164 "only the latter will apply.";
180 std::vector<std::unique_ptr<RPageSourceFile>>
sources;
183 while (
const auto &
pitr =
itr()) {
188 <<
inFile->GetName() <<
"'";
196 source->Attach(RNTupleSerializer::EDescriptorDeserializeMode::kRaw);
197 auto descriptor =
source->GetSharedDescriptorGuard();
198 auto clusterIter = descriptor->GetClusterIterable();
202 <<
"Asked to use the first source's compression as the output compression, but the "
203 "first source (file '"
205 <<
"') has an empty RNTuple, therefore the output compression could not be "
209 auto colRangeIter = (*firstCluster).GetColumnRangeIterable();
213 <<
"Asked to use the first source's compression as the output compression, but the "
214 "first source (file '"
216 <<
"') has an empty RNTuple, therefore the output compression could not be "
220 compression = (*firstColRange).GetCompressionSettings();
230 std::unique_ptr<ROOT::RNTupleModel> model;
234 outSource->Attach(RNTupleSerializer::EDescriptorDeserializeMode::kForWriting);
235 auto desc =
outSource->GetSharedDescriptorGuard();
236 model =
destination->InitFromDescriptor(desc.GetRef(),
true );
241 for (
const auto &s :
sources) {
266}
catch (
const std::exception &
ex) {
273struct RChangeCompressionFunc {
275 std::uint32_t fCompressionSettings;
279 std::size_t fBufSize;
282 void operator()()
const
290 if (fCompressionSettings != 0) {
301 if (fCompressionSettings != 0) {
314 std::optional<ROOT::Experimental::TTaskGroup> &fGroup;
316 template <
typename T>
317 void operator()(T &&
f)
339struct RColReprMapping {
340 std::uint32_t fSource;
346struct RColReprExtension : RColReprMapping {
348 std::vector<ROOT::Internal::RColumnFormat> fSourceRepr;
352 std::uint32_t fOrigFirstElementIndex = 0;
355static std::optional<std::uint32_t>
367struct RDescriptorsComparison {
368 std::vector<const ROOT::RFieldDescriptor *> fExtraDstFields;
369 std::vector<const ROOT::RFieldDescriptor *> fExtraSrcFields;
370 std::vector<RCommonField> fCommonFields;
378struct RColumnOutInfo {
383using ColumnIdMap_t = std::unordered_map<std::string, RColumnOutInfo>;
385struct RColumnInfoGroup {
386 std::vector<RColumnMergeInfo> fExtraDstColumns;
387 std::vector<RColumnMergeInfo> fCommonColumns;
433 std::deque<RPageStorage::SealedPageSequence_t>
fPagesV;
434 std::vector<RPageStorage::RSealedPageGroup>
fGroups;
435 std::vector<std::unique_ptr<std::byte[]>>
fBuffers;
444 RDescriptorsComparison &
result, std::vector<std::string> &
errors)
453 std::stringstream
ss;
455 <<
"` has a different column cardinality than previously-seen field with the same name (old: "
521 std::vector<ROOT::Internal::RColumnFormat>
newRepr;
555 std::vector<std::string>
errors;
556 RDescriptorsComparison res;
560 for (
const auto &
dstField :
dst.GetTopLevelFields()) {
566 res.fExtraDstFields.emplace_back(&
dstField);
569 for (
const auto &
srcField :
src.GetTopLevelFields()) {
572 res.fExtraSrcFields.push_back(&
srcField);
588 std::stringstream
ss;
589 ss <<
"Field `" <<
fieldName <<
"` is incompatible with previously-seen field with that name because the "
590 << (
field.fSrc->IsProjectedField() ?
"new" :
"old") <<
" one is projected and the other isn't";
592 }
else if (
field.fSrc->IsProjectedField()) {
594 const auto srcName =
src.GetQualifiedFieldName(
field.fSrc->GetProjectionSourceId());
595 const auto dstName =
dst.GetQualifiedFieldName(
field.fDst->GetProjectionSourceId());
597 std::stringstream
ss;
599 <<
"` is projected to a different field than a previously-seen field with the same name (old: "
610 std::stringstream
ss;
612 <<
"` has a type incompatible with a previously-seen field with the same name: (old: " <<
dstTyName
621 std::stringstream
ss;
622 ss <<
"Field `" <<
field.fSrc->GetFieldName()
623 <<
"` has a different type checksum than previously-seen field with the same name";
631 std::stringstream
ss;
632 ss <<
"Field `" <<
field.fSrc->GetFieldName()
633 <<
"` has a different type version than previously-seen field with the same name (old: " <<
dstTyVer
642 std::stringstream
ss;
643 ss <<
"Field `" <<
field.fSrc->GetFieldName()
644 <<
"` has a different field version than previously-seen field with the same name (old: " <<
dstFldVer
652 std::stringstream
ss;
653 ss <<
"Field `" <<
field.fSrc->GetFieldName()
654 <<
"` has a different structural role than previously-seen field with the same name (old: " <<
dstRole
655 <<
", new: " <<
srcRole <<
")";
660 if (!
field.fSrc->IsProjectedField()) {
668 std::stringstream
ss;
669 ss <<
"Field `" <<
field.fSrc->GetFieldName()
670 <<
"` has a different number of children than previously-seen field with the same name (old: "
683 for (
const auto &err :
errors)
684 errMsg += std::string(
"\n * ") + err;
708 if (
mergeData.fMergeOpts.fExtraVerbose) {
709 std::string
msg =
"destination doesn't contain field";
714 return acc + (acc.length() ?
", " :
"") +
'`' + field->GetFieldName() +
'`';
717 <<
" to the destination model (entry #" <<
mergeData.fNumDstEntries <<
").";
731 const auto &column =
mergeData.fSrcDescriptor->GetColumnDescriptor(
colId);
799 const auto *
field = *it;
818 for (
const auto &column :
columns) {
823 if (
field->GetLogicalColumnIds()[0] != column.fInputId)
843 const auto structure =
field->GetStructure();
846 return R__FAIL(
"Destination RNTuple contains a streamer field (" +
field->GetFieldName() +
847 ") that is not present in one of the sources. "
848 "Creating a default value for a streamer field is ill-defined, therefore the merging "
849 "process will abort.");
864 for (
size_t i = 0; i <
nPages; ++i) {
879 sealConf.fCompressionSettings =
mergeData.fMergeOpts.fCompressionSettings.value();
880 sealConf.fWriteChecksum =
mergeData.fDestination.GetWriteOptions().GetEnablePageChecksums();
915 const auto &
columnId = column.fInputId;
944 <<
mergeData.fMergeOpts.fCompressionSettings.value() <<
", onDiskType: "
986 sealedPage.VerifyChecksumIfEnabled().ThrowOnError();
999 RTaskVisitor{fTaskGroup}(RChangeCompressionFunc{
1006 mergeData.fDestination.GetWriteOptions()
1068 if (clusterDesc.ContainsColumn(column.fInputId)) {
1069 const auto &colRange = clusterDesc.GetColumnRange(column.fInputId);
1070 ++nCommonColumnsInCluster;
1071 columnsInCluster[column.fParentFieldDescriptor].push_back(column.fOutputId);
1072 if (!colRange.IsSuppressed()) {
1073 commonColumnSet.emplace(column.fInputId);
1076 mergeData.fDestination.CommitSuppressedColumn(ColumnHandle_t{column.fOutputId});
1094 for (
auto i = 0
u; i <
fieldDesc->GetLogicalColumnIds().size(); ++i) {
1155 if (
fieldType ==
"std::byte")
return typeid(std::byte);
1156 if (
fieldType ==
"char")
return typeid(char);
1157 if (
fieldType ==
"std::int8_t")
return typeid(std::int8_t);
1158 if (
fieldType ==
"std::uint8_t")
return typeid(std::uint8_t);
1159 if (
fieldType ==
"std::int16_t")
return typeid(std::int16_t);
1160 if (
fieldType ==
"std::uint16_t")
return typeid(std::uint16_t);
1161 if (
fieldType ==
"std::int32_t")
return typeid(std::int32_t);
1162 if (
fieldType ==
"std::uint32_t")
return typeid(std::uint32_t);
1163 if (
fieldType ==
"std::int64_t")
return typeid(std::int64_t);
1164 if (
fieldType ==
"std::uint64_t")
return typeid(std::uint64_t);
1165 if (
fieldType ==
"float")
return typeid(float);
1170 return std::nullopt;
1227 info.fOutputId = it->second.fColumnId;
1244 if (
mergeData.fMergeOpts.fExtraVerbose) {
1246 <<
", phys.id " <<
srcColumn.GetPhysicalId() <<
", type "
1248 <<
info.fOutputId <<
", type "
1274 RColumnInfoGroup res;
1286 ColumnIdMap_t &
colIdMap,
const std::string &prefix =
"")
1291 RColumnOutInfo
info{};
1294 name +
'.' + std::to_string(
colDesc.GetIndex()) +
'.' + std::to_string(
colDesc.GetRepresentationIndex());
1314 if (
field.IsProjectedField())
1324 std::unique_ptr<ROOT::RNTupleModel> model)
1329 fModel(std::
move(model))
1356 return R__FAIL(std::string(
"The compression given to RNTupleMergeOptions is different from that of the "
1359 ") This is currently unsupported.");
1364 std::unordered_map<ROOT::DescriptorId_t, std::vector<const ROOT::RFieldDescriptor *>>
projectionPointees;
1369 "passing an already-initialized destination to RNTupleMerger::Merge (i.e. trying to do incremental "
1370 "merging) can only be done by providing a valid ROOT::RNTupleModel when constructing the RNTupleMerger.");
1379 for (
const auto &
field :
mergeData.fDstDescriptor.GetTopLevelFields()) {
1384#define SKIP_OR_ABORT(errMsg) \
1386 if (mergeOpts.fErrBehavior == ENTupleMergeErrBehavior::kSkip) { \
1387 R__LOG_WARNING(NTupleMergeLog()) << "Skipping RNTuple due to: " << (errMsg); \
1390 return R__FAIL(errMsg); \
1398 source->LoadStreamerInfo();
1400 source->Attach(RNTupleSerializer::EDescriptorDeserializeMode::kForWriting);
1407 <<
"RNTuple '" <<
mergeData.fSrcDescriptor->GetName()
1408 <<
"' has a higher format version than the latest supported by this version "
1409 "of ROOT. Merging will work but some features may be dropped.";
1412 "' has a higher format version than the latest supported by this version. Refusing to "
1413 "merge, since RNTupleMergeOptions::fVersionBehavior is set to AbortOnHigherVersion.");
1427 SKIP_OR_ABORT(std::string(
"Source RNTuple has an incompatible schema with the destination:\n") +
1435 std::string
msg =
"Source RNTuple is missing the following fields:";
1437 msg +=
"\n " +
field->GetFieldName() +
" : " +
field->GetTypeName();
1443 if (!
descCmp.fExtraSrcFields.empty()) {
1451 std::string
msg =
"Source RNTuple has extra fields that the destination RNTuple doesn't have:";
1453 msg +=
"\n " +
field->GetFieldName() +
" : " +
field->GetTypeName();
1460 if (!
descCmp.fColReprExtensions.empty()) {
1462 if (
field->IsProjectedField())
1472 std::vector<std::pair<const RFieldDescriptor *, std::vector<RColReprExtension>>>
colExtensions;
1480 if (
field->IsProjectedField())
#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(...)
#define R__LOG_ERROR(...)
static void MatchColumnRepresentations(const ROOT::RNTupleDescriptor &srcDesc, const ROOT::RNTupleDescriptor &dstDesc, const ROOT::RFieldDescriptor &srcField, const ROOT::RFieldDescriptor &dstField, RDescriptorsComparison &result, std::vector< std::string > &errors)
static std::optional< std::type_index > ColumnInMemoryType(std::string_view fieldType, ENTupleColumnType onDiskType)
static ROOT::RResult< RDescriptorsComparison > CompareDescriptorStructure(const ROOT::RNTupleDescriptor &dst, const ROOT::RNTupleDescriptor &src)
Compares the top level fields of dst and src and determines whether they can be merged or not.
static ROOT::RResult< void > ExtendDestinationModel(RDescriptorsComparison &descCmp, ROOT::RNTupleModel &dstModel, RNTupleMergeData &mergeData)
static ROOT::RResult< void > GenerateZeroPagesForColumns(size_t nEntriesToGenerate, std::span< const RColumnMergeInfo > columns, RSealedPageMergeData &sealedPageData, ROOT::Internal::RPageAllocator &pageAlloc, const ROOT::RNTupleDescriptor &dstDescriptor, const RNTupleMergeData &mergeData)
static void AddColumnsFromField(std::vector< RColumnMergeInfo > &columns, const ROOT::RNTupleDescriptor &srcDesc, const FieldCollectionMap_t< RColReprMapping > &colReprMappings, RNTupleMergeData &mergeData, const ROOT::RFieldDescriptor &srcFieldDesc, const ROOT::RFieldDescriptor &dstFieldDesc, const std::string &prefix="")
static std::optional< ENTupleMergeErrBehavior > ParseOptionErrBehavior(const TString &opts)
static ROOT::RLogChannel & NTupleMergeLog()
#define SKIP_OR_ABORT(errMsg)
static std::optional< T > ParseStringOption(const TString &opts, const char *pattern, std::initializer_list< std::pair< const char *, T > > validValues)
static void AddColumnExtensionsInFieldOrder(const ROOT::RFieldDescriptor &field, const ROOT::RNTupleDescriptor &desc, const FieldCollectionMap_t< RColReprExtension > &extensions, std::vector< std::pair< const ROOT::RFieldDescriptor *, std::vector< RColReprExtension > > > &outExtensions, std::unordered_map< ROOT::DescriptorId_t, std::vector< const ROOT::RFieldDescriptor * > > &outProjectionPointees)
static std::optional< ENTupleMergingMode > ParseOptionMergingMode(const TString &opts)
static void PrefillColumnMap(const ROOT::RNTupleDescriptor &desc, const ROOT::RFieldDescriptor &fieldDesc, ColumnIdMap_t &colIdMap, const std::string &prefix="")
static RColumnInfoGroup GatherColumnInfos(const RDescriptorsComparison &descCmp, const ROOT::RNTupleDescriptor &srcDesc, RNTupleMergeData &mergeData)
static std::optional< ENTupleMergeVersionBehavior > ParseOptionVersionBehavior(const TString &opts)
static bool BeginsWithDelimitedWord(const TString &str, const char *word)
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
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 child
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t src
The available trivial, native content types of a column.
Given a set of RPageSources merge them into an RPagePersistentSink, optionally changing their compres...
std::optional< TTaskGroup > fTaskGroup
ROOT::RResult< void > MergeSourceClusters(ROOT::Internal::RPageSource &source, std::span< RColumnMergeInfo > commonColumns, std::span< const RColumnMergeInfo > extraDstColumns, RNTupleMergeData &mergeData)
std::unique_ptr< ROOT::RNTupleModel > fModel
RNTupleMerger(std::unique_ptr< ROOT::Internal::RPagePersistentSink > destination, std::unique_ptr< ROOT::RNTupleModel > model)
Creates a RNTupleMerger with the given destination.
std::unique_ptr< ROOT::Internal::RPagePersistentSink > fDestination
ROOT::RResult< void > MergeCommonColumns(ROOT::Internal::RClusterPool &clusterPool, const ROOT::RClusterDescriptor &clusterDesc, std::span< RColumnMergeInfo > commonColumns, const ROOT::Internal::RCluster::ColumnSet_t &commonColumnSet, RSealedPageMergeData &sealedPageData, const RNTupleMergeData &mergeData, ROOT::Internal::RPageAllocator &pageAlloc)
RResult< void > Merge(std::span< ROOT::Internal::RPageSource * > sources, const RNTupleMergeOptions &mergeOpts=RNTupleMergeOptions())
Merge a given set of sources into the destination.
A class to manage the asynchronous execution of work items.
Managed a set of clusters containing compressed and packed pages.
An in-memory subset of the packed and compressed pages of a cluster.
std::unordered_set< ROOT::DescriptorId_t > ColumnSet_t
A column element encapsulates the translation between basic C++ types and their column representation...
static const char * GetColumnTypeName(ROOT::ENTupleColumnType type)
static std::unique_ptr< RColumnElementBase > Generate(ROOT::ENTupleColumnType type)
If CppT == void, use the default C++ type for the given column type.
std::size_t GetPackedSize(std::size_t nElements=1U) const
The in-memory representation of a 32bit or 64bit on-disk index column.
Holds the index and the tag of a kSwitch column.
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.
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.
A helper class for serializing and deserialization of the RNTuple binary format.
Uses standard C++ memory allocation for the column data pages.
Abstract interface to allocate and release pages.
Abstract interface to write data into an ntuple.
RSealedPage SealPage(const ROOT::Internal::RPage &page, const ROOT::Internal::RColumnElementBase &element)
Helper for streaming a page.
Storage provider that reads ntuple pages from a file.
static std::unique_ptr< RPageSourceFile > CreateFromAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Used from the RNTuple class to build a datasource if the anchor is already available.
Abstract interface to read data from an ntuple.
Common functionality of an ntuple storage for both reading and writing.
static constexpr std::size_t kNBytesPageChecksum
The page checksum is a 64bit xxhash3.
std::deque< RSealedPage > SealedPageSequence_t
RColumnHandle ColumnHandle_t
The column handle identifies a column with the current open page storage.
std::unordered_map< const ROOT::RFieldBase *, const ROOT::RFieldBase * > FieldMap_t
The map keys are the projected target fields, the map values are the backing source fields Note that ...
RResult< void > Add(std::unique_ptr< ROOT::RFieldBase > field, const FieldMap_t &fieldMap)
Adds a new projected field.
Metadata for RNTuple clusters.
Base class for all ROOT issued exceptions.
std::vector< ROOT::ENTupleColumnType > ColumnRepresentation_t
Metadata stored for every field of an RNTuple.
ROOT::ENTupleStructure GetStructure() const
ROOT::DescriptorId_t GetParentId() const
std::uint64_t GetNRepetitions() const
A log configuration for a channel, e.g.
The on-storage metadata of an RNTuple.
const RColumnDescriptor & GetColumnDescriptor(ROOT::DescriptorId_t columnId) const
const RFieldDescriptor & GetFieldDescriptor(ROOT::DescriptorId_t fieldId) const
The RNTupleModel encapulates the schema of an RNTuple.
Common user-tunable settings for storing RNTuples.
bool GetEnablePageChecksums() const
Representation of an RNTuple data set in a ROOT file.
Long64_t Merge(TCollection *input, TFileMergeInfo *mergeInfo)
RNTuple implements the hadd MergeFile interface Merge this NTuple with the input list entries.
static constexpr std::uint64_t GetCurrentVersion()
Returns the RNTuple version in the following form: Epoch: 2 most significant bytes Major: next 2 byte...
const_iterator begin() const
const_iterator end() const
void ThrowOnError()
Short-hand method to throw an exception in the case of errors.
The class is used as a return type for operations that can fail; wraps a value of type T or an RError...
Collection abstract base class.
A class to pass information from the TFileMerger to the objects being merged.
A file, usually with extension .root, that stores data and code in the form of serialized objects in ...
Book space in a file, create I/O buffers, to fill them, (un)compress them.
Mother of all ROOT objects.
@ kStrict
The merger will refuse to merge any 2 RNTuples whose schema doesn't match exactly.
@ kUnion
The merger will update the output model to include all columns from all sources.
@ kWarnOnHigherVersion
The merger will emit a warning when merging RNTuples with higher version than the latest supported by...
std::unique_ptr< T[]> MakeUninitArray(std::size_t size)
Make an array of default-initialized elements.
RProjectedFields & GetProjectedFieldsOfModel(RNTupleModel &model)
std::unique_ptr< RColumnElementBase > GenerateColumnElement(std::type_index inMemoryType, ROOT::ENTupleColumnType onDiskType)
Bool_t IsImplicitMTEnabled()
Returns true if the implicit multi-threading in ROOT is enabled.
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
std::uint64_t NTupleSize_t
Integer type long enough to hold the maximum number of entries in a column.
constexpr DescriptorId_t kInvalidDescriptorId
ROOT::DescriptorId_t fOutputId
const ROOT::RFieldDescriptor * fParentFieldDescriptor
ROOT::DescriptorId_t fInputId
std::optional< std::type_index > fInMemoryType
const ROOT::RNTupleDescriptor * fParentNTupleDescriptor
std::uint16_t fOutputReprIndex
const ROOT::RNTupleDescriptor * fSrcDescriptor
ColumnIdMap_t fColumnIdMap
RNTupleMergeData(std::span< RPageSource * > sources, RPageSink &destination, const RNTupleMergeOptions &mergeOpts)
std::span< RPageSource * > fSources
ROOT::NTupleSize_t fNumDstEntries
const RNTupleMergeOptions & fMergeOpts
std::vector< RColumnMergeInfo > fColumns
const ROOT::RNTupleDescriptor & fDstDescriptor
Set of merging options to pass to RNTupleMerger.
std::vector< RPageStorage::RSealedPageGroup > fGroups
std::vector< std::unique_ptr< std::byte[]> > fBuffers
std::deque< RPageStorage::SealedPageSequence_t > fPagesV
The incremental changes to a RNTupleModel
On-disk pages within a page source are identified by the column and page number.
Parameters for the SealPage() method.
A sealed page contains the bytes of a page as written to storage (packed & compressed).
RResult< void > VerifyChecksumIfEnabled() const
std::uint32_t GetNElements() const
const void * GetBuffer() const
bool GetHasChecksum() const
std::size_t GetDataSize() const
@ kUseGeneralPurpose
Use the new recommended general-purpose setting; it is a best trade-off between compression ratio/dec...