From 12796ac35958a7a4e4f145b3993bfb41a74adceb Mon Sep 17 00:00:00 2001
From: mvelosob <miguel.veloso.barros@cern.ch>
Date: Thu, 5 Aug 2021 11:37:46 +0200
Subject: [PATCH] New options for cta-admin rtf ls (#1016)

---
 ReleaseNotes.md                               |  1 +
 catalogue/CatalogueTest.cpp                   | 75 ++++++++++++++++++-
 catalogue/MysqlCatalogue.cpp                  |  6 +-
 catalogue/MysqlCatalogue.hpp                  |  4 +-
 catalogue/OracleCatalogue.cpp                 |  6 +-
 catalogue/OracleCatalogue.hpp                 |  4 +-
 catalogue/PostgresCatalogue.cpp               |  6 +-
 catalogue/PostgresCatalogue.hpp               |  4 +-
 catalogue/RdbmsCatalogue.cpp                  | 13 ++--
 catalogue/RdbmsCatalogue.hpp                  |  8 +-
 .../RdbmsCatalogueGetFileRecycleLogItor.cpp   | 48 +++++++++---
 .../RdbmsCatalogueGetFileRecycleLogItor.hpp   | 19 ++---
 catalogue/RecyleTapeFileSearchCriteria.hpp    | 16 +++-
 catalogue/SqliteCatalogue.cpp                 |  6 +-
 catalogue/SqliteCatalogue.hpp                 |  4 +-
 cmdline/CtaAdminCmdParse.hpp                  |  5 +-
 xroot_plugins/XrdCtaRecycleTapeFileLs.hpp     | 15 +++-
 xroot_plugins/XrdSsiCtaRequestMessage.hpp     |  2 +-
 18 files changed, 186 insertions(+), 56 deletions(-)

diff --git a/ReleaseNotes.md b/ReleaseNotes.md
index 2cfab53283..7bdf5c98e7 100644
--- a/ReleaseNotes.md
+++ b/ReleaseNotes.md
@@ -3,6 +3,7 @@
 ## Summary
 
 ### Features
+ - cta/CTA#1016 New options for filtering deleted files using `cta-admin rtf ls` command.
 
 ### Bug fixes
 
diff --git a/catalogue/CatalogueTest.cpp b/catalogue/CatalogueTest.cpp
index 22939e944f..8130a34a02 100644
--- a/catalogue/CatalogueTest.cpp
+++ b/catalogue/CatalogueTest.cpp
@@ -15653,7 +15653,8 @@ TEST_P(cta_catalogue_CatalogueTest, filesArePutInTheFileRecycleLogInsteadOfBeing
     //Check the diskFileId search criteria
     std::string diskFileId = "12345678";
     catalogue::RecycleTapeFileSearchCriteria criteria;
-    criteria.diskFileId = diskFileId;
+    criteria.diskFileIds = std::vector<std::string>();
+    criteria.diskFileIds->push_back(diskFileId);
     auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
     ASSERT_TRUE(fileRecycleLogItor.hasMore());
     auto fileRecycleLog = fileRecycleLogItor.next();
@@ -15664,10 +15665,80 @@ TEST_P(cta_catalogue_CatalogueTest, filesArePutInTheFileRecycleLogInsteadOfBeing
     //Check the non existing diskFileId search criteria
     std::string diskFileId = "DOES_NOT_EXIST";
     catalogue::RecycleTapeFileSearchCriteria criteria;
-    criteria.diskFileId = diskFileId;
+    criteria.diskFileIds = std::vector<std::string>();
+    criteria.diskFileIds->push_back(diskFileId);
     auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
     ASSERT_FALSE(fileRecycleLogItor.hasMore());
   }
+  {
+    //Check the archiveID search criteria
+    uint64_t archiveFileId = 1;
+    catalogue::RecycleTapeFileSearchCriteria criteria;
+    criteria.archiveFileId = archiveFileId;
+    auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
+    ASSERT_TRUE(fileRecycleLogItor.hasMore());
+    auto fileRecycleLog = fileRecycleLogItor.next();
+    ASSERT_EQ(archiveFileId,fileRecycleLog.archiveFileId);
+    ASSERT_FALSE(fileRecycleLogItor.hasMore());
+  }
+  {
+    //Check the non existing archiveFileId search criteria
+    uint64_t archiveFileId = -1;
+    catalogue::RecycleTapeFileSearchCriteria criteria;
+    criteria.archiveFileId = archiveFileId;
+    auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
+    ASSERT_FALSE(fileRecycleLogItor.hasMore());
+  }
+  {
+    //Check the copynb search criteria
+    uint64_t copynb = 1;
+    catalogue::RecycleTapeFileSearchCriteria criteria;
+    criteria.copynb = copynb;
+    auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
+    int nbFileRecycleLogs = 0;
+    while(fileRecycleLogItor.hasMore()){
+      nbFileRecycleLogs++;
+      fileRecycleLogItor.next();
+    }
+    ASSERT_EQ(nbArchiveFiles,nbFileRecycleLogs);
+  }
+  {
+    //Check the disk instance search criteria
+    catalogue::RecycleTapeFileSearchCriteria criteria;
+    criteria.diskInstance = diskInstance;
+    auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
+    int nbFileRecycleLogs = 0;
+    while(fileRecycleLogItor.hasMore()){
+      nbFileRecycleLogs++;
+      fileRecycleLogItor.next();
+    }
+    ASSERT_EQ(nbArchiveFiles,nbFileRecycleLogs);
+  }
+  {
+    //Check multiple search criteria together
+    uint64_t copynb = 1;
+    uint64_t archiveFileId = 1;
+    std::string diskFileId = "12345678";
+    catalogue::RecycleTapeFileSearchCriteria criteria;
+    criteria.diskInstance = diskInstance;
+    criteria.copynb = copynb;
+    criteria.archiveFileId = archiveFileId;
+    criteria.diskFileIds = std::vector<std::string>();
+    criteria.diskFileIds->push_back(diskFileId);
+    criteria.vid = tape1.vid;
+    
+    auto fileRecycleLogItor = m_catalogue->getFileRecycleLogItor(criteria);
+    
+    ASSERT_TRUE(fileRecycleLogItor.hasMore());
+    auto fileRecycleLog = fileRecycleLogItor.next();
+    ASSERT_EQ(archiveFileId, fileRecycleLog.archiveFileId);
+    ASSERT_EQ(diskFileId, fileRecycleLog.diskFileId);
+    ASSERT_EQ(copynb, fileRecycleLog.copyNb);
+    ASSERT_EQ(tape1.vid, fileRecycleLog.vid);
+    ASSERT_EQ(diskInstance, fileRecycleLog.diskInstanceName);
+    
+    ASSERT_FALSE(fileRecycleLogItor.hasMore());
+  }
 }
 
 TEST_P(cta_catalogue_CatalogueTest, sameFileWrittenToSameTapePutThePreviousCopyOnTheFileRecycleLog) {
diff --git a/catalogue/MysqlCatalogue.cpp b/catalogue/MysqlCatalogue.cpp
index 552bb12973..606b5d151a 100644
--- a/catalogue/MysqlCatalogue.cpp
+++ b/catalogue/MysqlCatalogue.cpp
@@ -59,10 +59,10 @@ MysqlCatalogue::~MysqlCatalogue() {
 //------------------------------------------------------------------------------
 // createAndPopulateTempTableFxid
 //------------------------------------------------------------------------------
-std::string MysqlCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const {
+std::string MysqlCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const {
   const std::string tempTableName = "TEMP_DISK_FXIDS";
 
-  if(tapeFileSearchCriteria.diskFileIds) {
+  if(diskFileIds) {
     try {
       std::string sql = "CREATE TEMPORARY TABLE " + tempTableName + "(DISK_FILE_ID VARCHAR(100))";
       try {
@@ -76,7 +76,7 @@ std::string MysqlCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, co
 
       sql = "INSERT INTO " + tempTableName + " VALUES(:DISK_FILE_ID)";
       auto stmt = conn.createStmt(sql);
-      for(auto &diskFileId : tapeFileSearchCriteria.diskFileIds.value()) {
+      for(auto &diskFileId : diskFileIds.value()) {
         stmt.bindString(":DISK_FILE_ID", diskFileId);
         stmt.executeNonQuery();
       }
diff --git a/catalogue/MysqlCatalogue.hpp b/catalogue/MysqlCatalogue.hpp
index 1e6b178516..82d8360bff 100644
--- a/catalogue/MysqlCatalogue.hpp
+++ b/catalogue/MysqlCatalogue.hpp
@@ -60,10 +60,10 @@ protected:
    * Creates a temporary table from the list of disk file IDs provided in the search criteria.
    *
    * @param conn The database connection.
-   * @param tapeFileSearchCriteria Search criteria containing a list of disk file IDs (fxid).
+   * @param diskFileIds List of disk file IDs (fxid).
    * @return Name of the temporary table
    */
-  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const override;
+  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const override;
 
   /**
    * Returns a unique archive ID that can be used by a new archive file within
diff --git a/catalogue/OracleCatalogue.cpp b/catalogue/OracleCatalogue.cpp
index 08e63e5afd..bc1a42c354 100644
--- a/catalogue/OracleCatalogue.cpp
+++ b/catalogue/OracleCatalogue.cpp
@@ -154,11 +154,11 @@ OracleCatalogue::~OracleCatalogue() {
 //------------------------------------------------------------------------------
 // createAndPopulateTempTableFxid
 //------------------------------------------------------------------------------
-std::string OracleCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const {
+std::string OracleCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const {
   const std::string tempTableName = "ORA$PTT_DISK_FXIDS";
 
   try {
-    if(tapeFileSearchCriteria.diskFileIds) {
+    if(diskFileIds) {
       conn.setAutocommitMode(rdbms::AutocommitMode::AUTOCOMMIT_OFF);
       std::string sql = "CREATE PRIVATE TEMPORARY TABLE " + tempTableName +
         "(DISK_FILE_ID VARCHAR2(100))";
@@ -166,7 +166,7 @@ std::string OracleCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, c
   
       sql = "INSERT INTO " + tempTableName + " VALUES(:DISK_FILE_ID)";
       auto stmt = conn.createStmt(sql);
-      for(auto &diskFileId : tapeFileSearchCriteria.diskFileIds.value()) {
+      for(auto &diskFileId : diskFileIds.value()) {
         stmt.bindString(":DISK_FILE_ID", diskFileId);
         stmt.executeNonQuery();
       }
diff --git a/catalogue/OracleCatalogue.hpp b/catalogue/OracleCatalogue.hpp
index 03067d1d54..9dd68a65d9 100644
--- a/catalogue/OracleCatalogue.hpp
+++ b/catalogue/OracleCatalogue.hpp
@@ -61,10 +61,10 @@ public:
    * Creates a temporary table from the list of disk file IDs provided in the search criteria.
    *
    * @param conn The database connection.
-   * @param tapeFileSearchCriteria Search criteria containing a list of disk file IDs (fxid).
+   * @param diskFileIds List of disk file IDs (fxid).
    * @return Name of the temporary table
    */
-  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const override;
+  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const override;
 
   /**
    * Returns a unique archive ID that can be used by a new archive file within
diff --git a/catalogue/PostgresCatalogue.cpp b/catalogue/PostgresCatalogue.cpp
index da343a4a58..3073bb8935 100644
--- a/catalogue/PostgresCatalogue.cpp
+++ b/catalogue/PostgresCatalogue.cpp
@@ -152,10 +152,10 @@ PostgresCatalogue::~PostgresCatalogue() {
 //------------------------------------------------------------------------------
 // createAndPopulateTempTableFxid
 //------------------------------------------------------------------------------
-std::string PostgresCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const {
+std::string PostgresCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const {
   const std::string tempTableName = "TEMP_DISK_FXIDS";
 
-  if(tapeFileSearchCriteria.diskFileIds) {
+  if(diskFileIds) {
     try {
       std::string sql = "CREATE TEMPORARY TABLE " + tempTableName + "(DISK_FILE_ID VARCHAR(100))";
       try {
@@ -169,7 +169,7 @@ std::string PostgresCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn,
 
       sql = "INSERT INTO " + tempTableName + " VALUES(:DISK_FILE_ID)";
       auto stmt = conn.createStmt(sql);
-      for(auto &diskFileId : tapeFileSearchCriteria.diskFileIds.value()) {
+      for(auto &diskFileId : diskFileIds.value()) {
         stmt.bindString(":DISK_FILE_ID", diskFileId);
         stmt.executeNonQuery();
       }
diff --git a/catalogue/PostgresCatalogue.hpp b/catalogue/PostgresCatalogue.hpp
index ee2c914b85..da8d3d6bc4 100644
--- a/catalogue/PostgresCatalogue.hpp
+++ b/catalogue/PostgresCatalogue.hpp
@@ -88,10 +88,10 @@ public:
    * Creates a temporary table from the list of disk file IDs provided in the search criteria.
    *
    * @param conn The database connection.
-   * @param tapeFileSearchCriteria Search criteria containing a list of disk file IDs (fxid).
+   * @param diskFileIds List of disk file IDs (fxid).
    * @return Name of the temporary table
    */
-  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const override;
+  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const override;
 
   /**
    * Returns a unique archive ID that can be used by a new archive file within
diff --git a/catalogue/RdbmsCatalogue.cpp b/catalogue/RdbmsCatalogue.cpp
index 4db898a66b..262f58418a 100644
--- a/catalogue/RdbmsCatalogue.cpp
+++ b/catalogue/RdbmsCatalogue.cpp
@@ -6970,7 +6970,7 @@ Catalogue::ArchiveFileItor RdbmsCatalogue::getArchiveFilesItor(const TapeFileSea
   try {
     // Create a connection to populate the temporary table (specialised by database type)
     auto conn = m_archiveFileListingConnPool.getConn();
-    const auto tempDiskFxidsTableName = createAndPopulateTempTableFxid(conn, searchCriteria);
+    const auto tempDiskFxidsTableName = createAndPopulateTempTableFxid(conn, searchCriteria.diskFileIds);
     // Pass ownership of the connection to the Iterator object
     auto impl = new RdbmsCatalogueGetArchiveFilesItor(m_log, std::move(conn), searchCriteria, tempDiskFxidsTableName);
     return ArchiveFileItor(impl);
@@ -7002,9 +7002,8 @@ Catalogue::ArchiveFileItor RdbmsCatalogue::getTapeContentsItor(const std::string
 //------------------------------------------------------------------------------
 // checkRecycleTapeFileSearchCriteria
 //------------------------------------------------------------------------------
-void RdbmsCatalogue::checkRecycleTapeFileSearchCriteria(const RecycleTapeFileSearchCriteria & searchCriteria) const {
+void RdbmsCatalogue::checkRecycleTapeFileSearchCriteria(cta::rdbms::Conn &conn, const RecycleTapeFileSearchCriteria & searchCriteria) const {
   if(searchCriteria.vid) {
-    auto conn = m_connPool.getConn();
     if(!tapeExists(conn, searchCriteria.vid.value())) {
       throw exception::UserError(std::string("Tape ") + searchCriteria.vid.value() + " does not exist");
     }
@@ -7013,8 +7012,10 @@ void RdbmsCatalogue::checkRecycleTapeFileSearchCriteria(const RecycleTapeFileSea
 
 Catalogue::FileRecycleLogItor RdbmsCatalogue::getFileRecycleLogItor(const RecycleTapeFileSearchCriteria & searchCriteria) const {
   try {
-    checkRecycleTapeFileSearchCriteria(searchCriteria);
-    auto impl = new RdbmsCatalogueGetFileRecycleLogItor(m_log, m_archiveFileListingConnPool, searchCriteria);
+    auto conn = m_archiveFileListingConnPool.getConn();
+    checkRecycleTapeFileSearchCriteria(conn, searchCriteria);
+    const auto tempDiskFxidsTableName = createAndPopulateTempTableFxid(conn, searchCriteria.diskFileIds);
+    auto impl = new RdbmsCatalogueGetFileRecycleLogItor(m_log, std::move(conn), searchCriteria, tempDiskFxidsTableName);
     return FileRecycleLogItor(impl);
   } catch(exception::UserError &) {
     throw;
@@ -7182,7 +7183,7 @@ common::dataStructures::ArchiveFileSummary RdbmsCatalogue::getTapeFileSummary(
       addedAWhereConstraint = true;
     }
     if(searchCriteria.diskFileIds) {
-      const auto tempDiskFxidsTableName = createAndPopulateTempTableFxid(conn, searchCriteria);
+      const auto tempDiskFxidsTableName = createAndPopulateTempTableFxid(conn, searchCriteria.diskFileIds);
 
       if(addedAWhereConstraint) sql += " AND ";
       sql += "ARCHIVE_FILE.DISK_FILE_ID IN (SELECT DISK_FILE_ID FROM " + tempDiskFxidsTableName + ")";
diff --git a/catalogue/RdbmsCatalogue.hpp b/catalogue/RdbmsCatalogue.hpp
index 9b025b28c9..91189898e4 100644
--- a/catalogue/RdbmsCatalogue.hpp
+++ b/catalogue/RdbmsCatalogue.hpp
@@ -851,10 +851,10 @@ public:
   /**
    * Throws a UserError exception if the specified searchCriteria is not valid
    * due to a user error.
-   *
+   * @param conn The database connection.
    * @param searchCriteria The search criteria.
    */
-  void checkRecycleTapeFileSearchCriteria(const RecycleTapeFileSearchCriteria & searchCriteria) const;
+  void checkRecycleTapeFileSearchCriteria(cta::rdbms::Conn &conn, const RecycleTapeFileSearchCriteria & searchCriteria) const;
   
   /**
    * Returns all the currently deleted files by looking at the FILE_RECYCLE_LOG table
@@ -1575,10 +1575,10 @@ protected:
    * Creates a temporary table from the list of disk file IDs provided in the search criteria.
    *
    * @param conn The database connection.
-   * @param tapeFileSearchCriteria Search criteria containing a list of disk file IDs (fxid).
+   * @param diskFileIds List of disk file IDs (fxid).
    * @return Name of the temporary table
    */
-  virtual std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const = 0;
+  virtual std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const = 0;
 
   /**
    * Returns a unique archive ID that can be used by a new archive file within
diff --git a/catalogue/RdbmsCatalogueGetFileRecycleLogItor.cpp b/catalogue/RdbmsCatalogueGetFileRecycleLogItor.cpp
index ab9b81519c..71f66cb27f 100644
--- a/catalogue/RdbmsCatalogueGetFileRecycleLogItor.cpp
+++ b/catalogue/RdbmsCatalogueGetFileRecycleLogItor.cpp
@@ -27,10 +27,11 @@ namespace catalogue {
 //------------------------------------------------------------------------------
 RdbmsCatalogueGetFileRecycleLogItor::RdbmsCatalogueGetFileRecycleLogItor(
   log::Logger &log,
-  rdbms::ConnPool &connPool,
-  const RecycleTapeFileSearchCriteria & searchCriteria):
+  rdbms::Conn &&conn,
+  const RecycleTapeFileSearchCriteria & searchCriteria,
+  const std::string &tempDiskFxidsTableName):
   m_log(log),
-  m_connPool(connPool),
+  m_conn(std::move(conn)),
   m_searchCriteria(searchCriteria),
   m_rsetIsEmpty(true),
   m_hasMoreHasBeenCalled(false) {
@@ -65,7 +66,10 @@ RdbmsCatalogueGetFileRecycleLogItor::RdbmsCatalogueGetFileRecycleLogItor(
 
     const bool thereIsAtLeastOneSearchCriteria =
       searchCriteria.vid            ||
-      searchCriteria.diskFileId;
+      searchCriteria.diskFileIds    ||
+      searchCriteria.archiveFileId  ||
+      searchCriteria.copynb         ||
+      searchCriteria.diskInstance;
 
     if(thereIsAtLeastOneSearchCriteria) {
       sql += " WHERE ";
@@ -77,12 +81,29 @@ RdbmsCatalogueGetFileRecycleLogItor::RdbmsCatalogueGetFileRecycleLogItor(
       sql += "FILE_RECYCLE_LOG.VID = :VID";
       addedAWhereConstraint = true;
     }
+
+    if (searchCriteria.archiveFileId) {
+      if(addedAWhereConstraint) sql += " AND ";
+      sql += "FILE_RECYCLE_LOG.ARCHIVE_FILE_ID = :ARCHIVE_FILE_ID";
+      addedAWhereConstraint = true;
+    }
     
-    if(searchCriteria.diskFileId){
+    if(searchCriteria.diskFileIds) {
       if(addedAWhereConstraint) sql += " AND ";
-      sql += "FILE_RECYCLE_LOG.DISK_FILE_ID = :DISK_FILE_ID";
+      sql += "FILE_RECYCLE_LOG.DISK_FILE_ID IN (SELECT DISK_FILE_ID FROM " + tempDiskFxidsTableName + ")";
       addedAWhereConstraint = true;
     }
+
+    if (searchCriteria.diskInstance) {
+      if(addedAWhereConstraint) sql += " AND ";
+      sql += "FILE_RECYCLE_LOG.DISK_INSTANCE_NAME = :DISK_INSTANCE";
+      addedAWhereConstraint = true;
+    }
+
+    if (searchCriteria.copynb) {
+      if(addedAWhereConstraint) sql += " AND ";
+      sql += "FILE_RECYCLE_LOG.COPY_NB = :COPY_NB";
+    }
     
     // Order by FSEQ if we are listing the contents of a tape, else order by archive file ID
     if(searchCriteria.vid) {
@@ -91,15 +112,22 @@ RdbmsCatalogueGetFileRecycleLogItor::RdbmsCatalogueGetFileRecycleLogItor(
       sql += " ORDER BY FILE_RECYCLE_LOG.ARCHIVE_FILE_ID, FILE_RECYCLE_LOG.COPY_NB";
     }
     
-    m_conn = connPool.getConn();
     m_stmt = m_conn.createStmt(sql);
     
     if(searchCriteria.vid){
       m_stmt.bindString(":VID", searchCriteria.vid.value());
     }
-    
-    if(searchCriteria.diskFileId){
-      m_stmt.bindString(":DISK_FILE_ID", searchCriteria.diskFileId.value());
+
+    if (searchCriteria.archiveFileId) {
+      m_stmt.bindUint64(":ARCHIVE_FILE_ID", searchCriteria.archiveFileId.value());
+    }
+
+    if (searchCriteria.diskInstance) {
+      m_stmt.bindString(":DISK_INSTANCE", searchCriteria.diskInstance.value());
+    }
+
+    if (searchCriteria.copynb) {
+      m_stmt.bindUint64(":COPY_NB", searchCriteria.copynb.value());
     }
     
     m_rset = m_stmt.executeQuery();
diff --git a/catalogue/RdbmsCatalogueGetFileRecycleLogItor.hpp b/catalogue/RdbmsCatalogueGetFileRecycleLogItor.hpp
index c94cfe3949..90e996b82e 100644
--- a/catalogue/RdbmsCatalogueGetFileRecycleLogItor.hpp
+++ b/catalogue/RdbmsCatalogueGetFileRecycleLogItor.hpp
@@ -29,9 +29,13 @@ public:
    * Constructor.
    *
    * @param log Object representing the API to the CTA logging system.
-   * @param connPool The database connection pool.
+   * @param conn A database connection.
    */
-  RdbmsCatalogueGetFileRecycleLogItor(log::Logger &log, rdbms::ConnPool &connPool, const RecycleTapeFileSearchCriteria & searchCriteria);
+  RdbmsCatalogueGetFileRecycleLogItor(
+    log::Logger &log, 
+    rdbms::Conn &&conn, 
+    const RecycleTapeFileSearchCriteria & searchCriteria, 
+    const std::string &tempDiskFxidsTableName);
 
   /**
    * Destructor.
@@ -54,10 +58,12 @@ private:
    */
   log::Logger &m_log;
 
+
   /**
-   * The database connection pool.
+   * The database connection.
    */
-  rdbms::ConnPool &m_connPool;
+  rdbms::Conn m_conn;
+
   
   /**
    * The search criteria to be used when listing recycled tape files.
@@ -79,11 +85,6 @@ private:
    */
   bool m_hasMoreHasBeenCalled;
 
-  /**
-   * The database connection.
-   */
-  rdbms::Conn m_conn;
-
   /**
    * The database statement.
    */
diff --git a/catalogue/RecyleTapeFileSearchCriteria.hpp b/catalogue/RecyleTapeFileSearchCriteria.hpp
index 8fc4983064..eb4ab60cb7 100644
--- a/catalogue/RecyleTapeFileSearchCriteria.hpp
+++ b/catalogue/RecyleTapeFileSearchCriteria.hpp
@@ -32,6 +32,15 @@ namespace catalogue {
  * Please note that no wild cards, for example '*' or '%', are supported.
  */
 struct RecycleTapeFileSearchCriteria {
+  /**
+   * The unique identifier of an archive file.
+   */
+  optional<uint64_t> archiveFileId;
+
+  /**
+   * The name of a disk instance.
+   */
+  optional<std::string> diskInstance;
   
   /**
    * The volume identifier of a tape.
@@ -44,7 +53,12 @@ struct RecycleTapeFileSearchCriteria {
    * These are given as a list of strings in DECIMAL format. EOS provides the fxids in hex format. The parsing and
    * conversion into decimal is done in the cta-admin client, ready to be built into a SQL query string.
    */
-  optional<std::string> diskFileId;
+  optional<std::vector<std::string>> diskFileIds;
+
+  /**
+   * The copy number of the deleted tape file.
+   */
+  optional<uint64_t> copynb;
   
 }; // struct TapeFileSearchCriteria
 
diff --git a/catalogue/SqliteCatalogue.cpp b/catalogue/SqliteCatalogue.cpp
index c809025876..a48071da05 100644
--- a/catalogue/SqliteCatalogue.cpp
+++ b/catalogue/SqliteCatalogue.cpp
@@ -192,7 +192,7 @@ void SqliteCatalogue::DO_NOT_USE_deleteArchiveFile_DO_NOT_USE(const std::string
 //------------------------------------------------------------------------------
 // createAndPopulateTempTableFxid
 //------------------------------------------------------------------------------
-std::string SqliteCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const {
+std::string SqliteCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const {
   try {
     const std::string tempTableName = "TEMP.DISK_FXIDS";
 
@@ -200,9 +200,9 @@ std::string SqliteCatalogue::createAndPopulateTempTableFxid(rdbms::Conn &conn, c
     conn.executeNonQuery("DROP TABLE IF EXISTS " + tempTableName);
     conn.executeNonQuery("CREATE TEMPORARY TABLE " + tempTableName + "(DISK_FILE_ID TEXT)");
 
-    if(tapeFileSearchCriteria.diskFileIds) {
+    if(diskFileIds) {
       auto stmt = conn.createStmt("INSERT INTO " + tempTableName + " VALUES(:DISK_FILE_ID)");
-      for(auto &diskFileId : tapeFileSearchCriteria.diskFileIds.value()) {
+      for(auto &diskFileId : diskFileIds.value()) {
         stmt.bindString(":DISK_FILE_ID", diskFileId);
         stmt.executeNonQuery();
       }
diff --git a/catalogue/SqliteCatalogue.hpp b/catalogue/SqliteCatalogue.hpp
index 020589f930..f42f9f14ca 100644
--- a/catalogue/SqliteCatalogue.hpp
+++ b/catalogue/SqliteCatalogue.hpp
@@ -86,10 +86,10 @@ protected:
    * Creates a temporary table from the list of disk file IDs provided in the search criteria.
    *
    * @param conn The database connection.
-   * @param tapeFileSearchCriteria Search criteria containing a list of disk file IDs (fxid).
+   * @param diskFileIds List of disk file IDs (fxid).
    * @return Name of the temporary table
    */
-  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const TapeFileSearchCriteria &tapeFileSearchCriteria) const override;
+  std::string createAndPopulateTempTableFxid(rdbms::Conn &conn, const optional<std::vector<std::string>> &diskFileIds) const override;
 
   /**
    * Returns a unique archive ID that can be used by a new archive file within
diff --git a/cmdline/CtaAdminCmdParse.hpp b/cmdline/CtaAdminCmdParse.hpp
index 70b6123de3..da06f29a18 100644
--- a/cmdline/CtaAdminCmdParse.hpp
+++ b/cmdline/CtaAdminCmdParse.hpp
@@ -408,7 +408,7 @@ const std::map<AdminCmd::Cmd, CmdHelp> cmdHelp = {
    { AdminCmd::CMD_VERSION,              { "version",               "v",  { } }},
    { AdminCmd::CMD_SCHEDULINGINFOS,      { "schedulinginfo",        "si",  { "ls" } }},
    { AdminCmd::CMD_RECYCLETAPEFILE,      { "recycletf",        "rtf",  { "ls" },
-                            "  Tape files in the recycle log can be listed by VID or by EOS disk file ID.\n"
+                            "  Tape files in the recycle log can be listed by VID, EOS disk file ID, EOS disk instance, ArchiveFileId or copy number.\n"
                             "  Disk file IDs should be provided in hexadecimal (fxid).\n\n" }},
 };
 
@@ -614,7 +614,8 @@ const std::map<cmd_key_t, cmd_val_t> cmdOptions = {
       { }},
    {{ AdminCmd::CMD_VERSION,           AdminCmd::SUBCMD_NONE   }, { }},
    {{ AdminCmd::CMD_SCHEDULINGINFOS,      AdminCmd::SUBCMD_LS   }, { }},
-   {{ AdminCmd::CMD_RECYCLETAPEFILE, AdminCmd::SUBCMD_LS }, { opt_vid.optional(), opt_fid.optional() }},
+   {{ AdminCmd::CMD_RECYCLETAPEFILE, AdminCmd::SUBCMD_LS }, 
+   { opt_vid.optional(), opt_fid.optional(), opt_fidfile.optional(), opt_copynb.optional(), opt_archivefileid.optional(), opt_instance.optional() }},
 };
 
 
diff --git a/xroot_plugins/XrdCtaRecycleTapeFileLs.hpp b/xroot_plugins/XrdCtaRecycleTapeFileLs.hpp
index 7c18034dd8..86631b2386 100644
--- a/xroot_plugins/XrdCtaRecycleTapeFileLs.hpp
+++ b/xroot_plugins/XrdCtaRecycleTapeFileLs.hpp
@@ -66,15 +66,28 @@ RecycleTapeFileLsStream::RecycleTapeFileLsStream(const RequestMessage &requestMs
   searchCriteria.vid = requestMsg.getOptional(OptionString::VID, &has_any);
   
   auto diskFileId = requestMsg.getOptional(OptionString::FXID, &has_any);
+
+  searchCriteria.diskFileIds = requestMsg.getOptional(OptionStrList::FILE_ID, &has_any);
   
   if(diskFileId){
     // single option on the command line we need to do the conversion ourselves.
+    if(!searchCriteria.diskFileIds) searchCriteria.diskFileIds = std::vector<std::string>();
+
     auto fid = strtol(diskFileId->c_str(), nullptr, 16);
     if(fid < 1 || fid == LONG_MAX) {
        throw cta::exception::UserError(*diskFileId + " is not a valid file ID");
     }
-    searchCriteria.diskFileId = std::to_string(fid);
+
+    searchCriteria.diskFileIds->push_back(std::to_string(fid));
   }
+  // Disk instance on its own does not give a valid set of search criteria (no &has_any)
+  searchCriteria.diskInstance = requestMsg.getOptional(OptionString::INSTANCE);
+
+  searchCriteria.archiveFileId = requestMsg.getOptional(OptionUInt64::ARCHIVE_FILE_ID, &has_any);
+
+  // Copy number on its own does not give a valid set of search criteria (no &has_any)
+  searchCriteria.copynb = requestMsg.getOptional(OptionUInt64::COPY_NUMBER);
+
   if(!has_any){
     throw cta::exception::UserError("Must specify at least one search option");
   }
diff --git a/xroot_plugins/XrdSsiCtaRequestMessage.hpp b/xroot_plugins/XrdSsiCtaRequestMessage.hpp
index 24bf193b28..846024bb65 100644
--- a/xroot_plugins/XrdSsiCtaRequestMessage.hpp
+++ b/xroot_plugins/XrdSsiCtaRequestMessage.hpp
@@ -207,7 +207,7 @@ private:
   void processVirtualOrganization_Add(cta::xrd::Response &response);
   void processVirtualOrganization_Ch(cta::xrd::Response &response);
   void processVirtualOrganization_Rm(cta::xrd::Response &response);
-
+  
   /*!
    * Process AdminCmd events which can return a stream response
    *
-- 
GitLab