diff --git a/Control/xAODDataSource/CMakeLists.txt b/Control/xAODDataSource/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f44640b46fd6a0c07f6ede4bcc5221079ba1ab47
--- /dev/null
+++ b/Control/xAODDataSource/CMakeLists.txt
@@ -0,0 +1,62 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+
+# Set the name of the package.
+atlas_subdir( xAODDataSource )
+
+# Set its dependencies on other packages.
+atlas_depends_on_subdirs(
+   PUBLIC
+   Control/xAODRootAccess )
+
+# External dependencies. VDT is necessary because the DataFrame code has
+# a public dependency on it.
+find_package( ROOT COMPONENTS Core Tree TreePlayer RIO ROOTDataFrame )
+find_package( VDT )
+
+# Build the library.
+atlas_add_library( xAODDataSourceLib
+   xAODDataSource/*.h Root/*.h Root/*.cxx
+   PUBLIC_HEADERS xAODDataSource
+   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS} ${VDT_INCLUDE_DIRS}
+   LINK_LIBRARIES ${ROOT_LIBRARIES} ${VDT_LIBRARIES} xAODRootAccess )
+
+# Build its dictionary.
+atlas_add_dictionary( xAODDataSourceDict
+   xAODDataSource/xAODDataSourceDict.h xAODDataSource/selection.xml
+   LINK_LIBRARIES xAODDataSourceLib )
+
+# Build the package's test(s).
+atlas_add_test( dataSourceEvent_test
+   SOURCES test/dataSourceEvent_test.cxx
+   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+   LINK_LIBRARIES ${ROOT_LIBRARIES} xAODRootAccess xAODDataSourceLib )
+
+atlas_add_test( dataSource_test
+   SOURCES test/dataSource_test.cxx
+   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+   LINK_LIBRARIES ${ROOT_LIBRARIES} xAODRootAccess xAODDataSourceLib )
+
+atlas_add_test( dataFrame_test
+   SOURCES test/dataFrame_test.cxx
+   INCLUDE_DIRS ${ROOT_INCLUDE_DIRS}
+   LINK_LIBRARIES ${ROOT_LIBRARIES} xAODRootAccess xAODBase xAODDataSourceLib
+   # These patterns are needed to suppress some warning messages in debug builds
+   LOG_IGNORE_PATTERN "[0-9a-f][0-9a-f] [0-9a-f][0-9a-f]|\\^~~~|vptr" )
+
+atlas_add_test( dataFrameTypeConversion_test
+   SOURCES test/dataFrameTypeConversion_test.cxx
+   LINK_LIBRARIES xAODRootAccess xAODBase xAODEgamma xAODDataSourceLib
+   # These patterns are needed to suppress some warning messages in debug builds
+   LOG_IGNORE_PATTERN "[0-9a-f][0-9a-f] [0-9a-f][0-9a-f]|\\^~~~|vptr" )
+
+atlas_add_test( dataFrameElementLink_test
+   SOURCES test/dataFrameElementLink_test.cxx
+   LINK_LIBRARIES xAODRootAccess xAODBase xAODEgamma xAODMuon xAODDataSourceLib
+   # These patterns are needed to suppress some warning messages in debug builds
+   LOG_IGNORE_PATTERN "[0-9a-f][0-9a-f] [0-9a-f][0-9a-f]|\\^~~~|vptr" )
+
+atlas_add_test( dataFrame_pytest
+   SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/test/dataFrame_test.py )
+
+# Install files from the package.
+atlas_install_python_modules( python/*.py )
diff --git a/Control/xAODDataSource/README.md b/Control/xAODDataSource/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..3f13f0064c9961db2cc824abc17d580d344c0efb
--- /dev/null
+++ b/Control/xAODDataSource/README.md
@@ -0,0 +1,5 @@
+# xAOD Data Source for ROOT::RDataFrame
+
+This package holds some code to allow using
+[ROOT::RDataFrame](https://root.cern/doc/master/classROOT_1_1RDataFrame.html)
+for reading (D)xAOD files.
diff --git a/Control/xAODDataSource/Root/MakeDataFrame.cxx b/Control/xAODDataSource/Root/MakeDataFrame.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d6fa1b5516a75a59cbb0061daac809456a59a7ad
--- /dev/null
+++ b/Control/xAODDataSource/Root/MakeDataFrame.cxx
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "xAODDataSource/MakeDataFrame.h"
+#include "RDataSource.h"
+
+// System include(s).
+#include <memory>
+
+namespace xAOD {
+
+   ROOT::RDataFrame MakeDataFrame( std::string_view fileNameGlob,
+                                   std::string_view treeName,
+                                   bool verboseOutput ) {
+
+      auto source = std::make_unique< RDataSource >( fileNameGlob, treeName );
+      source->setVerboseOutput( verboseOutput );
+      return ROOT::RDataFrame( std::move( source ) );
+   }
+
+   ROOT::RDataFrame MakeDataFrame( const std::vector< std::string >& fileNames,
+                                   std::string_view treeName,
+                                   bool verboseOutput ) {
+
+      auto source = std::make_unique< RDataSource >( fileNames, treeName );
+      source->setVerboseOutput( verboseOutput );
+      return ROOT::RDataFrame( std::move( source ) );
+   }
+
+} // namespace xAOD
diff --git a/Control/xAODDataSource/Root/RDataSource.cxx b/Control/xAODDataSource/Root/RDataSource.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..2c7bd65f42e44f99dece8493f34548685fdf160c
--- /dev/null
+++ b/Control/xAODDataSource/Root/RDataSource.cxx
@@ -0,0 +1,432 @@
+//
+// Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "RDataSource.h"
+
+// xAOD include(s).
+#include "xAODRootAccess/TEvent.h"
+#include "xAODRootAccess/tools/Message.h"
+
+// ROOT include(s).
+#include <TChain.h>
+#include <TFile.h>
+#include <TROOT.h>
+#include <TError.h>
+
+// System include(s).
+#include <algorithm>
+#include <memory>
+#include <stdexcept>
+
+namespace {
+
+   /// Convenience function for creating a chain
+   std::unique_ptr< TChain >
+   makeChain( const std::vector< std::string >& fileNames,
+              std::string_view treeName ) {
+
+      // Create the chain object.
+      std::unique_ptr< TChain > chain;
+      {
+         R__LOCKGUARD( gROOTMutex );
+         chain = std::make_unique< TChain >( treeName.data() );
+      }
+
+      // Set it up.
+      chain->ResetBit( kMustCleanup );
+      for( const std::string& fileName : fileNames ) {
+         chain->Add( fileName.c_str() );
+      }
+
+      // Return the newly created chain.
+      return chain;
+   }
+
+} // private namespace
+
+namespace xAOD {
+
+   /// Helper print operator
+   template< typename FIRST, typename SECOND >
+   std::ostream& operator<< ( std::ostream& out,
+                              const std::pair< FIRST, SECOND >& pair ) {
+
+      out << "[" << pair.first << ", " << pair.second << "]";
+      return out;
+   }
+
+   /// Helper print operator
+   template< typename T >
+   std::ostream& operator<< ( std::ostream& out, const std::vector< T >& vec ) {
+
+      out << "[";
+      for( size_t i = 0; i < vec.size(); ++i ) {
+         out << vec[ i ];
+         if( i + 1 < vec.size() ) {
+            out << ", ";
+         }
+      }
+      out << "]";
+      return out;
+   }
+
+   /// Helper print operator
+   template< typename T >
+   std::ostream& operator<< ( std::ostream& out,
+                              const std::unordered_map< T, T >& umap ) {
+
+      out << "{";
+      bool first = true;
+      for( auto& upair : umap ) {
+         if( ! first ) {
+            out << ", ";
+         }
+         out << upair.first << " : " << upair.second;
+         first = false;
+      }
+      out << "}";
+      return out;
+   }
+
+} // namespace xAOD
+
+/// Helper macro for printing verbose messages for debugging
+#define PRINT_VERBOSE(MSG)                                              \
+   do {                                                                 \
+      if( m_verboseOutput ) {                                           \
+         std::cout << "xAOD::RDataSource VERBOSE " << MSG << std::endl; \
+      }                                                                 \
+   } while( false )
+
+namespace xAOD {
+
+   RDataSource::RDataSource( std::string_view fileNameGlob,
+                             std::string_view treeName )
+      : RDataSource( std::vector< std::string >( { fileNameGlob.data() } ),
+                     treeName ) {
+
+   }
+
+   RDataSource::RDataSource( const std::vector< std::string >& fileNames,
+                             std::string_view treeName )
+      : m_fileNames( fileNames ), m_treeName( treeName ),
+        m_verboseOutput( kFALSE ) {
+
+      readInputMetadata();
+   }
+
+   RDataSource::~RDataSource() {
+
+      // I don't understand why, but ROOT really doesn't like it if the
+      // chains are not the first to be deleted from memory. :-/
+      m_chains.clear();
+   }
+
+   void RDataSource::SetNSlots( unsigned int slots ) {
+
+      // Some sanity checks.
+      if( slots == 0 ) {
+         ::Error( "xAOD::RDataSource::SetNSlots",
+                  XAOD_MESSAGE( "Zero slots requested" ) );
+         throw std::invalid_argument( "Zero slots requested" );
+      }
+      if( m_events.size() != 0 ) {
+         ::Error( "xAOD::RDataSource::SetNSlots",
+                  XAOD_MESSAGE( "Function called multiple times" ) );
+         throw std::runtime_error( "Function called multiple times" );
+      }
+
+      // Reserve the correct number of elements.
+      m_chains.reserve( slots );
+      m_events.reserve( slots );
+      m_stores.reserve( slots );
+      PRINT_VERBOSE( "SetNSlots: Reserved " << slots
+                     << " slots for the chains, events and stores" );
+
+      // Create the event objects already at this point.
+      for( unsigned int i = 0; i < slots; ++i ) {
+
+         // Set up the chain, event and store.
+         m_chains.push_back( ::makeChain( m_fileNames, m_treeName ) );
+         m_events.push_back( std::make_unique< RDataSourceEvent >() );
+         m_stores.push_back( std::make_unique< TStore >() );
+         TChain* chain = m_chains.back().get();
+         RDataSourceEvent* event = m_events.back().get();
+
+         // Initialise the event object.
+         if( ! event->readFrom( chain ).isSuccess() ) {
+            ::Error( "xAOD::RDataSource::SetNSlots",
+                     XAOD_MESSAGE( "Failed to set up xAOD::RDataSourceEvent "
+                                   "for slot %u" ), i );
+            throw std::runtime_error( "Failed to set up "
+                                      "xAOD::RDataSourceEvent" );
+         }
+
+         // Load entry 0 for it. Notice that this is a waste of CPU and I/O
+         // on the surface. But it's not... This triggers the initialisation of
+         // the files/trees used by these chains. Which happens much more
+         // quickly in a serial way in a single thread than in multiple threads
+         // at the same time. To be followed up with the ROOT developers...
+         if( event->getEntry( 0 ) < 0 ) {
+            ::Error( "xAOD::RDataSource::SetNSlots",
+                     XAOD_MESSAGE( "Failed to load entry 0 for slot %u" ), i );
+            throw std::runtime_error( "Failed to load entry for slot" );
+         }
+         PRINT_VERBOSE( "SetNSlots: Initialised objects for slot " << i );
+      }
+
+      // Return gracefully.
+      return;
+   }
+
+   void RDataSource::Initialise() {
+
+      // A sanity check.
+      if( m_entryRanges.size() != 0 ) {
+         ::Fatal( "xAOD::RDataSource::Initialise",
+                  XAOD_MESSAGE( "Function called on an initialised object" ) );
+      }
+      PRINT_VERBOSE( "Initialise: Initialising the data source" );
+
+      // Create a chain that will help determine the optimal entry ranges
+      // to process.
+      auto chain = ::makeChain( m_fileNames, m_treeName );
+      TObjArray* filesInChain = chain->GetListOfFiles();
+
+      // Loop over the input files of the chain.
+      Long64_t fileOffset = 0;
+      for( Int_t ifile = 0; ifile < filesInChain->GetEntries(); ++ifile ) {
+
+         // Open the file directly.
+         const char* fileName = filesInChain->At( ifile )->GetTitle();
+         auto file = std::unique_ptr< TFile >( TFile::Open( fileName,
+                                                            "READ" ) );
+         if( ( ! file ) || file->IsZombie() ) {
+            ::Error( "xAOD::RDataSource::Initialise",
+                     XAOD_MESSAGE( "Failed to open file: %s" ), fileName );
+            throw std::runtime_error( "Failed to open file: " +
+                                      std::string( fileName ) );
+         }
+
+         // Access the event tree inside of it.
+         TTree* tree =
+            dynamic_cast< TTree* >( file->Get( m_treeName.c_str() ) );
+         if( ! tree ) {
+            // A file with no event tree is not necessarily a problem. It could
+            // just be a file that has no events left in it after all
+            // selections.
+            continue;
+         }
+
+         // Extract the ideal entry ranges from the file.
+         const Long64_t entries = tree->GetEntries();
+         TTree::TClusterIterator clusterIter( tree->GetClusterIterator( 0 ) );
+         Long64_t clusterStart = 0;
+         while( ( clusterStart = clusterIter() ) < entries ) {
+            m_entryRanges.emplace_back( fileOffset + clusterStart,
+                                        fileOffset +
+                                        clusterIter.GetNextEntry() );
+         }
+
+         // Increment the file offset value.
+         fileOffset += entries;
+      }
+      PRINT_VERBOSE( "Initialise: Created entry ranges: " << m_entryRanges );
+
+      // Return gracefully.
+      return;
+   }
+
+   void RDataSource::InitSlot( unsigned int slot, ULong64_t firstEntry ) {
+
+      // A sanity check.
+      if( m_events.size() <= slot ) {
+         ::Error( "xAOD::RDataSource::InitSlot",
+                  XAOD_MESSAGE( "Invalid slot (%u) received" ), slot );
+         throw std::runtime_error( "Invalid slot received" );
+      }
+
+      // Load the first entry for it.
+      if( m_events[ slot ]->getEntry( firstEntry ) < 0 ) {
+         ::Error( "xAOD::RDataSource::InitSlot",
+                  XAOD_MESSAGE( "Failed to load entry %lld for slot %u" ),
+                  firstEntry, slot );
+         throw std::runtime_error( "Failed to load entry for slot" );
+      }
+      PRINT_VERBOSE( "InitSlot: Retrieved entry " << firstEntry << " for slot "
+                     << slot );
+
+      // Activate and clear the store.
+      m_stores[ slot ]->setActive();
+      m_stores[ slot ]->clear();
+      PRINT_VERBOSE( "InitSlot: Activated and cleared transient store for slot "
+                     << slot );
+
+      // Return gracefully.
+      return;
+   }
+
+   void RDataSource::FinaliseSlot( unsigned int slot ) {
+
+      // Simply print what's happening.
+      PRINT_VERBOSE( "FinaliseSlot: Called for slot " << slot );
+
+      // Return gracefully.
+      return;
+   }
+
+   void RDataSource::Finalise() {
+
+      // Simply print what's happening.
+      PRINT_VERBOSE( "Finalise: Function called" );
+
+      // Return gracefully.
+      return;
+   }
+
+   const std::vector< std::string >& RDataSource::GetColumnNames() const {
+
+      return m_columnNames;
+   }
+
+   bool RDataSource::HasColumn( std::string_view name ) const {
+
+      return std::find( m_columnNames.begin(), m_columnNames.end(),
+                        name ) != m_columnNames.end();
+   }
+
+   std::string RDataSource::GetTypeName( std::string_view column ) const {
+
+      // Make sure that the column/object is known.
+      if( ! HasColumn( column ) ) {
+         ::Error( "xAOD::RDataSource::GetTypeName",
+                  XAOD_MESSAGE( "Column/object \"%s\" not available" ),
+                  column.data() );
+         throw std::runtime_error( "Column/object \"" + column +
+                                   "\" not available" );
+      }
+
+      // Get the type.
+      auto itr = m_classNameMap.find( column.data() );
+      if( itr == m_classNameMap.end() ) {
+         // Note that the fatal message will abort the entire job in all cases.
+         ::Fatal( "xAOD::RDataSource::GetTypeName",
+                  XAOD_MESSAGE( "Internal logic error found" ) );
+      }
+      PRINT_VERBOSE( "GetTypeName: Type name for column \"" << column
+                     << "\" is: " << itr->second );
+      return itr->second;
+   }
+
+   RDataSource::EntryRanges_t RDataSource::GetEntryRanges() {
+
+      // When ROOT asks for the entry ranges, we have to tell it which ones
+      // have not been processed yet. Since we process all entries right away
+      // (SetEntry(...) does not have logic for not processing a requested
+      // entry), the logic here is to empty out the m_entryRanges variable on
+      // this call. So that on the next call an empty range would be returned.
+      const EntryRanges_t dummy( std::move( m_entryRanges ) );
+      return dummy;
+   }
+
+   bool RDataSource::SetEntry( unsigned int slot, ULong64_t entry ) {
+
+      // A sanity check.
+      if( m_events.size() <= slot ) {
+         ::Error( "xAOD::RDataSource::SetEntry",
+                  XAOD_MESSAGE( "Invalid slot (%u) received" ), slot );
+         throw std::runtime_error( "Invalid slot received" );
+      }
+      PRINT_VERBOSE( "SetEntry: Called for slot " << slot << " and entry "
+                     << entry );
+
+      // Switch to the requested entry.
+      m_events[ slot ]->updateObjectsForEntry( entry );
+
+      // Activate and clear the store.
+      m_stores[ slot ]->setActive();
+      m_stores[ slot ]->clear();
+
+      // The entry is always processed.
+      return true;
+   }
+
+   void RDataSource::setVerboseOutput( Bool_t value ) {
+
+      m_verboseOutput = value;
+      return;
+   }
+
+   Bool_t RDataSource::isVerboseOutput() const {
+
+      return m_verboseOutput;
+   }
+
+   RDataSource::Record_t
+   RDataSource::GetColumnReadersImpl( std::string_view column,
+                                      const std::type_info& typeInfo ) {
+
+      // Make sure that the column/object is known.
+      if( ! HasColumn( column ) ) {
+         ::Error( "xAOD::RDataSource::GetColumnReadersImpl",
+                  XAOD_MESSAGE( "Column/object \"%s\" not available" ),
+                  column.data() );
+         throw std::runtime_error( "Column/object \"" + column +
+                                   "\" not available" );
+      }
+      PRINT_VERBOSE( "GetColumnReadersImpl: Creating column readers for \""
+                     << column << "/" << SG::normalizedTypeinfoName( typeInfo )
+                     << "\"" );
+
+      // Create the comlumn reader pointers.
+      Record_t result( m_events.size() );
+      for( size_t i = 0; i < m_events.size(); ++i ) {
+         result[ i ] = m_events[ i ]->columnReader( column, typeInfo );
+      }
+      return result;
+   }
+
+   void RDataSource::readInputMetadata() {
+
+      // Create a temporary event object.
+      auto chain = ::makeChain( m_fileNames, m_treeName );
+      RDataSourceEvent event;
+      if( ! event.readFrom( chain.get() ).isSuccess() ) {
+         ::Error( "xAOD::RDataSource::readInputMetadata",
+                  XAOD_MESSAGE( "Failed to connect to the input chain" ) );
+         throw std::runtime_error( "Failed to connect to the input chain" );
+      }
+
+      // Load the first event of the input, if one is available.
+      if( event.getEntries() > 0 ) {
+         if( event.getEntry( 0 ) < 0 ) {
+            ::Error( "xAOD::RDataSource::readInputMetadata",
+                     "Couldn't load the first event of the input" );
+            throw std::runtime_error( "Couldn't load the first event of the "
+                                      "input" );
+         }
+      }
+
+      // Fill the column and type name variables.
+      m_columnNames.clear(); m_classNameMap.clear();
+      auto names = event.columnAndTypeNames();
+      m_columnNames.reserve( names.size() );
+      m_classNameMap.reserve( names.size() );
+      for( const auto& pair : names ) {
+         m_columnNames.push_back( pair.first );
+         m_classNameMap[ pair.first ] = pair.second;
+      }
+      PRINT_VERBOSE( "readInputMetadata: m_columnNames = " << m_columnNames );
+      PRINT_VERBOSE( "readInputMetadata: m_classNameMap = " << m_classNameMap );
+
+      // ROOT memory management is weird... We must delete the chain first,
+      // before the TEvent object on top of it would be deleted...
+      chain.reset();
+
+      // Return gracefully.
+      return;
+   }
+
+} // namespace xAOD
diff --git a/Control/xAODDataSource/Root/RDataSource.h b/Control/xAODDataSource/Root/RDataSource.h
new file mode 100644
index 0000000000000000000000000000000000000000..fd37cc827a6dd71fa786ded8752421940016514a
--- /dev/null
+++ b/Control/xAODDataSource/Root/RDataSource.h
@@ -0,0 +1,142 @@
+// Dear emacs, this is -*- c++ -*-
+//
+// Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+//
+#ifndef XAODDATASOURCE_RDATASOURCE_H
+#define XAODDATASOURCE_RDATASOURCE_H
+
+// Local include(s).
+#include "RDataSourceEvent.h"
+
+// Framework include(s).
+#include "xAODRootAccess/TStore.h"
+
+// ROOT include(s).
+#include <ROOT/RDataSource.hxx>
+#include <ROOT/RStringView.hxx>
+#include <TChain.h>
+
+// System include(s).
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace xAOD {
+
+   /// Data source for xAOD input files
+   ///
+   /// This data source can be used to allow @c ROOT::RDataFrame to process
+   /// (D)xAOD inputs.
+   ///
+   /// @author Umesh Worlikar <Umesh.Dharmaji.Worlikar@cern.ch>
+   /// @author Attila Krasznahorkay <Attila.Krasznahorkay@cern.ch>
+   ///
+   class RDataSource final : public ROOT::RDF::RDataSource {
+
+   public:
+      /// Constructor with the file name pattern
+      RDataSource( std::string_view fileNameGlob,
+                   std::string_view treeName = "CollectionTree" );
+      /// Constructor with list of file names
+      RDataSource( const std::vector< std::string >& fileNames,
+                   std::string_view treeName = "CollectionTree" );
+      /// Destructor
+      ~RDataSource();
+
+      /// Type describing the entry ranges of the input file(s)
+      typedef std::vector< std::pair< ULong64_t, ULong64_t > > EntryRanges_t;
+
+      /// @name Functions implemented from @c ROOT::RDF::RDataSource
+      /// @{
+
+      /// Set the number of threads/slots that the data source should use
+      virtual void SetNSlots( unsigned int slots ) override final;
+      /// Initialise the data source, before the start of the event loop
+      virtual void Initialise() override final;
+      /// Initialise one of the slots/threads
+      virtual void
+      InitSlot( unsigned int slot, ULong64_t firstEntry ) override final;
+
+      /// Close the input file reading in one of the slots/threads
+      virtual void FinaliseSlot( unsigned int slot ) override final;
+      /// Finalise the data source, after the event loop
+      virtual void Finalise() override final;
+
+      /// Get the column/object names for the input file(s)
+      virtual const std::vector< std::string >&
+      GetColumnNames() const override final;
+      /// Check if the dataset has a certain column/object
+      virtual bool HasColumn( std::string_view name ) const override final;
+      /// Get the type name of a given column/object
+      virtual std::string
+      GetTypeName( std::string_view column ) const override final;
+
+      /// Get the entry ranges in the input file(s)
+      virtual EntryRanges_t GetEntryRanges() override final;
+      /// Set which entry a give slot/thread should be processing
+      virtual bool
+      SetEntry( unsigned int slot, ULong64_t entry ) override final;
+
+      /// @}
+
+      /// Set whether verbose output should be printed (for debugging)
+      void setVerboseOutput( Bool_t value = kTRUE );
+      /// Check whether verbose output is set up to be printed
+      Bool_t isVerboseOutput() const;
+
+   private:
+      /// Return the type-erased vector of pointers to pointers to column values
+      virtual Record_t
+      GetColumnReadersImpl( std::string_view column,
+                            const std::type_info& typeInfo ) override final;
+      /// Fill the metadata variables
+      void readInputMetadata();
+
+      /// @name Arguments provided by the user
+      /// @{
+
+      /// Files to read
+      std::vector< std::string > m_fileNames;
+      /// Name of the event tree in the input files
+      std::string m_treeName;
+      /// Whether verbose output should be printed or not
+      Bool_t m_verboseOutput;
+
+      /// @}
+
+      /// @name Variables valid right after construction
+      /// @{
+
+      /// Names of the columns/objects on the input
+      std::vector< std::string > m_columnNames;
+      /// The object name -> class name map
+      std::unordered_map< std::string, std::string > m_classNameMap;
+
+      /// @}
+
+      /// @name Variables that become valid after calling @c Initialise()
+      /// @{
+
+      /// Optimal entry ranges to split the processing into
+      EntryRanges_t m_entryRanges;
+
+      /// @}
+
+      /// @name Variables that become valid after calling @c SetNSlots(...)
+      /// @{
+
+      /// Chains used in the file I/O
+      std::vector< std::unique_ptr< TChain > > m_chains;
+      /// Event objects performing the file I/O
+      std::vector< std::unique_ptr< RDataSourceEvent > > m_events;
+      /// In-memory whiteboards used during the event loop
+      std::vector< std::unique_ptr< TStore > > m_stores;
+
+      /// @}
+
+   }; // class RDataSource
+
+} // namespace xAOD
+
+#endif // XAODDATASOURCE_RDATASOURCE_H
diff --git a/Control/xAODDataSource/Root/RDataSourceEvent.cxx b/Control/xAODDataSource/Root/RDataSourceEvent.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..d119d76838237b290081f13bf0c9b9b10337ee6a
--- /dev/null
+++ b/Control/xAODDataSource/Root/RDataSourceEvent.cxx
@@ -0,0 +1,141 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "RDataSourceEvent.h"
+
+// xAOD include(s).
+#include "xAODRootAccess/tools/Message.h"
+#include "xAODRootAccess/tools/Utils.h"
+#include "xAODRootAccess/tools/TObjectManager.h"
+#include "xAODRootAccess/tools/THolder.h"
+
+// ROOT include(s).
+#include <TClass.h>
+#include <TTree.h>
+#include <TError.h>
+
+// System include(s).
+#include <functional>
+
+namespace xAOD {
+
+   RDataSourceEvent::RDataSourceEvent()
+      : TEvent( TEvent::kClassAccess ) {
+
+   }
+
+   std::vector< std::pair< std::string, std::string > >
+   RDataSourceEvent::columnAndTypeNames() {
+
+      // This makes sure that the input is connected to.
+      if( ( ! m_inTree ) && ( getEntry( 0 ) < 0 ) ) {
+         ::Error( "xAOD::RDataSourceEvent::columnAndTypeNames",
+                  XAOD_MESSAGE( "No input file opened yet" ) );
+         throw std::runtime_error( "No input file opened yet" );
+      }
+
+      // A sanity check.
+      if( ! inputEventFormat() ) {
+         ::Error( "xAOD::RDataSourceEvent::columnAndTypeNames",
+                 XAOD_MESSAGE( "No xAOD::EventFormat object available" ) );
+         throw std::runtime_error( "No xAOD::EventFormat object available" );
+      }
+
+      // The result object.
+      std::vector< std::pair< std::string, std::string > > result;
+
+      // Loop over the known objects.
+      EventFormat::const_iterator ef_itr = inputEventFormat()->begin();
+      EventFormat::const_iterator ef_end = inputEventFormat()->end();
+      for( ; ef_itr != ef_end; ++ef_itr ) {
+
+         // A convenient reference.
+         const xAOD::EventFormatElement& efe = ef_itr->second;
+
+         // Skip auxiliary containers.
+         if( efe.branchName().rfind( "Aux." ) ==
+             ( efe.branchName().size() - 4 ) ) {
+            continue;
+         }
+         // Skip dynamic branches.
+         if( efe.parentName() != "" ) {
+            continue;
+         }
+         // Skip unavailable branches.
+         if( ! m_inTree->GetBranch( efe.branchName().c_str() ) ) {
+            continue;
+         }
+         // Skip unknown types.
+         ::TClass* cl = ::TClass::GetClass( efe.className().c_str() );
+         if( ! cl ) {
+            continue;
+         }
+         // Skip auxiliary containers that are mislabeled.
+         if( cl->InheritsFrom( "SG::IConstAuxStore" ) ) {
+            continue;
+         }
+
+         // Make sure that we have a functional dictionary for the type.
+         const std::type_info* ti = cl->GetTypeInfo();
+         if( ! ti ) {
+            ::Warning( "xAOD::RDataSourceEvent::columnAndTypeNames",
+                       "No std::type_info available for type %s (key=%s)",
+                       cl->GetName(), efe.branchName().c_str() );
+            continue;
+         }
+
+         // And do one final check that we can really read this type.
+         if( ! contains( efe.branchName(), *ti ) ) {
+            continue;
+         }
+
+         // Remember this objects.
+         result.emplace_back( efe.branchName(), efe.className() );
+      }
+
+      // Return the collected results.
+      return result;
+   }
+
+   void* RDataSourceEvent::columnReader( std::string_view columnName,
+                                         const std::type_info& typeInfo ) {
+
+      // Make sure that this is the active event in the current thread.
+      setActive();
+
+      // Create/access the pointer for this object.
+      return &( m_objects[ std::make_pair( std::string( columnName ),
+                                           &typeInfo ) ] );
+   }
+
+   void RDataSourceEvent::updateObjectsForEntry( Long64_t entry ) {
+
+      // Make sure that this is the active event in the current thread.
+      setActive();
+
+      // Switch to the new entry.
+      if( getEntry( entry ) < 0 ) {
+         ::Error( "xAOD::RDataSourceEvent::updateObjectsForEntry",
+                  XAOD_MESSAGE( "Failed to get entry %lld" ), entry );
+         throw std::runtime_error( "Failed to get entry" );
+      }
+
+      // Loop over all "registered" objects.
+      for( auto& obj : m_objects ) {
+         // (Re-)Access this object.
+         obj.second = getInputObject( obj.first.first, *( obj.first.second ) );
+      }
+
+      // Return gracefully.
+      return;
+   }
+
+   std::size_t
+   RDataSourceEvent::key_hash::operator()( const Key_t& key ) const {
+
+      return std::hash< std::string >()( key.first + key.second->name() );
+   }
+
+} // namespace xAOD
diff --git a/Control/xAODDataSource/Root/RDataSourceEvent.h b/Control/xAODDataSource/Root/RDataSourceEvent.h
new file mode 100644
index 0000000000000000000000000000000000000000..4b13bb45ea8548230664d341955d8bd0ab6074a8
--- /dev/null
+++ b/Control/xAODDataSource/Root/RDataSourceEvent.h
@@ -0,0 +1,63 @@
+// Dear emacs, this is -*- c++ -*-
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+#ifndef XAODDATASOURCE_RDATASOURCEEVENT_H
+#define XAODDATASOURCE_RDATASOURCEEVENT_H
+
+// xAOD include(s).
+#include "xAODRootAccess/TEvent.h"
+
+// ROOT include(s).
+#include <ROOT/RStringView.hxx>
+
+// System include(s).
+#include <string>
+#include <vector>
+#include <utility>
+#include <typeinfo>
+#include <unordered_map>
+
+namespace xAOD {
+
+   /// Extension to @c xAOD::TEvent, used by @c xAOD::RDataSource
+   ///
+   /// In order to get access to the internals of @c xAOD::TEvent, and provide
+   /// pointers for @c ROOT::RDataFrame in a convenient way, this class provides
+   /// a layer between the two codebases.
+   ///
+   /// @author Attila Krasznahorkay <Attila.Krasznahorkay@cern.ch>
+   ///
+   class RDataSourceEvent final : public TEvent {
+
+   public:
+      /// Default constructor
+      RDataSourceEvent();
+
+      /// Get the available columm and type names from the input
+      std::vector< std::pair< std::string, std::string > > columnAndTypeNames();
+      /// Get a "column reader", a pointer to a pointer to the object
+      void* columnReader( std::string_view columnName,
+                          const std::type_info& typeInfo );
+      /// Update all objects in memory for a new event
+      void updateObjectsForEntry( Long64_t entry );
+
+   private:
+      /// Type of the key used in the internal map of object pointers
+      typedef std::pair< std::string, const std::type_info* > Key_t;
+
+      /// Custom hash functor for use with @c Key_t
+      class key_hash {
+      public:
+         /// Function calculating the hash for this type
+         std::size_t operator()( const Key_t& key ) const;
+      };
+
+      /// Objects served to RDataFrame
+      std::unordered_map< Key_t, const void*, key_hash > m_objects;
+
+   }; // class RDataSourceEvent
+
+} // namespace xAOD
+
+#endif // XAODDATASOURCE_RDATASOURCEEVENT_H
diff --git a/Control/xAODDataSource/python/Helpers.py b/Control/xAODDataSource/python/Helpers.py
new file mode 100644
index 0000000000000000000000000000000000000000..48c3eb7db4fd63dd6a5409b78151ca737ead2b11
--- /dev/null
+++ b/Control/xAODDataSource/python/Helpers.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+
+# Necessary import(s).
+import ROOT
+
+def MakexAODDataFrame( inputs ):
+    """Helper function creating a ROOT::RDataFrame object reading xAOD files
+
+    The function returns an instance of ROOT::RDataFrame that uses
+    xAOD::RDataSource for reading its inputs.
+
+    Keyword arguments:
+      inputs -- A single string, or a list of string selecting the input file(s)
+                Note that the string(s) may contain wildcards and environment
+                variables as well.
+    """
+
+    # Make sure that the dictionary is loaded.
+    ROOT.xAOD.ROOT6_xAODDataSource_WorkAround_Dummy()
+
+    # Use the C++ function for the heavy lifting.
+    return ROOT.xAOD.MakeDataFrame( inputs )
diff --git a/Control/xAODDataSource/python/__init__.py b/Control/xAODDataSource/python/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e669a90b8bb38f0bfd2b2b839d01aa2194e673f
--- /dev/null
+++ b/Control/xAODDataSource/python/__init__.py
@@ -0,0 +1 @@
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
diff --git a/Control/xAODDataSource/share/dataFrameElementLink_test.ref b/Control/xAODDataSource/share/dataFrameElementLink_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..cbebbd9bcc4f59daa2b377258469d53ded9fe66a
--- /dev/null
+++ b/Control/xAODDataSource/share/dataFrameElementLink_test.ref
@@ -0,0 +1,3 @@
+xAOD::Init                INFO    Environment initialised for data access
+el_hist->GetMaximumBin() = 70
+mu_hist->GetMaximumBin() = 64
diff --git a/Control/xAODDataSource/share/dataFrameTypeConversion_test.ref b/Control/xAODDataSource/share/dataFrameTypeConversion_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..755cad1e963f841de40e489382a85398e6c8c3f1
--- /dev/null
+++ b/Control/xAODDataSource/share/dataFrameTypeConversion_test.ref
@@ -0,0 +1,3 @@
+xAOD::Init                INFO    Environment initialised for data access
+el_pt_from_iparticle.Mean() = 10735.6
+el_pt_from_electron.Mean() = 10735.6
diff --git a/Control/xAODDataSource/share/dataFrame_pytest.ref b/Control/xAODDataSource/share/dataFrame_pytest.ref
new file mode 100644
index 0000000000000000000000000000000000000000..5120eaf54adc80ebe9c1412cffb39fb1eb812f1f
--- /dev/null
+++ b/Control/xAODDataSource/share/dataFrame_pytest.ref
@@ -0,0 +1,2 @@
+xAOD::Init                INFO    Environment initialised for data access
+ElectronsPt entries = 15903
diff --git a/Control/xAODDataSource/share/dataFrame_test.ref b/Control/xAODDataSource/share/dataFrame_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..5120eaf54adc80ebe9c1412cffb39fb1eb812f1f
--- /dev/null
+++ b/Control/xAODDataSource/share/dataFrame_test.ref
@@ -0,0 +1,2 @@
+xAOD::Init                INFO    Environment initialised for data access
+ElectronsPt entries = 15903
diff --git a/Control/xAODDataSource/share/dataSourceEvent_test.ref b/Control/xAODDataSource/share/dataSourceEvent_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..703dfded31eac81f598b484318a643f25bbc064b
--- /dev/null
+++ b/Control/xAODDataSource/share/dataSourceEvent_test.ref
@@ -0,0 +1,2 @@
+xAOD::Init                INFO    Environment initialised for data access
+[[AntiKt10LCTopoJets, DataVector<xAOD::Jet_v1>], [AntiKt10LCTopoTrimmedPtFrac5SmallR20Jets, DataVector<xAOD::Jet_v1>], [AntiKt10PV0TrackJets, DataVector<xAOD::Jet_v1>], [AntiKt2PV0TrackJets, DataVector<xAOD::Jet_v1>], [AntiKt4EMPFlowJets, DataVector<xAOD::Jet_v1>], [AntiKt4EMTopoJets, DataVector<xAOD::Jet_v1>], [AntiKt4EMTopoLowPtJets, DataVector<xAOD::Jet_v1>], [AntiKt4LCTopoJets, DataVector<xAOD::Jet_v1>], [AntiKt4LCTopoLowPtJets, DataVector<xAOD::Jet_v1>], [AntiKt4PV0TrackJets, DataVector<xAOD::Jet_v1>], [AntiKtVR30Rmax4Rmin02TrackJets, DataVector<xAOD::Jet_v1>], [BTagging_AntiKt4EMPFlow, DataVector<xAOD::BTagging_v1>], [BTagging_AntiKt4EMPFlowSecVtx, DataVector<xAOD::Vertex_v1>], [BTagging_AntiKt4EMTopo, DataVector<xAOD::BTagging_v1>], [BTagging_AntiKt4EMTopoJFVtx, DataVector<xAOD::BTagVertex_v1>], [BTagging_AntiKt4EMTopoSecVtx, DataVector<xAOD::Vertex_v1>], [BTagging_AntiKtVR30Rmax4Rmin02Track, DataVector<xAOD::BTagging_v1>], [BTagging_AntiKtVR30Rmax4Rmin02TrackSecVtx, DataVector<xAOD::Vertex_v1>], [CombinedMuonTrackParticles, DataVector<xAOD::TrackParticle_v1>], [EMOriginTopoClusters, DataVector<xAOD::CaloCluster_v1>], [Electrons, DataVector<xAOD::Electron_v1>], [EventInfo, xAOD::EventInfo_v1], [ExtrapolatedMuonTrackParticles, DataVector<xAOD::TrackParticle_v1>], [GSFConversionVertices, DataVector<xAOD::Vertex_v1>], [GSFTrackParticles, DataVector<xAOD::TrackParticle_v1>], [HLT_xAOD__BTaggingContainer_HLTBjetFex, DataVector<xAOD::BTagging_v1>], [HLT_xAOD__ElectronContainer_egamma_Electrons, DataVector<xAOD::Electron_v1>], [HLT_xAOD__JetContainer_EFJet, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_GSCJet, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_SplitJet, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a10r_tcemsubjesFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a10tcemnojcalibFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a10tcemsubFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a10tcemsubjesFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a10tclcwsubFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a10tclcwsubjesFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a4tcemnojcalibFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__JetContainer_a4tcemsubjesFS, DataVector<xAOD::Jet_v1>], [HLT_xAOD__MuonContainer_MuonEFInfo, DataVector<xAOD::Muon_v1>], [HLT_xAOD__MuonContainer_MuonEFInfo_FullScan, DataVector<xAOD::Muon_v1>], [HLT_xAOD__PhotonContainer_egamma_Photons, DataVector<xAOD::Photon_v1>], [HLT_xAOD__TauJetContainer_TrigTauRecMerged, DataVector<xAOD::TauJet_v3>], [HLT_xAOD__TrackParticleContainer_InDetTrigTrackingxAODCnv_Muon_EFID, DataVector<xAOD::TrackParticle_v1>], [HLT_xAOD__TrackParticleContainer_InDetTrigTrackingxAODCnv_Muon_IDTrig, DataVector<xAOD::TrackParticle_v1>], [HLT_xAOD__TrigBphysContainer_EFBMuMuFex, DataVector<xAOD::TrigBphys_v1>], [HLT_xAOD__TrigBphysContainer_EFBMuMuXFex, DataVector<xAOD::TrigBphys_v1>], [HLT_xAOD__TrigBphysContainer_EFMultiMuFex, DataVector<xAOD::TrigBphys_v1>], [HLT_xAOD__TrigBphysContainer_EFTrackMass, DataVector<xAOD::TrigBphys_v1>], [HLT_xAOD__TrigBphysContainer_L2BMuMuFex, DataVector<xAOD::TrigBphys_v1>], [HLT_xAOD__TrigMissingETContainer_TrigEFMissingET, DataVector<xAOD::TrigMissingET_v1>], [HLT_xAOD__TrigMissingETContainer_TrigEFMissingET_mht, DataVector<xAOD::TrigMissingET_v1>], [HLT_xAOD__TrigMissingETContainer_TrigEFMissingET_topocl_PUC, DataVector<xAOD::TrigMissingET_v1>], [HLT_xAOD__TrigSpacePointCountsContainer_spacepoints, DataVector<xAOD::TrigSpacePointCounts_v1>], [HLT_xAOD__TrigT2MbtsBitsContainer_T2Mbts, DataVector<xAOD::TrigT2MbtsBits_v1>], [HLT_xAOD__TrigT2ZdcSignalsContainer_zdcsignals, DataVector<xAOD::TrigT2ZdcSignals_v1>], [HLT_xAOD__TrigTrackCountsContainer_trackcounts, DataVector<xAOD::TrigTrackCounts_v1>], [HLT_xAOD__TrigVertexCountsContainer_vertexcounts, DataVector<xAOD::TrigVertexCounts_v1>], [HLT_xAOD__VertexContainer_EFHistoPrmVtx, DataVector<xAOD::Vertex_v1>], [HLT_xAOD__VertexContainer_xPrimVx, DataVector<xAOD::Vertex_v1>], [InDetForwardTrackParticles, DataVector<xAOD::TrackParticle_v1>], [InDetTrackParticles, DataVector<xAOD::TrackParticle_v1>], [Kt4EMPFlowEventShape, xAOD::EventShape_v1], [Kt4EMTopoOriginEventShape, xAOD::EventShape_v1], [Kt4LCTopoOriginEventShape, xAOD::EventShape_v1], [LCOriginTopoClusters, DataVector<xAOD::CaloCluster_v1>], [LVL1EmTauRoIs, DataVector<xAOD::EmTauRoI_v2>], [LVL1EnergySumRoI, xAOD::EnergySumRoI_v2], [LVL1JetEtRoI, xAOD::JetEtRoI_v1], [LVL1JetRoIs, DataVector<xAOD::JetRoI_v2>], [LVL1MuonRoIs, DataVector<xAOD::MuonRoI_v1>], [METAssoc_AntiKt4EMPFlow, xAOD::MissingETAssociationMap_v1], [METAssoc_AntiKt4EMTopo, xAOD::MissingETAssociationMap_v1], [METAssoc_AntiKt4LCTopo, xAOD::MissingETAssociationMap_v1], [MET_Calo, xAOD::MissingETContainer_v1], [MET_Core_AntiKt4EMPFlow, xAOD::MissingETContainer_v1], [MET_Core_AntiKt4EMTopo, xAOD::MissingETContainer_v1], [MET_Core_AntiKt4LCTopo, xAOD::MissingETContainer_v1], [MET_EMTopo, xAOD::MissingETContainer_v1], [MET_EMTopoRegions, xAOD::MissingETContainer_v1], [MET_LocHadTopo, xAOD::MissingETContainer_v1], [MET_LocHadTopoRegions, xAOD::MissingETContainer_v1], [MET_Reference_AntiKt4EMPFlow, xAOD::MissingETContainer_v1], [MET_Reference_AntiKt4EMTopo, xAOD::MissingETContainer_v1], [MET_Reference_AntiKt4LCTopo, xAOD::MissingETContainer_v1], [MET_Track, xAOD::MissingETContainer_v1], [MuonSegments, DataVector<xAOD::MuonSegment_v1>], [MuonSpectrometerTrackParticles, DataVector<xAOD::TrackParticle_v1>], [Muons, DataVector<xAOD::Muon_v1>], [Photons, DataVector<xAOD::Photon_v1>], [PrimaryVertices, DataVector<xAOD::Vertex_v1>], [TauJets, DataVector<xAOD::TauJet_v3>], [TauTracks, DataVector<xAOD::TauTrack_v1>], [TrigConfKeys, xAOD::TrigConfKeys_v1], [TrigNavigation, xAOD::TrigNavigation_v1], [egammaClusters, DataVector<xAOD::CaloCluster_v1>], [xTrigDecision, xAOD::TrigDecision_v1]]
diff --git a/Control/xAODDataSource/share/dataSource_test.ref b/Control/xAODDataSource/share/dataSource_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..a93897127a571d335982eee89a1c9f664ee8a212
--- /dev/null
+++ b/Control/xAODDataSource/share/dataSource_test.ref
@@ -0,0 +1,3 @@
+xAOD::Init                INFO    Environment initialised for data access
+[[0, 53], [53, 106], [106, 159], [159, 212], [212, 265], [265, 318], [318, 371], [371, 424], [424, 477], [477, 530], [530, 583], [583, 636], [636, 689], [689, 742], [742, 795], [795, 848], [848, 901], [901, 954], [954, 1007], [1007, 1060], [1060, 1113], [1113, 1166], [1166, 1219], [1219, 1272], [1272, 1325], [1325, 1378], [1378, 1431], [1431, 1484], [1484, 1537], [1537, 1590], [1590, 1643], [1643, 1696], [1696, 1749], [1749, 1802], [1802, 1855], [1855, 1908], [1908, 1961], [1961, 2014], [2014, 2067], [2067, 2120], [2120, 2157]]
+[AntiKt10LCTopoJets, AntiKt10LCTopoTrimmedPtFrac5SmallR20Jets, AntiKt10PV0TrackJets, AntiKt2PV0TrackJets, AntiKt4EMPFlowJets, AntiKt4EMTopoJets, AntiKt4EMTopoLowPtJets, AntiKt4LCTopoJets, AntiKt4LCTopoLowPtJets, AntiKt4PV0TrackJets, AntiKtVR30Rmax4Rmin02TrackJets, BTagging_AntiKt4EMPFlow, BTagging_AntiKt4EMPFlowSecVtx, BTagging_AntiKt4EMTopo, BTagging_AntiKt4EMTopoJFVtx, BTagging_AntiKt4EMTopoSecVtx, BTagging_AntiKtVR30Rmax4Rmin02Track, BTagging_AntiKtVR30Rmax4Rmin02TrackSecVtx, CombinedMuonTrackParticles, EMOriginTopoClusters, Electrons, EventInfo, ExtrapolatedMuonTrackParticles, GSFConversionVertices, GSFTrackParticles, HLT_xAOD__BTaggingContainer_HLTBjetFex, HLT_xAOD__ElectronContainer_egamma_Electrons, HLT_xAOD__JetContainer_EFJet, HLT_xAOD__JetContainer_GSCJet, HLT_xAOD__JetContainer_SplitJet, HLT_xAOD__JetContainer_a10r_tcemsubjesFS, HLT_xAOD__JetContainer_a10tcemnojcalibFS, HLT_xAOD__JetContainer_a10tcemsubFS, HLT_xAOD__JetContainer_a10tcemsubjesFS, HLT_xAOD__JetContainer_a10tclcwsubFS, HLT_xAOD__JetContainer_a10tclcwsubjesFS, HLT_xAOD__JetContainer_a4tcemnojcalibFS, HLT_xAOD__JetContainer_a4tcemsubjesFS, HLT_xAOD__MuonContainer_MuonEFInfo, HLT_xAOD__MuonContainer_MuonEFInfo_FullScan, HLT_xAOD__PhotonContainer_egamma_Photons, HLT_xAOD__TauJetContainer_TrigTauRecMerged, HLT_xAOD__TrackParticleContainer_InDetTrigTrackingxAODCnv_Muon_EFID, HLT_xAOD__TrackParticleContainer_InDetTrigTrackingxAODCnv_Muon_IDTrig, HLT_xAOD__TrigBphysContainer_EFBMuMuFex, HLT_xAOD__TrigBphysContainer_EFBMuMuXFex, HLT_xAOD__TrigBphysContainer_EFMultiMuFex, HLT_xAOD__TrigBphysContainer_EFTrackMass, HLT_xAOD__TrigBphysContainer_L2BMuMuFex, HLT_xAOD__TrigMissingETContainer_TrigEFMissingET, HLT_xAOD__TrigMissingETContainer_TrigEFMissingET_mht, HLT_xAOD__TrigMissingETContainer_TrigEFMissingET_topocl_PUC, HLT_xAOD__TrigSpacePointCountsContainer_spacepoints, HLT_xAOD__TrigT2MbtsBitsContainer_T2Mbts, HLT_xAOD__TrigT2ZdcSignalsContainer_zdcsignals, HLT_xAOD__TrigTrackCountsContainer_trackcounts, HLT_xAOD__TrigVertexCountsContainer_vertexcounts, HLT_xAOD__VertexContainer_EFHistoPrmVtx, HLT_xAOD__VertexContainer_xPrimVx, InDetForwardTrackParticles, InDetTrackParticles, Kt4EMPFlowEventShape, Kt4EMTopoOriginEventShape, Kt4LCTopoOriginEventShape, LCOriginTopoClusters, LVL1EmTauRoIs, LVL1EnergySumRoI, LVL1JetEtRoI, LVL1JetRoIs, LVL1MuonRoIs, METAssoc_AntiKt4EMPFlow, METAssoc_AntiKt4EMTopo, METAssoc_AntiKt4LCTopo, MET_Calo, MET_Core_AntiKt4EMPFlow, MET_Core_AntiKt4EMTopo, MET_Core_AntiKt4LCTopo, MET_EMTopo, MET_EMTopoRegions, MET_LocHadTopo, MET_LocHadTopoRegions, MET_Reference_AntiKt4EMPFlow, MET_Reference_AntiKt4EMTopo, MET_Reference_AntiKt4LCTopo, MET_Track, MuonSegments, MuonSpectrometerTrackParticles, Muons, Photons, PrimaryVertices, TauJets, TauTracks, TrigConfKeys, TrigNavigation, egammaClusters, xTrigDecision]
diff --git a/Control/xAODDataSource/test/dataFrameElementLink_test.cxx b/Control/xAODDataSource/test/dataFrameElementLink_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..fc265ddbcc4a3118fd947cbdadee3b68fa34a79c
--- /dev/null
+++ b/Control/xAODDataSource/test/dataFrameElementLink_test.cxx
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "helpers.h"
+#include "xAODDataSource/MakeDataFrame.h"
+
+// Framework include(s).
+#include "xAODRootAccess/Init.h"
+
+// EDM include(s).
+#include "AthContainers/ConstDataVector.h"
+#include "xAODEgamma/ElectronContainer.h"
+#include "xAODMuon/MuonContainer.h"
+
+int main() {
+
+   // Set up the runtime environment.
+   ROOT::EnableImplicitMT();
+   CHECK( xAOD::Init() );
+
+   // Create a data frame object.
+   auto df = xAOD::MakeDataFrame( "${ASG_TEST_FILE_DATA}" );
+
+   // Define new variables using element links.
+   auto variables = df
+      .Define( "HighPtElectrons",
+               []( const xAOD::ElectronContainer& electrons ) {
+                  ConstDataVector< xAOD::ElectronContainer >
+                     result( SG::VIEW_ELEMENTS );
+                  for( const xAOD::Electron* el : electrons ) {
+                     if( el->pt() > 10000.0 ) {
+                        result.push_back( el );
+                     }
+                  }
+                  return xAOD::ElectronContainer( *( result.asDataVector() ) );
+               }, { "Electrons" } )
+      .Define( "el_trk_pt_diff",
+               []( const xAOD::ElectronContainer& electrons ) {
+                  std::vector< float > result;
+                  result.reserve( electrons.size() );
+                  for( const xAOD::Electron* el : electrons ) {
+                     result.push_back( el->pt() - el->trackParticle()->pt() );
+                  }
+                  return result;
+               }, { "HighPtElectrons" } )
+      .Define( "HighPtMuons",
+               []( const xAOD::MuonContainer& muons ) {
+                  ConstDataVector< xAOD::MuonContainer >
+                     result( SG::VIEW_ELEMENTS );
+                  for( const xAOD::Muon* mu : muons ) {
+                     if( ( mu->pt() > 10000.0 ) &&
+                         ( mu->muonType() == xAOD::Muon::Combined ) ) {
+                        result.push_back( mu );
+                     }
+                  }
+                  return xAOD::MuonContainer( *( result.asDataVector() ) );
+               }, { "Muons" } )
+      .Define( "mu_trk_pt_diff",
+               []( const xAOD::MuonContainer& muons ) {
+                  std::vector< float > result;
+                  result.reserve( muons.size() );
+                  for( const xAOD::Muon* mu : muons ) {
+                     result.push_back( mu->pt() -
+                                       mu->trackParticle( xAOD::Muon::InnerDetectorTrackParticle )->pt() );
+                  }
+                  return result;
+               }, { "HighPtMuons" } );
+
+   // Create histograms from these variables.
+   auto el_hist = variables.Histo1D( { "el_hist",
+                                       "Electron p_{T} - Track p_{T};"
+                                       "p_{T} diff. [MeV];Entries",
+                                       128, -100000.0, 100000.0 },
+                                     "el_trk_pt_diff" );
+   auto mu_hist = variables.Histo1D( { "mu_hist",
+                                       "Muon p_{T} - Track p_{T};"
+                                       "p_{T} diff. [MeV];Entries",
+                                       128, -10000.0, 10000.0 },
+                                     "mu_trk_pt_diff" );
+
+   // Print some basic properties of the created histograms.
+   std::cout << "el_hist->GetMaximumBin() = " << el_hist->GetMaximumBin()
+             << std::endl;
+   std::cout << "mu_hist->GetMaximumBin() = " << mu_hist->GetMaximumBin()
+             << std::endl;
+
+   // Return gracefully.
+   return 0;
+}
diff --git a/Control/xAODDataSource/test/dataFrameTypeConversion_test.cxx b/Control/xAODDataSource/test/dataFrameTypeConversion_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..ac1c3fd5c4d0c239c9824a151060baacb38ff58e
--- /dev/null
+++ b/Control/xAODDataSource/test/dataFrameTypeConversion_test.cxx
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "helpers.h"
+#include "xAODDataSource/MakeDataFrame.h"
+
+// Framework include(s).
+#include "xAODRootAccess/Init.h"
+
+// EDM include(s).
+#include "xAODBase/IParticleContainer.h"
+#include "xAODEgamma/ElectronContainer.h"
+
+// System include(s).
+#include <iostream>
+
+int main() {
+
+   // Set up the runtime environment.
+   ROOT::EnableImplicitMT();
+   CHECK( xAOD::Init() );
+
+   // Create a data frame object.
+   auto df = xAOD::MakeDataFrame( "${ASG_TEST_FILE_DATA}" );
+
+   // Use the electrons through their base class.
+   auto df1 = df.Define( "el_pt_from_iparticle",
+                         []( const xAOD::IParticleContainer& particles ) {
+                            std::vector< float > result;
+                            result.reserve( particles.size() );
+                            for( const xAOD::IParticle* p : particles ) {
+                               result.push_back( p->pt() );
+                            }
+                            return result;
+                         }, { "Electrons" } );
+
+   // Use the electrons through their concrete type.
+   auto df2 = df.Define( "el_pt_from_electron",
+                         []( const xAOD::ElectronContainer& electrons ) {
+                            std::vector< float > result;
+                            result.reserve( electrons.size() );
+                            for( const xAOD::Electron* el : electrons ) {
+                               result.push_back( el->pt() );
+                            }
+                            return result;
+                         }, { "Electrons" } );
+
+   // Make the code run.
+   std::cout << "el_pt_from_iparticle.Mean() = "
+             << df1.Mean( "el_pt_from_iparticle" ).GetValue() << std::endl;
+   std::cout << "el_pt_from_electron.Mean() = "
+             << df2.Mean( "el_pt_from_electron" ).GetValue() << std::endl;
+
+   // Return gracefully.
+   return 0;
+}
diff --git a/Control/xAODDataSource/test/dataFrame_test.cxx b/Control/xAODDataSource/test/dataFrame_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..fbd9eb49450a404ae1431e9b3feda77b138d4cbc
--- /dev/null
+++ b/Control/xAODDataSource/test/dataFrame_test.cxx
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "helpers.h"
+#include "xAODDataSource/MakeDataFrame.h"
+
+// xAOD include(s).
+#include "xAODRootAccess/Init.h"
+#include "xAODRootAccess/tools/Message.h"
+#include "xAODBase/IParticleContainer.h"
+
+// ROOT include(s).
+#include <ROOT/RDataFrame.hxx>
+#include <TError.h>
+
+// System include(s).
+#include <iostream>
+
+int main() {
+
+   // Set up the runtime environment.
+   CHECK( xAOD::Init() );
+
+   // Create a data frame object.
+   auto df = xAOD::MakeDataFrame( "${ASG_TEST_FILE_DATA}" );
+
+   // Test its histogramming:
+   auto elPt = df.Define( "ElectronsPt",
+                          []( const xAOD::IParticleContainer& particles ) {
+                             std::vector< float > result;
+                             result.reserve( particles.size() );
+                             for( const xAOD::IParticle* p : particles ) {
+                                result.push_back( p->pt() );
+                             }
+                             return result;
+                          },
+                          { "Electrons" } );
+   auto hist = elPt.Histo1D( "ElectronsPt" );
+   std::cout << "ElectronsPt entries = " << hist->GetEntries() << std::endl;
+
+   // Return gracefully.
+   return 0;
+}
diff --git a/Control/xAODDataSource/test/dataFrame_test.py b/Control/xAODDataSource/test/dataFrame_test.py
new file mode 100755
index 0000000000000000000000000000000000000000..556d3c92a813578dffdfcd914f6a9ed4522ee8a5
--- /dev/null
+++ b/Control/xAODDataSource/test/dataFrame_test.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+#
+
+# Set up the runtime environment.
+import ROOT
+ROOT.xAOD.Init().ignore()
+
+# Create a data frame object.
+from xAODDataSource.Helpers import MakexAODDataFrame
+df = MakexAODDataFrame( "${ASG_TEST_FILE_DATA}" )
+
+# Test its histogramming.
+elPt = df.Define( "ElectronsPt",
+                  """
+                  std::vector< float > result;
+                  result.reserve( Electrons.size() );
+                  for( const xAOD::IParticle* p : Electrons ) {
+                     result.push_back( p->pt() );
+                  }
+                  return result;
+                  """ )
+hist = elPt.Histo1D( "ElectronsPt" )
+print( "ElectronsPt entries = %i" % hist.GetEntries() )
diff --git a/Control/xAODDataSource/test/dataSourceEvent_test.cxx b/Control/xAODDataSource/test/dataSourceEvent_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..dcc83dc3200e5689410c6bda83c1467de0cc1d44
--- /dev/null
+++ b/Control/xAODDataSource/test/dataSourceEvent_test.cxx
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "helpers.h"
+#include "../Root/RDataSourceEvent.h"
+
+// xAOD include(s).
+#include "xAODRootAccess/Init.h"
+#include "xAODRootAccess/tools/Message.h"
+
+// ROOT include(s).
+#include <TFile.h>
+#include <TError.h>
+
+// System include(s).
+#include <iostream>
+#include <memory>
+
+int main() {
+
+   // Set up the runtime environment.
+   CHECK( xAOD::Init() );
+
+   // Open the input file.
+   std::unique_ptr< TFile > ifile( TFile::Open( "${ASG_TEST_FILE_DATA}",
+                                                "READ" ) );
+   if( ( ! ifile ) || ifile->IsZombie() ) {
+      Error( "dataSourceEvent_test",
+             XAOD_MESSAGE( "Could not open the input file" ) );
+      return 1;
+   }
+
+   // Set up the event object.
+   xAOD::RDataSourceEvent event;
+   CHECK( event.readFrom( ifile.get() ) );
+   if( event.getEntry( 0 ) < 0 ) {
+      Error( "dataSourceEvent_test",
+             XAOD_MESSAGE( "Couldn't load the first event of the input "
+                           "file" ) );
+      return 1;
+   }
+
+   // Print the column names and their C++ types.
+   std::cout << event.columnAndTypeNames() << std::endl;
+
+   // Return gracefully.
+   return 0;
+}
diff --git a/Control/xAODDataSource/test/dataSource_test.cxx b/Control/xAODDataSource/test/dataSource_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..31e44cb02c53cf498646117ff6437d654123e2dd
--- /dev/null
+++ b/Control/xAODDataSource/test/dataSource_test.cxx
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+
+// Local include(s).
+#include "helpers.h"
+#include "../Root/RDataSource.h"
+
+// xAOD include(s).
+#include "xAODRootAccess/Init.h"
+#include "xAODRootAccess/tools/Message.h"
+
+// ROOT include(s).
+#include <TError.h>
+
+// System include(s).
+#include <iostream>
+
+int main() {
+
+   // Set up the runtime environment.
+   CHECK( xAOD::Init() );
+
+   // Set up the data source.
+   xAOD::RDataSource ds( "${ASG_TEST_FILE_DATA}" );
+   ds.Initialise();
+
+   // Print the ideal entry ranges to process.
+   std::cout << ds.GetEntryRanges() << std::endl;
+   // Print the available column/object names.
+   std::cout << ds.GetColumnNames() << std::endl;
+
+   // Return gracefully.
+   return 0;
+}
diff --git a/Control/xAODDataSource/test/helpers.h b/Control/xAODDataSource/test/helpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..50140534c452158e8f3f992092e05834963b598d
--- /dev/null
+++ b/Control/xAODDataSource/test/helpers.h
@@ -0,0 +1,50 @@
+// Dear emacs, this is -*- c++ -*-
+//
+// Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration
+//
+#ifndef XAODDATASOURCE_TEST_HELPERS_H
+#define XAODDATASOURCE_TEST_HELPERS_H
+
+// Framework include(s).
+#include "xAODRootAccess/tools/Message.h"
+
+// System include(s).
+#include <iostream>
+#include <vector>
+
+/// Helper macro for checking return values
+#define CHECK( EXP )                                                    \
+   do {                                                                 \
+      auto ret = EXP;                                                   \
+      if( ! ret.isSuccess() ) {                                         \
+         Error( "dataSource_test",                                      \
+                XAOD_MESSAGE( "Failed to execute: %s" ), #EXP );        \
+         return 1;                                                      \
+      }                                                                 \
+   } while( false )
+
+/// Helper print operator
+template< typename FIRST, typename SECOND >
+std::ostream& operator<< ( std::ostream& out,
+                           const std::pair< FIRST, SECOND >& pair ) {
+
+   out << "[" << pair.first << ", " << pair.second << "]";
+   return out;
+}
+
+/// Helper print operator
+template< typename T >
+std::ostream& operator<< ( std::ostream& out, const std::vector< T >& vec ) {
+
+   out << "[";
+   for( size_t i = 0; i < vec.size(); ++i ) {
+      out << vec[ i ];
+      if( i + 1 < vec.size() ) {
+         out << ", ";
+      }
+   }
+   out << "]";
+   return out;
+}
+
+#endif // XAODDATASOURCE_TEST_HELPERS_H
diff --git a/Control/xAODDataSource/xAODDataSource/MakeDataFrame.h b/Control/xAODDataSource/xAODDataSource/MakeDataFrame.h
new file mode 100644
index 0000000000000000000000000000000000000000..4fe6a3853585e593401a08bb05b2f153f0aa976f
--- /dev/null
+++ b/Control/xAODDataSource/xAODDataSource/MakeDataFrame.h
@@ -0,0 +1,31 @@
+// Dear emacs, this is -*- c++ -*-
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+#ifndef XAODDATASOURCE_MAKEDATAFRAME_H
+#define XAODDATASOURCE_MAKEDATAFRAME_H
+
+// ROOT include(s).
+#include <ROOT/RDataFrame.hxx>
+#include <ROOT/RStringView.hxx>
+
+// System include(s).
+#include <string>
+#include <vector>
+
+namespace xAOD {
+
+   /// Helper function for creating an xAOD reading data frame
+   ROOT::RDataFrame
+   MakeDataFrame( std::string_view fileNameGlob,
+                  std::string_view treeName = "CollectionTree",
+                  bool verboseOutput = false );
+   /// Helper function for creating an xAOD reading data frame
+   ROOT::RDataFrame
+   MakeDataFrame( const std::vector< std::string >& fileNames,
+                  std::string_view treeName = "CollectionTree",
+                  bool verboseOutput = false );
+
+} // namespace xAOD
+
+#endif // XAODDATASOURCE_MAKEDATAFRAME_H
diff --git a/Control/xAODDataSource/xAODDataSource/selection.xml b/Control/xAODDataSource/xAODDataSource/selection.xml
new file mode 100644
index 0000000000000000000000000000000000000000..47cf3ce00ed73f311e963b29f3b728ff8e6f5a73
--- /dev/null
+++ b/Control/xAODDataSource/xAODDataSource/selection.xml
@@ -0,0 +1,7 @@
+<!-- Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration -->
+<lcgdict>
+
+  <struct name="xAOD::ROOT6_xAODDataSource_WorkAround_Dummy" />
+  <function name="xAOD::MakeDataFrame" />
+
+</lcgdict>
diff --git a/Control/xAODDataSource/xAODDataSource/xAODDataSourceDict.h b/Control/xAODDataSource/xAODDataSource/xAODDataSourceDict.h
new file mode 100644
index 0000000000000000000000000000000000000000..5f044bf21f07a004fe8104ba094b6a71897aec72
--- /dev/null
+++ b/Control/xAODDataSource/xAODDataSource/xAODDataSourceDict.h
@@ -0,0 +1,16 @@
+// Dear emacs, this is -*- c++ -*-
+//
+// Copyright (C) 2002-2019 CERN for the benefit of the ATLAS collaboration
+//
+#ifndef XAODDATASOURCE_XAODDATASOURCEDICT_H
+#define XAODDATASOURCE_XAODDATASOURCEDICT_H
+
+// Local include(s).
+#include "xAODDataSource/MakeDataFrame.h"
+
+namespace xAOD {
+   /// Dummy struct for auto-loading the library
+   struct ROOT6_xAODDataSource_WorkAround_Dummy {};
+}
+
+#endif // XAODDATASOURCE_XAODDATASOURCEDICT_H
diff --git a/Projects/AnalysisBase/package_filters.txt b/Projects/AnalysisBase/package_filters.txt
index d10788fd25a3d17825d6f2a0e3fbb751b961a932..931a1535ab3f936b75bf73f524f6ff75915f0b2c 100644
--- a/Projects/AnalysisBase/package_filters.txt
+++ b/Projects/AnalysisBase/package_filters.txt
@@ -9,6 +9,7 @@
 + Control/AthLinksSA
 + Control/AthToolSupport/.*
 + Control/CxxUtils
++ Control/xAODDataSource
 + Control/xAODRootAccess.*
 + Control/RootUtils
 + DetectorDescription/GeoPrimitives