mercredi 17 septembre 2014

Un langage de requêtes pour rechercher et consulter des archives depuis un front-office vers un SAE back-office

Un langage de requêtes pour rechercher et consulter des archives depuis un front-office vers un SAE back-office

Dans le cadre du programme VITAM, nous devons définir les interfaces externes de la solution logicielle que nous allons développer. Parmi ces interfaces, le schéma de données accompagnant un versement d’archives est un enjeu important.
Dans le cadre des normes et règlements français, nous nous inspirons de la norme MEDONA de l’AFNOR (Z 44-022) qui permet de définir le sur-ensemble des échanges entre partenaires autour d’un Système d’Archivage Électronique (SAE). Nous nous inspirons également de MoReq2010 pour le modèle de données.
En effet MEDONA définit les principes ainsi qu’un exemple de schéma, mais ne spécifie pas les parties liées aux contextes métiers concernés par les archives (plan de classement, métadonnées de gestion, ...). L’objet du schéma VITAM est donc de proposer une extension de la norme MEDONA pour proposer un modèle de données pour le versement d’archives, en tant que première interface d’un SAE.

Ce blog étant à caractère personnel, il ne constitue donc pas une publication officielle, mais il m'a semblé intéressant de recueillir des avis avant la mouture finale qui elle suivra une publication officielle, toujours avec appel à commentaires. Cette publication est assez technique, et je m'en excuse par avance...



A noter que nous avons regardé évidemment des langages pré-existants : 
  1. CMIS / PseudoSQL :
    • Les possibilités offertes par CMIS sont bien trop restrictives pour nos besoins : il ne s'agit pas de lister le contenu de répertoires ou de filtrer sur quelques rares propriétés comme l'auteur ou la taille de l'objet ; les recherches du type plein texte ou géomatique sont absentes.
    • L'approche PseudoSQL peut être cependant séduisante mais la logique d'imbrication des requêtes ("inside out", c'est à dire la requête sur la racine est le plus à l'intérieur, puis il faut "remonter" dans la requête pour trouver les requêtes sur les niveaux fils) ne nous semble pas non plus propice d'une part à une bonne appréhension de ce langage, mais en plus conduit à des requêtes inutilement complexes et difficilement optimisées.
  1. JSONiq, MQL, RDF :
    • Ces langages ne nous semblent pas simples d'appréhension et sont loin d'être complets eux aussi (recherche plein texte, géomatique, ...).
    • L'acceptation par des développeurs d'horizons très divers nous semble également difficile, notamment en raison des notations complexes induites par les triplets (RDF).
  • Exemple : 
      En PseudoSQL/RDF
        SELECT champs FROM InTree( SELECT FROM InTree(SELECT FROM racine WHERE conditionNiveau1) WHERE conditionNiveau2) WHERE conditionNiveau3

      Nous proposons: 
      • $query : [ { conditionNiveau1 }, { conditionNiveau2 }, { conditionNiveau3 } ], $projection : { fields : champs } }

L’objet de ce document est de proposer une définition d’un langage de requêtes qui pourrait être utilisé par des applications frontales d’un Système d’Archivage Électronique (SAE) basé sur le socle logiciel VITAM. En effet, un SAE s’appuyant sur VITAM constitue un back-office d’un service d’archives, permettant :
  • de verser, conserver et maintenir dans le temps la lisibilité des archives et des métadonnées associées ;
  • de requêter ces métadonnées (recherche) ;
  • d’accéder aux archives elles-mêmes.
D’une manière générale, il est rappelé que le modèle de données de VITAM s’appuie sur une extension de MoREQ2010, sous une forme de généralisation d’un modèle arborescent avec de multiples héritages (graphe sans cycle dirigé, noté DAG pour Directed Acyclic Graph).

La variété des métadonnées


Plus les archives sont récentes, plus leurs usages sont liés au métier d'origine et comportent donc des métadonnées riches et variées. C'est sur la base de ces métadonnées que les recherches peuvent s'effectuer ultérieurement, que ce soit pour des besoins métiers, juridiques, scientifiques ou historiques.
Ces métadonnées sont le cœur d'un système d'archivage : conserver une archive sans son contexte ni son classement ne sert à rien, car on ne pourra plus y accéder.
Jusqu'ici les SAE étaient organisés en silos en raison de la diversité des formats des métadonnées qui empêchaient la mutualisation. Ceci limite fortement ce que l'on nomme la mémoire de l'entreprise. Cette limitation empêchait également les économies liées à la mutualisation.
Le programme VITAM propose de résoudre cette problématique en permettant de prendre en charge des objets provenant de plusieurs contextes, avec chacun leur schéma de données. Ce modèle doit à la fois être adaptable en termes de contenu (par exemple les métadonnées descriptives adaptées au contexte métier concerné), qu’en termes de classement (arborescence associée).

Plans de classement arborescents et multiple classements


Afin de répondre aux différentes exigences, un plan de classement est conceptuellement un arbre où chaque nœud de l’arbre est un niveau dans le plan de classement, et les feuilles étant les archives elles-mêmes.
Un objet d’archive peut être classé en plusieurs endroits (par exemple une facture est associée à un marché public, un projet ayant passé la commande et la tenue du budget de l’entité concernée), ceci impliquant qu’une feuille puisse dépendre de plusieurs arbres, soit une représentation nommée « graphe dirigé sans cycle » (Directed Acyclic Graph en anglais).
Illustration 1: Modèle multiple classements
Chaque nœud dans ces graphes constitue un nœud de métadonnées, informations décrivant le niveau concerné, que nous nommons MAIP par extension des AIP de l’OAIS (M pour Métadonnées).

Modèles de requêtes

Pour essayer de répondre aux différents cas d’usages, nous avons essayé de déterminer les différentes possibilités de recherche qui seraient utiles dans le cadre d’activités utilisant un SAE comme un service back-office d’applications front-office.

Modèle direct

L’accès le plus simple consiste à connaître précisément l’objet recherché via un identifiant unique (comme un permalien). C’est le mode traditionnel d’accès des coffres-forts au sens où l’application client fournie l’identifiant d’objet (ON_ID) et le coffre-fort retourne l’objet numérique qui est ainsi référencé.
Par exemple, ce modèle permet d’accéder à une archive en spécifiant uniquement son identifiant unique (son UUID) fourni par le SAE.
Par extension, ceci sera possible dans VITAM pour les objets numériques mais aussi pour les nœuds de métadonnées descriptives (MAIP).

Ce type de recherche est appropriée lorsque le demandeur connaît déjà ce qu’il recherche mais n’a que son identifiant et pas son contenu.

Modèle dit arborescent

Un autre mode d’accès consiste à parcourir l’arbre de métadonnées, depuis la racine jusqu’à l’objet recherché. Ainsi il s’agirait, avec les schémas précédents, de sélectionner un Fond, puis un Sous-Fond fils de ce Fond, puis un Dossier fils de ce Sous-Fond, et enfin un Objet fils de ce Dossier.
Il s’agit donc d’une recherche itérative niveau par niveau, qui suit l’arbre pas à pas, d’où son nom de recherche arborescente.

Ce type de recherche est très appropriée quand le plan de classement permet un tel parcours très ciblé, niveau par niveau.

Modèle dit en profondeur

Enfin le dernier modèle consiste à rechercher depuis un nœud, dans le sous-arbre dont il est parent, un ou plusieurs nœuds répondant à un critère de sélection.
Par exemple, une fois sélectionné un Fond, il serait possible de rechercher n’importe quel nœud (Sous-Fond, Dossier ou Objet) qui contiendrait une information de type « Auteur »=  « valeur » ou toute autre champ et modèle de données.

Quels critères ?

Le critère de recherche peut être un critère simple (égalité, comparaison numérique ou de dates), un critère plus textuel (recherche plein texte, de mots-clefs), ou encore un critère plus complexe (associations de critères simples ou textuels par des opérateurs logiques comme « Et », « Ou »,...).
Ce critère peut donc être appliqué tant pour le modèle arborescent que pour le modèle en profondeur.

Définition du langage

Choix du modèle JSON

En 2014, le modèle d’échange sur la base du format JSON est très utilisé, notamment en raison de l’essor des modèles NoSQL, BigData et des interfaces REST. Le modèle REST nous semble très utile dans notre positionnement de Back Office, puisque cela place le SAE comme un service de manière explicite pour les applications frontales (Front-office) qui gèrent le métier.

Le modèle Json est une simplification du modèle XML, notamment car il est moins verbeux : les deux exemples ci-dessous sont équivalents, le premier au format JSON, le second au format XML
{ "champ1" : "chaine",
"champ2" : [
  { "champ3" : "val11", "champ4" : 123, "champ5" : { "champ6" : "val12" } },
  { "champ3" : "val21", "champ4" : 223, "champ5" : { "champ6" : "val22" } },
  { "champ3" : "val31", "champ4" : 323, "champ5" : { "champ6" : "val32" } }
]
}

<XML>
  <champ1>chaine</champ1>
  <champ2>
    <item>
      <champ3>val11</champ3><champ4>123</champ4>
      <champ5><champ6>val12</champ6></champ5>
    </item>
    <item>
      <champ3>val21</champ3><champ4>323</champ4>
      <champ5><champ6>val22</champ6></champ5>
    </item>
    <item>
      <champ3>val31</champ3><champ4>323</champ4>
      <champ5><champ6>val32</champ6></champ5>
    </item>
  </champ2>
</XML>

De manière normée (voir http://json.org/), un JSON est définie comme suit :
  • Un objet
    • { "champ" : valeur, "champ" : valeur, ... }
  • Un tableau
    • [ valeur1, valeur2, ... ]
    • L’accès a un élément d’un tableau
    • pour un tableau "champ" : [ valeur1, valeur2, ... ] , l’accès "champ"[0] donne valeur1
  • Une valeur peut être
    • une chaîne de caractères
      • "chaîne"
    • une date : écrite comme une chaîne de caractères, elle doit respecter la norme ISO 8601 et en particulier les formats suivants (date, date+heure) :
      • "YYYY-MM-DD" (exemple : "2014-09-15")
      • "YYYY-MM-DDThh:mm:ssTZD" (exemple : "2014-09-15T19:20:25+01:00")
    • un nombre
      • 123
      • 123.456
      • 123.456E78
    • un tableau
      • [ valeur, valeur, ... ]
    • un booléen
      • true, false
    • un objet vide
      • null
    • un objet
      • { "chaîne" : valeur, "chaîne" : valeur, ... }

Il est possible de définir, tout comme en XML, un schéma de données JSON. Son formalisme est tout aussi complexe qu’un schéma XSD, aussi nous nous limiterons à une notation simplifiée pour exprimer le langage de requête en JSON.

Cas particuliers de notation


  • Notation des opérateurs

C’est un objet dont le nom du champ est préfixé par ‘$’
{ "$opérateur" : valeur }

  • Notation des propriétés associées à un opérateur

C’est un objet dont le nom du champ est aussi préfixé par ‘$’
{ "$opérateur" : { "chaîne" : valeur, "$propriété" : valeur }

  • Notation de certaines propriétés, calculées ou enregistrées

L’usage du « @ » en préfixe permet de les identifier :

  • @nbleaves = nombre de fils
  • @dua = valeurs de la dua du nœud sélectionné (d’autres valeurs de ce type seront définies, permettant d’accéder directement à des champs de gestion archivistique)
  • @all = tous les champs existants pour le nœud sélectionné

  • Facilités de notation

Pour des facilités de notation, il est admis que le nom d’un opérateur ou d’une propriété (voire le nom des champs de manière générique) ne soit pas encadré par des guillemets ("). À noter que les valeurs sous forme de chaînes de caractères devront toujours être entourées de ces guillemets.
{ $opérateur : valeur, $propriété : valeur }

Opérateurs dans le langage de requête

Un langage de requête s’exprime par les opérateurs que peut exprimer ce langage. Plusieurs types d’opérateurs existent et sont présentés ci-dessous. Les arguments des opérateurs sont représentés par la valeur associée à l’opérateur. Cette valeur peut prendre plusieurs formes (chaînes, nombre, objet, tableau, …).

Opérateur d’accès direct

Si l’identifiant unique d’accès à un nœud ou un objet numérique précis est connu, il peut être accédé directement par l’opérateur « path ». Cet opérateur prend en argument un ou plusieurs identifiants. Un identifiant est une chaîne de caractères (donc entourée par des guillemets).
  • { $path : [ "id" ] }
  • { $path : [ "id1", "id2" ] }

Opérateurs booléens

Il est possible d’unifier plusieurs sous opérateurs via des opérateurs logiques (et, ou, négation).

  • Opérateur ET
    • { $and : [ opérateur1, opérateur2, … ] }
  • Opérateur OU
    • { $or : [ opérateur1, opérateur2, … ] }
  • Opérateur NEGATION
    • Implicitement l’opérateur $and relie l’ensemble des sous-opérateurs
    • { $not : [ opérateur1, opérateur2, … ] }
      • implicitement équivalent à { $and : [ $not : [ opérateur1 ], $not : [ opérateur2 ], … ] }

Opérateurs de comparaison

Les opérateurs de comparaison permettent de filtrer des éléments pour des valeurs de type nombres ou dates et chaînes de caractères (pour ce dernier uniquement sur un plan lexicographique).
Opérateurs égalité, non égal, inférieur, inférieur ou égal, supérieur, supérieur ou égal

  • { $eq : { champ : valeur } }
  • { $ne : { champ : valeur } }
  • { $lt : { champ : valeur } }
  • { $lte : { champ : valeur } }
  • { $gt : { champ : valeur } }
  • { $gte : { champ : valeur } }
    • Exemple :
      • { $lt : { uneDate : "1974-04-22" } }
      • { $gte : { unNombre : 123.45 } }
      • { $lte : { uneChaine : "abcd" } } ("abbd" est <= "abcd")
Opérateur d’intervalle

  • { $range : { champ : { $gt / $gte : valeur, $lt / $ lte : valeur } } }

Opérateurs d’existence

Les opérateurs d’existence permettent de déterminer l’existence ou l’absence ou la valuation vide d’un champ (le champ est présent mais vide ou à null).

  • Existence
    • { $exists : champ }
  • Absence
    • { $missing : champ }
  • Valeur nulle ou absente
    • { $isNull : champ }

Opérateurs sur les tableaux

Les opérateurs sur les tableaux permettent de comparer le nombre d’éléments contenus dans un tableau à une valeur, de déterminer la présence ou l’absence d’un élément ou plusieurs éléments dans un tableau.
Comparaison du nombre d’éléments dans un tableau

  • { $size : { champTableau : taille } }
Présence (au moins 1) ou absence (toutes) de valeurs dans un tableau

  • { $in : { champTableau : [ valeur1, valeur2, … ] } }
  • { $nin : { champTableau : [ valeur1, valeur2, … ] } }
Accès à un élément d’un tableau

  • champTableau[i] pour l’accès au (i+1)ème élément du tableau

Opérateurs textuels

Concernant les champs textuels, plusieurs modes d’opérateurs de recherche peuvent exister en fonction de la nature du texte embarqué :
  1. Texte sous forme de mots-clef unique (identifiant, code, …), sans espace ni tabulation ni ponctuation :
  • Égalité parfaite :
    • { $term : { champ : valeur, champ : valeur, … } }
    • Exemple :
      • { $term : { monTexte : "chat" } }
      • Cherche les documents tels que le champ « monTexte » contienne exactement le mot « chat », et pas « château » ou « achat »
  • Égalité modulo des caractères de complétion ( ? = n’importe quel caractère, * = une série de n’importe quels caractères) :
    • { $wildcard : { champ : valeur } }
    • Exemple :
      • { $wildcard : { monTexte : "ch?t*" } }
      • Cherche les documents tels que le champ « monTexte » contienne le mot commençant par « ch », puis un caractère, puis « t », puis un nombre quelconque de caractères, comme « chat », « chut », « chateau », mais pas « chaut », ni « achat ».
  1. Texte regroupant plusieurs mots (composant ou non une phrase, un paragraphe) :
  • $max_expansions (optionnel) indique la distance entre la racine des mots fournis dans la requête et les mots présents dans le texte (exemple : « valide » est distant de 5 de « validation ») ; cet argument est optionnel et vaut 10 par défaut.
  • Recherche de mots (avec expansions pour chaque mot) dans un texte (recherche plein texte), chaque mot étant éventuellement distants dans le texte :
    • { $match : { champ : mots, $max_expansions : n } }
    • Exemple :
      • { $match : { monTexte : "le petit chat est mort" } }
      • Cherche les documents tels que le champ « monTexte » contienne les mots « petit », « chat », « est » et « mort » (« le » est ignoré car considéré comme un mot trop générique), par exemple « Dans l’histoire, les petits et jeunes chats sont vivants mais un seul est mort hélas » répond à cette requête.
  • Recherche d’une phrase (sans trou entre les mots), mais toujours avec une expansion mais sur le dernier mot de la phrase passée en paramètre :
    • { $matchPhrase : { champ : phrase, $max_expansions : n } }
    • Exemple :
      • { $matchPhrase : { monTexte : "le petit chat est mort" } }
      • Cherche les documents tels que le champ « monTexte » contienne la phrase indiquée avec une expansion possible sur le dernier mot, comme « Dans l’histoire, le petit chat est mortel hélas ».
  • Recherche d’une phrase (sans trou entre les mots), avec une expansion sur le dernier mot de la phrase passée en paramètre, cette phrase devant être absolument en début du texte du champ :
    • { $matchPhrasePrefix : { champ : phrase, $max_expansions : n } }
    • Exemple :
      • { $matchPhrasePrefix : { monTexte : "le petit chat est mort" } }
      • Cherche les documents tels que le champ « monTexte » contienne la phrase indiquée en début de texte uniquement et avec une expansion possible sur le dernier mot, comme « le petit chat est mort de faim », mais pas « dans l’histoire, le petit chat est mort ».
  • Recherche d’une chaîne de caractères exprimée sous forme d’une expression régulière :
    • { $regex : { champ : regex } }
    • Exemple :
      • { $regex : { monTexte : "le(|s) petit(|s) chat(|s) (est|sont) mort(|s).*" } }
      • Cherche les documents tels que le champ « monTexte » contienne par exemple : « le petits chat sont mort » ou « les petits chats sont morts » ou « le petit chat est mort » (et toutes les variations possibles), sans ajout avant mais avec des ajouts possibles après.
  • Recherche sous une forme proche de l’expression des moteurs de recherche du Net (utilisation des caractères + et – pour respectivement rendre obligatoire ou interdit un mot, du caractère " pour entourer une expression exacte, des caractères ( et ) pour entourer des mots (séparés par des blancs) pour exprimer la condition « ou ») :
    • { $search : { champ : parametres } }
    • Exemple :
      • { $search : { monTexte : "le +petit +chat -sont \"est mort\"" } }
      • Cherche les documents tels que le champ « monTexte » contienne notamment les mots « petit » et « chat » de manière obligatoire, ne doivent pas contenir le mot « sont », et peuvent contenir (de préférence) les mots « le » et l’expression « est mort ».
  • Recherche dans un ou plusieurs champs de document ayant des valeurs proches de l’argument fourni en paramètre :
    • More Like This
      • { $mlt : {$fields : [ champ1, champ2, … ], $like : likeText }}
    • Fuzzy Like This
      • { $flt : {$fields : [ champ1, champ2, … ], $like : likeText }}
    • Exemple :
      • { $mlt : { $fields : [ monTexte, monTexte2 ], $like : "le petit chat est mort" } }
      • Cherche les documents tels que les champs « monTexte » et « monTexte2 » ressemblent par leur contenu à l’expression « le petit chat est mort », comme par exemple « le petit chien est mort ».

Opérateurs géomatiques

  • Pour définir une forme géométrique ou une position :
    • $geometry : { $type : "type", $coordinates : [ [ lng1, lta1 ], [ lng2, lta2 ], ... ] }
      • type = Point (un seul couple lng, lta) ou Box (2 couples) ou Polygon (n couples)
    • $box : [ [ lng1, lta1 ], [ lng2, lta2 ] ]
    • $polygon : [ [ lng1, lta1 ], [ lng2, lta2 ], ... ]
    • $center : [ [ lng1, lta1 ], radius ]
  • Pour comparer une position avec une forme géométrique :
    • $geoWithin : { champGeo : { geometry|box|polygon|center } }
      • Sélectionne un document sur la base du champ « champGeo » exprimant une position géographique en vérifiant que celle-ci se trouve dans la forme décrite.
    • $geoIntersects : { champGeo : { geometry|box|polygon|center } }
      • Sélectionne un document sur la base du champ « champGeo » exprimant une position géographique en vérifiant que celle-ci possède une intersection non vide avec la forme décrite.
    • $near : { champGeo : { geometry_point|[ lng1, lta1], $maxDistance : distance } }
      • Sélectionne un document sur la base du champ « champGeo » exprimant une position géographique en vérifiant que celle-ci est à une distance inférieure ou égale à celle indiquée avec la forme décrite.

Résumé des opérateurs



Catégorie
Opérateur
Arguments
Commentaire
Accès direct
$path
identifiants
Accès direct à un nœud
Booléens
$and, $or, $not
opérateurs
Combinaison logique d’opérateurs
Comparaison
$eq, $ne, $lt, $lte, $gt, $gte
Champ et valeur
Comparaison de la valeur d’un champ et la valeur passée en argument
$range
Champ, $lt, $lte, $gt, $gte et valeurs
Comparaison de la valeur d’un champ avec l’intervalle passé en argument
Existence
$exists, $missing, $isNull
Champ
Existence d’un champ
Tableau
$in, $nin
Champ et valeurs
Présence de valeurs dans un tableau
$size
Champ et taille
Taille d’un tableau
[n]
Position (n >= 0)
Élément d’un tableau
Textuel
$term, $wildcard
Champ, mot clef
Comparaison de champs mots-clefs
$match, $matchPhrase, $matchPhrasePrefix
Champ, phrase,
$max_expansions
Recherche de phrase
$regex
Champ, Expression régulière
Recherche via une expréssion régulière
$search
Champ, valeur
Recherche du type moteur de recherche
$flt, $mlt
Champ, valeur
Recherche « More Like This »
Géomatique
$geometry, $box, $polygon, $center
Positions
Définition d’une position géographique
$geoWithin, $geoIntersects, $near
Une forme
Recherche par rapport à une forme géométrique



Expression d’une requête sur un document

Inspiré du modèle SQL, le langage propose 4 opérations de base : la création, la lecture, la mise à jour et l’effacement (aussi nommées CRUD pour l’anglais Create, Read, Update, Delete).

L’objet $query

Toutes les requêtes s’appuient a minima sur un objet $query qui contient une liste (exprimée sous la forme d’un tableau JSON) d’au moins un opérateur (parmi les opérateurs définis auparavant) ou combinaison d’opérateurs.
Chaque élément de la liste est interprété comme une sélection itérative dans un parcours d’arbre.
Chaque requête s’appuie sur un contexte, à savoir les nœuds issus de la requête précédente. Pour la première requête, son contexte est l’ensemble des nœuds racines des différents plans de classement autorisés. La première requête s’exprime donc
  • soit avec un opérateur $path, impliquant un filtre sur les nœuds racines comme points de départs autorisés ;
  • soit avec un opérateur permettant de sélectionner un ou plusieurs de ces nœuds racines.
Les éléments suivants permettent une sélection itérative.
À chaque étape, il est possible d’indiquer si la recherche concerne une recherche sur un niveau précis à partir des nœuds issus du contexte.

$depth : n

  • Permet de spécifier la profondeur exacte maximale jusqu’où la requête va s’exécuter à partir des nœuds issus du contexte.
$relativedepth : +/-n

  • Permet de spécifier une profondeur relative :
    • +n pour une recherche jusqu’à la profondeur courante +n (vers les fils)
    • -n pour une recherche jusqu’à la profondeur courante -n (vers les pères)
$relativedepth: 0

  • Permet de spécifier que la recherche pourra s’effectuer vers les descendants sans limite de profondeur
Un seul des $depth ou $relativedepth peut être positionné. Si les deux le sont, seul $depth sera conservé.
Exemple :
{ $query : [
     { $path : [ 'id1', 'id2'] },
    { $and : [ {$exists : 'mavar1'}, {$in : { 'mavar1' : [1, 2, 'maval1'] }}, $depth : 4 ],
     { $term : { 'mavar14' : 'motMajuscule', 'mavar15' : 'simplemot' }, $relativedepth : +2 }
] }

Une création : $insert

L’opération de création prend 3 paramètres : le premier pour sélectionner le contexte, le deuxième pour limiter sa portée  et le troisième pour spécifier les données insérées.
{ $insert : { $query : query, $filter : filter, $data : data }
argument query

  • Permet de sélectionner le ou les nœuds parents du nouveau nœud à créer
argument filter

  • $mult : true/false pour indiquer si la sélection des parents peut donner de multiples parents (true) ou ne doit en retourner qu’un seul pour que l’opération d’insert soit valide. Si multiple est autorisé, l’objet créé aura pour parents l’ensemble des parents sélectionnés.
argument data

  • l’objet JSON qui sera inséré ; exemple :
    • { champ : valeur, champ : { champ : valeur } }

Une lecture : $select

L’opération de lecture prend 3 paramètres : le premier pour sélectionner le contexte, le deuxième pour filtrer les résultats et le troisième pour spécifier la nature de ce qui est retourné (quels champs en fonction des droits d’accès).
{ $select : {$query : query, $filter : filter, $projection : projection} }
argument query

  • Permet de sélectionner le ou les nœuds qui seront retournés par l’opération de lecture
argument filter

  • $limit : nb
    • Limite le nombre d’éléments retournés (une limite haute sera toujours positionnée)
  • $offset : start
    • Permet d’obtenir des éléments à partir du « start »ième
    • La combinaison de $offset et $limit permet de paginer les résultats retournés.
  • $orderby : [ { champ : +/-1 }, ... ]
    • Permet d’ordonner (trier) les résultats retournés selon un ordre ascendant (+1) ou descendant (-1) sur les champs indiqués, et dans l’ordre de la liste des champs (tri par rapport au 1er champ, puis par rapport au 2nd champ, ...)
  • $hint : code
    • Permet de donner une aide pour l’exécution de la requête : à ce jour, une des aides possibles est la directive « cache » ou « nocache » qui indique si cette requête devrait ou non être mise en cache.
argument projection

  • $field : { champ : 0/1, ... }
    • Permet de sélectionner les champs à inclure, ou à exclure dans le résultat. Il est possible d’utiliser les champs spéciaux (@all, @nbleaves, @dua, ...)
  • $usage : contractId
    • Permet d’indiquer un identifiant de contrat associé à cette requête. Cette identification permettra de déterminer la compatibilité de la requête avec le contrat, ou d’en limiter l’exécution.

Une mise à jour : $update

L’opération de mise à jour prend 3 paramètres : le premier pour sélectionner le contexte, le deuxième pour limiter sa portée  et le troisième pour spécifier les données insérées.
{ $update : { $query : query, $filter : filter, $action : action }
argument query

  • Permet de sélectionner le ou les nœuds qui seront mis à jour
argument filter

  • $mult : true/false pour indiquer si la sélection des nœuds à mettre à jour peut donner de multiples nœuds (true) ou ne doit en retourner qu’un seul.
argument action

  • { $set : { champ : valeur, champ : valeur, ... } }
    • Positionne ou ajoute (si elle n'existait pas) une valeur pour un champ
  • { $unset : { champ : "", ... } }
    • Efface le champ
  • { $inc : { champ : valeur, champ : valeur, ... } }
    • Incrémente le champ avec la valeur
  • { $rename : { champ : nouveauNomDeChamp, ... } }
    • Renomme un champ
  • { $push : { champ : valeur, ... }
    • Ajoute une valeur à un champ de liste
      • Si la liste maliste est [ a, b, c], $push : { maliste : b } donnera maliste = [ a, b, c, b])
  • { $push : { champ : { $each : [valeur, valeur, ... ] } } }
    • Ajoute plusieurs valeurs en une fois à un champ de liste
      • $push : { maliste : { $each : [ b, d, e, a] } } donnera  maliste = [ a, b, c, b, d, e, a]
  • { $add : { champ : valeur, ... }
    • Ajoute une valeur à un champ de liste mais si celle-ci n'y est pas déjà
      • Si la liste maliste est [ a, b, c], $add : { maliste : b } ne changera pas la liste, tandis que $add : { maliste : d } donnera maliste = [ a, b, c, d]
      • Si valeur est multiple (une liste) et que chacune des valeurs doit être intégrées : $add : { maliste : { $each : [ b, d, e, a] } } donnera maliste = [ a, b, c, d, e]
  • { $pop : { champ : 1 ou -1 } }
    • Retire le dernier (1) ou le premier (-1) élément de la liste
  • { $pull : { champ : valeur } }
    • Retire l'élément valeur de la liste
  • { $pull : { champ : { $each : [valeur, valeur, ... ] } } }
    • Retire plusieurs valeurs de la liste en une fois
  • { $sort : { champ : 1 ou -1 } }
    • Pour trier une liste selon un ordre ascendant (1) ou descendant (-1)

Un effacement : $delete

L’opération d’effacement prend 2 paramètres : le premier pour sélectionner les nœuds, le second pour contrôler son exécution.
{ $delete : { $query : query, $filter : filter }
argument query

  • Permet de sélectionner le ou les nœuds qui seront effacés
argument filter

  • $mult : true/false pour indiquer si la sélection des nœuds à mettre à jour peut donner de multiples nœuds (true) ou ne doit en retourner qu’un seul.
À noter que la suppression d’un nœud n’implique pas la suppression de ses fils. Si ses fils n’ont aucun père « vivant » (ils ont tout été effacés), ils sont alors à leurs tours supprimés de façon récursive.
La suppression de nœuds représentant des objets numériques (des feuilles) n’est possible que si le profil de requête l’autorise, avec une procédure de confirmation si nécessaire. Si le profil ne l’autorise pas, les feuilles ne sont pas supprimées et les objets numériques sont donc conservés a minima dans le référentiel du journal des entrées du SAE pour toute la durée requise de conservation.


Cas d’usage

Recherche par une indexation précise

La requête suivante recherche tous les nœuds fils du nœud « IdentifiantDuNoeudDeDepart », quelque soit la profondeur depuis ce nœud de départ, tels que le champ « champPrecis » contienne la valeur 12345 exactement. La requête retournera les enregistrements résultats de rang 30 à 39 selon un tri ascendant par le champ « creationDate », en retournant l’ensemble des champs disponibles de ces nœuds.


{ $select : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    {$eq : { champPrecis : 12345 }, $relativedepth : 0}],
  $filter : { $limite : 10, $offset : 30,
    $orderby : [{ creationDate : 1 }] },
  $projection : { fields : [ @all : 1] } } }

Recherche par mots-clefs ou recherche plein-texte

La requête suivante recherche tous les nœuds fils du nœud « IdentifiantDuNoeudDeDepart », jusqu’à une profondeur de +5 depuis ce nœud de départ, tels que le champ « champPrecis » contienne le mot clef « motclef » exactement ou que le champ « resume » contienne les mots « Un contenu à rechercher ». La requête retournera les 10 premiers enregistrements résultats selon un tri descendant par le champ « creationDate », en retournant les champs « identifiant » et « resume » de ces nœuds.


{ $select : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    {$or : [
      {$term : { champPrecis : "motclef" }},
      {$match : { resume : "Un contenu à rechercher"}} ],
    $relativedepth : +5}],
  $filter : { $limite : 10,
    $orderby : [{ creationDate : -1 }] },
  $projection : { fields : [ identifiant : 1, resume : 1 ] } } }

Recherche par affinité

La requête suivante recherche tous les nœuds fils du nœud « IdentifiantDuNoeudDeDepart », jusqu’à une profondeur de 5 (profondeur exacte), tels que les champs « resume » ou « title » ressemblent en termes de contenus à la phrase « le petit chat est mort » (requête « more like this »). La requête retournera les n premiers enregistrements résultats (n dépendant de la configuration du moteur de recherche) selon le score obtenu en ressemblance, en retournant tous les champs sauf « champNonVoulu » de ces nœuds.


{ $select : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    { $mlt : { $fields : [ resume, title ],
      $like : "le petit chat est mort" } },
    $depth : 5}],
  $filter : { },
  $projection : { fields : [ champNonVoulu : -1 ] } } }

Recherche par filiation

La requête suivante recherche d’abord tous les nœuds fils du nœud « IdentifiantDuNoeudDeDepart », sans limite de profondeur, tels que le champ « auteur » contienne obligatoirement la valeur « nomAuteur ». Puis la requête recherche tous les pères d’un niveau (-1) de ces nœuds tels que le champ « identifiantDossier » commence par la valeur « IdentifiantTronqué » (le ‘*’ permet d’indiquer que le mot peut être complété). La requête retournera les n premiers enregistrements résultats (n dépendant de la configuration du moteur de recherche) selon le score obtenu en ressemblance, en retournant tous les champs ainsi que le nombre de fils de chacun ces nœuds.


{ $select : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    {$search : { auteur : "+nomAuteur"}, $relativedepth : 0},
    {$wildcard : { identifiantDossier : "IdentifiantTronqué*" },
    $relativedepth : -1} ],
  $filter : { },
  $projection : { fields : [ @all : 1, @nbleaves : 1 ] } } }

Insertion d’un item depuis un dossier

La requête suivante permet d’insérer dans l’arbre le nœud représenté par le contenu de « $data » comme fils (item) de tous (« $mult » à « true ») les nœuds sélectionnés par la requête « $query ».


{ $insert : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    {$or : [
      {$term : { champPrecis : "motclef" }},
      {$eq : { identifiantNumerique : 123456 }} ],
    $relativedepth : +5}],
  $filter : { $mult : true },
  $data : { sousIdentifiant : 1234567, champPrecis2 : "motclef",
    autreChamp : "texte de description", liste : [10, 21],
autreValeur : 41 } } }

Mise à jour de valeurs

La requête suivante met à jour le précédent item ajouté à l’arbre (partie « $query ») avec les opérations suivantes (dans l’ordre) :
  • incrémente la valeur « autreValeur » de 1, soit la valeur finale 42 ;
  • retire le dernier élément de la liste « liste », soit liste = [ 10 ]
  • ajoute toutes les valeurs si elles ne sont pas déjà dans la liste, soit liste = [ 10, 22, 33 ]
  • crée un nouveau champ « nouveauChampDate » avec la valeur fournie en paramètre


{ $update : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    {$eq : { sousIdentifiant : 1234567 }},
    $relativedepth : +6}],
  $filter : { $mult : false },
  $action : { $inc : { autreValeur : 1 },
    $pop : { liste : 1 },
    $add : { liste : { $each : [10, 22, 33] } },
    $set : { nouveauChampDate : "Date au format Iso8601" } }
} }

Suppression d’un nœud

La requête suivante permet de demander l’effacement de l’item ajouté et mis à jour dans l’arbre via la sélection « $query ». Tous les nœuds parents verront leur lien vers ce fils supprimé.


{ $delete : {$query : [
    {$path : ["IdentifiantDuNoeudDeDepart"]},
    {$eq : { sousIdentifiant : 1234567 }},
    $relativedepth : +6}],
  $filter : { $mult : false }

} }

Aucun commentaire:

Enregistrer un commentaire