samedi 12 mars 2011

Variadic Templates et pqxx

Je me suis lancé de nouveau dans l'utilisation des variadic templates pour simplifier (!) les appels à la base de données. La chose est un poil touffue, et compile avec g++4.5. Tout d'abord,un exemple d'utilisation: on créé un type Query avec les types des paramètres d'entrée et de sortie en paramètres template. L'on peut ensuite faire tourner la requête avec execute, lequel prend les paramètres d'entrée fortement typés. Ensuite, un itérateur permet de récupérer les résultats sous forme de std::tuple.


typedef
db::Query<db::ParamSet<std::string, std::string>,
db::ResultSet<std::string, std::string> >
QueryT;
QueryT query(dbConn, "select $2::text, $1::text");
query.execute("abc", "def");

for_each(query.begin(),
query.end(),
[](const QueryT::ResultType & result)
{
std::cout << std::get<0>(result) << "\t"
<< std::get<1>(result) << std::endl;
});

Et pour ceux que ça intéresse, le code derrière tout ça. Il ne supporte pour l'instant que les chaînes de caractères en entrée, je laisse de côté le long et ennuyeux code pour gérer d'autres types (basé sur les Traits). Le principe est un peu toujours le même: dérouler le variadic template à l'aide de classes récursives partiellement spécialisées. Notez que le ResultSet est instancié, bien que n'ayant pas d'attributs. Lassé de me battre avec le compilo qui ne comprenait pas mes appels de méthodes statiques, j'ai instancié tout ça.

namespace db
{
template <typename ... Args>
class ParamSet;

template<>
class ParamSet<>
{
public:
void prepare(const pqxx::prepare::declaration &)
{
}

pqxx::prepare::invocation & execute(pqxx::prepare::invocation & invocation)
{
return invocation;
}
};

template <typename T, typename ... Args>
class ParamSet<T, Args...>
{
public:
void prepare(const pqxx::prepare::declaration & declaration)
{
m_sub.prepare(declaration("text", pqxx::prepare::treat_string));
}

pqxx::prepare::invocation & execute(pqxx::prepare::invocation & invocation,
const T & data,
Args... others)
{
return m_sub.execute(invocation(data), others...);
}

private:
ParamSet<Args...> m_sub;
};

template<typename ... Args>
class ResultSet;

template<>
class ResultSet<>
{
public:
template<size_t N, typename TUPLE>
void doPopulate(const pqxx::result::tuple::const_iterator &, TUPLE &)
{
}
};

template<typename T, typename ... Args>
class ResultSet<T, Args...>
{
public:
typedef std::tuple<T, Args...> TupleT;

void populate(const pqxx::result::tuple & input, TupleT & output)
{
doPopulate<0>(input.begin(), output);
}

template<size_t N, typename TUPLE>
void doPopulate(const pqxx::result::tuple::const_iterator & it, TUPLE & output)
{
std::get<N>(output) = it->as<T>();
m_sub.doPopulate<N + 1>(it + 1, output);
}

private:
ResultSet<Args...> m_sub;
};

template<typename PARAMSET, typename RESULTSET>
class Query
{
public:
Query(const std::shared_ptr<pqxx::connection> & dbConn,
const std::string & sql):
m_dbConn(dbConn),
m_sql(sql)
{
m_paramSet.prepare(m_dbConn->prepare(sql, sql));
}

template<typename ... Args>
void execute(Args... args)
{
pqxx::work transaction(*m_dbConn, "Transaction");
pqxx::prepare::invocation invocation =
transaction.prepared(m_sql);

pqxx::result result = m_paramSet.execute(invocation, args...).exec();
m_result.resize(result.size());

auto it = result.begin();
auto end = result.end();
auto it_r = m_result.begin();

for(; it != end; ++it, ++it_r)
{
m_resultSet.populate(*it, *it_r);
}
}

size_t size() const
{
return m_result.size();
}

typedef typename RESULTSET::TupleT ResultType;
typedef typename std::vector<typename RESULTSET::TupleT>::const_iterator
const_iterator;

const_iterator begin()
{
return m_result.begin();
}

const_iterator end()
{
return m_result.end();
}

private:
std::shared_ptr<pqxx::connection> m_dbConn;
std::string m_sql;
PARAMSET m_paramSet;
RESULTSET m_resultSet;
std::vector<typename RESULTSET::TupleT> m_result;
};
}

Aucun commentaire: