Commit 4549a513 authored by Steven Murray's avatar Steven Murray
Browse files

Replaced Catalogue::getArchiveFiles() with getArchiveFileItor()

parent a9601a54
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2015 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "catalogue/ArchiveFileItor.hpp"
#include "common/exception/Exception.hpp"
namespace cta {
namespace catalogue {
//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
ArchiveFileItor::~ArchiveFileItor() {
}
} // namespace catalogue
} // namespace cta
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2015 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "common/dataStructures/ArchiveFile.hpp"
namespace cta {
namespace catalogue {
/**
* Abstract class defining the interface to an iterator over a list of archive
* files.
*/
class ArchiveFileItor {
public:
/**
* Destructor.
*/
virtual ~ArchiveFileItor() = 0;
/**
* Returns true if a call to next would return another archive file.
*/
virtual bool hasMore() const = 0;
/**
* Returns the next archive or throws an exception if there isn't one.
*/
virtual common::dataStructures::ArchiveFile next() = 0;
}; // class ArchiveFileItor
} // namespace catalogue
} // namespace cta
/*
* The CERN Tape Archive (CTA) project
* Copyright (C) 2015 CERN
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <string>
namespace cta {
namespace catalogue {
/**
* A set of database login details.
*/
struct ArchiveFileSearchCriteria {
/**
* The unique identifier of an archive file.
*/
std::string archiveFileId;
/**
* The name of a disk instance.
*/
std::string diskInstance;
/**
* The unique identifier of a disk file within its disk instance.
*
* The combination of diskInstance and diskFileId is unique across all disk
* instances.
*/
std::string diskFileId;
/**
* The absolute path of a file within its disk instance.
*/
std::string diskFilePath;
/**
* The owner of a file within its disk instance.
*/
std::string diskFileUser;
/**
* The group of a file within its disk instance.
*/
std::string diskFileGroup;
/**
* The storage class name of the file.
*/
std::string storageClass;
/**
* The volume identifier of a tape.
*/
std::string vid;
/**
* The copy number of a tape file.
*/
std::string tapeFileCopyNb;
/**
* The name of a tape pool.
*/
std::string tapePool;
}; // struct ArchiveFileSearchCriteria
} // namespace catalogue
} // namespace cta
......@@ -22,6 +22,7 @@ include_directories (${ORACLE-INSTANTCLIENT_INCLUDE_DIRS})
set (CATALOGUE_LIB_SRC_FILES
ArchiveFileRow.cpp
ArchiveFileItor.cpp
AutoRollback.cpp
Catalogue.cpp
CatalogueFactory.cpp
......
......@@ -18,11 +18,10 @@
#pragma once
#include <list>
#include <stdint.h>
#include <string>
#include <map>
#include "catalogue/ArchiveFileItor.hpp"
#include "catalogue/ArchiveFileSearchCriteria.hpp"
#include "catalogue/TapeFileWritten.hpp"
#include "catalogue/TapeForWriting.hpp"
#include "common/dataStructures/AdminHost.hpp"
#include "common/dataStructures/AdminUser.hpp"
#include "common/dataStructures/ArchiveFile.hpp"
......@@ -60,8 +59,12 @@
#include "common/dataStructures/UserIdentity.hpp"
#include "common/dataStructures/VerifyInfo.hpp"
#include "common/dataStructures/WriteTestResult.hpp"
#include "TapeFileWritten.hpp"
#include "TapeForWriting.hpp"
#include <list>
#include <stdint.h>
#include <string>
#include <map>
#include <memory>
namespace cta {
......@@ -256,10 +259,27 @@ public:
virtual void modifyDedicationUntil(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const uint64_t untilTimestamp) = 0;
virtual void modifyDedicationComment(const common::dataStructures::SecurityIdentity &cliIdentity, const std::string &drivename, const std::string &comment) = 0;
virtual std::list<common::dataStructures::ArchiveFile> getArchiveFiles(const std::string &id, const std::string &eosid,
const std::string &copynb, const std::string &tapepool, const std::string &vid, const std::string &owner, const std::string &group, const std::string &storageclass, const std::string &path) = 0;
virtual common::dataStructures::ArchiveFileSummary getArchiveFileSummary(const std::string &id, const std::string &eosid,
const std::string &copynb, const std::string &tapepool, const std::string &vid, const std::string &owner, const std::string &group, const std::string &storageclass, const std::string &path) = 0;
/**
* Returns an iterator over the list of archive files that meet the specified
* search criteria. Please note that the list is ordered by archive file ID.
*
* @param searchCriteria The search criteria.
* @param nbArchiveFilesToPrefetch The number of archive files to prefetch.
* @return An iterator over the list of archive files.
*/
virtual std::unique_ptr<ArchiveFileItor> getArchiveFileItor(
const ArchiveFileSearchCriteria &searchCriteria = ArchiveFileSearchCriteria(),
const uint64_t nbArchiveFilesToPrefetch = 1000) const = 0;
/**
* Returns a summary of the archive files that meet the specified search
* criteria.
*
* @param searchCriteria The search criteria.
* @return The summary.
*/
virtual common::dataStructures::ArchiveFileSummary getArchiveFileSummary(
const ArchiveFileSearchCriteria &searchCriteria = ArchiveFileSearchCriteria()) const = 0;
/**
* Returns the archive file with the specified unique identifier.
......
......@@ -84,10 +84,9 @@ protected:
}
}
{
const std::list<common::dataStructures::ArchiveFile> archiveFiles =
m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "");
for(auto &archiveFile: archiveFiles) {
m_catalogue->deleteArchiveFile(archiveFile.archiveFileID);
std::unique_ptr<ArchiveFileItor> itor = m_catalogue->getArchiveFileItor();
while(itor->hasMore()) {
m_catalogue->deleteArchiveFile(itor->next().archiveFileID);
}
}
{
......@@ -157,6 +156,32 @@ protected:
throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
}
}
/**
* Creates a map from archive file ID to archive file from the specified iterator.
*
* @param itor Iterator over archive files.
* @return Map from archive file ID to archive file.
*/
std::map<uint64_t, cta::common::dataStructures::ArchiveFile> archiveFileItorToMap(cta::catalogue::ArchiveFileItor &itor) {
using namespace cta;
try {
std::map<uint64_t, common::dataStructures::ArchiveFile> m;
while(itor.hasMore()) {
const common::dataStructures::ArchiveFile archiveFile = itor.next();
if(m.end() != m.find(archiveFile.archiveFileID)) {
exception::Exception ex;
ex.getMessage() << "Archive file with ID " << archiveFile.archiveFileID << " is a duplicate";
throw ex;
}
m[archiveFile.archiveFileID] = archiveFile;
}
return m;
} catch(exception::Exception &ex) {
throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
}
}
};
TEST_F(cta_catalogue_InMemoryCatalogueTest, createBootstrapAdminAndHostNoAuth) {
......@@ -1867,7 +1892,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, prepareToRetrieveFile) {
const uint64_t archiveFileId = 1234;
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
const std::string storageClassName = "storage_class";
......@@ -2085,7 +2110,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, fileWrittenToTape_2_tape_files_diffe
const uint64_t archiveFileId = 1234;
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
const std::string storageClassName = "storage_class";
......@@ -2252,7 +2277,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, fileWrittenToTape_2_tape_files_same_
const uint64_t archiveFileId = 1234;
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
const std::string storageClassName = "storage_class";
......@@ -2372,7 +2397,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, fileWrittenToTape_2_tape_files_corru
const uint64_t archiveFileId = 1234;
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
const std::string storageClassName = "storage_class";
......@@ -2515,7 +2540,7 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, deleteArchiveFile) {
const uint64_t archiveFileId = 1234;
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
ASSERT_THROW(m_catalogue->getArchiveFileById(archiveFileId), exception::Exception);
const std::string storageClassName = "storage_class";
......@@ -2548,6 +2573,37 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, deleteArchiveFile) {
ASSERT_EQ(1, tape.lastFSeq);
}
{
std::unique_ptr<catalogue::ArchiveFileItor> archiveFileItor = m_catalogue->getArchiveFileItor();
const std::map<uint64_t, common::dataStructures::ArchiveFile> m = archiveFileItorToMap(*archiveFileItor);
ASSERT_EQ(1, m.size());
auto mItor = m.find(file1Written.archiveFileId);
ASSERT_FALSE(m.end() == mItor);
const common::dataStructures::ArchiveFile archiveFile = mItor->second;
ASSERT_EQ(file1Written.archiveFileId, archiveFile.archiveFileID);
ASSERT_EQ(file1Written.diskFileId, archiveFile.dstURL);
ASSERT_EQ(file1Written.size, archiveFile.fileSize);
ASSERT_EQ(file1Written.storageClassName, archiveFile.storageClass);
ASSERT_EQ(file1Written.diskInstance, archiveFile.diskInstance);
ASSERT_EQ(file1Written.diskFilePath, archiveFile.drData.drPath);
ASSERT_EQ(file1Written.diskFileUser, archiveFile.drData.drOwner);
ASSERT_EQ(file1Written.diskFileGroup, archiveFile.drData.drGroup);
ASSERT_EQ(file1Written.diskFileRecoveryBlob, archiveFile.drData.drBlob);
ASSERT_EQ(1, archiveFile.tapeFiles.size());
auto copyNbToTapeFile1Itor = archiveFile.tapeFiles.find(1);
ASSERT_FALSE(copyNbToTapeFile1Itor == archiveFile.tapeFiles.end());
const common::dataStructures::TapeFile &tapeFile1 = copyNbToTapeFile1Itor->second;
ASSERT_EQ(file1Written.vid, tapeFile1.vid);
ASSERT_EQ(file1Written.fSeq, tapeFile1.fSeq);
ASSERT_EQ(file1Written.blockId, tapeFile1.blockId);
ASSERT_EQ(file1Written.compressedSize, tapeFile1.compressedSize);
ASSERT_EQ(file1Written.copyNb, tapeFile1.copyNb);
}
{
const common::dataStructures::ArchiveFile archiveFile = m_catalogue->getArchiveFileById(archiveFileId);
......@@ -2598,6 +2654,50 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, deleteArchiveFile) {
ASSERT_EQ(1, tape.lastFSeq);
}
{
std::unique_ptr<catalogue::ArchiveFileItor> archiveFileItor = m_catalogue->getArchiveFileItor();
const std::map<uint64_t, common::dataStructures::ArchiveFile> m = archiveFileItorToMap(*archiveFileItor);
ASSERT_EQ(1, m.size());
{
auto mItor = m.find(file1Written.archiveFileId);
ASSERT_FALSE(m.end() == mItor);
const common::dataStructures::ArchiveFile archiveFile = mItor->second;
ASSERT_EQ(file2Written.archiveFileId, archiveFile.archiveFileID);
ASSERT_EQ(file2Written.diskFileId, archiveFile.dstURL);
ASSERT_EQ(file2Written.size, archiveFile.fileSize);
ASSERT_EQ(file2Written.storageClassName, archiveFile.storageClass);
ASSERT_EQ(file2Written.diskInstance, archiveFile.diskInstance);
ASSERT_EQ(file2Written.diskFilePath, archiveFile.drData.drPath);
ASSERT_EQ(file2Written.diskFileUser, archiveFile.drData.drOwner);
ASSERT_EQ(file2Written.diskFileGroup, archiveFile.drData.drGroup);
ASSERT_EQ(file2Written.diskFileRecoveryBlob, archiveFile.drData.drBlob);
ASSERT_EQ(2, archiveFile.tapeFiles.size());
auto copyNbToTapeFile1Itor = archiveFile.tapeFiles.find(1);
ASSERT_FALSE(copyNbToTapeFile1Itor == archiveFile.tapeFiles.end());
const common::dataStructures::TapeFile &tapeFile1 = copyNbToTapeFile1Itor->second;
ASSERT_EQ(file1Written.vid, tapeFile1.vid);
ASSERT_EQ(file1Written.fSeq, tapeFile1.fSeq);
ASSERT_EQ(file1Written.blockId, tapeFile1.blockId);
ASSERT_EQ(file1Written.compressedSize, tapeFile1.compressedSize);
ASSERT_EQ(file1Written.copyNb, tapeFile1.copyNb);
auto copyNbToTapeFile2Itor = archiveFile.tapeFiles.find(2);
ASSERT_FALSE(copyNbToTapeFile2Itor == archiveFile.tapeFiles.end());
const common::dataStructures::TapeFile &tapeFile2 = copyNbToTapeFile2Itor->second;
ASSERT_EQ(file2Written.vid, tapeFile2.vid);
ASSERT_EQ(file2Written.fSeq, tapeFile2.fSeq);
ASSERT_EQ(file2Written.blockId, tapeFile2.blockId);
ASSERT_EQ(file2Written.compressedSize, tapeFile2.compressedSize);
ASSERT_EQ(file2Written.copyNb, tapeFile2.copyNb);
}
}
{
const common::dataStructures::ArchiveFile archiveFile = m_catalogue->getArchiveFileById(archiveFileId);
......@@ -2668,13 +2768,13 @@ TEST_F(cta_catalogue_InMemoryCatalogueTest, deleteArchiveFile) {
ASSERT_EQ(file2Written.copyNb, tapeFile2.copyNb);
}
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
}
TEST_F(cta_catalogue_InMemoryCatalogueTest, deleteArchiveFile_non_existant) {
using namespace cta;
ASSERT_TRUE(m_catalogue->getArchiveFiles("", "", "", "", "", "", "", "", "").empty());
ASSERT_FALSE(m_catalogue->getArchiveFileItor()->hasMore());
ASSERT_THROW(m_catalogue->deleteArchiveFile(12345678), catalogue::UserError);
}
......
......@@ -2292,20 +2292,87 @@ void RdbmsCatalogue::insertArchiveFile(const ArchiveFileRow &row) {
}
//------------------------------------------------------------------------------
// getArchiveFiles
// getArchiveFileItor
//------------------------------------------------------------------------------
std::list<common::dataStructures::ArchiveFile> RdbmsCatalogue::getArchiveFiles(
const std::string &id,
const std::string &eosid,
const std::string &copynb,
const std::string &tapepool,
const std::string &vid,
const std::string &owner,
const std::string &group,
const std::string &storageclass,
const std::string &path) {
std::unique_ptr<ArchiveFileItor> RdbmsCatalogue::getArchiveFileItor(const ArchiveFileSearchCriteria &searchCriteria,
const uint64_t nbArchiveFilesToPrefetch) const {
try {
const char *const sql =
return std::unique_ptr<ArchiveFileItor>(new ArchiveFileItorImpl(*this, nbArchiveFilesToPrefetch, searchCriteria));
} catch(exception::Exception &ex) {
throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
}
}
//------------------------------------------------------------------------------
// constructor
//------------------------------------------------------------------------------
RdbmsCatalogue::ArchiveFileItorImpl::ArchiveFileItorImpl(
const RdbmsCatalogue &catalogue,
const uint64_t nbArchiveFilesToPrefetch,
const ArchiveFileSearchCriteria &searchCriteria):
m_catalogue(catalogue),
m_nbArchiveFilesToPrefetch(nbArchiveFilesToPrefetch),
m_searchCriteria(searchCriteria),
m_nextArchiveFileId(1) {
try {
m_prefechedArchiveFiles = m_catalogue.getArchiveFilesForItor(m_nextArchiveFileId, m_nbArchiveFilesToPrefetch,
m_searchCriteria);
if(!m_prefechedArchiveFiles.empty()) {
m_nextArchiveFileId = m_prefechedArchiveFiles.back().archiveFileID + 1;
}
} catch(exception::Exception &ex) {
throw exception::Exception(std::string(__FUNCTION__) + " failed: " +ex.getMessage().str());
}
}
//------------------------------------------------------------------------------
// destructor
//------------------------------------------------------------------------------
RdbmsCatalogue::ArchiveFileItorImpl::~ArchiveFileItorImpl() {
}
//------------------------------------------------------------------------------
// hasMore
//------------------------------------------------------------------------------
bool RdbmsCatalogue::ArchiveFileItorImpl::hasMore() const {
return !m_prefechedArchiveFiles.empty();
}
//------------------------------------------------------------------------------
// next
//------------------------------------------------------------------------------
common::dataStructures::ArchiveFile RdbmsCatalogue::ArchiveFileItorImpl::next() {
try {
if(m_prefechedArchiveFiles.empty()) {
throw exception::Exception("No more archive files to iterate over");
}
common::dataStructures::ArchiveFile archiveFile = m_prefechedArchiveFiles.front();
m_prefechedArchiveFiles.pop_front();
if(m_prefechedArchiveFiles.empty()) {
m_prefechedArchiveFiles = m_catalogue.getArchiveFilesForItor(m_nextArchiveFileId, m_nbArchiveFilesToPrefetch,
m_searchCriteria);
if(!m_prefechedArchiveFiles.empty()) {
m_nextArchiveFileId = m_prefechedArchiveFiles.back().archiveFileID + 1;
}
}
return archiveFile;
} catch(exception::Exception &ex) {
throw exception::Exception(std::string(__FUNCTION__) + " failed: " + ex.getMessage().str());
}
}
//------------------------------------------------------------------------------
// getArchiveFilesForItor
//------------------------------------------------------------------------------
std::list<common::dataStructures::ArchiveFile> RdbmsCatalogue::getArchiveFilesForItor(
const uint64_t startingArchiveFileId,
const uint64_t maxNbArchiveFiles,
const ArchiveFileSearchCriteria &searchCriteria) const {
try {
std::string sql =
"SELECT "
"ARCHIVE_FILE.ARCHIVE_FILE_ID AS ARCHIVE_FILE_ID,"
"ARCHIVE_FILE.DISK_INSTANCE AS DISK_INSTANCE,"
......@@ -2325,19 +2392,83 @@ std::list<common::dataStructures::ArchiveFile> RdbmsCatalogue::getArchiveFiles(
"TAPE_FILE.BLOCK_ID AS BLOCK_ID,"
"TAPE_FILE.COMPRESSED_SIZE AS COMPRESSED_SIZE,"
"TAPE_FILE.COPY_NB AS COPY_NB,"
"TAPE_FILE.CREATION_TIME AS TAPE_FILE_CREATION_TIME "
"TAPE_FILE.CREATION_TIME AS TAPE_FILE_CREATION_TIME, "
"TAPE.TAPE_POOL_NAME AS TAPE_POOL_NAME "
"FROM "
"ARCHIVE_FILE "
"LEFT OUTER JOIN TAPE_FILE ON "
"ARCHIVE_FILE.ARCHIVE_FILE_ID = TAPE_FILE.ARCHIVE_FILE_ID "
"ORDER BY "
"ARCHIVE_FILE.ARCHIVE_FILE_ID,"
"TAPE_FILE.COPY_NB"
;
"LEFT OUTER JOIN TAPE ON "
"TAPE_FILE.VID = TAPE.VID "
"WHERE "
"ARCHIVE_FILE.ARCHIVE_FILE_ID >= :STARTING_ARCHIVE_FILE_ID";
if(!searchCriteria.archiveFileId.empty()) {
sql += " AND ARCHIVE_FILE.ARCHIVE_FILE_ID = :ARCHIVE_FILE_ID";
}
if(!searchCriteria.diskInstance.empty()) {
sql += " AND ARCHIVE_FILE.DISK_INSTANCE = :DISK_INSTANCE";
}
if(!searchCriteria.diskFileId.empty()) {
sql += " AND ARCHIVE_FILE.DISK_FILE_ID = :DISK_FILE_ID";
}
if(!searchCriteria.diskFilePath.empty()) {
sql += " AND ARCHIVE_FILE.DISK_FILE_PATH = :DISK_FILE_PATH";
}
if(!searchCriteria.diskFileUser.empty()) {
sql += " AND ARCHIVE_FILE.DISK_FILE_USER = :DISK_FILE_USER";
}
if(!searchCriteria.diskFileGroup.empty()) {
sql += " AND ARCHIVE_FILE.DISK_FILE_GROUP = :DISK_FILE_GROUP";
}
if(!searchCriteria.storageClass.empty()) {
sql += " AND ARCHIVE_FILE.STORAGE_CLASS_NAME = :STORAGE_CLASS_NAME";
}
if(!searchCriteria.vid.empty()) {
sql += " AND TAPE_FILE.VID = :VID";
}
if(!searchCriteria.tapeFileCopyNb.empty()) {
sql += " AND TAPE_FILE.COPY_NB = :TAPE_FILE_COPY_NB";
}
if(!searchCriteria.tapePool.empty()) {
sql += " AND TAPE.TAPE_POOL_NAME = :TAPE_POOL_NAME";
}
sql += " ORDER BY ARCHIVE_FILE.ARCHIVE_FILE_ID, TAPE_FILE.COPY_NB";
std::unique_ptr<DbStmt> stmt(m_conn->createStmt(sql));
stmt->bindUint64(":STARTING_ARCHIVE_FILE_ID", startingArchiveFileId);
if(!searchCriteria.archiveFileId.empty()) {
stmt->bindUint64(":ARCHIVE_FILE_ID", utils::toUint64(searchCriteria.archiveFileId));
}
if(!searchCriteria.diskInstance.empty()) {
stmt->bindString(":DISK_INSTANCE", searchCriteria.diskInstance);
}
if(!searchCriteria.diskFileId.empty()) {
stmt->bindString(":DISK_FILE_ID", searchCriteria.diskFileId);