REMARQUE : Le suivi des problèmes est désactivé. Vous êtes invités à contribuer, les demandes de tirage sont acceptées.
EJDB2 est un moteur de base de données JSON intégrable publié sous licence MIT.
L'histoire de la dépression informatique, des oiseaux et de l'EJDB 2.0
Linux | macOS | IOS | Androïde | Fenêtres | |
---|---|---|---|---|---|
bibliothèque C | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ 1 |
NoeudJS | ✔️ | ✔️ | 3 | ||
Java | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
DartVM 5 | ✔️ | ✔️ 2 | 3 | ||
Flutter 5 | ✔️ | ✔️ | |||
Réagir natif 5 | 4 | ✔️ | |||
Rapide 5 | ✔️ | ✔️ | ✔️ |
[5]
Les liaisons ne sont pas maintenues . Les contributeurs sont nécessaires.
[1]
Pas de support HTTP/Websocket #257
[2]
Les binaires ne sont pas distribués avec Dart pub.
Vous pouvez le construire manuellement
[3]
Peut être construit, mais nécessite un lien avec libs
Windows node/dart.
[4]
Portage en cours #273
Linux
, macOS
et FreeBSD
. A une prise en charge limitée de WindowsUtilisez-vous EJDB ? Fais-moi savoir!
Code EJDB2 porté et testé sur High Sierra
/ Mojave
/ Catalina
Liaison EJDB2 Swift pour MacOS, iOS et Linux. La liaison Swift est actuellement obsolète. Recherche de contributeurs.
brew install ejdb
cmake v3.24 ou supérieur requis
git clone --recurse-submodules [email protected]:Softmotions/ejdb.git
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make install
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_DEB=ON
make package
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_RPM=ON
make package
EJDB2 peut être compilé de manière croisée pour Windows
Remarque : L'API réseau HTTP/Websocket est désactivée et n'est pas encore prise en charge
Les liaisons Nodejs/Dart ne sont pas encore portées sur Windows.
Guide de compilation croisée pour Windows
IWSTART est un générateur automatique de projet initial CMake pour les projets C basé sur les bibliothèques iowow/iwnet/ejdb2.
https://github.com/Softmotions/iwstart
https://github.com/Softmotions/ejdb/tree/master/src/bindings/ejdb2_android/test
https://github.com/Softmotions/ejdb_android_todo_app
Syntaxe du langage de requête EJDB (JQL) inspirée des idées derrière les tubes shell XPath et Unix. Il est conçu pour faciliter l'interrogation et la mise à jour d'ensembles de documents JSON.
Analyseur JQL créé par peg/leg — générateurs d'analyseurs à descente récursive pour C Voici la grammaire formelle de l'analyseur : https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg
La notation utilisée ci-dessous est basée sur la description de la syntaxe SQL :
Règle | Description |
---|---|
' ' | La chaîne entre guillemets simples désigne une chaîne littérale sans guillemets dans le cadre de la requête. |
{ a | b } | Les accolades entourent au moins deux choix alternatifs obligatoires, séparés par des barres verticales. |
[ ] | Les crochets indiquent un élément ou une clause facultatif. Plusieurs éléments ou clauses sont séparés par des barres verticales. |
| | Des barres verticales séparent deux ou plusieurs éléments de syntaxe alternatifs. |
... | Les points de suspension indiquent que l'élément précédent peut être répété. La répétition est illimitée sauf indication contraire. |
( ) | Les parenthèses regroupent des symboles. |
Mot non cité en minuscule | Désigne la sémantique d’une partie de requête. Par exemple : placeholder_name - nom de n'importe quel espace réservé. |
QUERY = FILTERS [ '|' APPLY ] [ '|' PROJECTIONS ] [ '|' OPTS ];
STR = { quoted_string | unquoted_string };
JSONVAL = json_value;
PLACEHOLDER = { ':'placeholder_name | '?' }
FILTERS = FILTER [{ and | or } [ not ] FILTER];
FILTER = [@collection_name]/NODE[/NODE]...;
NODE = { '*' | '**' | NODE_EXPRESSION | STR };
NODE_EXPRESSION = '[' NODE_EXPR_LEFT OP NODE_EXPR_RIGHT ']'
[{ and | or } [ not ] NODE_EXPRESSION]...;
OP = [ '!' ] { '=' | '>=' | '<=' | '>' | '<' | ~ }
| [ '!' ] { 'eq' | 'gte' | 'lte' | 'gt' | 'lt' }
| [ not ] { 'in' | 'ni' | 're' };
NODE_EXPR_LEFT = { '*' | '**' | STR | NODE_KEY_EXPR };
NODE_KEY_EXPR = '[' '*' OP NODE_EXPR_RIGHT ']'
NODE_EXPR_RIGHT = JSONVAL | STR | PLACEHOLDER
APPLY = { 'apply' | 'upsert' } { PLACEHOLDER | json_object | json_array } | 'del'
OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
ORDERBY = { 'asc' | 'desc' } PLACEHOLDER | json_path
PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ]
PROJECTION = 'all' | json_path
json_value
: toute valeur JSON valide : objet, tableau, chaîne, booléen, nombre.json_path
: pointeur JSON simplifié. Ex. : /foo/bar
ou /foo/"bar with spaces"/
*
dans le contexte de NODE
: tout nom de clé d'objet JSON à un niveau d'imbrication particulier.**
dans le contexte de NODE
: tout nom de clé d'objet JSON à un niveau d'imbrication arbitraire.*
dans le contexte de NODE_EXPR_LEFT
: Nom de clé à un niveau spécifique.**
dans le contexte de NODE_EXPR_LEFT
: valeur de tableau imbriquée de l'élément du tableau sous une clé spécifique. Jouons avec quelques données et requêtes très basiques. Pour plus de simplicité, nous utiliserons l'API réseau ejdb websocket qui nous fournit une sorte de CLI interactive. Le même travail peut également être effectué en utilisant l'API C
pure ( ejdb2.h jql.h
).
REMARQUE : jetez un œil aux cas de test JQL pour plus d’exemples.
{
"firstName" : " John " ,
"lastName" : " Doe " ,
"age" : 28 ,
"pets" : [
{ "name" : " Rexy rex " , "kind" : " dog " , "likes" : [ " bones " , " jumping " , " toys " ]},
{ "name" : " Grenny " , "kind" : " parrot " , "likes" : [ " green color " , " night " , " toys " ]}
]
}
Enregistrez json sous sample.json
puis téléchargez-le dans la collection family
:
# Start HTTP/WS server protected by some access token
./jbs -a ' myaccess01 '
8 Mar 16:15:58.601 INFO: HTTP/WS endpoint at localhost:9191
Le serveur est accessible à l’aide du point de terminaison HTTP ou Websocket. Plus d'informations
curl -d ' @sample.json ' -H ' X-Access-Token:myaccess01 ' -X POST http://localhost:9191/family
Nous pouvons jouer en utilisant le client Websocket interactif wscat.
wscat -H ' X-Access-Token:myaccess01 ' -c http://localhost:9191
connected (press CTRL+C to quit)
> k info
< k {
" version " : " 2.0.0 " ,
" file " : " db.jb " ,
" size " : 8192,
" collections " : [
{
" name " : " family " ,
" dbid " : 3,
" rnum " : 1,
" indexes " : []
}
]
}
> k get family 1
< k 1 {
" firstName " : " John " ,
" lastName " : " Doe " ,
" age " : 28,
" pets " : [
{
" name " : " Rexy rex " ,
" kind " : " dog " ,
" likes " : [
" bones " ,
" jumping " ,
" toys "
]
},
{
" name " : " Grenny " ,
" kind " : " parrot " ,
" likes " : [
" green color " ,
" night " ,
" toys "
]
}
]
}
Notez le préfixe k
avant chaque commande ; Il s'agit d'une clé arbitraire choisie par le client et désignée pour identifier une demande Websocket particulière, cette clé sera renvoyée avec la réponse à la demande et permettra au client d'identifier cette réponse pour sa demande particulière. Plus d'informations
La commande de requête sur Websocket a le format suivant :
<key> query <collection> <query>
Nous ne considérerons donc que la partie <query>
dans ce document.
k query family /*
ou
k query family /**
ou spécifiez explicitement le nom de la collection dans la requête
k @family/*
Nous pouvons exécuter une requête par requête HTTP POST
curl --data-raw '@family/[firstName = John]' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191
1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}]}
k @family/* | limit 10
L'élément à l'index 1
existe dans un tableau likes
au sein d'un sous-objet pets
> k query family /pets/*/likes/1
< k 1 {"firstName":"John"...
L'élément à l'index 1
existe dans le tableau likes
à n'importe quel niveau d'imbrication likes
> k query family /**/likes/1
< k 1 {"firstName":"John"...
À partir de ce point et ci-dessous, j'omettra k query family
du préfixe spécifique au websocket et ne considérerai que les requêtes JQL.
Afin d'obtenir des documents par clé primaire, les options suivantes sont disponibles :
Utilisez l'appel API ejdb_get()
const doc = await db . get ( 'users' , 112 ) ;
Utilisez la construction de requête spéciale : /=:?
ou @collection/=:?
Récupérer le document de la collection users
avec la clé primaire 112
> k @users/=112
Mettre à jour le tableau de balises pour le document dans la collection jobs
(TypeScript) :
await db . createQuery ( '@jobs/ = :? | apply :? | count' )
. setNumber ( 0 , id )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Un tableau de clés primaires peut également être utilisé pour la correspondance :
await db . createQuery ( '@jobs/ = :?| apply :? | count' )
. setJSON ( 0 , [ 23 , 1 , 2 ] )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Vous trouverez ci-dessous un ensemble de requêtes auto-explicatives :
/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]
Notez les guillemets autour des mots avec des espaces.
Obtenez tous les documents dont le propriétaire a plus de 20
age
et possède un animal de compagnie qui aime bones
ou toys
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
Ici, **
désigne un élément du tableau likes
.
ni
est l'opérateur inverse de in
. Obtenez des documents où bones
quelque part dans le tableau likes
.
/pets/*/[likes ni "bones"]
Nous pouvons créer des filtres plus compliqués
( /[age <= 20] or /[lastName re "Do.*"] )
and /pets/*/likes/[** in ["bones", "toys"]]
Remarque sur le regroupement des parenthèses et la correspondance des expressions régulières à l'aide de l'opérateur re
.
~
est un opérateur de correspondance de préfixe (depuis ejdb v2.0.53
). La correspondance de préfixe peut bénéficier de l’utilisation d’index.
Obtenez les documents où /lastName
commence par "Do"
.
/[lastName ~ Do]
Filtrer les documents avec un tableau likes
correspondant exactement à ["bones","jumping","toys"]
/**/[likes = ["bones","jumping","toys"]]
Les algorithmes de correspondance pour les tableaux et les cartes sont différents :
{"f":"d","e":"j"}
et {"e":"j","f":"d"}
sont des cartes égales. Recherchez le document JSON ayant la clé firstName
au niveau racine.
/[* = "firstName"]
Dans ce contexte, *
désigne un nom de clé.
Vous pouvez utiliser des conditions sur le nom de la clé et la valeur de la clé en même temps :
/[[* = "firstName"] = John]
Le nom de la clé peut être soit firstName
, soit lastName
, mais doit dans tous les cas avoir la valeur John
.
/[[* in ["firstName", "lastName"]] = John]
Cela peut être utile dans les requêtes avec des espaces réservés dynamiques (API C) :
/[[* = :keyName] = :keyValue]
Section APPLY
responsable de la modification du contenu des documents.
APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del'
Les spécifications du correctif JSON étaient conformes aux spécifications rfc7386
ou rfc6902
suivies du mot clé apply
.
Ajoutons un objet address
à tous les documents correspondants
/[firstName = John] | apply {"address":{"city":"New York", "street":""}}
Si l'objet JSON est un argument de la section apply
, il sera traité comme une correspondance de fusion ( rfc7386
), sinon il devrait s'agir d'un tableau qui désigne le correctif JSON rfc6902
. Espaces réservés également pris en charge par la section apply
.
/* | apply :?
Définir le nom de la rue dans address
/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]
Ajoutez du poisson Neo
à l'ensemble des pets
de John
/[firstName = John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]
upsert
met à jour le document existant en fonction de l'argument json donné utilisé comme correctif de fusion ou insère l'argument json fourni en tant que nouvelle instance de document.
/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}
Incrémente la valeur numérique identifiée par le chemin JSON par la valeur spécifiée.
Exemple:
Document: {"foo": 1}
Patch: [{"op": "increment", "path": "/foo", "value": 2}]
Result: {"foo": 3}
Identique add
de correctif JSON, mais crée des nœuds d'objet intermédiaires pour les segments de chemin JSON manquants.
Exemple:
Document: {"foo": {"bar": 1}}
Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}]
Result: {"foo":{"bar":1,"zaz":{"gaz":22}}}
Exemple:
Document: {"foo": {"bar": 1}}
Patch: [{"op": "add_create", "path": "/foo/bar/gaz", "value": 22}]
Result: Error since element pointed by /foo/bar is not an object
Échange deux valeurs du document JSON à partir from
chemin.
Règles d'échange
from
n’existe pas, une erreur sera générée.path
n'existe pas, elle sera définie par la valeur from
chemin, puis l'objet pointé par from
chemin sera supprimé.from
et path
sont présentées, elles seront échangées.Exemple:
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]
Result: {"foo": [11], "baz": {"gaz": "bar"}}
Exemple (Démo de la règle 2) :
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]
Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}
Utilisez le mot-clé del
pour supprimer les éléments correspondants de la collection :
/FILTERS | del
Exemple:
> k add family {"firstName":"Jack"}
< k 2
> k query family /[firstName re "Ja.*"]
< k 2 {"firstName":"Jack"}
# Remove selected elements from collection
> k query family /[firstName=Jack] | del
< k 2 {"firstName":"Jack"}
PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ]
PROJECTION = 'all' | json_path | join_clause
La projection permet d'obtenir uniquement un sous-ensemble du document JSON, à l'exclusion des données inutiles.
L’API des espaces réservés de requête est prise en charge dans les projections.
Ajoutons un document supplémentaire à notre collection :
$ cat << EOF | curl -d @- -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family
{
"firstName":"Jack",
"lastName":"Parker",
"age":35,
"pets":[{"name":"Sonic", "kind":"mouse", "likes":[]}]
}
EOF
Interrogez désormais uniquement les propriétaires d'animaux firstName et lastName de la collection.
> k query family /* | /{firstName,lastName}
< k 3 {"firstName":"Jack","lastName":"Parker"}
< k 1 {"firstName":"John","lastName":"Doe"}
< k
Ajouter un tableau pets
pour chaque document
> k query family /* | /{firstName,lastName} + /pets
< k 3 {"firstName":"Jack","lastName":"Parker","pets":[...
< k 1 {"firstName":"John","lastName":"Doe","pets":[...
Exclure uniquement le champ pets
des documents
> k query family /* | all - /pets
< k 3 {"firstName":"Jack","lastName":"Parker","age":35}
< k 1 {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}}
< k
Ici, all
les mots-clés utilisés désignent l'ensemble du document.
Obtenez age
et le premier animal de compagnie dans le tableau pets
.
> k query family /[age > 20] | /age + /pets/0
< k 3 {"age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]}
< k 1 {"age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]}]}
< k
La jointure matérialise la référence au document à un objet de document réel qui remplacera la référence sur place.
Les documents sont joints uniquement par leurs clés primaires.
Les clés de référence doivent être stockées dans le document référent sous forme de champ numérique ou de chaîne.
Les jointures peuvent être spécifiées dans le cadre d'une expression de projection sous la forme suivante :
/.../field<collection
Où
field
‐ Le champ JSON contient la clé primaire du document joint.<
‐ Le symbole de marque spécial qui demande au moteur EJDB de remplacer la clé field
par le corps du document joint.collection
- nom de la collection DB où se trouvent les documents joints.Un document référent ne sera pas modifié si le document associé n'est pas trouvé.
Voici la démonstration simple des jointures de collection dans notre shell websocket interactif :
> k add artists {"name":"Leonardo Da Vinci", "years":[1452,1519]}
< k 1
> k add paintings {"name":"Mona Lisa", "year":1490, "origin":"Italy", "artist": 1}
< k 1
> k add paintings {"name":"Madonna Litta - Madonna And The Child", "year":1490, "origin":"Italy", "artist": 1}
< k 2
# Lists paintings documents
> k @paintings/*
< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":1}
< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":1}
< k
>
# Do simple join with artists collection
> k @paintings/* | /artist<artists
< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy",
"artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy",
"artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k
# Strip all document fields except `name` and `artist` join
> k @paintings/* | /artist<artists + /name + /artist/*
< k 2 {"name":"Madonna Litta - Madonna And The Child","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k 1 {"name":"Mona Lisa","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k
>
# Same results as above:
> k @paintings/* | /{name, artist<artists} + /artist/*
< k 2 {"name":"Madonna Litta - Madonna And The Child","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k 1 {"name":"Mona Lisa","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k
Références invalides :
> k add paintings {"name":"Mona Lisa2", "year":1490, "origin":"Italy", "artist": 9999}
< k 3
> k @paintings/* | /artist<artists
< k 3 {"name":"Mona Lisa2","year":1490,"origin":"Italy","artist":9999}
< k 2 {"name":"Madonna Litta - Madonna And The Child","year":1490,"origin":"Italy","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
< k 1 {"name":"Mona Lisa","year":1490,"origin":"Italy","artist":{"name":"Leonardo Da Vinci","years":[1452,1519]}}
ORDERBY = ({ 'asc' | 'desc' } PLACEHOLDER | json_path)...
Ajoutons un document supplémentaire, puis trions les documents de la collection par ordre croissant firstName
et age
décroissant.
> k add family {"firstName":"John", "lastName":"Ryan", "age":39}
< k 4
> k query family /* | /{firstName,lastName,age} | asc /firstName desc /age
< k 3 {"firstName":"Jack","lastName":"Parker","age":35}
< k 4 {"firstName":"John","lastName":"Ryan","age":39}
< k 1 {"firstName":"John","lastName":"Doe","age":28}
< k
Les instructions asc, desc
peuvent utiliser des index définis pour la collecte afin d'éviter une étape de tri des documents distincte.
OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
skip n
Ignorer les n
premiers enregistrements avant le premier élément du jeu de résultatslimit n
Définir le nombre maximum de documents dans le jeu de résultatscount
Renvoie uniquement count
de documents correspondants > k query family /* | count
< k 3
< k
noidx
N'utilisez aucun index pour l'exécution des requêtes.inverse
Par défaut, la requête analyse les documents les plus récemment ajoutés aux plus anciens. Cette option inverse la direction de numérisation en sens inverse et active le mode noidx
. N'a aucun effet si la requête comporte des clauses de tri asc/desc
. L'index de base de données peut être construit pour n'importe quel chemin de champ JSON contenant des valeurs de type nombre ou chaîne. L'index peut être unique
- ne permettant pas la duplication de valeur et non unique
. Les indicateurs de masque de bits en mode index suivants sont utilisés (définis dans ejdb2.h
) :
Mode index | Description |
---|---|
0x01 EJDB_IDX_UNIQUE | L'index est unique |
0x04 EJDB_IDX_STR | Index pour le type de valeur du champ string JSON |
0x08 EJDB_IDX_I64 | Index pour les valeurs de champ entier signé 8 bytes width |
0x10 EJDB_IDX_F64 | Index pour les valeurs de champ à virgule flottante signées 8 bytes width . |
Par exemple, l'index unique de type chaîne sera spécifié par EJDB_IDX_UNIQUE | EJDB_IDX_STR
= 0x05
. L'index peut être défini pour un seul type de valeur situé sous un chemin spécifique dans le document json.
Définissons un index de chaîne non unique pour le chemin /lastName
:
> k idx family 4 /lastName
< k
Sélection d'index pour les requêtes basées sur un ensemble de règles heuristiques.
Vous pouvez toujours vérifier l'utilisation de l'index en exécutant la commande explain
dans l'API WS :
> k explain family /[lastName=Doe] and /[age!=27]
< k explain [INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ
[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ
[COLLECTOR] PLAIN
Les instructions suivantes sont prises en compte lors de l'utilisation des index EJDB2 :
Un seul index peut être utilisé pour l'exécution d'une requête particulière
Si la requête est constituée d' or
partie jointe au niveau supérieur ou contient des expressions negated
au niveau supérieur de l'expression de requête, les index ne seront pas utilisés du tout. Donc pas d'index ci-dessous :
/[lastName != Andy]
/[lastName = "John"] or /[lastName = Peter]
Mais sera utilisé l'index /lastName
défini ci-dessus
/[lastName = Doe]
/[lastName = Doe] and /[age = 28]
/[lastName = Doe] and not /[age = 28]
/[lastName = Doe] and /[age != 28]
Les opérateurs suivants sont pris en charge par les index (ejdb 2.0.x) :
eq, =
gt, >
gte, >=
lt, <
lte, <=
in
~
(Correspondance du préfixe depuis ejdb 2.0.53) Les clauses ORDERBY
peuvent utiliser des index pour éviter le tri des jeux de résultats.
Les champs de tableau peuvent également être indexés. Décrivons un cas d'utilisation typique : indexation de certaines balises d'entité :
> k add books {"name":"Mastering Ultra", "tags":["ultra", "language", "bestseller"]}
< k 1
> k add books {"name":"Learn something in 24 hours", "tags":["bestseller"]}
< k 2
> k query books /*
< k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]}
< k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]}
< k
Créer un index de chaîne pour /tags
> k idx books 4 /tags
< k
Filtrez les livres par balise bestseller
et affichez l'utilisation de l'index dans la requête :
> k explain books /tags/[** in ["bestseller"]]
< k explain [INDEX] MATCHED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ
[INDEX] SELECTED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ
[COLLECTOR] PLAIN
< k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]}
< k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]}
< k
Tous les documents en collection sont triés par leur clé primaire par ordre descending
. Ainsi, si vous utilisez des clés générées automatiquement ( ejdb_put_new
), vous pouvez être sûr que les documents récupérés à la suite d'une requête d'analyse complète seront classés en fonction du moment de l'insertion dans l'ordre descendant, à moins que vous n'utilisiez le tri des requêtes, les index ou les mots-clés inverse
.
Dans de nombreux cas, l’utilisation d’index peut réduire les performances globales des requêtes. Étant donné que la collection d'index ne contient que des références de document ( id
) et que le moteur peut effectuer une récupération de document supplémentaire par sa clé primaire pour terminer la correspondance des requêtes. Ainsi, pour des collections moins volumineuses, une analyse brute peut être plus efficace qu'une analyse à l'aide d'index. Cependant, les opérations de correspondance exacte : eq
, in
et sorting
par ordre d'index naturel bénéficieront de l'index dans la plupart des cas.
Si vous souhaitez mettre à jour un ensemble de documents avec des opérations apply
ou del
mais que vous ne souhaitez pas les récupérer tous à la suite d'une requête, ajoutez simplement un modificateur count
à la requête pour vous débarrasser du transfert de données inutile et de la conversion de données json.
Le moteur EJDB offre la possibilité de démarrer un point de terminaison HTTP/Websocket distinct exposant l'API réseau pour les requêtes et les modifications de données. SSL (TLS 1.2) est pris en charge par le serveur jbs
.
Le moyen le plus simple d'exposer la base de données sur le réseau consiste à utiliser le serveur jbs
autonome. (Bien sûr, si vous souhaitez éviter l'intégration C API
).
Usage:
./jbs [options]
-v, --version Print program version.
-f, --file=<> Database file path. Default: ejdb2.db
-p, --port=NUM HTTP server port numer. Default: 9191
-l, --listen=<> Network address server will listen. Default: localhost
-k, --key=<> PEM private key file for TLS 1.2 HTTP server.
-c, --certs=<> PEM certificates file for TLS 1.2 HTTP server.
-a, --access=TOKEN|@FILE Access token to match 'X-Access-Token' HTTP header value.
-r, --access-read Allows unrestricted read-only data access.
-C, --cors Enable COSR response headers for HTTP server
-t, --trunc Cleanup/reset database file on open.
-w, --wal use the write ahead log (WAL). Used to provide data durability.
Advanced options:
-S, --sbz=NUM Max sorting buffer size. If exceeded, an overflow temp file for data will be created.
Default: 16777216, min: 1048576
-D, --dsz=NUM Initial size of buffer to process/store document on queries. Preferable average size of document.
Default: 65536, min: 16384
-T, --trylock Exit with error if database is locked by another process.
If not set, current process will wait for lock release.
Le point de terminaison HTTP peut être protégé par un jeton spécifié avec l'indicateur --access
ou la structure EJDB_HTTP
de l'API C. Si le jeton d'accès a été défini, le client doit fournir l'en-tête HTTP X-Access-Token
. Si le jeton est requis mais n'est pas fourni par le client, le code HTTP 401
sera signalé. Si le jeton d'accès ne correspond pas au jeton fourni par le serveur client, il répondra avec un code HTTP 403
.
Ajouter un nouveau document à la collection
.
200
succès. Corps : un nouvel identifiant de document sous forme de numéro int64
Remplace/stocke le document sous id
numérique spécifique
200
en cas de réussite. Corps vide Supprime le document identifié par id
d'une collection
200
en cas de réussite. Corps vide404
introuvable Corrigez un document identifié par id
par les données rfc7396, rfc6902.
200
en cas de réussite. Corps vide Récupérer le document identifié par id
dans une collection
.
200
en cas de réussite. Corps : texte du document JSON.content-type:application/json
content-length:
404
introuvable Interrogez une collection à l’aide de la requête fournie en tant que corps POST. Le corps de la requête doit contenir le nom de la collection utilisée dans le premier élément de filtre : @collection_name/...
En-têtes de requête :
X-Hints
pour le moteur de base de données ejdb2.explain
Afficher le plan d'exécution de la requête avant le premier élément du jeu de résultats, séparé par la ligne --------------------
. Réponse:200
en cas de réussite.n
au format suivant : rn<document id>t<document JSON body>
...
Exemple:
curl -v --data-raw '@family/[age > 18]' -H 'X-Access-Token:myaccess01' http://localhost:9191
* Rebuilt URL to: http://localhost:9191/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9191 (#0)
> POST / HTTP/1.1
> Host: localhost:9191
> User-Agent: curl/7.58.0
> Accept: */*
> X-Access-Token:myaccess01
> Content-Length: 18
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 200 OK
< connection:keep-alive
< content-type:application/json
< transfer-encoding:chunked
<
4 {"firstName":"John","lastName":"Ryan","age":39}
3 {"firstName":"Jack","lastName":"Parker","age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]}
1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}],"address":{"city":"New York","street":"Fifth Avenue"}}
* Connection #0 to host localhost left intact
curl --data-raw '@family/[lastName = "Ryan"]' -H 'X-Access-Token:myaccess01' -H 'X-Hints:explain' http://localhost:9191
[INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ
[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ
[COLLECTOR] PLAIN
--------------------
4 {"firstName":"John","lastName":"Ryan","age":39}
Récupérez les métadonnées JSON ejdb et les méthodes HTTP disponibles dans l'en-tête de réponse Allow
. Exemple:
curl -X OPTIONS -H 'X-Access-Token:myaccess01' http://localhost:9191/
{
"version": "2.0.0",
"file": "db.jb",
"size": 16384,
"collections": [
{
"name": "family",
"dbid": 3,
"rnum": 3,
"indexes": [
{
"ptr": "/lastName",
"mode": 4,
"idbf": 64,
"dbid": 4,
"rnum": 3
}
]
}
]
}
EJDB prend en charge un protocole simple basé sur du texte sur le protocole HTTP Websocket. Vous pouvez utiliser l'outil CLI websocket interactif wscat pour communiquer manuellement avec le serveur.
Répondra avec le message texte d'aide suivant :
wscat -H 'X-Access-Token:myaccess01' -c http://localhost:9191
> ?
<
<key> info
<key> get <collection> <id>
<key> set <collection> <id> <document json>
<key> add <collection> <document json>
<key> del <collection> <id>
<key> patch <collection> <id> <patch json>
<key> idx <collection> <mode> <path>
<key> rmi <collection> <mode> <path>
<key> rmc <collection>
<key> query <collection> <query>
<key> explain <collection> <query>
<key> <query>
>
Remarque sur le préfixe <key>
avant chaque commande ; Il s'agit d'une clé arbitraire choisie par le client et désignée pour identifier une demande Websocket particulière, cette clé sera renvoyée avec la réponse à la demande et permettra au client d'identifier cette réponse pour sa demande particulière.
Les erreurs sont renvoyées au format suivant :
<key> ERROR: <error description>
<key> info
Obtenez les métadonnées de la base de données sous forme de document JSON.
<key> get <collection> <id>
Récupérer le document identifié par id
dans une collection
. Si le document n'est pas trouvé, IWKV_ERROR_NOTFOUND
sera renvoyé.
Exemple:
> k get family 3
< k 3 {
"firstName": "Jack",
"lastName": "Parker",
"age": 35,
"pets": [
{
"name": "Sonic",
"kind": "mouse",
"likes": []
}
]
}
Si le document n'est pas trouvé, nous obtiendrons l'erreur :
> k get family 55
< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND)
>
<key> set <collection> <id> <document json>
Remplace/ajoute un document sous id
numérique spécifique. Collection
sera créée automatiquement si elle n'existe pas.
<key> add <collection> <document json>
Ajouter un nouveau document à <collection>
Le nouvel id
du document sera généré et renvoyé en réponse. `Collection> sera créée automatiquement si elle n'existe pas.
Exemple:
> k add mycollection {"foo":"bar"}
< k 1
> k add mycollection {"foo":"bar"}
< k 2
>
<key> del <collection> <id>
Supprimer le document identifié par id
de la collection
. Si le document n'est pas trouvé, IWKV_ERROR_NOTFOUND
sera renvoyé.
<key> patch <collection> <id> <patch json>
Appliquez le correctif rfc7396 ou rfc6902 au document identifié par id
. Si le document n'est pas trouvé, IWKV_ERROR_NOTFOUND
sera renvoyé.
<key> query <collection> <query>
Exécuter une requête sur les documents de collection
spécifiée. Réponse : Un ensemble de messages WS avec des corps de document terminés par le dernier message avec un corps vide.
> k query family /* | /firstName
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Remarque sur le dernier message : <key>
sans corps.
<key> explain <collection> <query>
Identique à <key> query <collection> <query>
mais le premier message de réponse sera préfixé par <key> explain
et contient le plan d'exécution de la requête.
Exemple:
> k explain family /* | /firstName
< k explain [INDEX] NO [COLLECTOR] PLAIN
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Exécute le texte de la requête. Le corps de la requête doit contenir le nom de la collection utilisée dans le premier élément de filtre : @collection_name/...
. Le comportement est le même que pour : <key> query <collection> <query>
<key> idx <collection> <mode> <path>
Garantit l'index avec mode
spécifié (indicateur de masque de bits) pour path
et collection
json donnés. La collection sera créée si elle n’existe pas.
Mode index | Description |
---|---|
0x01 EJDB_IDX_UNIQUE | L'index est unique |
0x04 EJDB_IDX_STR | Index pour le type de valeur du champ string JSON |
0x08 EJDB_IDX_I64 | Index pour les valeurs de champ entier signé 8 bytes width |
0x10 EJDB_IDX_F64 | Index pour les valeurs de champ à virgule flottante signées 8 bytes width . |
Définissez l'index de chaîne unique (0x01 & 0x04) = 5
sur le champ JSON /name
:
k idx mycollection 5 /name
<key> rmi <collection> <mode> <path>
Supprimez l'index avec mode
spécifié (indicateur de masque de bits) pour path
et collection
json donnés. Renvoie une erreur si l'index donné est introuvable.
<key> rmc <collection>
Supprimez la collection et toutes ses données. Remarque : Si collection
n'est pas trouvée, aucune erreur ne sera signalée.
Si Docker est installé, vous pouvez créer une image Docker et l'exécuter dans un conteneur
cd docker
docker build -t ejdb2 .
docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey
ou obtenez une image d' ejdb2
directement depuis le Docker Hub
docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey
EJDB peut être intégré dans n'importe quelle application C/C++
. C API
documentée dans les en-têtes suivants :
Exemple d'application :
#include <ejdb2/ejdb2.h>
#define CHECK ( rc_ )
if (rc_) {
iwlog_ecode_error3(rc_);
return 1;
}
static iwrc documents_visitor ( EJDB_EXEC * ctx , const EJDB_DOC doc , int64_t * step ) {
// Print document to stderr
return jbl_as_json ( doc -> raw , jbl_fstream_json_printer , stderr , JBL_PRINT_PRETTY );
}
int main () {
EJDB_OPTS opts = {
. kv = {
. path = "example.db" ,
. oflags = IWKV_TRUNC
}
};
EJDB db ; // EJDB2 storage handle
int64_t id ; // Document id placeholder
JQL q = 0 ; // Query instance
JBL jbl = 0 ; // Json document
iwrc rc = ejdb_init ();
CHECK ( rc );
rc = ejdb_open ( & opts , & db );
CHECK ( rc );
// First record
rc = jbl_from_json ( & jbl , "{"name":"Bianca", "age":4}" );
RCGO ( rc , finish );
rc = ejdb_put_new ( db , "parrots" , jbl , & id );
RCGO ( rc , finish );
jbl_destroy ( & jbl );
// Second record
rc = jbl_from_json ( & jbl , "{"name":"Darko", "age":8}" );
RCGO ( rc , finish );
rc = ejdb_put_new ( db , "parrots" , jbl , & id );
RCGO ( rc , finish );
jbl_destroy ( & jbl );
// Now execute a query
rc = jql_create ( & q , "parrots" , "/[age > :age]" );
RCGO ( rc , finish );
EJDB_EXEC ux = {
. db = db ,
. q = q ,
. visitor = documents_visitor
};
// Set query placeholder value.
// Actual query will be /[age > 3]
rc = jql_set_i64 ( q , "age" , 0 , 3 );
RCGO ( rc , finish );
// Now execute the query
rc = ejdb_exec ( & ux );
finish:
jql_destroy ( & q );
jbl_destroy ( & jbl );
ejdb_close ( & db );
CHECK ( rc );
return 0 ;
}
Compilez et exécutez :
gcc -std=gnu11 -Wall -pedantic -c -o example1.o example1.c
gcc -o example1 example1.o -lejdb2
./example1
{
"name": "Darko",
"age": 8
}{
"name": "Bianca",
"age": 4
}
MIT License
Copyright (c) 2012-2024 Softmotions Ltd <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.