Abstraction, quand tu nous tiens

Je suis toujours en cours de développement d’un SQL query builder, dont la première version a été poussée sur le dépôt Clearbricks, mais j’avoue que son usage n’est pas encore celui que j’espérais idéalement ; il reste encore des lourdeurs d’écriture.

Un petit exemple avec le code pour constituer cette requête SQL (syntaxe PostgreSQL) :

SELECT "a"."fullname", "a"."id", "a"."score" FROM "scores" AS "a" INNER JOIN (SELECT "fullname", MAX("score") FROM "scores" WHERE "fullname" = 'Jane Doe' GROUP BY "fullname") AS "b" ON "a"."fullname" = "b"."fullname" AND "a"."score" = "b"."score" WHERE "a"."score" > 50

Le code correspondant ressemble à ça :

use dbQuery as sql;
use dbQueryExpression as sqlExpr;
use dbQueryConditions as sqlCond;
use dbQueryAlias as sqlAlias;

$join = sql::make($con);
$join
    ->columns(
        'fullname',
        sqlExpr::make($join, 'MAX(%s)', 'score')
    )
    ->from('scores')
    ->where(sqlCond::make($join, 'fullname = ?', 'Jane Doe'))
    ->groupBy('fullname');

$query = sql::make($con);
$query
    ->columns(
        'a.fullname',
        'a.id',
        'a.score'
    )
    ->from('scores a')
    ->innerJoin(
        sqlAlias::make($query, $join, 'b'),
        sqlCond::make($query, 'a.fullname = b.fullname')
            ->with('a.score = b.score'))
    ->where(sqlCond::make($query, 'a.score > ?', 50));

Entre autre chose, je voudrais éviter d’avoir à réinjecter l’objet dbQuery a la construction de chaque élément (voir les méthodes make()), ce qui donnerait ceci idéalement :

use dbQuery as sql;

$join = sql::make($con);
$join
    ->columns(
        'fullname',
        $join->expression('MAX(%s)', 'score')
    )
    ->from('scores')
    ->where($join->condition('fullname = ?', 'Jane Doe'))
    ->groupBy('fullname');

$query = sql::make($con);
$query
    ->columns(
        'a.fullname',
        'a.id',
        'a.score'
    )
    ->from('scores a')
    ->innerJoin(
        $query->alias($join, 'b'),
        $query->condition('a.fullname = b.fullname')
            ->with('a.score = b.score'))
    ->where($query->condition('a.score > ?', 50));

Reste également à permettre l’usage du constructeur de requête, à l’origine prévu pour fonctionner via PDO::execute() avec les données injectées à ce moment, en remettant ces mêmes données dans la requête. C’est là que je mesure tout l’intérêt des tests unitaires !

Un autre avantage de ce constructeur est que tout ce qui est possible d’être « échappé » (nom de table, de champs, données, alias, …) l’est, ce qui permet d’éviter autant que possible l’injection de choses bizarres, et que cet échappement se conforme à la syntaxe du gestionnaire de base de données utilisé ;-)

Quoi qu’il en soit je m’amuse bien depuis trois jours !

Ajouter un commentaire

Les champs suivis d'un * sont obligatoires

Les commentaires peuvent être formatés en utilisant la syntaxe Markdown Extra.

Ajouter un rétrolien

URL de rétrolien : https://open-time.net/trackback/13735

Haut de page