Artisan Numérique

/développement/bases de données/sgbdr/database api/drupal 7/module schema/mysql/postgresql/schema api/ Convertir un site Drupal MySQL vers PostgreSQL simplement

Combien de fois ai-je entendu "Postgres c'est super lent comparé à MySQL" (version polie). Et ce qui est amusant c'est que généralement cette affirmation s'appuie uniquement sur un ressenti de développeur assis devant sa bécane de compétition qui n'aura donc jamais qu'un seul pauvre utilisateur à servir... Assez faible vous en conviendrez. Le problème c'est que pour faire une vraie comparaison, il faudrait travailler sur un vrai site qui existerait pour les deux bases de données. Un cas de test généralement délicat voir impossible à monter tant les SGBDR sont mauvais dés qu'il s'agit de les interchanger.

Partant de cela, je me suis demandé, dans le cas exclusif d'un site sous Drupal, comment permettre une telle conversion. Et j'ai fini par tomber sur une méthode finalement assez simple.

Déjà, pourquoi est-ce normalement compliqué ?

En effet, puisque tout SGBDR utilise le langage SQL, convertir une application devrait être l'enfance de l'art. Malheureusement, ça c'est sur le papier. Dans la réalité, c'est un peu comme la norme CSS et Internet Explorer, chaque éditeur de base de données y vas de ses exotismes tant et si bien qu'à la fin, l'application se retrouve pieds et poings liés. Si l'on réduit le champ à une application PHP, les problèmes sont les suivants :

  • Les fonctions PHP (connexion, requêtes, etc) utilisées sont différentes d'une base à l'autre.
  • Les créations de tables ne fonctionnent que pour un moteur de base de données.
  • Les requêtes utilisent des fonctions spécifiques et même parfois des syntaxes propres à une base.

En bref, tester les performances sous PostgreSQL d'une application PHP écrite pour MySQL, c'est même pas la peine d'y penser?

Abstraction

C'est pour cela que Drupal, comme une majorité de cadres applicatifs, cherche depuis bien longtemps à mettre en place une certaine forme d'abstraction de la couche de stockage, adressant les trois aspects vu plus haut : la connexion et l'échange de données, la création de tables et le requêtage.

Drupal étant assez simpliste (euphémisme) dans son usage d'une base (pas de clefs étrangères, pas de procédures stockées, pas de triggers, pas de vues, etc), il s'est longtemps borné avec succès à l'abstraction de la connexion et ce principalement pour deux bases : PostgreSQL et MySQL. Avec Drupal 7 et l'adoption de PDO, cette aspect a été encore grandement amélioré.

Depuis Drupal6, Schema API) adresse l'épineux problème des disparités dans la syntaxe de création des tables. Il est depuis cette version possible d'écrire des modules qui créent des tables dans une sorte de langage pivot (dans le plus pur style d'Array Oriented Programming Drupalien) laissant au système le soin de le traduire dans celui de la base utilisée.

Enfin, avec Drupal 7, arrive l'excellente Database API qui vient compléter l'attirail en proposant l'abstraction des requêtes dynamiques.

En somme, depuis Drupal 7, nous pouvons donc, avec un peu de soin et de méthode, commencer à écrire des modules qui sont ainsi quasiment agnostiques concernant la couche de stockage.

Schema API et conversion de base

Pour revenir à notre question d'origine, dans la mesure où Drupal peut facilement basculer d'une base à l'autre (abstraction de la connexion), convertir une base de donnée Drupal de MySQL à PostgreSQL se simplifie donc à la résolution de deux problématiques :

  • Créer une base PostgreSQL content l'ensemble des tables de notre installation MySQL.
  • Injecter dans PostgreSQL un dump provenant de MySQL.

Si le second point ne semble pas bien sorcier (ce ne sont au fond juste que des insert), la création du schéma de la base est autrement plus épineux car il s'agit littéralement de traduire le langage MySQL en langage PostgreSQL. Bon courage...

C'est là que Schema API vient à notre rescousse. En effet, à quoi sert de convertir le schéma de MySQL puisque drupal depuis sa version 6 dispose pour l'ensemble de ses modules activés d'une description précise des tables créées. La seule astuce qu'il nous faut trouver, c'est comment le forcer à produire du code "PostgreSQL" lorsqu'il travaille avec MySQL.

Problématique résolue par l'usage de l'indispensable module Schema. Ce module a été écrit pour lister les schémas définis par chaque module installés. Il est surtout utilisé pour vérifier que la base de donnée physique est bien en accord avec la description que drupal en a gardé et permet notamment de faire du ménage sur des tables laissées sans objets par exemple lors de mises à jour.

Mais schema permet aussi de générer un script SQL de création de l'ensemble des tables répertoriées. Premier pas vers notre conversion qui s'arrête rapidement car il se limite à utiliser la syntaxe du moteur en cours d'usage.

Pour contourner cela, nous allons utiliser les fonctionnalités du module Schema dans un petit script très simple :

#! /usr/bin/php
<?php
define('DRUPAL_ROOT', getcwd());

require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

$schema = drupal_get_schema(NULL, TRUE);
$sql = '';
$class_name = 'SchemaDatabaseSchema_pgsql';
$engine = new $class_name(Database::getConnection());
foreach ($schema as $name => $table) {
  if (substr($name, 0, 1) == '#') {
    continue;
  }
  $stmts = $engine->getCreateTableSql($name, $table);
  $sql .= implode(";\n", $stmts) . ";\n\n";
}
echo str_replace(array('{', '}'), '', $sql);
dump_schema.php

Tout le début n'est qu'une recopie du script PHP de démarrage de Drupal (index.php). Ce qui suit le boostrap est la fonction du module Schema modifiée de sorte à forcer l'usage du traducteur PostgreSQL et ainsi produire un script SQL pour ce moteur.

Il suffit ensuite de lancer ce script pour avoir régler 80% de notre problématique avec code SQL généré en sortie standard qu'il suffira d'injecter dans PostgreSQL. Ensuite il ne restera plus qu'à injecter les données elles-mêmes. La procédure complète donne dés lors quelque chose comme ceci :

Création de la base de donnée sur postgres
gastonecho "create database mon_site" | psql -Upostgres

Injection du schema
gastonphp ./dump_schema.php | psql -Upostgres mon_site

Injection des données
gastonmysqldump -uroot -pmot_de_passe_root --no-create-info --compatible=postgresql  mon_site | sed "s/\\\'/\'\'/g" | psql -Upostgres mon_site

Avouez que ce n'est pas sorcier, et le pire c'est que cela fonctionne. La dernière ligne de la procédure, un peu longuette, permet de générer un dump MySQL contenant seulement les inserts (--no-create-info) et formalisé pour PostgreSQL (--compatible=postgresql). La commande SED est juste là pour convertir les \' en ''. L'exécution de cet import va générer un paquet de warning car PostgreSQL aimerait bien que l'on utilise un approche standard pour les autres échappements (\r, \n, etc.) mais au final, il ne faut que crier et les données sont bien insérées.

Conclusion

Cette méthode est grandement améliorable, automatisable et généralisable à d'autres échanges. Je verrais si j'ai le temps d'écrire une procédure plus générique. Mais déjà en l'état j'ai pu la tester sur un gros site en Drupal 7/MySQL qui s'est mis à tourner sous PostgreSQL sans autre tracas. Magique.

J'ai à cette occasion pu constater que les performances de MySQL teeeeellllement meilleurs que PostgreSQL était pipo/clarinette mais ça, c'est une autre histoire :-)