Migration de bases de données, deuxième partie

· 7 min de lecture

Publication originale

Cet article a été publié à l'origine le 19 juin 2008 sur The Standard, le blogue de Raincity Studios, sous la signature « Audrey Foo ». Cet article reflète le contexte et les outils de l'époque; certains liens et services mentionnés n'existent plus. Article traduit de l'anglais. La version anglaise est le texte original.

Voir l'original dans l'Internet Archive

Dans le premier billet sur la migration de bases de données, j'ai survolé quelques informations pour amorcer le périple. Ce billet-ci est un peu plus aride et plus détaillé : il énumère certaines grandes étapes de l'importation d'une base de données et présente quelques exemples de code. Ci-dessous, mon complice pour le deuxième projet, Harry. Les méthodes actuelles restent toujours ouvertes à de nouvelles améliorations.

[Photo intégrée à l'origine ici : Harry, image gracieuseté de Steve Krueger.]

Tables de correspondance

Pour tout contenu importé, il existe une rangée de correspondance qui associe l'identifiant unique de Drupal à l'identifiant unique d'origine de ce contenu. Ces tables sont très utiles pour remonter à la source d'un problème. Elles le sont aussi lors de l'importation de nouvelles données, pour déterminer quels nœuds, termes ou utilisateurs ne pas réimporter.

Selon les préférences, chaque vocabulaire ou type de contenu peut avoir sa propre table de correspondance; si l'on combine vocabulaires et types de contenu dans une même table, on y stocke aussi le nom de la table d'origine.

Les colonnes minimales requises dans une table de correspondance sont : une clé primaire à incrémentation automatique, l'identifiant Drupal (nid, uid ou tid) et l'ancien identifiant unique. D'autres colonnes peuvent être créées au besoin pour faciliter la consultation et les vérifications.

Connexion à l'ancienne source de données

Les scripts d'importation utilisent des fonctions Drupal pour manipuler les objets nœud, les objets utilisateur, etc. Au début du script PHP, incluez ce qui suit :

include_once("includes/bootstrap.inc");
include_once("includes/common.inc");
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);

Si l'ancienne source de données utilise un type de base de données différent de celui de l'installation Drupal, par exemple PostgreSQL, les scripts d'importation peuvent se connecter directement à l'installation PostgreSQL au moyen de pg_connect, pg_query, pg_fetch_object, etc.

Sinon, le vidage PostgreSQL (généré avec les bons paramètres) peut parfois être converti en vidage MySQL en modifiant manuellement la définition des tables, puis exécuté tel quel, puisqu'aucune fonction particulière n'est nécessaire pour importer des données dans la base de données Drupal.

Propriété du contenu

Si les nœuds à insérer n'ont pas de propriétaire, on crée habituellement un utilisateur Drupal « archive », et les nœuds sont créés au nom de cet utilisateur.

Sinon, une fois les utilisateurs importés et la table de correspondance des utilisateurs remplie, le contenu est importé en se référant à cette table pour enregistrer chaque nœud avec l'uid approprié.

Réutiliser les scripts d'importation

Remplissez un tableau avec les anciens identifiants uniques des termes, nœuds ou utilisateurs qui existent déjà dans le système Drupal.

$sql = "SELECT class_id FROM map_terms WHERE tid != 0 AND vid = %d";
$result = db_query($sql, $vid);
while ($obj = db_fetch_object($result)) {
  $current[$obj->class_id] = $obj->class_id;
}

Sélectionnez toutes les rangées de données de la table source d'origine, mais sautez celles qui ont déjà un élément de données correspondant dans Drupal.

while ($obj = pg_fetch_object($result)) {
  if (! array_key_exists($obj->class_id, $current)) {
    // Save taxonomy term with taxonomy_save_term and insert tid in term mapping table
    // Alternatively save node using node_save() and insert nid in node mapping table
  }
}

Importer des champs de contenu personnalisés

Consultez l'article de Lullabot pour un exemple d'utilisation de drupal_execute; la vue de CCK côté base de données est aussi une excellente ressource.

Code pour importer un nœud simple ne contenant qu'un titre.

$n = new stdClass();
$n->uid = 36;
$n->name = 'archive';
$n->type = 'ppl_label';
if ($obj->active == 'Y') {
  $n->status = 1;
}
$n->validated = 1;
$n->title = trim($obj->company_name);
node_save($n);

Installer le module Devel et consulter l'onglet « devel load » d'un nœud est excellent pour comprendre comment structurer l'insertion de données dans les champs CCK.

Harry a découvert que, pour certains champs, $node->field_abc[0] = array('nid' => 1); fonctionne, mais pas $node->field_abc[0]['nid'] = 1.

La mise à jour des champs imagefield exige d'ajouter une rangée à la table files, à l'aide des fonctions basename() et filesize(), et de déterminer le type MIME de l'image. On met ensuite le nœud à jour avec $node->field_image[0] = array('fid' => 552); où 552 est l'identifiant du fichier inséré.

Faire confirmer la configuration Drupal

J'aimerais bien imaginer un monde agile, mais je ne sais pas encore comment l'importation de bases de données peut s'intégrer au paradigme des méthodes agiles. La migration de bases de données a tendance à exiger beaucoup de temps : manipuler des données en masse, télécharger la dernière base de données Drupal, exécuter les scripts en local, vérifier les tests, apporter des modifications, réimporter la dernière base de données Drupal, réexécuter les scripts en local, les exécuter sur le site en production et vérifier.

En comparaison, lorsqu'un client décide de modifier un type de contenu, le temps de développement sera probablement bien moindre : modifier le code en local, rafraîchir le navigateur, tester, soumettre au contrôle de versions. Les étapes pour tester des modifications de données peuvent être beaucoup plus longues. Même si la modification des données est appliquée comme correctif urgent, il peut encore falloir : télécharger la dernière base de données Drupal, exécuter les scripts en local, tester, corriger, réimporter la base de données Drupal, réexécuter les scripts en local, soumettre le script au contrôle de versions, l'exécuter en production et vérifier.

À mon avis, il est beaucoup plus simple d'avoir une compréhension plus complète et confirmée avec le client, afin de réduire au minimum les correctifs urgents et de pouvoir exécuter toutes les étapes énumérées ci-dessous le moins de fois possible, avec une pleine compréhension du contenu à importer ou à modifier dans l'installation Drupal. Je suggère une rencontre en personne, avec possiblement une confirmation signée que la configuration des types de contenu, les vocabulaires et les profils d'utilisateurs fonctionnent comme prévu et contiennent les données que le client s'attend à voir.

Liste des étapes pour importer le contenu

  • Dernier vidage de données : il faut avoir accès à un vidage de la base de données mis à jour régulièrement
  • Images : le plus simple est d'utiliser rsync ou une archive tar des dernières images. Il faut avoir accès au serveur du client, ou passer par HTTP
  • Nœuds simples non dépendants : nœuds relativement simples, qui ne dépendent pas (par des champs de référence de nœud) d'autres types de contenu
  • Termes de vocabulaire : insérer les termes dans les vocabulaires créés au préalable
  • Utilisateurs : champs de profil ou nœuds créés, selon les modules utilisés
  • Rôles d'utilisateurs : fournir au client une liste des candidats probables et faire confirmer les rôles Drupal
  • Autres utilisateurs : par exemple les abonnés à l'infolettre, ou d'autres modules qui stockent des renseignements sur les utilisateurs dans leurs propres tables
  • Le contenu principal qui ne fait pas référence à d'autres nœuds : peut contenir de nombreux champs CCK, dont des champs personnalisés, des imagefields, etc.
  • Les autres contenus qui font référence à des nœuds déjà créés
  • Importer le contenu de sources tierces : par exemple, les métadonnées vidéo au besoin.
  • Importer le contenu dans d'autres modules qui ont besoin de ces données : modules particuliers qui pourraient stocker une partie des données.
  • Redirection de chemins : le module Path redirect sert à rediriger les anciennes URL vers les nouvelles URL Drupal. Pour la nouvelle URL, utilisez le chemin Drupal sans alias, p. ex. user/123 ou node/234, plutôt que l'URL générée par Pathauto. Ainsi, si le client change d'idée au sujet du modèle Pathauto, seule la table url_alias devra être modifiée, et non la table path_redirect.

$rid = db_next_id('{path_redirect}_rid');
$old_url = 'old-path';
$new_url = 'new-path';
db_query("INSERT INTO {path_redirect} (rid, path, redirect, type) VALUES (%d, '%s', '%s', '%s')", $rid, trim($old_url, '/'), trim($new_url, '/'), '301');

  • Pathauto : node_save() et user_save() devraient générer un alias d'URL. Il faut toutefois parfois procéder à une régénération en lot.
  • Recherche : n'oubliez pas de configurer cron pour indexer le contenu afin de pouvoir commencer à tester la recherche; l'inconvénient, c'est que la base de données devient alors très volumineuse, ce qui ralentit toutes les étapes d'importation suivantes.

En prime! Convertir des termes de vocabulaire en nœuds d'un type de contenu

Il peut arriver qu'après avoir configuré Drupal et importé le contenu, on décide d'étendre les fonctionnalités d'un vocabulaire; il faut alors convertir les termes en nœuds. Voici le code pour le faire.

$vid = 3;
$sql = "SELECT * FROM term_data WHERE vid = %d";
$result = db_query($sql, $vid);
while ($obj = db_fetch_object($result)) {
  // Create the award node
  $n = new stdClass();
  $n->uid = 12; // Where 12 is the uid of a valid user
  $n->name = 'archive'; // archive is the login of user with uid 12
  $n->title = $obj->name;
  $n->type = 'award';
  $n->status = 1;
  $n->validated = 1;
  node_save($n);

  // Reference all nodes that have the term to the new node
  $sql2 = "SELECT nid FROM term_node WHERE tid = %d";
  $result2 = db_query($sql2, $obj->tid);
  while ($obj2 = db_fetch_object($result2)) {
    $n2 = node_load($obj2->nid);
    $n2->field_award[0]['nid'] = $n->nid;
    node_save($n2);
  }

  //taxonomy_del_term($obj->tid);
}

Après avoir exécuté le script, vérifiez...

Comptez tous les nœuds de type « award » avant d'exécuter le script, et affichez aussi le nid maximal. Par exemple, le nombre est 5 et le nid maximal est 123.

SELECT COUNT(*) FROM node WHERE type LIKE 'award';
SELECT MAX(nid) FROM node WHERE type LIKE 'award';

Comptez les termes qui appartiennent au vocabulaire $vid. Par exemple, le vid est 3 et le nombre est 100.

SELECT COUNT(*) FROM term_data WHERE vid = 3;

Comptez tous les nœuds de type « award » après l'exécution du script et, à l'aide du nid maximal obtenu avant l'importation, affichez les prix importés. Par exemple, le nombre devrait être 105.

SELECT COUNT(*) FROM node WHERE type LIKE 'award';
SELECT * FROM node WHERE type LIKE 'award' AND nid > 123;

Si tout semble correct, supprimez le vocabulaire et les termes associés.

Votre entreprise mérite d'être trouvée.

Parlez-nous de votre projet. Nous vous répondons en français ou en anglais, et nous nous déplaçons partout dans la MRC de Papineau.

Affichage

Vos choix restent dans ce navigateur. Rien n'est envoyé.

Taille du texte
100 %
Couleurs
Lecture
Autres aides
Accessibilité du site