I have received feature request to add support for user-defined last minute query transformation applied just before the query execution:
a) Add ITransformer
interface with method ostringstream transform(ostringstream& str) = 0;
b) Add method register_transformer(ITransformer& tr);
to session
class.
c) Apply the transformation just before the query execution, in method session::get_query_stream()
Presumably, they want to perform something that is equivalent to this:
sql << my_transformer(my_query) ...
but, presumably, the actual code operating on the sql
object does not have any knmowledge of my_transformer
:
sql.register_filter(my_transformer);
some_other_module::foo(sql);
This is an interesting idea.
A few modifications to the original idea discussed below:
1. Simple approach
Uses plain function pointer interface.
namespace soci {
typedef std::string (*query_transformer_t)(const std::string & query);
}
void soci::session::set_query_transformer(query_transformer_t t);
then, somewhere just before call to prepare()
, in final_action()
, the transformer could be applied:
if (query_transformer_)
{
query = query_transformer_(query);
}
This might not be the best option if one wants to manage transformation state easily.
2. Simple and flexible approach
It supports both free functions and function objects.
Note, the code below is just an illustration and does not map 1:1 with SOCI interface.
#include <functional>
#include <string>
// Prototype of additions in SOCI code
namespace soci {
namespace detail {
// TODO: use std::unary_function<const std::string&, std::string>
struct query_transformation_function : public std::unary_function<std::string, std::string>
{
virtual ~query_transformation_function() {}
virtual result_type operator()(argument_type a) const = 0;
};
template <typename T>
struct query_transformation : public query_transformation_function
{
query_transformation(T cb) : cb_(cb) {}
result_type operator()(argument_type a) const
{
return cb_(a);
}
private:
T cb_;
};
}
struct session
{
template <typename T>
void set_query_transformation(T cb)
{
delete query_transformation_;
query_transformation_= new detail::query_transformation<T>(cb);
}
// This represents the original chain of calls: final_action, get_query_stream, execute
void execute(std::string s)
{
query_ = s;
if (query_transformation_)
{
query_ = query_transformation_->operator()(query_);
}
// backEnd_->execute(query_);
}
session() : query_transformation_(0) {}
~session() { delete query_transformation_; }
private:
std::string query_;
detail::query_transformation_function* query_transformation_;
};
}
// Usage example
std::string transform_x(std::string query)
{
return query + " WHERE id = 1";
}
struct TransformX : std::unary_function<std::string, std::string>
{
result_type operator()(argument_type query) const
{
return query + " WHERE name = 'John'";
}
};
std::string query = "SELECT * FROM x";
soci::session s;
s.set_query_transformation(transform_x);
s.execute(query);
s.set_query_transformation(TransformX());
s.execute(query);
Likely, the option 2. will be implemented.