I am working with filters in C++ and I would like to create a query like ODB does:
db->query (query::age > 30)
I don't know what kind of data is being passed there. I have been reading about filters in C++ (with functors, lambda expressions, ...) but it is not as simple and compact as ODB queries. In addition, these query conditions can be concatenated.
db->query_one (query::first == "Joe" && query::last == "Dirt")
I have been looking what I think that the query class code is, but I have not been able to identify how it is achieved.
I need to filter a list of instances of Systems:
class Model
{
int id;
QString name;
};
class System
{
int id;
int user_id;
Model model;
};
I would like to have something like this:
Filter<System> system_filter;
Query<System> system_query(System::id == 1 && System::Model::name == "MyModelName")
system_filter.filter(system_list, system_query);
Currently I have this code (similar to an example of the book Design Patterns in Modern C++):
template <typename T>
struct Specification
{
virtual ~Specification(){}
virtual bool is_specified(T* item) = 0;
};
template <typename T>
struct Filter
{
virtual QList<T*> filter(QList<T*> items, Specification<T>& spec)
{
QList<T*> result;
for (auto& item:items)
{
if (spec.is_specified(item))
{
result.push_back(item);
}
}
return result;
}
};
template <typename T>
struct IdSpecification: common::Specification<T>
{
int id;
IdSpecification(int id) : id(id){}
public:
bool is_specified(T* item) override
{
return item->getId() == id;
}
};
template <typename T>
struct NameSpecification: common::Specification<T>
{
QRegularExpression regex;
NameSpecification(const QRegularExpression& regex) : regex(regex){}
public:
bool is_specified(T* item) override
{
QRegularExpressionMatch match = regex.match(item->getName());
return regex.match(item->getName()).hasMatch();
}
};
template <typename T, typename B>
template <typename T>
struct AndSpecification: Specification<T>
{
Specification<T>& first;
Specification<T>& second;
AndSpecification(Specification<T>& first, Specification<T>& second): first(first), second(second){}
public:
bool is_specified(T* item) override
{
return first.is_specified(item) && second.is_specified(item);
}
};
So now I can do something like this:
IdSpecification<System> system_specification(3);
NameSpecification<System> name_specification("SytemName");
AndSpecification<System> and_specification(system_specification, type_specification);
auto system_filtered = system_filter.filter(system_list, and_specification);
Thanks!
You want expression trees.
You might start with:
template<auto Member>
struct member_query_t;
template<class T, class V>
struct member_query_t< V T::* M > {
V const& operator()(T const& t) const { return t.*M; }
};
template<auto Member>
constexpr member_query_t<Member> member_q = {};
now member_q<&Model::id>
is a C++ object that knows both what class it is operating on, and how to access the member id
.
We can now build something called exprssion trees. Here, we make some types and overload some operators to return a compile-time type with the information about the expression encoded in it.
We can augment this with an ordering, a hash, a comparison, and the like, so our store can see we are being queried for Member
and store fast lookup information inside itself for Member
.
template<class T, class V>
struct member_query_t< V T::* M > {
V const& operator()(T const& t) const { return t.*M; }
auto hasher() const {
return std::hash<T>{};
}
auto ordering() const {
return std::less<T>{};
}
auto comparison() const {
return std::equal_to<T>{};
}
};
you can get fancy to avoid requiring full specialization for those traits. This kind of syntax would be possible:
auto system_query(
member_query<&System::id> == 1 &&
member_query<&System::model>[member_query<&Model::name>] == "MyModelName"
);
where system_query
has the entire process of how a lookup works stored its type.
We can then do a completely different advanced technique called type erasure in C++ to make this into a Query<System>
.
Each of these is easily 100s of lines of relatively dense template code to get working right.
What more, even after you do that, writing Filter<System>
to work requires delving into RTTI and writing an efficient generic database system that can handle nearly arbitrary lookup mechanisms and compose them together on request.
So you have like 3 hard challenges to do what you want to describe, any one of which would require me multiple iterated versions to get right.