LT Project: Record Sources
RecordPopulatorSetFactory
Before jumping into the actual data source classes, there's one small bridge class we have to look at.
class IRecordSetPopulatorFactory
{
public:
virtual ~IRecordSetPopulatorFactory();
virtual std::unique_ptr<IRecordSetPopulator>
create(const IFieldAdderSet &inAdders,
const IRecordSetCriteria &inCriteria) const = 0;
};
This (and its implementation) is the class that provides the bridge between the criteria specified by the user (plus the general rules selected for by the type of display, represented by the field adder set) and the class that actually does the work of doing the matching, which we looked at a short time ago.
The implementation is simple:
class RecordSetPopulatorFactory : public IRecordSetPopulatorFactory
{
public:
~RecordSetPopulatorFactory() override;
std::unique_ptr<IRecordSetPopulator>
create(const IFieldAdderSet &inAdders,
const IRecordSetCriteria &inCriteria) const override
{
if (!inCriteria.hasSingleId())
{
return std::make_unique<RecordSetPopulator>(inAdders, inCriteria);
}
else
{
return std::make_unique<SingleRecordSetPopulator>(inCriteria.getId());
}
}
};
(The SingleRecordSetPopulator has much simpler logic than the general case. Implementations of its key functions are:
ILibraryBookRecord *
SingleRecordSetPopulator::createRecord(const std::string &inString) const
{
if (std::atoi(inString.c_str()) == m_key)
{
LibraryBookRecord *rval
= new LibraryBookRecord(inString, theAdder, true, true);
rval->setIsOnHeap(true);
return rval;
}
else
{
return &theNullrval;
}
}
ILibraryBookRecord *SingleRecordSetPopulator::filterRecord(
const BaseLibraryBookRecord &inRec) const
{
if (inRec.getId() == m_key)
{
return inRec.createRecord(theAdder, true, true);
}
else
{
return &theNullrval;
}
}
void SingleRecordSetPopulator::updateStats(
LtStats &outStats, const BaseLibraryBookRecord &inRec) const
{
if (inRec.getId() == m_key)
{
outStats.add(inRec);
}
}
Note that we take advantage of the fact that the line records begin with the record ID by using std::atoi() to avoid the need for parsing the record except for the one which we want. And we use atoi() rather than stoi() to avoid the effect of throwing an exception on a defective record.)
FileRecordSource
The simpler of the two sources is the FileRecordSource:
class FileRecordSource : public LtLibrary::IRecordSource
{
public:
FileRecordSource(const JSBUtil::IFileLineSourceFactory &inSourceFactory,
const LtLibrary::IRecordSetPopulatorFactory &inFactory,
std::shared_ptr<Log::ILogger> inLogger);
~FileRecordSource() override;
std::unique_ptr<LtLibrary::ILibraryRecordSet>
getRecords(const LtLibrary::IFieldAdderSet &inAdders,
const LtLibrary::IRecordSetCriteria &inCriteria) const override;
void getStats(LtLibrary::LtStats &outStats,
const LtLibrary::IFieldAdderSet &inAdders,
const LtLibrary::IRecordSetCriteria &inCriteria) const override;
private:
mutable std::unique_ptr<JSBUtil::IFileLineSource> m_source;
const LtLibrary::IRecordSetPopulatorFactory &m_factory;
std::shared_ptr<Log::ILogger> m_logger;
};
This is used only in the command-line application, which does a single pass over the records.
The constructor just captures parameters:
FileRecordSource::FileRecordSource(
const JSBUtil::IFileLineSourceFactory &inSourceFactory,
const LtLibrary::IRecordSetPopulatorFactory &inFactory,
std::shared_ptr<Log::ILogger> inLogger):
m_source(inSourceFactory.create()),
m_factory(inFactory), m_logger(inLogger)
{ }
All the activity takes place during the single pass in getRecords() or getStats():
std::unique_ptr<LtLibrary::ILibraryRecordSet> FileRecordSource::getRecords(
const LtLibrary::IFieldAdderSet &inAdders,
const LtLibrary::IRecordSetCriteria &inCriteria) const
{
m_source->openSource();
std::vector<std::string> vec;
while (true)
{
std::string s;
if (!m_source->getNextLine(s))
break;
vec.push_back(std::move(s));
}
std::unique_ptr<LtLibrary::IRecordSetPopulator> populator
= m_factory.create(inAdders, inCriteria);
return std::make_unique<LtLibrary::LibraryRecordSet>(
vec, *populator, inCriteria.isBibliographic(), m_logger);
}
void FileRecordSource::getStats(
LtLibrary::LtStats &outStats, const LtLibrary::IFieldAdderSet &inAdders,
const LtLibrary::IRecordSetCriteria &inCriteria) const
{
m_source->openSource();
std::unique_ptr<LtLibrary::IRecordSetPopulator> populator
= m_factory.create(inAdders, inCriteria);
while (true)
{
std::string s;
if (!m_source->getNextLine(s))
break;
populator->updateStats(outStats, LtLibrary::BaseLibraryBookRecord(s));
}
}
MemoryRecordSource
The MemoryRecordSource stores the contents of the text database as an in-memory vector:
class MemoryRecordSource : public IRecordSource
{
using storage_type = std::vector<BaseLibraryBookRecord>;
public:
MemoryRecordSource (const JSBUtil::IFileLineSourceFactory &inFileFactory,
const IRecordSetPopulatorFactory &inFactory,
std::shared_ptr<Log::ILogger> inLogger);
~MemoryRecordSource () override;
std::unique_ptr<ILibraryRecordSet>
getRecords (const IFieldAdderSet &inAdders,
const IRecordSetCriteria &inCriteria) const override;
void getStats (LtStats &outStats, const IFieldAdderSet &inAdders,
const IRecordSetCriteria &inCriteria) const override;
private:
storage_type m_records;
const IRecordSetPopulatorFactory &m_factory;
std::shared_ptr<Log::ILogger> m_logger;
};
Unlike in the previous class, the constructor loads all the records into memory. If there is a file-related failure there will be an exception and the whole application will fail fast.
MemoryRecordSource::MemoryRecordSource(
const JSBUtil::IFileLineSourceFactory &inFileFactory,
const IRecordSetPopulatorFactory &inFactory,
std::shared_ptr<Log::ILogger> inLogger)
: m_factory(inFactory), m_logger(inLogger)
{
auto source = inFileFactory.create();
source->openSource();
std::string s;
while (source->getNextLine(s))
m_records.emplace_back(s);
}
std::unique_ptr<ILibraryRecordSet>
MemoryRecordSource::getRecords(const IFieldAdderSet &inAdders,
const IRecordSetCriteria &inCriteria) const
{
std::unique_ptr<IRecordSetPopulator> populator
= m_factory.create(inAdders, inCriteria);
if (!inCriteria.hasSingleId())
return std::make_unique<LibraryRecordSet>(
m_records, *populator, inCriteria.isBibliographic(), m_logger);
else
{
if (auto val
= std::ranges::find_if(m_records, [&inCriteria](const auto &inVal) {
return inCriteria.matchesId(inVal.getId());
}); val != m_records.end()) [[likely]]
return std::make_unique<SingleLibraryRecordSet>(*val, *populator,
m_logger);
else
return std::make_unique<EmptyLibraryRecordSet>();
}
}
We optimize for the single id case with a faster find_if approach. In practice, the id passed should come from an already retrieved record and the case where the lookup fails is a "can't happen" case, but we handle it in any case.
void
MemoryRecordSource::getStats(LtStats &outStats, const IFieldAdderSet &inAdders,
const IRecordSetCriteria &inCriteria) const
{
std::unique_ptr<IRecordSetPopulator> populator
= m_factory.create(inAdders, inCriteria);
std::ranges::for_each(m_records, [&](const auto &inRec) {
populator->updateStats(outStats, inRec);
});
}
In practice, there shouldn't be a stats call on a single id query, so we just use general logic.
Comments
Post a Comment