mardi 22 mars 2011

Être consistent...

Rhaaa, IBM! Ils viennent de confirmer après 3 jours d'arrachage de cheveux qu'ils ne supportent pas une fonctionnalité qui semble pourtant basique.

DB2, ou plutôt son API C, répondant au doux nom de CLI, fournit un mode très efficace exécuter plusieurs fois une même requête. L'idée est de créer un tableau, de "binder" ce tableau à une colonne (via un appel à SQLBindParameter), puis d'exécuter la requête préparée. Tout est envoyé en une fois à la base, ce qui évite l'effet ping-pong entre le client et le serveur. Par exemple, une requête "insert into mytable values(?)" permet d'insérer des milliers de lignes en un seul appel.

Nous codons donc une belle couche d'abstraction en C++, et continuons joyeusement à coder.

Jusqu'au jour où je découvre que le binding par colonnes ne fonctionne pas avec les procédures stockées. Dans le genre, je créé une procédure qui fait un bête insert dans mytable, et j'appelle "call myproc(?)". Pire, DB2 va exécuter la procédure, mais seulement sur le premier élément, ce qui cause un beau bordel dans la base. La documentation est peu claire sur ce point précis, la spec ODBC dit que ça devrait marcher. Eh ben non.

Reprenons: Une fonctionnalité qui semble pourtant aller de soi manque, ce manquement est mal documenté, et au lieu d'envoyer un message d'erreur clair, seule une partie de la requête tourne, causant une corruption des données. Peut-être que la raison fondamentale est que insert, update et delete supportent un vrai mode d'insertion par tableau, mais dans ce cas pourquoi ne pas au moins tenter d'être consistent, et pour le moteur de base de données de revenir dans un mode ligne par ligne dans le cas des procédures stockées? J'ai du mal à penser qu'IBM n'a pas les moyens humains de fournir quelque chose qui semble aussi trivial.

La solution, c'est d'utiliser le binding par valeur pour toute requête contenant une procédure stockée, mais c'est difficile à abstraire, puisqu'il faut soit laisser à l'utilisateur le choix (et donc la chance de se tromper), soit tenter d'analyser la requête pour détecter si elle est optimisable. En l'occurence, j'ai choisi la sale mais raisonnablement triviale solution d'optimiser si le premier mot de la requête est "insert". Et le code de la couche d'abstraction devient plus moche car il doit supporter deux types de bindings, et donc deux chemins de code, avec les presque duplications et l'augmentation des risques de se bugs qui vont avec. Beurk.

dimanche 13 mars 2011

g++ 4.5 et C++0x - Les nouveautés

J'ai porté Openrailz sous g++4.5, ce qui me permet d'utiliser plus de fonctionnalités de C++0x, mais a également soulevé quelque problèmes inattendus. Petit topo.

Destructeur virtuel et = default

Tout d'abord, les mauvaises nouvelles. Je ne sais si vous avez déjà souffert du problème, mais je n'ai jamais trouvé une manière complétement satisfaisante d'implémenter le destructeur virtuel en écrivant une interface. L'on peut inliner le code:


class Miaou
{
virtual ~Miaou() {}
};

mais c'est moche. L'on peut mettre le code en dessous:

class Miaou
{
virtual ~Miaou();
};

inline Miaou::~Miaou()
{
}

mais c'est encore plus moche, sans compter la verbosité lorsque la déclaration est un peu plus complexe (gros template, par exemple). Enfin, l'on peut juste mettre la déclaration dans son .cpp, mais on se retrouve avec un .cpp qui ne contient qu'une déclaration vide, et toujours aussi potentiellement verbeuse.

Lorsque j'ai découvert le = default de C++0x, je me suis dit, bonne nouvelle, voilà enfin comment résoudre le problème:

class Miaou
{
virtual ~Miaou() = default;
};

C'est joli, moderne, et ça fait exactement ce que ça dit. Malheureusement, c'est également interdit par le standard, et si g++4.4 laissait passer, g++4.5 le bloque. Le standard interdit en effet de déclarer un destructeur virtuel par défaut à la première déclaration. C'est à dire que pour écrire du code correct, il faut faire:

class Miaou
{
virtual ~Miaou();
};

inline Miaou::~Miaou() = default;

ce qui est tout aussi moche. Retour donc a la case départ.

Strict aliasing rules et boost::optional

J'avais commencé à virer les boost::optional de ma codebase, car ils causaient un ennuyeux warning dans g++4.4:

#include <iostream>
#include <boost/optional.hpp>

int main()
{
boost::optional<int> b;
if(b)
{
std::cout << *b << std::endl;
}

return 0;
}

La compilation avec g++4.4 renvoie ce message:

optional.cpp: In function ‘int main()’:
optional.cpp:10: warning: dereferencing pointer ‘’ does break strict-aliasing rules
optional.cpp:10: note: initialized from here

Alors, bien sûr, l'on peut utiliser le flag -fno-strict-aliasing, mais l'on réduit les performances.

Les discussions sont allé bon train sur les mailing-lists de Boost, mais il semblerait que le code soit en fait bon, et qu'il s'agisse d'un bug de g++4.4. G++4.5 ne se plaint pas sur ce code, et je compte donc bien le réintégrer vite fait, en particulier pour gérer les colonnes nullables dans mon code de base de données (voir le post précédent).

Et bien sûr... Les lambdas!

Va falloir commencer à s'habituer aux listes de captures, et faire gaffe au passage par valeur / référence. Raison de plus pour commencer le plus tôt possible, et en coller partout. 'tention les yeux! Notons également des améliorations sur les variadic templates, et les chaînes de caractères unicode.

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;
};
}

dimanche 6 mars 2011

GnuCash 2.4.2 débarque sur Debian Wheezy

Enfin, les paquets intéressants débarquent dans Debian Testing. Je fais mes comptes depuis plus d'un an déjà (essayez au moins une fois quelques mois pour voir, c'est très intéressant!), j'utilise GnuCash et j'en suis fort satisfait. Peut-être moins joli que Grisbi et KMyMoney, mais il fait le boulot, et c'est le principal.

La fonctionnalité la plus intéressante de cette nouvelle release est la possibilité d'utiliser une base de données au lieu de fichiers XML. Voici donc la marche à suivre pour bouger sur Postgresql.

  • Tout d'abord, assurez-vous que Postgres est installé, avec un compte qui marche et les permissions qui vont bien

  • Installez le paquet libdbd-pgsql

  • Créez la base gnucash avec la commande suivante: createdb gnucash

  • Démarrez gnucash, et ouvrez votre fichier de compte

  • Allez dans File -> Save as, et choisissez "postgres" dans la section "data format". La fenêtre change, et vous demande vos identifiants de connexion. Entrez le login (et le mot de passe si vous en avez un), zappez le baratin de Gnome-Keyring (OK à tout (sauf si vous êtes un accro de sa sécurité)), et basta!

Les plus curieux d'entre vous pourront aller jeter un coup d'œil à la base, et admirer les tables accounts, transactions et splits qui contiennent l'essentiel des données intéressantes.

En dehors de faire son geek, passer à un backend sur base de données est pratique par exemple si l'on a déjà des systèmes de sauvegarde tournant sur la base. Et c'est toujours ça de moins à penser lors d'une migration sur une autre machine.

vendredi 4 mars 2011

Ais-je rêvé? Pas de boost::mutex::is_locked!

Je l'aurais pourtant juré (et j'ai eu l'air bête au boulot à cause de ça), mais contrairement à ce que je croyais et malgré ma certitude que je l'avait déjà utilisée, les {boost,std}::mutex n'ont pas de méthode is_locked qui permettrait de savoir si le mutex est acquis ou pas.

Dès que je mentionne cette fonctionnalité, l'on me regarde avec des grands yeux: "Mais pourquoi voudrais-tu faire ça? Si tu en as besoin, c'est que ton design est pourri / que tu ne sais pas utiliser les mutex / que tu est bête" (rayer la mention inutile). Alors, oui, je comprends bien que la plupart des utilisations d'une telle méthode sont des aberrations. Mais voilà mon cas d'utilisation:

Imaginez que vous avez une belle classe, gentiment thread-safe (et Wikipédia peut aller se rhabiller avec son "à fil sécurisé", non mais...), et donc possédant un beau mutex (privé, mutable, un beau mutex, quoi). Les méthodes publiques acquièrent le mutex, et appellent des méthodes privées qui s'attendent donc à ce que le mutex soit déjà pris, ce qui est donc une précondition. Afin que le prochain gus écrivant sa méthode publique dans ma classe ne casse tout, j'aimerais beaucoup pouvoir effectivement vérifier cette précondition, avec par exemple un assert(m_mutex.is_locked()). Si mon gus oublie d'acquérir le verrou, il aura une belle assertion lors de ses tests unitaires (encore faut-il qu'il écrive des tests unitaires, mais c'est un autre débat).

Et la, généralement, mon interlocuteur change d'avis: "Tiens, c'est vrai, ça serait pratique...".

Les solutions
Car elles existent.

Une possibilité est pour chaque méthode privée s'attendant à être protégée de prendre en arguments un lock (et pas un mutex), inutilisé dans le corps de la méthode. C'est une sorte de rappel, l'appelant de la fonction devant obligatoirement créer un objet lock. Bien sûr, l'on peut le contourner en créant un objet lock vide, ou qui acquiert un autre mutex, ou qui appelle unlock avant de passer l'objet, mais bon, il faut vraiment être pervers. Autre iconvénient: c'est assez moche, car ça bave dans l'interface.

Une autre possibilité est d'écrire la méthode is_locked, en tentant d'acquérir à nouveau le verrou avec un try_lock, et en vérifiant le résultat. Ce n'est pas spécialement efficace, mais dans une assertion virée des builds optimisés, ce n'est probablement pas un problème. Inconvénient: cela ne marche que si le mutex est non récursif. Sinon, c'est cassé.

Décevant.