Skip to content

Commit 57425dd

Browse files
authored
[TTree] when merging, ignore trees without branches (#17652)
* [TTree] when merging, ignore trees without branches Fixes #14558 Fixes #12510
1 parent 6981421 commit 57425dd

File tree

2 files changed

+150
-16
lines changed

2 files changed

+150
-16
lines changed

io/io/test/TFileMergerTests.cxx

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "ROOT/TestSupport.hxx"
22

33
#include "TFileMerger.h"
4+
#include "TFileMergeInfo.h"
45

56
#include "TMemFile.h"
67
#include "TTree.h"
@@ -84,12 +85,12 @@ TEST(TFileMerger, MergeSingleOnlyListed)
8485
hist3->Fill(1); hist3->Fill(1); hist3->Fill(1);
8586
hist4->Fill(1); hist4->Fill(1); hist4->Fill(1); hist4->Fill(1);
8687
a.Write();
87-
88+
8889
TFileMerger merger;
8990
auto output = std::unique_ptr<TFile>(new TFile("SingleOnlyListed.root", "RECREATE"));
9091
bool success = merger.OutputFile(std::move(output));
9192
ASSERT_TRUE(success);
92-
93+
9394
merger.AddObjectNames("hist1");
9495
merger.AddObjectNames("hist2");
9596
merger.AddFile(&a, false);
@@ -101,3 +102,60 @@ TEST(TFileMerger, MergeSingleOnlyListed)
101102
ASSERT_TRUE(output.get() && output->GetListOfKeys());
102103
EXPECT_EQ(output->GetListOfKeys()->GetSize(), 2);
103104
}
105+
106+
// https://github.com/root-project/root/issues/14558 aka https://its.cern.ch/jira/browse/ROOT-4716
107+
TEST(TFileMerger, MergeBranches)
108+
{
109+
TTree atree("atree", "atitle");
110+
int value;
111+
atree.Branch("a", &value);
112+
113+
TTree abtree("abtree", "abtitle");
114+
abtree.Branch("a", &value);
115+
abtree.Branch("b", &value);
116+
value = 11;
117+
abtree.Fill();
118+
value = 42;
119+
abtree.Fill();
120+
121+
TTree dummy("emptytree", "emptytitle");
122+
TList treelist;
123+
124+
// Case 1 - Static: NoBranch + NoEntries + 2 entries
125+
treelist.Add(&dummy);
126+
treelist.Add(&atree);
127+
treelist.Add(&abtree);
128+
std::unique_ptr<TFile> file1(TFile::Open("b_4716.root", "RECREATE"));
129+
auto rtree = TTree::MergeTrees(&treelist);
130+
ASSERT_TRUE(rtree->FindBranch("a") != nullptr);
131+
EXPECT_EQ(rtree->FindBranch("a")->GetEntries(), 2);
132+
ASSERT_TRUE(rtree->FindBranch("b") == nullptr);
133+
file1->Write();
134+
135+
// Case 2 - This (NoBranch) + NoEntries + 2 entries
136+
treelist.Clear();
137+
treelist.Add(&atree);
138+
treelist.Add(&abtree);
139+
std::unique_ptr<TFile> file2(TFile::Open("c_4716.root", "RECREATE"));
140+
TFileMergeInfo info2(file2.get());
141+
dummy.Merge(&treelist, &info2);
142+
ASSERT_TRUE(dummy.FindBranch("a") != nullptr);
143+
ASSERT_TRUE(dummy.FindBranch("b") == nullptr);
144+
EXPECT_EQ(dummy.FindBranch("a")->GetEntries(), 2);
145+
EXPECT_EQ(atree.FindBranch("a")->GetEntries(), 2);
146+
// atree has now 2 entries instead of zero since it was used as skeleton for dummy
147+
file2->Write();
148+
149+
// Case 3 - This (NoEntries) + 2 entries
150+
TTree a0tree("a0tree", "a0title"); // We cannot reuse atree since it was cannibalized by dummy
151+
a0tree.Branch("a", &value);
152+
treelist.Clear();
153+
treelist.Add(&abtree);
154+
std::unique_ptr<TFile> file3(TFile::Open("d_4716.root", "RECREATE"));
155+
TFileMergeInfo info3(file3.get());
156+
a0tree.Merge(&treelist, &info3);
157+
ASSERT_TRUE(a0tree.FindBranch("a") != nullptr);
158+
ASSERT_TRUE(a0tree.FindBranch("b") == nullptr);
159+
EXPECT_EQ(a0tree.FindBranch("a")->GetEntries(), 2);
160+
file3->Write();
161+
}

tree/tree/src/TTree.cxx

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
/** \class TTree
3131
\ingroup tree
3232
33-
A TTree represents a columnar dataset. Any C++ type can be stored in its columns. The modern
33+
A TTree represents a columnar dataset. Any C++ type can be stored in its columns. The modern
3434
version of TTree is RNTuple: please consider using it before opting for TTree.
3535
3636
A TTree, often called in jargon *tree*, consists of a list of independent columns or *branches*,
@@ -141,19 +141,19 @@ It is strongly recommended to persistify those as objects rather than lists of l
141141
~~~ {.cpp}
142142
auto branch = tree.Branch( branchname, STLcollection, buffsize, splitlevel);
143143
~~~
144-
`STLcollection` is the address of a pointer to a container of the standard
145-
library such as `std::vector`, `std::list`, containing pointers, fundamental types
144+
`STLcollection` is the address of a pointer to a container of the standard
145+
library such as `std::vector`, `std::list`, containing pointers, fundamental types
146146
or objects.
147147
If the splitlevel is a value bigger than 100 (`TTree::kSplitCollectionOfPointers`)
148-
then the collection will be written in split mode, i.e. transparently storing
148+
then the collection will be written in split mode, i.e. transparently storing
149149
individual data members as arrays, therewith potentially increasing compression ratio.
150150
151-
### Note
152-
In case of dynamic structures changing with each entry, see e.g.
151+
### Note
152+
In case of dynamic structures changing with each entry, see e.g.
153153
~~~ {.cpp}
154154
branch->SetAddress(void *address)
155155
~~~
156-
one must redefine the branch address before filling the branch
156+
one must redefine the branch address before filling the branch
157157
again. This is done via the `TBranch::SetAddress` member function.
158158
159159
\anchor addingacolumnofobjs
@@ -186,8 +186,8 @@ Another available syntax is the following:
186186
- `p_object` is a pointer to an object.
187187
- If `className` is not specified, the `Branch` method uses the type of `p_object`
188188
to determine the type of the object.
189-
- If `className` is used to specify explicitly the object type, the `className`
190-
must be of a type related to the one pointed to by the pointer. It should be
189+
- If `className` is used to specify explicitly the object type, the `className`
190+
must be of a type related to the one pointed to by the pointer. It should be
191191
either a parent or derived class.
192192
193193
Note: The pointer whose address is passed to `TTree::Branch` must not
@@ -205,13 +205,13 @@ or not
205205
tree.Branch(branchname, &p_object);
206206
~~~
207207
In either case, the ownership of the object is not taken over by the `TTree`.
208-
Even though in the first case an object is be allocated by `TTree::Branch`,
208+
Even though in the first case an object is be allocated by `TTree::Branch`,
209209
the object will <b>not</b> be deleted when the `TTree` is deleted.
210210
211211
\anchor addingacolumnoftclonesarray
212212
## Add a column holding TClonesArray instances
213213
214-
*The usage of `TClonesArray` should be abandoned in favour of `std::vector`,
214+
*The usage of `TClonesArray` should be abandoned in favour of `std::vector`,
215215
for which `TTree` has been heavily optimised, as well as `RNTuple`.*
216216
217217
~~~ {.cpp}
@@ -347,7 +347,7 @@ int main()
347347
~~~
348348
## TTree Diagram
349349
350-
The following diagram shows the organisation of the federation of classes related to TTree.
350+
The following diagram shows the organisation of the federation of classes related to TTree.
351351
352352
Begin_Macro
353353
../../../tutorials/legacy/tree/tree.C
@@ -6841,6 +6841,8 @@ bool TTree::MemoryFull(Int_t nbytes)
68416841
///
68426842
/// Trees in the list can be memory or disk-resident trees.
68436843
/// The new tree is created in the current directory (memory if gROOT).
6844+
/// Trees with no branches will be skipped, the branch structure
6845+
/// will be taken from the first non-zero-branch Tree of {li}
68446846

68456847
TTree* TTree::MergeTrees(TList* li, Option_t* options)
68466848
{
@@ -6852,8 +6854,15 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
68526854
while ((obj=next())) {
68536855
if (!obj->InheritsFrom(TTree::Class())) continue;
68546856
TTree *tree = (TTree*)obj;
6857+
if (tree->GetListOfBranches()->IsEmpty()) {
6858+
if (gDebug > 2) {
6859+
tree->Warning("MergeTrees","TTree %s has no branches, skipping.", tree->GetName());
6860+
}
6861+
continue; // Completely ignore the empty trees.
6862+
}
68556863
Long64_t nentries = tree->GetEntries();
6856-
if (nentries == 0) continue;
6864+
if (newtree && nentries == 0)
6865+
continue; // If we already have the structure and we have no entry, save time and skip
68576866
if (!newtree) {
68586867
newtree = (TTree*)tree->CloneTree(-1, options);
68596868
if (!newtree) continue;
@@ -6867,7 +6876,8 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
68676876
newtree->ResetBranchAddresses();
68686877
continue;
68696878
}
6870-
6879+
if (nentries == 0)
6880+
continue;
68716881
newtree->CopyEntries(tree, -1, options, true);
68726882
}
68736883
if (newtree && newtree->GetTreeIndex()) {
@@ -6880,9 +6890,43 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
68806890
/// Merge the trees in the TList into this tree.
68816891
///
68826892
/// Returns the total number of entries in the merged tree.
6893+
/// Trees with no branches will be skipped, the branch structure
6894+
/// will be taken from the first non-zero-branch Tree of {this+li}
68836895

68846896
Long64_t TTree::Merge(TCollection* li, Option_t *options)
68856897
{
6898+
if (fBranches.IsEmpty()) {
6899+
if (!li || li->IsEmpty())
6900+
return 0; // Nothing to do ....
6901+
// Let's find the first non-empty
6902+
TIter next(li);
6903+
TTree *tree;
6904+
while ((tree = (TTree *)next())) {
6905+
if (tree == this || tree->GetListOfBranches()->IsEmpty()) {
6906+
if (gDebug > 2) {
6907+
Warning("Merge","TTree %s has no branches, skipping.", tree->GetName());
6908+
}
6909+
continue;
6910+
}
6911+
// We could come from a list made up of different names, the first one still wins
6912+
tree->SetName(this->GetName());
6913+
auto prevEntries = tree->GetEntries();
6914+
auto result = tree->Merge(li, options);
6915+
if (result != prevEntries) {
6916+
// If there is no additional entries, the first write was enough.
6917+
tree->Write();
6918+
}
6919+
// Make sure things are really written out to disk before attempting any reading.
6920+
if (tree->GetCurrentFile()) {
6921+
tree->GetCurrentFile()->Flush();
6922+
// Read back the complete info in this TTree, so that caller does not
6923+
// inadvertently write the empty tree.
6924+
tree->GetDirectory()->ReadTObject(this, this->GetName());
6925+
}
6926+
return result;
6927+
}
6928+
return 0; // All trees have empty branches
6929+
}
68866930
if (!li) return 0;
68876931
Long64_t storeAutoSave = fAutoSave;
68886932
// Disable the autosave as the TFileMerge keeps a list of key and deleting the underlying
@@ -6915,11 +6959,43 @@ Long64_t TTree::Merge(TCollection* li, Option_t *options)
69156959
/// info->fOutputDirectory and then overlay the new TTree information onto
69166960
/// this TTree object (so that this TTree object is now the appropriate to
69176961
/// use for further merging).
6962+
/// Trees with no branches will be skipped, the branch structure
6963+
/// will be taken from the first non-zero-branch Tree of {this+li}
69186964
///
69196965
/// Returns the total number of entries in the merged tree.
69206966

69216967
Long64_t TTree::Merge(TCollection* li, TFileMergeInfo *info)
69226968
{
6969+
if (fBranches.IsEmpty()) {
6970+
if (!li || li->IsEmpty())
6971+
return 0; // Nothing to do ....
6972+
// Let's find the first non-empty
6973+
TIter next(li);
6974+
TTree *tree;
6975+
while ((tree = (TTree *)next())) {
6976+
if (tree == this || tree->GetListOfBranches()->IsEmpty()) {
6977+
if (gDebug > 2) {
6978+
Warning("Merge","TTree %s has no branches, skipping.", tree->GetName());
6979+
}
6980+
continue;
6981+
}
6982+
// We could come from a list made up of different names, the first one still wins
6983+
tree->SetName(this->GetName());
6984+
auto prevEntries = tree->GetEntries();
6985+
auto result = tree->Merge(li, info);
6986+
if (result != prevEntries) {
6987+
// If there is no additional entries, the first write was enough.
6988+
tree->Write();
6989+
}
6990+
// Make sure things are really written out to disk before attempting any reading.
6991+
info->fOutputDirectory->GetFile()->Flush();
6992+
// Read back the complete info in this TTree, so that TFileMerge does not
6993+
// inadvertently write the empty tree.
6994+
info->fOutputDirectory->ReadTObject(this, this->GetName());
6995+
return result;
6996+
}
6997+
return 0; // All trees have empty branches
6998+
}
69236999
const char *options = info ? info->fOptions.Data() : "";
69247000
if (info && info->fIsFirst && info->fOutputDirectory && info->fOutputDirectory->GetFile() != GetCurrentFile()) {
69257001
if (GetCurrentFile() == nullptr) {

0 commit comments

Comments
 (0)