URL: https://linuxfr.org/news/retour-d-experience-sur-sql-js Title: Retour d'expérience sur sql.js Authors: lovasoa Nÿco, ZeroHeure, palm123, Benoît Sibaud et Nils Ratusznik Date: 2014年06月11日T11:04:24+02:00 License: CC By-SA Tags: sqljs, sqlite, javascript et firefox Score: 33 J'aimerais parler ici de mon expérience lors du développement de [sql.js](https://github.com/lovasoa/sql.js), un port de [SQLite](http://sqlite.org) en JavaScript. Pour ceux qui ne s’intéressent pas aux technologies du web, la deuxième partie de cette dépêche pourrait quand même vous intéresser, on va parler de SQLite. Note : cette dépêche a initialement été postée [en tant que journal](http://linuxfr.org/users/lovasoa/journaux/web-moderne-bases-de-donnees-et-beaute-logiciel-libre). ---- [Page GitHub du Projet](https://github.com/lovasoa/sql.js) [Démonstation, qui permet d’exécuter du SQL, mais aussi de lire, modifier, puis sauvegarder des bases](http://lovasoa.github.io/sql.js/GUI) [Page officielle de emscripten](http://emscripten.org) [SQLite, le SGBD le plus cool qui soit](http://sqlite.org) ---- # Web moderne Ceux d'entre vous qui s'intéressent aux technologies modernes du web ont certainement entendu parler d’[emscripten](http://emscripten.org) et d’[asm.js](http://asmjs.org). Emscripten est un compilateur de bytecode LLVM en JavaScript. Il permet de compiler du code C ou C++ en JavaScript, très simplement. Le code JavaScript généré n’est bien sûr pas aussi rapide que le code natif équivalent, mais les performances sont assez bonnes. Sur Firefox, avec asm.js, le code tourne à peu près deux fois plus lentement que la version native. Le principal inconvénient que je trouve à emscripten est qu'il faut, pour l'utiliser sous Linux, télécharger et compiler [sa propre version de LLVM et de clang](https://github.com/kripken/emscripten-fastcomp), ce qui est long et pas pratique à mettre à jour. [asm.js](http://asmjs.org), quant à lui, est un sous-ensemble de JavaScript conçu pour être facile à optimiser, et ainsi pouvoir s'exécuter rapidement. La syntaxe est [dégueulasse](http://asmjs.org/spec/latest/#putting-it-all-together), mais c'est pas grave, emscripten génère du code asm.js pour nous. # SQLite Vous connaissez certainement déjà [SQLite3](https://fr.wikipedia.org/wiki/Sqlite), le moteur de bases de données le plus utilisé au monde. Si vous vous y êtes déjà un peu intéressé, vous connaissez sûrement ses caractéristiques, qui le rendent unique : * Une base de données est stockée dans un seul fichier. * Le binaire SQLite est minuscule (moins d'un Mo), et la bibliothèque SQLite peut être liée statiquement dans votre programme. Tout le code source tient dans un fichier `sqlite3.c` de 5 Mo. * Le code a été placé dans le domaine public. * Les données sont typées dynamiquement. Une même colonne peut contenir un entier, un nombre à virgule flottante, et une chaîne de caractères, par exemple. Par contre, vous ne connaissez peut-être pas [SQLite4](http://sqlite.org/src4/doc/trunk/www/design.wiki), une évolution de SQLite3 (par les mêmes développeurs), qui n’a pas encore de version stable (et que j’ai aussi [porté en JavaScript](https://github.com/lovasoa/sql.js/tree/sqlite4)). Cette version apporte de bien meilleures performances, et surtout, elle utilise un système de base de données de type clef-valeur, que l'on peut changer à la volée. Et ça, c’est génial ! Cela signifie que l'on pourra bientôt profiter de tous les avantages de SQLite même pour de grosses bases de données. Il suffira d’utiliser un système de base de données clef-valeur qui supporte les grands ensembles de données, comme [LevelDB](http://leveldb.googlecode.com/svn/trunk/doc/index.html). # Quand on mélange les deux... ## sql.js, avant `sql.js`, un port de SQLite en JavaScript, était au départ un projet de [kripken](https://github.com/kripken), le créateur et principal mainteneur d’emscripten, qui date de début 2012. Le port fonctionnait, mais question fonctionnalités, on restait un peu sur sa faim : une seule méthode `db.exec`, qui exécutait du SQL et retournait les résultats dans un format pas pratique. Les données étaient toujours converties en chaînes de caractères avant d’être retournées. Le projet n’avait aucun test unitaire, le dernier commit date d’il y a plus d’un an, et l’auteur ne répond plus sur le bugtracker... (NdM.: voir le commentaire indiquant que l'auteur [a depuis incorporé la contribution](//linuxfr.org/news/retour-d-experience-sur-sql-js#comment-1543841)) Pourtant, le projet semble avoir des utilisateurs. 104 forks et 883 *stars* sur github, et plusieurs téléchargements par jour sur [npm](https://www.npmjs.org/package/node-sqlite-purejs) à l’heure où j’écris ces lignes. ## sql.js, maintenant Je suis étudiant, et lors d’un TD, j’ai eu besoin de pouvoir tester des commandes en SQL, sur un ordi avec rien du tout d’installé. Je ne connaissais pas encore [SQLfiddle](http://sqlfiddle.com/), mais j’avais déjà entendu parler de sql.js, donc j’ai utilisé sa [démonstration en ligne](http://kripken.github.io/sql.js/test/demo.html). Le soir, en rentrant chez moi, très agaçé des petits défauts de la démonstration que j'avais utilisée, j'ai *forké* le projet, et commencé à travailler sur une meilleure interface graphique. Quand j'ai été content de moi, j’ai fait une *pull request*. Comme l’auteur tardait à répondre, j’ai commencé à bidouiller le reste du code. Et de fil en aiguille, j'ai fini par réécrire tout le code, à changer l’API pour avoir quelque chose de facile à utiliser, à ajouter des tests, de la documentation... Et je suis assez fier de l’état du projet aujourd’hui. Il est utilisable sans modification à la fois depuis node.js, dans le navigateur, et en tant que web worker. Il est [disponible sur npm](https://npmjs.org/package/sql.js), et s’utilise avec un simple : ```javascript var sql = require('sql.js'); var db = new sql.Database(); ``` Il retourne les données dans leur format original, y-compris les BLOBs, retournés sous forme de tableau d’octets : ```javascript var res = db.exec("SELECT * FROM table1; SELECT * FROM table2"); // Ce qui peut retourner: [ {columns:['a','b'], values:[[0,'hello'],[1,'world']]}, //Le contenu de table1 {columns:['c','d'], values:[[null,[1,2,3]],[42,'blabla'],[666,666]]} //Celui de table2 // Et oui, la colonne d contient des données de types différents. C’est possible grâce à SQLite. C’est impossible avec presque tous les autres SGBD ] ``` Et il permet d'utiliser des requêtes préparées (*prepared statements*), auxquelles on associe les paramètres que l'on veut, sans risquer de vilaines injections SQL : ```javascript db.run("INSERT INTO table1 VALUES (?,?)", [3,[121,111,117,99,114,97,99,107,101,100,105,116]]); ``` Mais aussi : ```javascript var stmt = db.prepare("SELECT * FROM table1 WHERE a=$aval AND b=$bval"); var result = stmt.getAsObject({$aval : 1, $bval : 'world'}); // result contient maintenant: {a:1, b:'world'} ``` ## Comment ça marche SQLite est distribué sous différentes formes, dont une qui est particulièrement pratique : l’*amalgamation*. C’est un unique fichier `.c` de 5Mo qui contient tout le code, et que l’on peut compiler sans aucune bibliothèque externe: `gcc -DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_THREADSAFE=0 -c sqlite3.c` Et on peut aussi le compiler, sans aucune modification, avec emscripten. Il suffit de remplacer `gcc` par `emcc` dans la commande précédente, et le tour est joué. Vous allez peut-être trouver bizarre que ça *juste marche*. En effet, le code de SQLite fait plein de trucs que l’on ne peut pas faire de base en JavaScript dans un navigateur, comme par exemple ouvrir des fichiers, ou simplement accéder à la mémoire à partir de pointeurs. Heureusement, emscripten s’occupe de tout ça pour nous. Il fabrique un grand tableau d’entiers en JavaScript qui contiendra toute la mémoire de notre programme. Les performances ne sont pas trop mauvaises grâce aux *[typed arrays (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays)*. Les pointeurs ne sont dès lors plus que des index dans ce tableau. Il implémente également les fonctions `fopen`, `fwrite`, etc. en émulant un système de fichiers entièrement en mémoire. L’inconvénient étant bien sûr que l’on a beaucoup moins de place pour stocker ses données. Mon travail consiste alors uniquement à définir les bonnes options de compilation, et à écrire le code qui va faire l’interface entre les programmes en JavaScript et le code compilé, en JavaScript lui aussi. En effet, appeler directement le code résultant de la compilation par emscripten serait terriblement rebutant : ça parle de pointeurs partout, il faut allouer et désallouer de la mémoire dès que l’on veut faire des trucs compliqués, on n’a accès qu’à une série de fonctions, et pas à des objets qui contiennent des méthodes... Pour avoir un code un peu plus léger, j’ai choisi le [CoffeeScript](http://coffeescript.org), langage par excellence du web moderne. Pour vous donner un exemple du genre de code qui fait l’interface, voilà la fonction `prepare`, qui permet de créer une requête préparée : ```coffeescript 'prepare': (sql, params) -> setValue apiTemp, 0, 'i32' @handleError sqlite3_prepare_v2 @db, sql, -1, apiTemp, NULL pStmt = getValue apiTemp, 'i32' # pointer to a statement, or null if pStmt is NULL then throw 'Nothing to prepare' stmt = new Statement pStmt, this if params? then stmt.bind params @statements.push stmt return stmt ``` On voit bien que tout le vrai travail est fait par les fonctions compilées de SQLite, mon code se contentant de la gestion des erreurs, de la mémoire, et de la conversion des données dans un format utile au programmeur JavaScript. # Conclusion Aujourd’hui, on fait tout ce qu’on veut sur le web, et bientôt, ce sera encore mieux. Les trucs funs qui sont pour bientôt : [ECMASCRIPT 6, aka Harmony](http://tc39wiki.calculist.org/es6/), [emscripten SDK](https://github.com/kripken/emscripten/wiki/Emscripten-SDK) pour Linux, [sqlite4.js](https://github.com/lovasoa/sql.js/tree/sqlite4)...