HINWEIS: Der Issues-Tracker ist deaktiviert. Sie können gerne einen Beitrag leisten, Pull-Requests werden akzeptiert.
EJDB2 ist eine einbettbare JSON-Datenbank-Engine, die unter MIT-Lizenz veröffentlicht wird.
Die Geschichte der IT-Depression, Vögel und EJDB 2.0
Linux | macOS | iOS | Android | Windows | |
---|---|---|---|---|---|
C-Bibliothek | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ 1 |
NodeJS | ✔️ | ✔️ | 3 | ||
Java | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
DartVM 5 | ✔️ | ✔️ 2 | 3 | ||
Flattern 5 | ✔️ | ✔️ | |||
Native reagieren 5 | 4 | ✔️ | |||
Swift 5 | ✔️ | ✔️ | ✔️ |
[5]
Bindungen werden nicht gepflegt . Mitwirkende werden benötigt.
[1]
Keine HTTP/Websocket-Unterstützung #257
[2]
Binärdateien werden nicht mit Dart pub.
Sie können es manuell erstellen
[3]
Kann erstellt werden, benötigt aber eine Verknüpfung mit Windows Node/Dart libs
.
[4]
Portierung läuft #273
Linux
, macOS
und FreeBSD
. Hat eingeschränkte Windows-UnterstützungVerwenden Sie EJDB? Lass es mich wissen!
EJDB2-Code portiert und getestet auf High Sierra
/ Mojave
/ Catalina
EJDB2 Swift-Bindung für MacOS, iOS und Linux. Die schnelle Bindung ist mittlerweile veraltet. Auf der Suche nach Mitwirkenden.
brew install ejdb
cmake v3.24 oder höher erforderlich
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 kann für Windows überkompiliert werden
Hinweis: Die HTTP/Websocket-Netzwerk-API ist deaktiviert und wird noch nicht unterstützt
Nodejs/Dart-Bindungen noch nicht auf Windows portiert.
Cross-Compilation-Handbuch für Windows
IWSTART ist ein automatischer CMake-Erstprojektgenerator für C-Projekte basierend auf iowow/iwnet/ejdb2-Bibliotheken.
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
Die Syntax der EJDB-Abfragesprache (JQL) wurde von den Ideen hinter XPath und Unix-Shell-Pipes inspiriert. Es wurde für die einfache Abfrage und Aktualisierung von JSON-Dokumentsätzen entwickelt.
JQL-Parser erstellt von peg/leg – rekursiv absteigende Parsergeneratoren für C Hier ist die formale Parser-Grammatik: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg
Die unten verwendete Notation basiert auf der SQL-Syntaxbeschreibung:
Regel | Beschreibung |
---|---|
' ' | Eine Zeichenfolge in einfachen Anführungszeichen bezeichnet ein Zeichenfolgenliteral ohne Anführungszeichen als Teil der Abfrage. |
{ a | b } | Geschweifte Klammern umschließen zwei oder mehr erforderliche Alternativmöglichkeiten, getrennt durch vertikale Balken. |
[ ] | Eckige Klammern weisen auf ein optionales Element oder eine optionale Klausel hin. Mehrere Elemente oder Klauseln werden durch vertikale Balken getrennt. |
| | Vertikale Balken trennen zwei oder mehr alternative Syntaxelemente. |
... | Ellipsen zeigen an, dass das vorhergehende Element wiederholt werden kann. Die Wiederholung ist unbegrenzt, sofern nicht anders angegeben. |
( ) | Klammern sind Gruppierungssymbole. |
Wort ohne Anführungszeichen in Kleinbuchstaben | Bezeichnet die Semantik eines Abfrageteils. Beispiel: placeholder_name – Name eines beliebigen Platzhalters. |
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
: Jeder gültige JSON-Wert: Objekt, Array, String, Bool, Zahl.json_path
: Vereinfachter JSON-Zeiger. Bsp.: /foo/bar
oder /foo/"bar with spaces"/
*
im Kontext von NODE
: Jeder JSON-Objektschlüsselname auf einer bestimmten Verschachtelungsebene.**
im Kontext von NODE
: Beliebiger JSON-Objektschlüsselname auf beliebiger Verschachtelungsebene.*
im Kontext von NODE_EXPR_LEFT
: Schlüsselname auf einer bestimmten Ebene.**
im Kontext von NODE_EXPR_LEFT
: Verschachtelter Array-Wert des Array-Elements unter einem bestimmten Schlüssel. Spielen wir mit einigen sehr grundlegenden Daten und Abfragen. Der Einfachheit halber verwenden wir die ejdb-Websocket-Netzwerk-API, die uns eine Art interaktives CLI bietet. Die gleiche Aufgabe kann auch mit der reinen C
API ( ejdb2.h jql.h
) erledigt werden.
HINWEIS: Weitere Beispiele finden Sie in den JQL-Testfällen.
{
"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 " ]}
]
}
Speichern Sie JSON als sample.json
und laden Sie es dann in die family
hoch:
# 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
Auf den Server kann über HTTP oder einen Websocket-Endpunkt zugegriffen werden. Weitere Informationen
curl -d ' @sample.json ' -H ' X-Access-Token:myaccess01 ' -X POST http://localhost:9191/family
Wir können mit dem interaktiven wscat-Websocket-Client herumspielen.
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 "
]
}
]
}
Hinweis zum Präfix k
vor jedem Befehl; Dabei handelt es sich um einen willkürlichen Schlüssel, der vom Client ausgewählt und zur Identifizierung einer bestimmten Websocket-Anfrage bestimmt ist. Dieser Schlüssel wird mit der Antwort auf die Anfrage zurückgegeben und ermöglicht es dem Client, diese Antwort für seine bestimmte Anfrage zu identifizieren. Weitere Informationen
Der Abfragebefehl über Websocket hat das folgende Format:
<key> query <collection> <query>
Daher werden wir in diesem Dokument nur den Teil <query>
betrachten.
k query family /*
oder
k query family /**
oder geben Sie den Sammlungsnamen in der Abfrage explizit an
k @family/*
Wir können eine Abfrage per HTTP POST
Anfrage ausführen
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
Element mit Index 1
existiert im likes
Array innerhalb eines pets
-Unterobjekts
> k query family /pets/*/likes/1
< k 1 {"firstName":"John"...
Das Element mit Index 1
existiert im likes
Array auf jeder likes
Verschachtelungsebene
> k query family /**/likes/1
< k 1 {"firstName":"John"...
Ab diesem Punkt und im Folgenden werde ich die WebSocket-spezifische Präfix- k query family
weglassen und nur JQL-Abfragen berücksichtigen.
Um Dokumente per Primärschlüssel abzurufen, stehen folgende Optionen zur Verfügung:
Verwenden Sie den API-Aufruf ejdb_get()
const doc = await db . get ( 'users' , 112 ) ;
Verwenden Sie die spezielle Abfragekonstruktion: /=:?
oder @collection/=:?
Holen Sie sich das Dokument aus der users
mit dem Primärschlüssel 112
> k @users/=112
Tag-Array für Dokument in jobs
aktualisieren (TypeScript):
await db . createQuery ( '@jobs/ = :? | apply :? | count' )
. setNumber ( 0 , id )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Für den Abgleich kann auch ein Array von Primärschlüsseln verwendet werden:
await db . createQuery ( '@jobs/ = :?| apply :? | count' )
. setJSON ( 0 , [ 23 , 1 , 2 ] )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Nachfolgend finden Sie eine Reihe selbsterklärender Abfragen:
/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]
Hinweis zu Anführungszeichen um Wörter mit Leerzeichen.
Besorgen Sie sich alle Dokumente, wenn der Besitzer älter als 20
age
und ein Haustier hat, das bones
oder toys
mag
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
Hier bezeichnet **
ein Element im likes
Array.
ni
ist der Umkehroperator zu in
. Holen Sie sich Dokumente, in denen bones
irgendwo im likes
Array vorhanden sind.
/pets/*/[likes ni "bones"]
Wir können kompliziertere Filter erstellen
( /[age <= 20] or /[lastName re "Do.*"] )
and /pets/*/likes/[** in ["bones", "toys"]]
Hinweis zum Gruppieren von Klammern und zum Abgleich regulärer Ausdrücke mit re
Operator.
~
ist ein Präfix-Matching-Operator (seit ejdb v2.0.53
). Der Präfixabgleich kann von der Verwendung von Indizes profitieren.
Rufen Sie Dokumente ab, bei denen /lastName
mit "Do"
beginnt.
/[lastName ~ Do]
Filtern Sie Dokumente mit einem likes
Array, das genau auf ["bones","jumping","toys"]
abgestimmt ist.
/**/[likes = ["bones","jumping","toys"]]
Die Matching-Algorithmen für Arrays und Maps sind unterschiedlich:
{"f":"d","e":"j"}
und {"e":"j","f":"d"}
sind gleiche Karten. Suchen Sie ein JSON-Dokument mit dem Schlüssel firstName
auf Stammebene.
/[* = "firstName"]
In diesem Kontext bezeichnet *
einen Schlüsselnamen.
Sie können Bedingungen für den Schlüsselnamen und den Schlüsselwert gleichzeitig verwenden:
/[[* = "firstName"] = John]
Der Schlüsselname kann entweder firstName
oder lastName
lauten, sollte aber in jedem Fall den Wert John
haben.
/[[* in ["firstName", "lastName"]] = John]
Dies kann bei Abfragen mit dynamischen Platzhaltern (C-API) nützlich sein:
/[[* = :keyName] = :keyValue]
Der Abschnitt APPLY
ist für die Änderung des Dokumentinhalts zuständig.
APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del'
Die JSON-Patch-Spezifikationen entsprachen den Spezifikationen rfc7386
oder rfc6902
und folgten nach dem Schlüsselwort apply
.
Fügen wir allen übereinstimmenden Dokumenten address
hinzu
/[firstName = John] | apply {"address":{"city":"New York", "street":""}}
Wenn das JSON-Objekt ein Argument des apply
-Abschnitts ist, wird es als Merge-Match ( rfc7386
) behandelt, andernfalls sollte es ein Array sein, das den JSON-Patch rfc6902
angibt. Platzhalter werden auch vom Abschnitt apply
unterstützt.
/* | apply :?
Geben Sie den Straßennamen als address
ein
/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]
Fügen Sie Neo
Fische zu Johns pets
hinzu
/[firstName = John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]
upsert
aktualisiert das vorhandene Dokument mit dem angegebenen JSON-Argument, das als Merge-Patch verwendet wird, oder fügt das bereitgestellte JSON-Argument als neue Dokumentinstanz ein.
/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}
Erhöht den durch den JSON-Pfad identifizierten numerischen Wert um den angegebenen Wert.
Beispiel:
Document: {"foo": 1}
Patch: [{"op": "increment", "path": "/foo", "value": 2}]
Result: {"foo": 3}
Identisch mit JSON-Patch add
erstellt jedoch Zwischenobjektknoten für fehlende JSON-Pfadsegmente.
Beispiel:
Document: {"foo": {"bar": 1}}
Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}]
Result: {"foo":{"bar":1,"zaz":{"gaz":22}}}
Beispiel:
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
Vertauscht zwei Werte des JSON-Dokuments beginnend from
dem Pfad.
Regeln austauschen
from
zeigt, nicht vorhanden ist, wird ein Fehler ausgelöst.path
zeigt, nicht vorhanden ist, wird er durch den Wert from
Pfads festgelegt. Das Objekt, auf das from
Pfad zeigt, wird entfernt.from
“ und path
verwiesen wird, werden sie vertauscht.Beispiel:
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]
Result: {"foo": [11], "baz": {"gaz": "bar"}}
Beispiel (Demo von Regel 2):
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]
Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}
Verwenden Sie das Schlüsselwort del
, um übereinstimmende Elemente aus der Sammlung zu entfernen:
/FILTERS | del
Beispiel:
> 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
Die Projektion ermöglicht es, nur eine Teilmenge des JSON-Dokuments abzurufen, mit Ausnahme nicht benötigter Daten.
Die Abfrage-Platzhalter-API wird in Projektionen unterstützt.
Fügen wir unserer Sammlung ein weiteres Dokument hinzu:
$ 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
Fragen Sie jetzt nur den Vornamen und Nachnamen des Haustierbesitzers aus der Sammlung ab.
> k query family /* | /{firstName,lastName}
< k 3 {"firstName":"Jack","lastName":"Parker"}
< k 1 {"firstName":"John","lastName":"Doe"}
< k
Fügen Sie für jedes Dokument ein pets
-Array hinzu
> k query family /* | /{firstName,lastName} + /pets
< k 3 {"firstName":"Jack","lastName":"Parker","pets":[...
< k 1 {"firstName":"John","lastName":"Doe","pets":[...
Nur das Feld pets
aus den Dokumenten ausschließen
> 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
Hier werden all
Schlüsselwörter verwendet, die das gesamte Dokument bezeichnen.
Ermitteln Sie age
und das erste Haustier in der Liste 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
Durch den Join wird ein Verweis auf ein Dokument mit realen Dokumentobjekten materialisiert, wodurch der Verweis vor Ort ersetzt wird.
Dokumente werden nur durch ihre Primärschlüssel verknüpft.
Referenzschlüssel sollten im Referenzdokument als Zahlen- oder Zeichenfolgenfeld gespeichert werden.
Joins können als Teil des Projektionsausdrucks in der folgenden Form angegeben werden:
/.../field<collection
Wo
field
– Das JSON-Feld enthält den Primärschlüssel des verbundenen Dokuments.<
‐ Das spezielle Markierungssymbol, das die EJDB-Engine anweist, field
durch den Hauptteil des verbundenen Dokuments zu ersetzen.collection
– Name der DB-Sammlung, in der sich verbundene Dokumente befinden.Ein Verweisdokument bleibt unberührt, wenn das zugehörige Dokument nicht gefunden wird.
Hier ist die einfache Demonstration von Sammlungsverknüpfungen in unserer interaktiven Websocket-Shell:
> 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
Ungültige Referenzen:
> 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)...
Fügen wir ein weiteres Dokument hinzu und sortieren wir dann die Dokumente in der Sammlung nach firstName
aufsteigend und age
absteigend.
> 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
Die Anweisungen asc, desc
können für die Sammlung definierte Indizes verwenden, um eine separate Sortierphase der Dokumente zu vermeiden.
OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
skip n
Überspringt die ersten n
Datensätze vor dem ersten Element im Ergebnissatzlimit n
Legen Sie die maximale Anzahl von Dokumenten im Ergebnissatz festcount
Gibt nur count
der übereinstimmenden Dokumente zurück > k query family /* | count
< k 3
< k
noidx
Verwenden Sie keine Indizes für die Abfrageausführung.inverse
Standardmäßig scannt die Abfrage Dokumente von den zuletzt hinzugefügten bis hin zu den älteren. Diese Option kehrt die Scanrichtung in die entgegengesetzte Richtung um und aktiviert noidx
-Modus. Hat keine Auswirkung, wenn die Abfrage asc/desc
Sortierklauseln enthält. Der Datenbankindex kann für jeden JSON-Feldpfad erstellt werden, der Werte vom Typ Zahl oder Zeichenfolge enthält. Der Index kann unique
sein – er erlaubt keine Wertduplizierung und non unique
. Die folgenden Indexmodus-Bitmaskenflags werden verwendet (definiert in ejdb2.h
):
Indexmodus | Beschreibung |
---|---|
0x01 EJDB_IDX_UNIQUE | Der Index ist eindeutig |
0x04 EJDB_IDX_STR | Index für den Werttyp des JSON- string |
0x08 EJDB_IDX_I64 | Index für 8 bytes width vorzeichenbehaftete Ganzzahlfeldwerte |
0x10 EJDB_IDX_F64 | Index für vorzeichenbehaftete Gleitkommafeldwerte mit 8 bytes width . |
Beispielsweise wird ein eindeutiger Index vom Typ Zeichenfolge durch EJDB_IDX_UNIQUE | EJDB_IDX_STR
angegeben EJDB_IDX_UNIQUE | EJDB_IDX_STR
= 0x05
. Der Index kann nur für einen Werttyp definiert werden, der sich unter einem bestimmten Pfad im JSON-Dokument befindet.
Definieren wir einen nicht eindeutigen String-Index für /lastName
-Pfad:
> k idx family 4 /lastName
< k
Indexauswahl für Abfragen basierend auf einer Reihe heuristischer Regeln.
Sie können die Indexnutzung jederzeit überprüfen, indem Sie den Befehl explain
in der WS-API ausgeben:
> 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
Bei der Verwendung von EJDB2-Indizes werden folgende Anweisungen berücksichtigt:
Für die Ausführung einer bestimmten Abfrage kann nur ein Index verwendet werden
Wenn die Abfrage aus or
verknüpften Teil auf der obersten Ebene besteht oder negated
Ausdrücke auf der obersten Ebene des Abfrageausdrucks enthält, werden Indizes überhaupt nicht verwendet. Daher keine Indizes unten:
/[lastName != Andy]
/[lastName = "John"] or /[lastName = Peter]
Es wird jedoch der oben definierte /lastName
Index verwendet
/[lastName = Doe]
/[lastName = Doe] and /[age = 28]
/[lastName = Doe] and not /[age = 28]
/[lastName = Doe] and /[age != 28]
Die folgenden Operatoren werden von Indizes unterstützt (ejdb 2.0.x):
eq, =
gt, >
gte, >=
lt, <
lte, <=
in
~
(Präfixübereinstimmung seit EJDB 2.0.53) ORDERBY
Klauseln können Indizes verwenden, um eine Sortierung der Ergebnismengen zu vermeiden.
Array-Felder können auch indiziert werden. Lassen Sie uns einen typischen Anwendungsfall skizzieren: Indizierung einiger Entitäts-Tags:
> 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
Erstellen Sie einen String-Index für /tags
> k idx books 4 /tags
< k
Filtern Sie Bücher nach bestseller
-Tag und zeigen Sie die Indexnutzung in der Abfrage an:
> 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
Alle Dokumente in der Sammlung werden nach ihrem Primärschlüssel in descending
Reihenfolge sortiert. Wenn Sie also automatisch generierte Schlüssel ( ejdb_put_new
) verwenden, können Sie sicher sein, dass die als Ergebnis der vollständigen Scan-Abfrage abgerufenen Dokumente nach dem Zeitpunkt der Einfügung in absteigender Reihenfolge sortiert werden, es sei denn, Sie verwenden keine Abfragesortierung, Indizes oder inverse
Schlüsselwörter.
In vielen Fällen kann die Verwendung eines Index die Gesamtabfrageleistung beeinträchtigen. Da die Indexsammlung nur Dokumentverweise ( id
) enthält, kann die Engine einen zusätzlichen Dokumentabruf über ihren Primärschlüssel durchführen, um den Abfrageabgleich abzuschließen. Bei nicht so großen Sammlungen kann ein Brute-Scan daher eine bessere Leistung erbringen als ein Scan mithilfe von Indizes. Allerdings profitieren in den meisten Fällen exakte Matching-Operationen: eq
, in
und sorting
nach natürlicher Indexreihenfolge vom Index.
Wenn Sie einige Dokumente mit apply
oder del
-Vorgängen aktualisieren möchten, aber nicht alle als Ergebnis der Abfrage abrufen möchten, fügen Sie einfach count
Modifikator zur Abfrage hinzu, um unnötige Datenübertragungen und JSON-Datenkonvertierungen zu vermeiden.
Die EJDB-Engine bietet die Möglichkeit, einen separaten HTTP/Websocket-Endpunkt-Worker zu starten, der die Netzwerk-API für Abfragen und Datenänderungen verfügbar macht. SSL (TLS 1.2) wird vom jbs
Server unterstützt.
Der einfachste Weg, eine Datenbank über das Netzwerk verfügbar zu machen, ist die Verwendung des eigenständigen jbs
Servers. (Natürlich, wenn Sie C API
Integration vermeiden möchten).
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.
Der HTTP-Endpunkt kann durch ein Token geschützt werden, das mit dem Flag --access
oder der C-API- EJDB_HTTP
Struktur angegeben wird. Wenn ein Zugriffstoken festgelegt wurde, sollte der Client den HTTP-Header X-Access-Token
bereitstellen. Wenn ein Token erforderlich ist, aber nicht vom Client 401
bereitgestellt wird, wird der HTTP-Code gemeldet. Wenn das Zugriffstoken nicht mit dem vom Client bereitgestellten Token übereinstimmt, antwortet der Server mit dem HTTP-Code 403
.
Fügen Sie der collection
ein neues Dokument hinzu.
200
Erfolg. Body: eine neue Dokumentkennung als int64
Nummer Ersetzt/speichert das Dokument unter einer bestimmten numerischen id
200
auf Erfolg. Leerer Körper Entfernt das durch id
identifizierte Dokument aus einer collection
200
auf Erfolg. Leerer Körper404
Dokument nicht gefunden Patchen Sie ein Dokument, das anhand der id
von RFC7396- und RFC6902-Daten identifiziert wurde.
200
auf Erfolg. Leerer Körper Rufen Sie das durch id
identifizierte Dokument aus einer collection
ab.
200
auf Erfolg. Text: JSON-Dokumenttext.content-type:application/json
content-length:
404
Dokument nicht gefunden Fragen Sie eine Sammlung mit der bereitgestellten Abfrage als POST-Text ab. Der Abfragetext sollte den im ersten Filterelement verwendeten Sammlungsnamen enthalten: @collection_name/...
Anforderungsheader:
X-Hints
durch Kommas getrennte zusätzliche Hinweise zur EJDB2-Datenbank-Engine.explain
Zeigt den Abfrageausführungsplan vor dem ersten Element im Ergebnissatz an, getrennt durch die Zeile --------------------
. Antwort:200
auf Erfolg.n
getrennte JSON-Dokumente im folgenden Format: rn<document id>t<document JSON body>
...
Beispiel:
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}
Rufen Sie EJDB-JSON-Metadaten und verfügbare HTTP-Methoden im Antwortheader Allow
ab. Beispiel:
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 unterstützt ein einfaches textbasiertes Protokoll über das HTTP-Websocket-Protokoll. Sie können das interaktive WebSocket-CLI-Tool wscat verwenden, um manuell mit dem Server zu kommunizieren.
Ich antworte mit der folgenden Hilfe-SMS:
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>
>
Hinweis zum <key>
-Präfix vor jedem Befehl; Dabei handelt es sich um einen willkürlichen Schlüssel, der vom Client ausgewählt und zur Identifizierung einer bestimmten Websocket-Anfrage bestimmt ist. Dieser Schlüssel wird mit der Antwort auf die Anfrage zurückgegeben und ermöglicht es dem Client, diese Antwort für seine bestimmte Anfrage zu identifizieren.
Fehler werden im folgenden Format zurückgegeben:
<key> ERROR: <error description>
<key> info
Datenbankmetadaten als JSON-Dokument abrufen.
<key> get <collection> <id>
Rufen Sie das durch id
identifizierte Dokument aus einer collection
ab. Wenn das Dokument nicht gefunden wird, wird IWKV_ERROR_NOTFOUND
zurückgegeben.
Beispiel:
> k get family 3
< k 3 {
"firstName": "Jack",
"lastName": "Parker",
"age": 35,
"pets": [
{
"name": "Sonic",
"kind": "mouse",
"likes": []
}
]
}
Wenn das Dokument nicht gefunden wird, erhalten wir die Fehlermeldung:
> k get family 55
< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND)
>
<key> set <collection> <id> <document json>
Ersetzt/fügt ein Dokument unter einer bestimmten numerischen id
hinzu. Collection
wird automatisch erstellt, wenn sie nicht vorhanden ist.
<key> add <collection> <document json>
Neues Dokument zur <collection>
hinzufügen. Neue id
des Dokuments wird generiert und als Antwort zurückgegeben. „Sammlung“ wird automatisch erstellt, wenn sie nicht vorhanden ist.
Beispiel:
> k add mycollection {"foo":"bar"}
< k 1
> k add mycollection {"foo":"bar"}
< k 2
>
<key> del <collection> <id>
Durch id
identifiziertes Dokument aus der collection
entfernen. Wenn das Dokument nicht gefunden wird, wird IWKV_ERROR_NOTFOUND
zurückgegeben.
<key> patch <collection> <id> <patch json>
Wenden Sie den Patch rfc7396 oder rfc6902 auf das durch id
identifizierte Dokument an. Wenn das Dokument nicht gefunden wird, wird IWKV_ERROR_NOTFOUND
zurückgegeben.
<key> query <collection> <query>
Führen Sie eine Abfrage für Dokumente in der angegebenen collection
aus. Antwort: Eine Reihe von WS-Nachrichten mit Dokumentkörpern, die durch die letzte Nachricht mit leerem Körper abgeschlossen werden.
> k query family /* | /firstName
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Hinweis zur letzten Nachricht: <key>
ohne Text.
<key> explain <collection> <query>
Identisch mit <key> query <collection> <query>
, aber der ersten Antwortnachricht wird <key> explain
vorangestellt und sie enthält einen Abfrageausführungsplan.
Beispiel:
> k explain family /* | /firstName
< k explain [INDEX] NO [COLLECTOR] PLAIN
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Abfragetext ausführen. Der Abfragetext sollte den im ersten Filterelement verwendeten Sammlungsnamen enthalten: @collection_name/...
. Das Verhalten ist das gleiche wie für: <key> query <collection> <query>
<key> idx <collection> <mode> <path>
Stellen Sie sicher, dass der Index den angegebenen mode
(Bitmasken-Flag) für den angegebenen JSON- path
und die angegebene collection
aufweist. Die Sammlung wird erstellt, falls sie nicht vorhanden ist.
Indexmodus | Beschreibung |
---|---|
0x01 EJDB_IDX_UNIQUE | Der Index ist eindeutig |
0x04 EJDB_IDX_STR | Index für den Werttyp des JSON- string |
0x08 EJDB_IDX_I64 | Index für 8 bytes width vorzeichenbehaftete Ganzzahlfeldwerte |
0x10 EJDB_IDX_F64 | Index für vorzeichenbehaftete Gleitkommafeldwerte mit 8 bytes width . |
Legen Sie den eindeutigen Zeichenfolgenindex (0x01 & 0x04) = 5
für das JSON-Feld /name
fest:
k idx mycollection 5 /name
<key> rmi <collection> <mode> <path>
Entfernen Sie den Index mit dem angegebenen mode
(Bitmasken-Flag) für den angegebenen JSON- path
und collection
. Gibt einen Fehler zurück, wenn der angegebene Index nicht gefunden wurde.
<key> rmc <collection>
Entfernen Sie die Sammlung und alle ihre Daten. Hinweis: Wenn keine collection
gefunden wird, werden keine Fehler gemeldet.
Wenn Sie Docker installiert haben, können Sie ein Docker-Image erstellen und es in einem Container ausführen
cd docker
docker build -t ejdb2 .
docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey
oder holen Sie sich ein Image von ejdb2
direkt vom Docker Hub
docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey
EJDB kann in jede C/C++
Anwendung eingebettet werden. C API
dokumentiert in den folgenden Headern:
Beispielanwendung:
#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 ;
}
Kompilieren und ausführen:
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.