Tutoriel CouchDB #2 publié le mercredi 16 décembre 2015 16:59

(brouillon)

CouchDB est un serveur de base de données (documents JSON) avec une interface HTTP.

À la fin de ce tutoriel, vous afficherez de l'information sur l'ensemble des documents dans une page HTML complète sur votre serveur CouchDB en mode local. Seul la commande curl et un fureteur sont requis, au delà de l'installation.

Installer CouchDB

Consultez le premier tutoriel sur CouchDB pour l'installation et la création de votre première base de données.

L'index principal

On poursuit dans les traces du premier tutoriel. Vous connaissez déjà la clé _id, c'est aussi l'index principal de la base de données. On verra dans le prochain tutoriel comment faire des index secondaires, des views dans l'environnement CouchDB, mais pour le moment, on va s'attarder au view de l'index par défaut, soit _all_docs:

$ curl  http://localhost:5984/madb/_all_docs
{"total_rows":2,"offset":0,"rows":[
{"id":"_design/app","key":"_design/app","value":{"rev":"1-f2887f3d4cc17f945b7d1d5b5d9d7691"}},
{"id":"premier-doc","key":"premier-doc","value":{"rev":"2-2b5984495aa9086f131c8102b3cae868"}}
]}

Vous devriez voir la même chose, ou plus de documents si vous êtes allé plus loin avec le tutoriel #1. Les clé rev seront probablement aussi différents.

C'est la liste de tous les documents. S'il y en avait 1000 dans la base de données, on aurait ici 1000 rangées (rows). Les documents sont énumérés dans l'ordre des _id, le caractère "_" venant avant "p" alphabétiquement.

Les views (comme _all_docs) prennent des arguments, comme "reverse" pour inverser l'ordre:

$ curl  http://localhost:5984/madb/_all_docs?reverse=true
{"total_rows":2,"offset":0,"rows":[
{"id":"premier-doc","key":"premier-doc","value":{"rev":"2-2b5984495aa9086f131c8102b3cae868"}},
{"id":"_design/app","key":"_design/app","value":{"rev":"1-f2887f3d4cc17f945b7d1d5b5d9d7691"}}
]}

On peut aussi donner une limite:

$ curl  http://localhost:5984/madb/_all_docs?limit=1
{"total_rows":2,"offset":0,"rows":[
{"id":"_design/app","key":"_design/app","value":{"rev":"1-f2887f3d4cc17f945b7d1d5b5d9d7691"}}
]}

Vous noterez que la valeur de la clé total_rows est toujours 2 (ou plus si vous avez plus de 2 documents), même si on met une limite. Ça représente bien le totale selon ce view.

Les views supportent plusieurs autres arguments au delà des trois derniers qu'on va démontrer.

En plus de l'argument "limit", on peut aussi donner des clés de début et/ou de fin, "startkey" et "endkey" respectivement:

$ curl 'http://localhost:5984/madb/_all_docs?startkey="pre"'
{"total_rows":2,"offset":1,"rows":[
{"id":"premier-doc","key":"premier-doc","value":{"rev":"2-2b5984495aa9086f131c8102b3cae868"}}
]}

Dans cet exemple, il est important d'utiliser les guillemets doubles pour la chaine "pre" comme argument. Sans les guillemets doubles, vous obtiendrez une erreur de JSON invalide. Selon le shell (souvent bash sur GNU/Linux), les guillemets doubles risquent d'être avalés avant d'être passés à curl et puis CouchDB. C'est pourquoi les guillemets simples sous aussi utilisés pour conserver les guillemets doubles.

Dans tous les exemples, on reçoit un tableau d'objets et ces objets ont tous trois clés: id, key et value. Pour l'index principal, id etkey aurons les même valeurs, tandis que value est un objet avec une seule clé rev (les index secondaires auront la forme que vous voudrez), le champ _rev du document représenté.

Le dernier argument qu'on va montrer ici sert donc à obtenir les documents entiers:

$ curl http://localhost:5984/madb/_all_docs?include_docs=true
{"total_rows":2,"offset":0,"rows":[
{"id":"_design/app","key":"_design/app","value":{"rev":"1-f2887f3d4cc17f945b7d1d5b5d9d7691"},"doc":{"_id":"_design/app","_rev":"1-f2887f3d4cc17f945b7d1d5b5d9d7691","language":"javascript","shows":{"page":"function (doc) {return '<html><head><title>' + doc.titre + '</title></head><body><h1>' + doc.titre + '</h1><ol>' + doc.tags.map(function(tag){return '<li>' + tag + '</li>';}).join() + '</ol></body></html>';}"}}},
{"id":"premier-doc","key":"premier-doc","value":{"rev":"2-2b5984495aa9086f131c8102b3cae868"},"doc":{"_id":"premier-doc","_rev":"2-2b5984495aa9086f131c8102b3cae868","titre":"Mon premier document","dateheure":"2015-12-13T04:19:43.347Z","tags":["accueil","personnel"]}}
]}

Voici une version indentée du même résultat:

{
    "total_rows": 2,
    "offset": 0,
    "rows": [{
        "id": "_design/app",
        "key": "_design/app",
        "value": {
            "rev": "1-f2887f3d4cc17f945b7d1d5b5d9d7691"
        },
        "doc": {
            "_id": "_design/app",
            "_rev": "1-f2887f3d4cc17f945b7d1d5b5d9d7691",
            "language": "javascript",
            "shows": {
                "page": "function (doc) {return '<html><head><title>' + doc.titre + '</title></head><body><h1>' + doc.titre + '</h1><ol>' + doc.tags.map(function(tag){return '<li>' + tag + '</li>';}).join() + '</ol></body></html>';}"
            }
        }
    }, {
        "id": "premier-doc",
        "key": "premier-doc",
        "value": {
            "rev": "2-2b5984495aa9086f131c8102b3cae868"
        },
        "doc": {
            "_id": "premier-doc",
            "_rev": "2-2b5984495aa9086f131c8102b3cae868",
            "titre": "Mon premier document",
            "dateheure": "2015-12-13T04:19:43.347Z",
            "tags": ["accueil", "personnel"]
        }
    }]
}

Avec "include_docs" on obtient une 4e clé par rangée, doc qui contient le document complet.

Vous pouvez bien entendu combiner les arguments, utilisez le caractère "&" (sans guillemets) pour joindre les arguments. Par exemple: http://.../madb/_all_docs?limit=2&reverse=true

Une dernière chose à savoir sur l'index principal (_all_docs) c'est qu'il n'est pas accessible à tous les utilisateurs (ou visiteurs) de la base de données. Pour le moment, tout le monde est admin et c'est bien ainsi, tant que c'est pour apprendre. Les index secondaires (views) qu'on verra dans le prochain tutoriel n'ont pas cette limitation.

Plus de documents

Pour rendre l'expérience plus intéressante, on crée le 2e document deuxieme-doc.json:

{
  "titre": "Mon deuxième document",
  "dateheure": "2015-12-16T03:28:34Z",
  "tags": ["accueil", "travail"]
}

Qu'on insère dans la base de données:

$ curl -X PUT -H "content-type: application/json" -d@deuxieme-doc.json http://localhost:5984/madb/deuxieme-doc
{"ok":true,"id":"deuxieme-doc","rev":"1-1882a47f0eb95c6bfcd0427b178f8748"}

Et le troisième document, troisieme-doc.json:

{
  "titre": "Un autre doc",
  "dateheure": "2015-12-16T03:48:19Z",
  "tags": ["travail"]
}

Qu'on insère aussi dans la base de données:

$ curl -X PUT -H "content-type: application/json" -d@troisieme-doc.json http://localhost:5984/madb/troisieme-doc
{"ok":true,"id":"troisieme-doc","rev":"1-27b1e38e01ab72b7145a0f9925bdd203"}

Les fonctions list

Les fonctions show transforment un document tandis que les fonctions list transforment un ensemble de documents.

Fonctions CouchDB start(), send() et getRow()

Puisqu'on doit introduire trois nouvelles fonctions spécifiques à CouchDB, on va commencer avec un exemple simple:

function() {
    var n = 0;
    start({
        'headers': {
            'Content-Type': 'text/html'
        }
    });
    send('<html><head><title>Tous les docs</title></head><body><h1>Tous les docs</h1><p>à suivre... ');
    while (getRow()) {
        send(++n);
        send(' ');
    }
    send('</p></body></html>');
}

Première chose qu'on notera c'est que contrairement à la fonction show du tutoriel précédent, on ne passe pas d'argument doc à la fonction list. On utilise plutôt la fonction getRow() dans une boucle pour énumérer les documents émis par le view.

Deuxièmement, la fonction start() est utilisée pour retourner le bon content-type. On ne devrait appeler cette fonction qu'une seule fois, avant tout appel à getRow() ou send().

La troisième fonction introduite est send() qui envoie un bout de chaine au client. On peut appeler cette fonction à plusieurs reprises. On l'utilise entre autre dans la boucle while pour envoyer le numéro de chaque document. getRow() retourne les rangées une par une et false (ou null?) quand il n'y a plus de rangées. Dans cet exemple, on ne fait rien avec les rangées (la valeur retournée par getRow()) à part les compter. On verra dans un prochain tutoriel la vraie manière de compter les documents avec les fonction reduce mais ici c'est suffisant pour un exemple.

Normalement, on utiliserait getRow() ainsi:

function() {
    var row;
    // ...
    while (row = getRow()) {
      // faire quelque chose d'extraordinaire
      // avec row.key, row.id, row.value et/ou row.doc
    }

Mise à jour du Design document

Le Design document ressemblait à ceci à la fin du tutoriel précédent:

{
  "language": "javascript",
  "shows": {
    "page": "function (doc) {...}"
  }
}

On va ajouter la clé lists et notre fonction sous la clé num. Rappelez-vous que les fonctions list comme les fonctions show doivent être présentées sous forme de chaines sur une seule ligne. Soyez assuré qu'il existe toute une gamme d'outil pour faciliter ces tâches, mais avant de se perdre dans les dédales des outils, il est important de bien comprendre comment ça fonctionne.

{
  "_rev": "6-6b72ce68884076712dc49273c3e6f69c",
  "language": "javascript",
  "lists": {
    "num": "function () {var n = 0;start({'headers':{'Content-Type':'text/html;charset=utf-8'}});send('<html><head><title>Tous les docs</title></head><body><h1>Tous les docs</h1><p>à suivre... ');while (getRow()) {send(++n);send(' ');};send('</p></body></html>');}"
  },
  "shows": {
    "page": "function (doc) {return '<html><head><title>' + doc.titre + '</title></head><body><h1>' + doc.titre + '</h1><ol>' + doc.tags.map(function(tag){return '<li>' + tag + '</li>';}).join('') + '</ol></body></html>';}"
  }
}

Pour modifier un document dans la base de donnéee, il faut passer la dernière valeur de _rev avec le document. Pour obtenir la version et tous les autres champs par le fait même:

$ curl http://localhost:5984/madb/_design/app

Un autre truc pour obtenir la version c'est de regarder le header ETag:

$ curl -I  http://localhost:5984/madb/_design/app

On va mettre le contenu du nouveau Design document dans le fichier ddoc-2.json (avec la bonne valeur pour le champs _rev) et ensuite on pourra le mettre à jour dans la base de données:

$ curl -X PUT -H "content-type: application/json" -d@ddoc-2.json http://localhost:5984/madb/_design/app
{"ok":true,"id":"_design/app","rev":"7-ae48b4966ece6c912697acc2ea67498b"}

On peut maintenant obtenir la pseudo-liste:

$ curl http://localhost:5984/madb/_design/app/_list/num/_all_docs
<html><head><title>Tous les docs</title></head><body><h1>Tous les docs</h1><p>à suivre... 1 2 3 4 </p></body></html>

Prochaines étapes

(copié du tuto #1)

La première chose à faire serait de créer un autre document avec les champs titres et tags et le ID de votre choix pour le convertir en HTML à son tour et voir que tout fonctionne comme promis. Appelons ça un exercise du lecteur. Sentez-vous bien à l'aise d'adapter la fonction page, peut-être ajouter un champ contenu aux documents et l'afficher en plus du titre?

Vous avez maintenant un serveur web (HTTP/HTML) et une méthode de base mais en même temps universelle pour créer, mettre à jour et effacer des documents. Vous savez aussi comment faire une fonction pour convertir un document JSON en HTML.

Dans le prochain tutoriel, vous apprendrez comment faire une liste de documents dans l'ordre de votre choix et aussi comment l'afficher sous forme de HTML. Les autres tutoriels couvriront les utilisateurs (et les rôles), la sécurité (les accès), des URL plus jolis (rewrites), les fichiers attachés aux documents, comment faire un serveur web public, la réplication, les fils de changements, les outils de développement et bien plus.