{"id":44,"date":"2014-12-26T15:26:38","date_gmt":"2014-12-26T15:26:38","guid":{"rendered":"http:\/\/www.fluxnumerique.fr\/?p=44"},"modified":"2020-12-13T10:12:43","modified_gmt":"2020-12-13T09:12:43","slug":"utilisez-basex-base-native-xml","status":"publish","type":"post","link":"https:\/\/www.fluxnumerique.fr\/?p=44","title":{"rendered":"BaseX, ou le FLWOR power en open-source"},"content":{"rendered":"<p>BaseX (basex.org) est un SGBD natif XML open-source, un outil particuli\u00e8rement adapt\u00e9 \u00e0 la manipulation et \u00e0 la transformation de donn\u00e9es complexes. Il me sert actuellement en particulier \u00e0 migrer les donn\u00e9es d\u2019un ensemble de services VOD OTT (Universcine.fr, Universcine.be, Volta.ie, LeKino.ch, Cinemasalademande.com) et ses fonctionnalit\u00e9s m\u2019\u00e9tonnent tous les jours. J\u2019esp\u00e8re que ce qui suit vous mettra l\u2019eau \u00e0 la bouche.<\/p>\n<h2 class=\"western\">La complexit\u00e9 d\u2019un catalogue VOD<\/h2>\n<p>Un catalogue VOD est constitu\u00e9 de films, d\u2019artistes, de soci\u00e9t\u00e9s, d\u2019\u00e9v\u00e8nements, d\u2019articles. Les utilisateurs des services VOD louent ou ach\u00e8tent les films, \u00e9mettent des avis, cr\u00e9ent des playlists, se suivent les uns les autres. Toutes ces entit\u00e9s ont de nombreuses propri\u00e9t\u00e9s et relations, ce qui aboutit \u00e0 un mod\u00e8le conceptuel complexe. UniversCin\u00e9 g\u00e8re ainsi plus de 4.000 films, 40.000 artistes, des milliers de soci\u00e9t\u00e9s, \u00e9ditions de festivals ou utilisateurs. Migrer ces donn\u00e9es d\u2019un mod\u00e8le datant de 2006 \u00e0 un mod\u00e8le plus complet n\u2019est pas \u00e9vident, d\u2019autant que certaines donn\u00e9es doivent \u00eatre nettoy\u00e9es ou enrichies au passage\u00a0: par exemple, les mots-cl\u00e9s libres sont mapp\u00e9s sur des taxonomies de genres et th\u00e8mes, des collections de films, les films acqui\u00e8rent des identifiants standardis\u00e9s (ISAN), les donn\u00e9es HTML des articles sont nettoy\u00e9es, des recommandations de films sont ajout\u00e9es dans les structures finales.<\/p>\n<h2 class=\"western\">Migrer, exporter<\/h2>\n<p>Outre une migration ais\u00e9e des donn\u00e9es de catalogues VOD, nous avons besoin d\u2019exporter ces donn\u00e9es vers Free, qui d\u00e9veloppe un moteur de recherche VOD cross-catalogues sur la Freebox, ainsi que vers le CNC (Centre National du Cin\u00e9ma), qui d\u00e9veloppe actuellement un service d\u2019aide au r\u00e9f\u00e9rencement de l\u2019offre VOD l\u00e9gale. Free demande un export XML classique, mais le CNC demande la mise au point d\u2019un export JSON par Web Service REST.<\/p>\n<p>De telles transformations de donn\u00e9es sont g\u00e9n\u00e9ralement r\u00e9alis\u00e9es avec un outil d\u2019ETL (Extract-Transform-Load) type Talend, mais la complexit\u00e9 des mod\u00e8les, leur \u00e9volutivit\u00e9, leur impl\u00e9mentation (la source \u00e9tait g\u00e9r\u00e9e par le framework Python Django, la destination par le framework PHP Symfony 2 et par une base de donn\u00e9es documentaire Jackrabbit), les \u00e9tapes de nettoyage de contenu, tous ces besoins aboutissent \u00e0 des projets demandant une tr\u00e8s grande expertise sur l\u2019outil ETL choisi, et ont un co\u00fbt final \u00e9lev\u00e9.<\/p>\n<p>C\u00f4t\u00e9 BaseX, notre effort se r\u00e9sout \u00e0 la mise en place de l\u2019applicatif BaseX sur une VM, \u00e0 une expertise du langage de requ\u00eate XQuery et \u00e0 un script Python pour animer les transformations.<\/p>\n<h2 class=\"western\">BaseX<\/h2>\n<p>Une base de donn\u00e9es XML s\u2019av\u00e8re parfaite\u00a0pour la migration ou l\u2019export de donn\u00e9es complexes. Elle permet d\u2019ing\u00e9rer des dumps XML provenant d\u2019un SGBD relationnel source, de modifier au vol la structure de ces donn\u00e9es, int\u00e9grer des donn\u00e9es externes, exporter le r\u00e9sultat sous la forme d\u2019archives XML \/ JSON ou bien d\u2019exposer le contenu via une API REST.<\/p>\n<p>La version 7.9 de BaseX s\u2019installe sous Windows en moins de 10 secondes. Sous Linux, l\u2019installation et la configuration de l\u2019applicatif est un peu plus complexe, surtout si l\u2019on d\u00e9sire utiliser les possibilit\u00e9s d\u2019acc\u00e8s \u00e0 la base via une interface REST (il faut mettre en place le WAR dans ce cas).<\/p>\n<p>BaseX repose sur une architecture client-serveur. Le produit dispose d\u2019un client en ligne de commande, d\u2019une API en diff\u00e9rents langages et d\u2019une interface homme-machine bien pratique.<\/p>\n<h2 class=\"western\">Le GUI BaseX<\/h2>\n<p>Je lance le GUI BaseX, et cr\u00e9e une base locale \u00e0 partir d\u2019un dump XML (Database -&gt; New -&gt; Browse -&gt; film-dump.xml ; name : ucfr\u00a0; format : XML). Un fichier de 17 Mo est charg\u00e9 et index\u00e9 en moins de 4 sc. Je fais de m\u00eame pour le dump des artistes, des soci\u00e9t\u00e9s \u2026 qui composent la base de donn\u00e9es source d\u2019UniversCin\u00e9.<\/p>\n<p>La base de donn\u00e9es locale est d\u00e8s \u00e0 pr\u00e9sent accessible \u00e0 des requ\u00eates XQuery ou des recherches plein-texte.<\/p>\n<p><a href=\"http:\/\/www.fluxnumerique.fr\/wp-content\/uploads\/2014\/12\/Capture-baseX.png\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-45 size-full\" src=\"http:\/\/www.fluxnumerique.fr\/wp-content\/uploads\/2014\/12\/Capture-baseX.png\" alt=\"Capture baseX\" width=\"813\" height=\"694\" srcset=\"https:\/\/www.fluxnumerique.fr\/wp-content\/uploads\/2014\/12\/Capture-baseX.png 813w, https:\/\/www.fluxnumerique.fr\/wp-content\/uploads\/2014\/12\/Capture-baseX-300x256.png 300w\" sizes=\"auto, (max-width: 813px) 100vw, 813px\" \/><\/a><\/p>\n<p>Illustration\u00a0: Recherche dans la collection ucfr de tous les films produits en 2013<\/p>\n<p>Une remarque en passant\u00a0: si l\u2019on installe BaseX sur un serveur distant, seule la connexion et la cr\u00e9ation d\u2019utilisateurs sont possibles. Il n\u2019est pas possible de requ\u00eater un serveur distant avec cet outil [<a class=\"sdfootnoteanc\" href=\"#sdfootnote1sym\" name=\"sdfootnote1anc\"><sup>1<\/sup><\/a>] et seul le client en ligne de commande peut interagir avec un serveur distant.<\/p>\n<h2 class=\"western\">XQuery<\/h2>\n<p>Je ne vais pas faire ici un cours sur XQuery, disons simplement qu\u2019il s\u2019agit du langage de requ\u00eates de bases de donn\u00e9es du monde XML, standardis\u00e9 par le W3C d\u00e8s 2007, adopt\u00e9 par l\u2019ensemble des SGBD natifs XML mais aussi par les SGBDR majeurs (IBM DB2 PureXML, SQLServer 2005, Oracle 11g R1).<\/p>\n<p>Une requ\u00eate XQuery s\u2019applique sur un document XML ou sur une collection de documents (la structure qui est au c\u0153ur de toute base de donn\u00e9es XML). Toute expression XPath 2.0 est une forme simple de requ\u00eate XQuery, mais XQuery constitue en fait un langage de programmation complet, qui permet de cr\u00e9er des requ\u00eates complexes et des fonctions r\u00e9utilisables.<\/p>\n<p>Une requ\u00eate XQuery \u00e9volu\u00e9e s\u2019\u00e9crit sous une forme nomm\u00e9e FLWOR (prononcer \u00ab\u00a0flower\u00a0\u00bb, d&rsquo;o\u00f9 l&rsquo;illustration de l&rsquo;article ;-)), qui s\u2019inspire beaucoup du SELECT FROM WHERE ORDER BY de SQL.<\/p>\n<p>Une requ\u00eate XQuery permet de r\u00e9aliser des filtres sur des donn\u00e9es index\u00e9es, des jointures inter-documents et des transformations de structure\u00a0: c\u2019est ce qui en fait un outil adapt\u00e9 \u00e0 des manipulations de type ETL.<\/p>\n<p>XQuery a b\u00e9n\u00e9fici\u00e9 lors de sa conception de f\u00e9es du monde SQL, documentaire et XML. Mature d\u00e8s sa premi\u00e8re version, XQuery a n\u00e9anmoins \u00e9volu\u00e9 pour atteindre en 2014 une version 3.0 d\u00e9j\u00e0 parfaitement int\u00e9gr\u00e9e \u00e0 BaseX.<\/p>\n<h2 class=\"western\">Le client en ligne de commande et l\u2019API<\/h2>\n<p>Les commandes du client en ligne de commande sont pratiques [<a class=\"sdfootnoteanc\" href=\"#sdfootnote2sym\" name=\"sdfootnote2anc\"><sup>2<\/sup><\/a>], en voici quelques exemples\u00a0:<\/p>\n<ul>\n<li>show users<\/li>\n<li>list<\/li>\n<li>create db ucfr<\/li>\n<li>open ucfr<\/li>\n<li><span lang=\"en-US\">add ftp:\/\/user:password@ftp.server.com:21\/ucfr\/film-dump.xml<\/span><\/li>\n<li>optimize<\/li>\n<li>info db<\/li>\n<li><span lang=\"en-US\">xquery \/films\/film[productionYear=\u201d2014\u201d]<\/span><\/li>\n<li><span lang=\"en-US\">close<\/span><\/li>\n<li><span lang=\"en-US\">exit<\/span><\/li>\n<\/ul>\n<p>Une API dot\u00e9e de commandes identiques est disponible dans la plupart des langages de programmation, dont Java, C#, Python, PHP ou javascript (node.js).<\/p>\n<h2 class=\"western\">BaseX REST API<\/h2>\n<p>BaseX dispose d\u2019un serveur REST qui permet de lancer des requ\u00eates XQuery pass\u00e9es en param\u00e8tre.<\/p>\n<p>Ex\u00a0: http:\/\/localhost:8984\/rest\/ucfr?query=films\/film[1]<\/p>\n<p>Dans cet exemple, la requ\u00eate s\u2019applique au contenu de la collection (i.e. base de donn\u00e9es) \u00ab\u00a0ucfr\u00a0\u00bb.<\/p>\n<p>La structure retourn\u00e9e doit avoir un \u00e9l\u00e9ment racine. Si la requ\u00eate ne retourne pas d\u2019\u00e9l\u00e9ment racine, on peut en ajouter un via le param\u00e8tre wrap=yes<\/p>\n<p>http:\/\/localhost:8984\/rest\/ucfr?query=films\/film[productionYear=%222014%22]&#038;wrap=yes<\/p>\n<p>le r\u00e9sultat est alors encadr\u00e9 d\u2019une balise<\/p>\n<pre style=\"font-family: 'Courier New', serif;\">&lt;rest:results\u00a0xmlns:rest=\"http:\/\/basex.org\/rest\"&gt;&lt;rest:result&gt; \u2026 &lt;\/rest:result&gt;&lt;\/rest:results&gt;<\/pre>\n<p>Les requ\u00eates XQuery complexes peuvent \u00eatre stock\u00e9es sur le serveur, dans le r\u00e9pertoire [home]\/webapp (Windows) ou [home]\/BaseXWeb (Linux), dans des fichiers d\u2019extension <strong>.xq<\/strong>. Elles sont alors appel\u00e9es via le nom du fichier\u00a0:<\/p>\n<p>Ex\u00a0: http:\/\/localhost:8984\/rest?run=somefilms.xq<\/p>\n<p>A noter\u00a0: la requ\u00eate stock\u00e9e doit identifier chaque collection (i.e. base de donn\u00e9es)\u00a0sur laquelle s\u2019applique une expression XPath par une instruction de type collection(\u00ab\u00a0&lt;database&gt;\u00a0\u00bb)\/&lt;xpath&gt;<\/p>\n<h2 class=\"western\">BaseX RESTXQ API<\/h2>\n<p>L\u2019interface REST introduit ci-dessus est tr\u00e8s pratique, mais ne permet pas de \u00ab\u00a0dessiner\u00a0\u00bb les URLs d\u2019acc\u00e8s aux requ\u00eates. Une interface alternative, nomm\u00e9e RESTXWQ, ajoute cette fonctionnalit\u00e9 \u00e9l\u00e9gante.<\/p>\n<p>Les modules XQuery, d\u2019extension .xqm, sont d\u00e9pos\u00e9s dans le r\u00e9pertoire [home]\/webapp. Les fonctions sont d\u00e9cor\u00e9es de propri\u00e9t\u00e9s qui pr\u00e9cises quel verbe http donne acc\u00e8s \u00e0 la fonction, quels param\u00e8tres de la fonction sont v\u00e9hicul\u00e9s dans l\u2019URL, la query string ou les headers http.<\/p>\n<p>Voici un exemple de fonction ainsi d\u00e9cor\u00e9e, appel\u00e9e en local via http:\/\/localhost:8984\/api\/companies \u00a0:<\/p>\n<pre style=\"font-family: 'Courier New', serif;\">module namespace api = 'http:\/\/universcine.com\/v3\/api';\r\ndeclare\r\n%rest:path(\"\/api\/companies\")\r\n%rest:query-param(\"offset\", \"{$offset}\", 0)\r\n%rest:query-param(\"limit\", \"{$limit}\", 10)\r\n%rest:query-param(\"locale\", \"{$locale}\", \"fr\")\r\n%rest:GET\r\nfunction api:companies(\r\n$limit as xs:integer, $offset as xs:integer, $locale as xs:string)\r\nas element(companies)\r\n{\r\n&lt;companies&gt;\r\n{\r\nfor $c in collection(\"cinedb\")\/company[position() gt $offset and position() le $offset+$limit]\r\nreturn\r\n$c\r\n}\r\n&lt;\/companies&gt;\r\n};<\/pre>\n<h2 class=\"western\">S\u00e9rialisation JSON<\/h2>\n<p>Quand on parle d\u2019interface REST, on parle \u00e9galement souvent de s\u00e9rialisation JSON. Je ne vais pas m\u2019\u00e9tendre ici sur les avantages et inconv\u00e9nients de JSON par rapport \u00e0 XML \u2026 les deux formats sont compl\u00e9mentaires, c\u2019est tout. Les concepteurs de BaseX ont pris ce besoin en compte, en permettant en particulier de transformer en JSON les structures XML issues d\u2019une requ\u00eate XQuery.<\/p>\n<p>Cependant, les deux mod\u00e8les pr\u00e9sentent des diff\u00e9rences fondamentales\u00a0: XML est adapt\u00e9 aux donn\u00e9es semi-structur\u00e9es tandis que JSON est adapt\u00e9 aux donn\u00e9es orient\u00e9es objet. Pour r\u00e9aliser la structure JSON de nos r\u00eaves, il faut travailler un peu.<\/p>\n<p>En r\u00e9sum\u00e9, chaque \u00e9l\u00e9ment XML destin\u00e9 \u00e0 devenir un objet JSON doit \u00eatre dot\u00e9 d\u2019un attribut \u00ab\u00a0type=\u2019object\u2019\u00a0\u00bb, chaque \u00e9l\u00e9ment XML destin\u00e9 \u00e0 devenir un array JSON doit \u00eatre dot\u00e9 d\u2019un attribut \u00ab\u00a0type=\u2019array\u2019\u00a0\u00bb, et chaque futur \u00e9l\u00e9ment d\u2019un array JSON doit \u00eatre nomm\u00e9 \u00ab\u00a0_\u00a0\u00bb.<\/p>\n<p>Voici un petit exemple de sculpture XML n\u00e9cessaire pour l\u2019obtention d\u2019une structure JSON sympathique\u00a0:<\/p>\n<pre style=\"font-family: 'Courier New', serif;\">&lt;images type=\"array\"&gt;\r\n{ for $i in $f\/images\/image\r\nreturn\r\n&lt;_ type=\"object\"&gt;\r\n{ element uuid {data($i\/@uuid)},\r\nelement src {data($i\/@src)},\r\n$i\/category, $i\/title\r\n}\r\n&lt;\/_&gt;\r\n}\r\n&lt;\/images&gt;,<\/pre>\n<p><span lang=\"en-US\">Qui donnera :<\/span><\/p>\n<pre style=\"font-family: 'Courier New', serif;\">\"images\": [\r\n{\r\n\"uuid\": \"ucdb:image:82122\",\r\n\"src\": \"http:\/\/media.universcine.com\/3c\/49\/3c49f4ba-3384-11e4-944a-954866b2bff7.jpg\",\r\n\"category\": \"poster\",\r\n\"title\": \"aya-de-yopougon-affiche.jpg\"\r\n},<\/pre>\n<p>Le document XML doit \u00eatre encadr\u00e9 par un \u00e9l\u00e9ment &lt;json type=\u00a0\u00bbobject\u00a0\u00bb&gt; ou &lt;json type=\u00a0\u00bbarray\u00a0\u00bb&gt;, et la transformation JSON peut \u00eatre provoqu\u00e9e dans la requ\u00eate XQuery elle-m\u00eame \u00e0 l\u2019aide de la fonction json\u00a0:serialize() encadrant la structure XML destin\u00e9e \u00e0 transformation JSON.<\/p>\n<h2 class=\"western\">Conclusion<\/h2>\n<p>Gr\u00e2ce \u00e0 la mise en place d\u2019un serveur BaseX, par transformation XML de donn\u00e9es export\u00e9es des bases de donn\u00e9es relationnelles de mon client, int\u00e9gration de donn\u00e9es compl\u00e9mentaires (les nouvelles taxonomies de genres et th\u00e8mes par exemple), transformation XQuery et nouvelle indexation des donn\u00e9es r\u00e9sultantes, cr\u00e9ation d\u2019une API REST JSON et de plusieurs exports XML, je peux exposer avec efficacit\u00e9 les donn\u00e9es des diff\u00e9rents services VOD d\u2019UniversCin\u00e9, un travail r\u00e9alis\u00e9 en quelques jours seulement.<\/p>\n<p>Cela me donne envie de poursuivre l\u2019utilisation de BaseX pour d\u2019autres cas d\u2019utilisation, malgr\u00e9 quelques limitations que je vais citer rapidement\u00a0:<\/p>\n<ol>\n<li>L\u2019indexation incr\u00e9mentale des donn\u00e9es n\u2019est pas toujours efficace\u00a0: c\u2019est un probl\u00e8me oubli\u00e9 des utilisateurs de SGDBR, mais un cas d\u00e9j\u00e0 rencontr\u00e9 avec des moteurs de recherche plein-texte et leurs indexes invers\u00e9s. Par d\u00e9faut, les indexes sont d\u00e9sactiv\u00e9s d\u00e8s qu\u2019un document est ajout\u00e9 ou mis \u00e0 jour dans une collection. Une commande d\u2019optimisation recr\u00e9e ces indexes, mais cette proc\u00e9dure peut prendre quelques secondes sur une collection importante. Une option (UPDINDEX, et AUTOOPTIMZE dans la version 8.0) active l\u2019indexation incr\u00e9mentale, mais augmente le temps de mise \u00e0 jour de la collection, ce qui limite son int\u00e9r\u00eat aux collections de petite et moyenne taille.<\/li>\n<li>La m\u00e9moire utilis\u00e9e pour les transformations n\u2019est pas toujours facile \u00e0 g\u00e9rer. Par d\u00e9faut, la JVM utilis\u00e9e par BaseX est limit\u00e9e \u00e0 512 Mo. J\u2019ai \u00e9t\u00e9 amen\u00e9 \u00e0 saturer cette m\u00e9moire lors de transformations complexes, ce qui m\u2019a amen\u00e9 \u00e0 devoir tron\u00e7onner certains processus de transformation.<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<div id=\"sdfootnote1\">\n<p><a class=\"sdfootnotesym\" href=\"#sdfootnote1anc\" name=\"sdfootnote1sym\">1<\/a><sup>\u0002<\/sup> https:\/\/mailman.uni-konstanz.de\/pipermail\/basex-talk\/2013-December\/005863.html<\/p>\n<\/div>\n<div id=\"sdfootnote2\">\n<p><a class=\"sdfootnotesym\" href=\"#sdfootnote2anc\" name=\"sdfootnote2sym\">2<\/a><sup>\u0002<\/sup> http:\/\/docs.basex.org\/wiki\/Standalone_Mode<\/p>\n<p class=\"sdfootnote\">\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>BaseX (basex.org) est un SGBD natif XML open-source, un outil particuli\u00e8rement adapt\u00e9 \u00e0 la manipulation et \u00e0 la transformation de donn\u00e9es complexes. Il me sert actuellement en particulier \u00e0 migrer les donn\u00e9es d\u2019un ensemble de services VOD OTT (Universcine.fr, Universcine.be, Volta.ie, LeKino.ch, Cinemasalademande.com) et ses fonctionnalit\u00e9s m\u2019\u00e9tonnent tous les jours. J\u2019esp\u00e8re que ce qui suit&hellip;<\/p>\n","protected":false},"author":1,"featured_media":46,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-44","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-xml"],"_links":{"self":[{"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/posts\/44","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=44"}],"version-history":[{"count":6,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/posts\/44\/revisions"}],"predecessor-version":[{"id":52,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/posts\/44\/revisions\/52"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=\/wp\/v2\/media\/46"}],"wp:attachment":[{"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=44"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=44"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fluxnumerique.fr\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=44"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}