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
:
- 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.
- 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 :
- $query : [ { conditionNiveau1 }, { conditionNiveau2 }, { conditionNiveau3 } ], $projection : { fields : champs } }
En PseudoSQL/RDFSELECT champs FROM InTree( SELECT FROM InTree(SELECT FROM racine WHERE conditionNiveau1) WHERE conditionNiveau2) WHERE conditionNiveau3Nous proposons:
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" } }
]
"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")
- { $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 } }
- { $in : { champTableau : [ valeur1, valeur2, … ] } }
- { $nin : { champTableau : [ valeur1, valeur2, … ] } }
- 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é :
- 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 ».
- 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.
- Permet de spécifier la profondeur exacte maximale jusqu’où la requête va s’exécuter à partir des nœuds issus du contexte.
- 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)
- 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
- $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.
- 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
- $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.
- $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
- $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.
- { $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
- $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