@@ -1503,6 +1503,10 @@ void SetBranchesHelper(TTree *inputTree, TTree &outputTree, const std::string &i
15031503 }
15041504}
15051505
1506+ void SetEmptyBranchesHelper (TTree *inputTree, TTree &outputTree, RBranchSet &outputBranches,
1507+ const std::string &inputBranchName, const std::string &outputBranchName,
1508+ const std::type_info &typeInfo, int basketSize);
1509+
15061510// / Ensure that the TTree with the resulting snapshot can be written to the target TFile. This means checking that the
15071511// / TFile can be opened in the mode specified in `opts`, deleting any existing TTrees in case
15081512// / `opts.fOverwriteIfExists = true`, or throwing an error otherwise.
@@ -1618,6 +1622,18 @@ public:
16181622 (void )expander; // avoid unused variable warnings for older compilers such as gcc 4.9
16191623 }
16201624
1625+ template <std::size_t ... S>
1626+ void SetEmptyBranches (TTree *inputTree, TTree &outputTree, std::index_sequence<S...>)
1627+ {
1628+ RBranchSet outputBranches{};
1629+ // We use the expander trick rather than a fold expression to avoid incurring in the bracket depth limit of clang
1630+ int expander[] = {(SetEmptyBranchesHelper (inputTree, outputTree, outputBranches, fInputBranchNames [S],
1631+ fOutputBranchNames [S], typeid (ColTypes), fOptions .fBasketSize ),
1632+ 0 )...,
1633+ 0 };
1634+ (void )expander;
1635+ }
1636+
16211637 void Initialize ()
16221638 {
16231639 fOutputFile .reset (
@@ -1648,6 +1664,12 @@ public:
16481664 assert (fOutputTree != nullptr );
16491665 assert (fOutputFile != nullptr );
16501666
1667+ // There were no entries to fill the TTree with (either the input TTree was empty or no event passed after
1668+ // filtering). We have already created an empty TTree, now also create the branches to preserve the schema
1669+ if (fOutputTree ->GetEntries () == 0 ) {
1670+ using ind_t = std::index_sequence_for<ColTypes...>;
1671+ SetEmptyBranches (fInputTree , *fOutputTree , ind_t {});
1672+ }
16511673 // use AutoSave to flush TTree contents because TTree::Write writes in gDirectory, not in fDirectory
16521674 fOutputTree ->AutoSave (" flushbaskets" );
16531675 // must destroy the TTree first, otherwise TFile will delete it too leading to a double delete
@@ -1715,6 +1737,7 @@ class R__CLING_PTRCHECK(off) SnapshotTTreeHelperMT : public RActionImpl<Snapshot
17151737 std::vector<bool > fIsDefine ;
17161738 ROOT::Detail::RDF::RLoopManager *fOutputLoopManager ;
17171739 ROOT::Detail::RDF::RLoopManager *fInputLoopManager ;
1740+ TFile *fOutputFile ; // Non-owning view on the output file
17181741
17191742public:
17201743 using ColumnTypes_t = TypeList<ColTypes...>;
@@ -1847,39 +1870,67 @@ public:
18471870 (void )expander; // avoid unused parameter warnings (gcc 12.1)
18481871 }
18491872
1873+ template <std::size_t ... S>
1874+ void SetEmptyBranches (TTree *inputTree, TTree &outputTree, std::index_sequence<S...>)
1875+ {
1876+ RBranchSet outputBranches{};
1877+ // We use the expander trick rather than a fold expression to avoid incurring in the bracket depth limit of clang
1878+ int expander[] = {(SetEmptyBranchesHelper (inputTree, outputTree, outputBranches, fInputBranchNames [S],
1879+ fOutputBranchNames [S], typeid (ColTypes), fOptions .fBasketSize ),
1880+ 0 )...,
1881+ 0 };
1882+ (void )expander;
1883+ }
1884+
18501885 void Initialize ()
18511886 {
18521887 const auto cs = ROOT::CompressionSettings (fOptions .fCompressionAlgorithm , fOptions .fCompressionLevel );
1853- auto out_file = TFile::Open (fFileName .c_str (), fOptions .fMode .c_str (), /* ftitle=*/ fFileName .c_str (), cs);
1854- if (!out_file)
1888+ auto outFile = std::unique_ptr<TFile>{
1889+ TFile::Open (fFileName .c_str (), fOptions .fMode .c_str (), /* ftitle=*/ fFileName .c_str (), cs)};
1890+ if (!outFile)
18551891 throw std::runtime_error (" Snapshot: could not create output file " + fFileName );
1856- fMerger = std::make_unique<ROOT::TBufferMerger>(std::unique_ptr<TFile>(out_file));
1892+ fOutputFile = outFile.get ();
1893+ fMerger = std::make_unique<ROOT::TBufferMerger>(std::move (outFile));
18571894 }
18581895
18591896 void Finalize ()
18601897 {
18611898 assert (std::any_of (fOutputFiles .begin (), fOutputFiles .end (), [](const auto &ptr) { return ptr != nullptr ; }));
18621899
1863- auto fileWritten = false ;
18641900 for (auto &file : fOutputFiles ) {
18651901 if (file) {
18661902 file->Write ();
18671903 file->Close ();
1868- fileWritten = true ;
18691904 }
18701905 }
18711906
1872- if (!fileWritten) {
1873- Warning (" Snapshot" ,
1874- " No input entries (input TTree was empty or no entry passed the Filters). Output TTree is empty." );
1907+ // If there were no entries to fill the TTree with (either the input TTree was empty or no event passed after
1908+ // filtering), create an empty TTree in the output file and create the branches to preserve the schema
1909+ auto fullTreeName = fDirName .empty () ? fTreeName : fDirName + ' /' + fTreeName ;
1910+ assert (fOutputFile && " Missing output file in Snapshot finalization." );
1911+ if (!fOutputFile ->Get (fullTreeName.c_str ())) {
1912+
1913+ // First find in which directory we need to write the output TTree
1914+ TDirectory *treeDirectory = fOutputFile ;
1915+ if (!fDirName .empty ()) {
1916+ treeDirectory = fOutputFile ->mkdir (fDirName .c_str (), " " , true );
1917+ }
1918+ ::TDirectory::TContext c{treeDirectory};
1919+
1920+ // Create the output TTree and create the user-requested branches
1921+ auto outTree =
1922+ std::make_unique<TTree>(fTreeName .c_str (), fTreeName .c_str (), fOptions .fSplitLevel , /* dir=*/ treeDirectory);
1923+ using ind_t = std::index_sequence_for<ColTypes...>;
1924+ SetEmptyBranches (fInputLoopManager ->GetTree (), *outTree, ind_t {});
1925+
1926+ fOutputFile ->Write ();
18751927 }
18761928
18771929 // flush all buffers to disk by destroying the TBufferMerger
18781930 fOutputFiles .clear ();
18791931 fMerger .reset ();
18801932
18811933 // Now connect the data source to the loop manager so it can be used for further processing
1882- auto fullTreeName = fDirName .empty () ? fTreeName : fDirName + ' /' + fTreeName ;
18831934 fOutputLoopManager ->SetDataSource (std::make_unique<ROOT::Internal::RDF::RTTreeDS>(fullTreeName, fFileName ));
18841935 }
18851936
0 commit comments