Skip to content

Commit bde31d1

Browse files
committed
[TTree] when merging, ignore trees without branches
[TTree] when merging, do not clone branch structure if no entries Fixes #14558 Fixes #12510 [test] add fully qualified tree [tree] skip trees with zero branches in static merge By pcanal [test] amend tests with cases [tree] restore prev behavior [tree] skip "this" when Merge starts with empty-branch tree as implemented by pcanal [skip-ci] clarify comments [tree] early exit if all empty [test] fix merge substitution
1 parent 8e8fb53 commit bde31d1

File tree

2 files changed

+138
-16
lines changed

2 files changed

+138
-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: 78 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,11 @@ 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+
continue; // Completely ignore the empty trees.
68556859
Long64_t nentries = tree->GetEntries();
6856-
if (nentries == 0) continue;
6860+
if (newtree && nentries == 0)
6861+
continue; // If we already have the structure and we have no entry, save time and skip
68576862
if (!newtree) {
68586863
newtree = (TTree*)tree->CloneTree(-1, options);
68596864
if (!newtree) continue;
@@ -6867,7 +6872,8 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
68676872
newtree->ResetBranchAddresses();
68686873
continue;
68696874
}
6870-
6875+
if (nentries == 0)
6876+
continue;
68716877
newtree->CopyEntries(tree, -1, options, true);
68726878
}
68736879
if (newtree && newtree->GetTreeIndex()) {
@@ -6880,9 +6886,39 @@ TTree* TTree::MergeTrees(TList* li, Option_t* options)
68806886
/// Merge the trees in the TList into this tree.
68816887
///
68826888
/// Returns the total number of entries in the merged tree.
6889+
/// Trees with no branches will be skipped, the branch structure
6890+
/// will be taken from the first non-zero-branch Tree of {this+li}
68836891

68846892
Long64_t TTree::Merge(TCollection* li, Option_t *options)
68856893
{
6894+
if (fBranches.IsEmpty()) {
6895+
if (!li || li->IsEmpty())
6896+
return 0; // Nothing to do ....
6897+
// Let's find the first non-empty
6898+
TIter next(li);
6899+
TTree *tree;
6900+
while ((tree = (TTree *)next())) {
6901+
if (tree == this || tree->GetListOfBranches()->IsEmpty())
6902+
continue;
6903+
// We could come from a list made up of different names, the first one still wins
6904+
tree->SetName(this->GetName());
6905+
auto prevEntries = tree->GetEntries();
6906+
auto result = tree->Merge(li, options);
6907+
if (result != prevEntries) {
6908+
// If there is no additional entries, the first write was enough.
6909+
tree->Write();
6910+
}
6911+
// Make sure things are really written out to disk before attempting any reading.
6912+
if (tree->GetCurrentFile()) {
6913+
tree->GetCurrentFile()->Flush();
6914+
// Read back the complete info in this TTree, so that caller does not
6915+
// inadvertently write the empty tree.
6916+
tree->GetDirectory()->ReadTObject(this, this->GetName());
6917+
}
6918+
return result;
6919+
}
6920+
return 0; // All trees have empty branches
6921+
}
68866922
if (!li) return 0;
68876923
Long64_t storeAutoSave = fAutoSave;
68886924
// Disable the autosave as the TFileMerge keeps a list of key and deleting the underlying
@@ -6915,11 +6951,39 @@ Long64_t TTree::Merge(TCollection* li, Option_t *options)
69156951
/// info->fOutputDirectory and then overlay the new TTree information onto
69166952
/// this TTree object (so that this TTree object is now the appropriate to
69176953
/// use for further merging).
6954+
/// Trees with no branches will be skipped, the branch structure
6955+
/// will be taken from the first non-zero-branch Tree of {this+li}
69186956
///
69196957
/// Returns the total number of entries in the merged tree.
69206958

69216959
Long64_t TTree::Merge(TCollection* li, TFileMergeInfo *info)
69226960
{
6961+
if (fBranches.IsEmpty()) {
6962+
if (!li || li->IsEmpty())
6963+
return 0; // Nothing to do ....
6964+
// Let's find the first non-empty
6965+
TIter next(li);
6966+
TTree *tree;
6967+
while ((tree = (TTree *)next())) {
6968+
if (tree == this || tree->GetListOfBranches()->IsEmpty())
6969+
continue;
6970+
// We could come from a list made up of different names, the first one still wins
6971+
tree->SetName(this->GetName());
6972+
auto prevEntries = tree->GetEntries();
6973+
auto result = tree->Merge(li, info);
6974+
if (result != prevEntries) {
6975+
// If there is no additional entries, the first write was enough.
6976+
tree->Write();
6977+
}
6978+
// Make sure things are really written out to disk before attempting any reading.
6979+
info->fOutputDirectory->GetFile()->Flush();
6980+
// Read back the complete info in this TTree, so that TFileMerge does not
6981+
// inadvertently write the empty tree.
6982+
info->fOutputDirectory->ReadTObject(this, this->GetName());
6983+
return result;
6984+
}
6985+
return 0; // All trees have empty branches
6986+
}
69236987
const char *options = info ? info->fOptions.Data() : "";
69246988
if (info && info->fIsFirst && info->fOutputDirectory && info->fOutputDirectory->GetFile() != GetCurrentFile()) {
69256989
if (GetCurrentFile() == nullptr) {

0 commit comments

Comments
 (0)