Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

🏂 EJDB2 — Embeddable JSON Database engine C library. Simple XPath like query language (JQL).

License

NotificationsYou must be signed in to change notification settings

Softmotions/ejdb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NOTE: Issues tracker is disabled. You are welcome to contribute, pull requests accepted.

licensemaintained

EJDB2 is an embeddable JSON database engine published under MIT license.

The Story of the IT-depression, birds and EJDB 2.0



EJDB2 Presentation

EJDB2 platforms matrix

LinuxmacOSiOSAndroidWindows
C library✔️✔️✔️✔️✔️1
NodeJS✔️✔️3
Java✔️✔️✔️✔️2
DartVM5✔️✔️23
Flutter5✔️✔️
React Native54✔️
Swift5✔️✔️✔️


[5]Bindings are unmaintained Contributors needed.
[1] No HTTP/Websocket support#257
[2] Binaries are not distributed with dartpub. You can build itmanually
[3] Can be build, but needed a linkage with windows node/dartlibs.
[4] Porting in progress#273

Native language bindings

Unofficial EJDB2 language bindings

Status

  • EJDB 2.0 core engine is well tested and used in various heavily loaded deployments
  • Tested onLinux,macOS andFreeBSD.Has limited Windows support
  • Old EJDB 1.x version can be found in separateejdb_1.x branch.We are not maintaining ejdb 1.x.

Used by

Are you using EJDB?Let me know!

macOS

EJDB2 code ported and tested onHigh Sierra /Mojave /Catalina

EJDB2 Swift binding for MacOS, iOS and Linux.Swift binding is outdated at now. Looking for contributors.

brew install ejdb

Building from sources

cmake v3.24 or higher required

git clone --recurse-submodules git@github.com:Softmotions/ejdb.gitmkdir build && cd buildcmake .. -DCMAKE_BUILD_TYPE=Releasemake install

Linux

Building debian packages

mkdir build&&cd buildcmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_DEB=ONmake package

RPM based Linux distributions

mkdir build&&cd buildcmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_RPM=ONmake package

Windows

EJDB2 can be cross-compiled for windows

Note: HTTP/Websocket network API is disabled and not yet supported

Nodejs/Dart bindings not yet ported to Windows.

Cross-compilation Guide for Windows

IWSTART

IWSTART is an automatic CMake initial project generator for C projects based oniowow /iwnet /ejdb2 libs.

https://github.com/Softmotions/iwstart

WARNING: Unsupported now. Maintainer needed.

Android

Sample Android application

JQL

EJDB query language (JQL) syntax inspired by ideas behind XPath and Unix shell pipes.It designed for easy querying and updating sets of JSON documents.

JQL grammar

JQL parser created created bypeg/leg — recursive-descent parser generators for C Here is the formal parser grammar:https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg

Non formal JQL grammar adapted for brief overview

Notation used below is based on SQL syntax description:

RuleDescription
' 'String in single quotes denotes unquoted string literal as part of query.
{ a | b }Curly brackets enclose two or more required alternative choices, separated by vertical bars.
[ ]Square brackets indicate an optional element or clause. Multiple elements or clauses are separated by vertical bars.
|Vertical bars separate two or more alternative syntax elements.
...Ellipses indicate that the preceding element can be repeated. The repetition is unlimited unless otherwise indicated.
( )Parentheses are grouping symbols.
Unquoted word in lower caseDenotes semantic of some query part. For example:placeholder_name - name of any placeholder.
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 | PLACEHOLDERAPPLY = { 'apply' | 'upsert' } { PLACEHOLDER | json_object | json_array  } | 'del'OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...  ORDERBY = { 'asc' | 'desc' } PLACEHOLDER | json_pathPROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ]  PROJECTION = 'all' | json_path
  • json_value: Any valid JSON value: object, array, string, bool, number.
  • json_path: Simplified JSON pointer. Eg.:/foo/bar or/foo/"bar with spaces"/
  • * in context ofNODE: Any JSON object key name at particular nesting level.
  • ** in context ofNODE: Any JSON object key name at arbitrary nesting level.
  • * in context ofNODE_EXPR_LEFT: Key name at specific level.
  • ** in context ofNODE_EXPR_LEFT: Nested array value of array element under specific key.

JQL quick introduction

Lets play with some very basic data and queries.For simplicity we will use ejdb websocket network API which provides us a kind of interactive CLI. The same job can be done using pureC API too (ejdb2.h jql.h).

NOTE: Take a look intoJQL test cases for more examples.

{"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"]}  ]}

Save json assample.json then upload it thefamily collection:

# 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

Server can be accessed using HTTP or Websocket endpoint.More info

curl -d'@sample.json' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family

We can play around using interactivewscat websocket client.

wscat  -H'X-Access-Token:myaccess01' -c http://localhost:9191connected (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"   ]  } ]}

Note about thek prefix before every command; It is an arbitrary key chosen by client and designated to identify particularwebsocket request, this key will be returned with response to request and allows client toidentify that response for his particular request.More info

Query command over websocket has the following format:

<key> query <collection> <query>

So we will consider only<query> part in this document.

Get all elements in collection

k query family /*

or

k query family /**

or specify collection name in query explicitly

k @family/*

We can execute query by HTTPPOST request

curl --data-raw '@family/[firstName = John]' -H'X-Access-Token:myaccess01' -X POST http://localhost:91911{"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"]}]}

Set the maximum number of elements in result set

k @family/* | limit 10

Get documents where specified json path exists

Element at index1 exists inlikes array within apets sub-object

> k query family /pets/*/likes/1< k     1       {"firstName":"John"...

Element at index1 exists inlikes array at anylikes nesting level

> k query family /**/likes/1< k     1       {"firstName":"John"...

From this point and below I will omit websocket specific prefixk query family andconsider only JQL queries.

Get documents by primary key

In order to get documents by primary key the following options are available:

  1. Use API callejdb_get()

    constdoc=awaitdb.get('users',112);
  2. Use the special query construction:/=:? or@collection/=:?

Get document fromusers collection with primary key112

> k @users/=112

Update tags array for document injobs collection (TypeScript):

awaitdb.createQuery('@jobs/ = :? | apply :? | count').setNumber(0,id).setJSON(1,{ tags}).completionPromise();

Array of primary keys can also be used for matching:

awaitdb.createQuery('@jobs/ = :?| apply :? | count').setJSON(0,[23,1,2]).setJSON(1,{ tags}).completionPromise();

Matching JSON entry values

Below is a set of self explaining queries:

/pets/*/[name = "Rexy rex"]/pets/*/[name eq "Rexy rex"]/pets/*/[name = "Rexy rex" or name = Grenny]

Note about quotes around words with spaces.

Get all documents where ownerage greater than20 and have some pet who likebones ortoys

/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]

Here** denotes some element inlikes array.

ni is the inverse operator toin.Get documents wherebones somewhere inlikes array.

/pets/*/[likes ni "bones"]

We can create more complicated filters

( /[age <= 20] or /[lastName re "Do.*"] )  and /pets/*/likes/[** in ["bones", "toys"]]

Note about grouping parentheses and regular expression matching usingre operator.

~ is a prefix matching operator (Since ejdbv2.0.53).Prefix matching can benefit from using indexes.

Get documents where/lastName starts with"Do".

/[lastName ~ Do]

Arrays and maps can be matched as is

Filter documents withlikes array exactly matched to["bones","jumping","toys"]

/**/[likes = ["bones","jumping","toys"]]

Matching algorithms for arrays and maps are different:

  • Array elements are matched from start to end. In equal arraysall values at the same index should be equal.
  • Object maps matching consists of the following steps:
    • Lexicographically sort object keys in both maps.
    • Do matching keys and its values starting from the lowest key.
    • If all corresponding keys and values in one map are fully matched to ones in otherand vice versa, maps considered to be equal.For example:{"f":"d","e":"j"} and{"e":"j","f":"d"} are equal maps.

Conditions on key names

Find JSON document havingfirstName key at root level.

/[* = "firstName"]

I this context* denotes a key name.

You can use conditions on key name and key value at the same time:

/[[* = "firstName"] = John]

Key name can be eitherfirstName orlastName but should haveJohn value in any case.

/[[* in ["firstName", "lastName"]] = John]

It may be useful in queries with dynamic placeholders (C API):

/[[* = :keyName] = :keyValue]

JQL data modification

APPLY section responsible for modification of documents content.

APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array  }) | 'del'

JSON patch specs conformed torfc7386 orrfc6902 specifications followed afterapply keyword.

Let's addaddress object to all matched document

/[firstName = John] | apply {"address":{"city":"New York", "street":""}}

If JSON object is an argument ofapply section it will be treated as merge match (rfc7386) otherwiseit should be array which denotesrfc6902 JSON patch. Placeholders also supported byapply section.

/* | apply :?

Set the street name inaddress

/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]

AddNeo fish to the set of John'spets

/[firstName = John]| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]

upsert updates existing document by given json argument used as merge patchor inserts provided json argument as new document instance.

/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}

Non standard JSON patch extensions

increment

Increments numeric value identified by JSON path by specified value.

Example:

 Document:  {"foo": 1} Patch:     [{"op": "increment", "path": "/foo", "value": 2}] Result:    {"foo": 3}

add_create

Same as JSON patchadd but creates intermediate object nodes for missing JSON path segments.

Example:

Document: {"foo": {"bar": 1}}Patch:    [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}]Result:   {"foo":{"bar":1,"zaz":{"gaz":22}}}

Example:

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

swap

Swaps two values of JSON document starting fromfrom path.

Swapping rules

  1. If value pointed byfrom not exists error will be raised.
  2. If value pointed bypath not exists it will be set by value fromfrom path,then object pointed byfrom path will be removed.
  3. If both values pointed byfrom andpath are presented they will be swapped.

Example:

Document: {"foo": ["bar"], "baz": {"gaz": 11}}Patch:    [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]Result:   {"foo": [11], "baz": {"gaz": "bar"}}

Example (Demo of rule 2):

Document: {"foo": ["bar"], "baz": {"gaz": 11}}Patch:    [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]Result:   {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}

Removing documents

Usedel keyword to remove matched elements from collection:

/FILTERS | del

Example:

> 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"}

JQL projections

PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ]  PROJECTION = 'all' | json_path | join_clause

Projection allows to get only subset of JSON document excluding not needed data.

Query placeholders API is supported in projections.

Lets add one more document to our 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

Now query only pet owners firstName and lastName from collection.

> k query family /* | /{firstName,lastName}< k     3       {"firstName":"Jack","lastName":"Parker"}< k     1       {"firstName":"John","lastName":"Doe"}< k

Addpets array for every document

> k query family /* | /{firstName,lastName} + /pets< k     3       {"firstName":"Jack","lastName":"Parker","pets":[...< k     1       {"firstName":"John","lastName":"Doe","pets":[...

Exclude onlypets field from 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

Hereall keyword used denoting whole document.

Getage and the first pet inpets array.

> 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

JQL collection joins

Join materializes reference to document to a real document objects which will replace reference inplace.

Documents are joined by their primary keys only.

Reference keys should be stored in referrer document as number or string field.

Joins can be specified as part of projection expressionin the following form:

/.../field<collection

Where

  • field ‐ JSON field contains primary key of joined document.
  • < ‐ The special mark symbol which instructs EJDB engine to replacefield key by body of joined document.
  • collection ‐ name of DB collection where joined documents located.

A referrer document will be untouched if associated document is not found.

Here is the simple demonstration of collection joins in our interactive 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

Invalid references:

>  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]}}

JQL results ordering

  ORDERBY = ({ 'asc' | 'desc' } PLACEHOLDER | json_path)...

Lets add one more document then sort documents in collection according tofirstName ascending andage descending order.

> 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

asc, desc instructions may use indexes defined for collection to avoid a separate documents sorting stage.

JQL Options

OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
  • skip n Skip firstn records before first element in result set
  • limit n Set max number of documents in result set
  • count Returns onlycount of matched documents
    > k query family /* | count< k     3< k
  • noidx Do not use any indexes for query execution.
  • inverse By default query scans documents from most recently added to older ones.This option inverts scan direction to opposite and activatesnoidx mode.Has no effect if query hasasc/desc sorting clauses.

JQL Indexes and performance tips

Database index can be build for any JSON field path containing values of number or string type.Index can be anunique ‐ not allowing value duplication andnon unique.The following index mode bit mask flags are used (defined inejdb2.h):

Index modeDescription
0x01 EJDB_IDX_UNIQUEIndex is unique
0x04 EJDB_IDX_STRIndex for JSONstring field value type
0x08 EJDB_IDX_I64Index for8 bytes width signed integer field values
0x10 EJDB_IDX_F64Index for8 bytes width signed floating point field values.

For example unique index of string type will be specified byEJDB_IDX_UNIQUE | EJDB_IDX_STR =0x05.Index can be defined for only one value type located under specific path in json document.

Lets define non unique string index for/lastName path:

> k idx family 4 /lastName< k

Index selection for queries based on set of heuristic rules.

You can always check index usage by issuingexplain command in WS API:

> 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

The following statements are taken into account when using EJDB2 indexes:

  • Only one index can be used for particular query execution

  • If query consist ofor joined part at top level or containsnegated expressions at the top levelof query expression - indexes will not be in use at all.So no indexes below:

    /[lastName != Andy]/[lastName = "John"] or /[lastName = Peter]

    But will be used/lastName index defined above

    /[lastName = Doe]/[lastName = Doe] and /[age = 28]/[lastName = Doe] and not /[age = 28]/[lastName = Doe] and /[age != 28]
  • The following operators are supported by indexes (ejdb 2.0.x):

    • eq, =
    • gt, >
    • gte, >=
    • lt, <
    • lte, <=
    • in
    • ~ (Prefix matching since ejdb 2.0.53)
  • ORDERBY clauses may use indexes to avoid result set sorting.

  • Array fields can also be indexed. Let's outline typical use case: indexing of some entity 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

    Create string index for/tags

    > k idx books 4 /tags< k

    Filter books bybestseller tag and show index usage in query:

    > 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

Performance tip: Physical ordering of documents

All documents in collection are sorted by their primary key indescending order.So if you use auto generated keys (ejdb_put_new) you may be sure what documents fetched as result offull scan query will be ordered according to the time of insertion in descendant order,unless you don't use query sorting, indexes orinverse keyword.

Performance tip: Brute force scan vs indexed access

In many cases, using index may drop down the overall query performance.Because index collection contains only document references (id) and engine may performan addition document fetching by its primary key to finish query matching.So for not so large collections a brute scan may perform better than scan using indexes.However, exact matching operations:eq,in andsorting by natural index orderwill benefit from index in most cases.

Performance tip: Get rid of unnecessary document data

If you'd like update some set of documents withapply ordel operationsbut don't want fetching all of them as result of query - just addcountmodifier to the query to get rid of unnecessary data transferring and json data conversion.

HTTP REST/Websocket API endpoint

EJDB engine provides the ability to start a separate HTTP/Websocket endpoint worker exposing network API for quering and data modifications.SSL (TLS 1.2) is supported byjbs server.

The easiest way to expose database over the network is use the standalonejbs server. (Of course if you want to avoidC API integration).

jbs server

Usage: ./jbs [options]-v, --versionPrint program version.-f, --file=<>Database file path. Default: ejdb2.db-p, --port=NUMHTTP 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|@FILEAccess token to match 'X-Access-Token' HTTP header value.-r, --access-readAllows unrestricted read-only data access.-C, --corsEnable COSR response headers for HTTP server-t, --truncCleanup/reset database file on open.-w, --waluse the write ahead log (WAL). Used to provide data durability.Advanced options:-S, --sbz=NUMMax sorting buffer size. If exceeded, an overflow temp file for data will be created.                  Default: 16777216, min: 1048576-D, --dsz=NUMInitial 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.

HTTP API

HTTP endpoint may be protected by a token specified with--access flag or C APIEJDB_HTTP struct.If access token was set, client should provideX-Access-Token HTTP header.If token is required but not provided by client401 HTTP code will be reported.If access token is not matched to the token provided by client server will respond with403 HTTP code.

REST API

POST /{collection}

Add a new document to thecollection.

  • 200 success. Body: a new document identifier asint64 number

PUT /{collection}/{id}

Replaces/store document under specific numericid

  • 200 on success. Empty body

DELETE /{collection}/{id}

Removes document identified byid from acollection

  • 200 on success. Empty body
  • 404 document not found

PATCH /{collection}/{id}

Patch a document identified byid byrfc7396,rfc6902 data.

  • 200 on success. Empty body

GET | HEAD /{collections}/{id}

Retrieve document identified byid from acollection.

  • 200 on success. Body: JSON document text.
    • content-type:application/json
    • content-length:
  • 404 document not found

POST /

Query a collection by provided query as POST body.Body of query should contains collection name in use in the first filter element:@collection_name/...Request headers:

  • X-Hints comma separated extra hints to ejdb2 database engine.
    • explain Show query execution plan before first element in result set separated by-------------------- line.Response:
  • Response data transfered usingHTTP chunked transfer encoding
  • 200 on success.
  • JSON documents separated by\n in the following format:
    \r\n<document id>\t<document JSON body>...

Example:

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}

OPTIONS /

Fetch ejdb JSON metadata and available HTTP methods inAllow response header.Example:

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    }   ]  } ]}

Websocket API

EJDB supports simple text based protocol over HTTP websocket protocol.You can use interactive websocket CLI toolwscat to communicate with server by hands.

Commands

?

Will respond with the following help text message:

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>>

Note about<key> prefix before every command; It is an arbitrary key chosen by client and designated to identify particular websocket request, this key will be returned with response to request and allows client to identify that response for his particular request.

Errors are returned in the following format:

<key> ERROR: <error description>

<key> info

Get database metadatas as JSON document.

<key> get <collection> <id>

Retrieve document identified byid from acollection.If document is not foundIWKV_ERROR_NOTFOUND will be returned.

Example:

> k get family 3< k     3       { "firstName": "Jack", "lastName": "Parker", "age": 35, "pets": [  {   "name": "Sonic",   "kind": "mouse",   "likes": []  } ]}

If document not found we will get error:

> k get family 55< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND)>

<key> set <collection> <id> <document json>

Replaces/add document under specific numericid.Collection will be created automatically if not exists.

<key> add <collection> <document json>

Add new document to<collection> Newid of document will be generatedand returned as response. `Collection> will be created automatically if not exists.

Example:

> k add mycollection {"foo":"bar"}< k     1> k add mycollection {"foo":"bar"}< k     2>

<key> del <collection> <id>

Remove document identified byid from thecollection.If document is not foundIWKV_ERROR_NOTFOUND will be returned.

<key> patch <collection> <id> <patch json>

Applyrfc7396 orrfc6902 patch to the document identified byid.If document is not foundIWKV_ERROR_NOTFOUND will be returned.

<key> query <collection> <query>

Execute query on documents in specifiedcollection.Response: A set of WS messages with document boidies terminated by the lastmessage with empty body.

> k query family /* | /firstName< k     4       {"firstName":"John"}< k     3       {"firstName":"Jack"}< k     1       {"firstName":"John"}< k

Note about last message:<key> with no body.

<key> explain <collection> <query>

Same as<key> query <collection> <query> but the first response message willbe prefixed by<key> explain and contains query execution plan.

Example:

> k explain family /* | /firstName< k     explain [INDEX] NO [COLLECTOR] PLAIN< k     4       {"firstName":"John"}< k     3       {"firstName":"Jack"}< k     1       {"firstName":"John"}< k

Execute query text. Body of query should contains collection name in use in the first filter element:@collection_name/.... Behavior is the same as for:<key> query <collection> <query>

<key> idx <collection> <mode> <path>

Ensure index with specifiedmode (bitmask flag) for given jsonpath andcollection.Collection will be created if not exists.

Index modeDescription
0x01 EJDB_IDX_UNIQUEIndex is unique
0x04 EJDB_IDX_STRIndex for JSONstring field value type
0x08 EJDB_IDX_I64Index for8 bytes width signed integer field values
0x10 EJDB_IDX_F64Index for8 bytes width signed floating point field values.
Example

Set unique string index(0x01 & 0x04) = 5 on/name JSON field:

k idx mycollection 5 /name

<key> rmi <collection> <mode> <path>

Remove index with specifiedmode (bitmask flag) for given jsonpath andcollection.Return error if given index not found.

<key> rmc <collection>

Remove collection and all of its data.Note: Ifcollection is not found no errors will be reported.

Docker support

If you have Docker installed, you can build a Docker image and run it in a container

cd dockerdocker build -t ejdb2 .docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey

or get an image ofejdb2 directly from the Docker Hub

docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey

C API

EJDB can be embedded into anyC/C++ application.C API documented in the following headers:

  • ejdb.h Main API functions
  • jbl.h JSON documents management API
  • jql.h Query building API

Example application:

#include<ejdb2/ejdb2.h>#defineCHECK(rc_)          \  if (rc_) {                 \    iwlog_ecode_error3(rc_); \    return 1;                \  }staticiwrcdocuments_visitor(EJDB_EXEC*ctx,constEJDB_DOCdoc,int64_t*step) {// Print document to stderrreturnjbl_as_json(doc->raw,jbl_fstream_json_printer,stderr,JBL_PRINT_PRETTY);}intmain() {EJDB_OPTSopts= {    .kv= {      .path="example.db",      .oflags=IWKV_TRUNC    }  };EJDBdb;// EJDB2 storage handleint64_tid;// Document id placeholderJQLq=0;// Query instanceJBLjbl=0;// Json documentiwrcrc=ejdb_init();CHECK(rc);rc=ejdb_open(&opts,&db);CHECK(rc);// First recordrc=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 recordrc=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 queryrc=jql_create(&q,"parrots","/[age > :age]");RCGO(rc,finish);EJDB_EXECux= {    .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 queryrc=ejdb_exec(&ux);finish:jql_destroy(&q);jbl_destroy(&jbl);ejdb_close(&db);CHECK(rc);return0;}

Compile and run:

gcc -std=gnu11 -Wall -pedantic -c -o example1.o example1.cgcc -o example1 example1.o -lejdb2./example1{ "name": "Darko", "age": 8}{ "name": "Bianca", "age": 4}

License

MIT LicenseCopyright (c) 2012-2024 Softmotions Ltd <info@softmotions.com>Permission is hereby granted, free of charge, to any person obtaining a copyof this software and associated documentation files (the "Software"), to dealin the Software without restriction, including without limitation the rightsto use, copy, modify, merge, publish, distribute, sublicense, and/or sellcopies of the Software, and to permit persons to whom the Software isfurnished to do so, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, 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 THESOFTWARE.

[8]ページ先頭

©2009-2025 Movatter.jp