注: 問題トラッカーは無効になっています。貢献は大歓迎です。プル リクエストも受け付けます。
EJDB2 は、MIT ライセンスの下で公開されている組み込み可能な JSON データベース エンジンです。
IT不況と鳥とEJDB 2.0の物語
Linux | macOS | iOS | アンドロイド | 窓 | |
---|---|---|---|---|---|
Cライブラリ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️1 |
NodeJS | ✔️ | ✔️ | 3 | ||
ジャワ | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
ダーツVM5 | ✔️ | ✔️ 2 | 3 | ||
フラッター5 | ✔️ | ✔️ | |||
リアクトネイティブ5 | 4 | ✔️ | |||
スイフト5 | ✔️ | ✔️ | ✔️ |
[5]
バインディングがメンテナンスされていない貢献者が必要です。
[1]
HTTP/Websocket サポートなし #257
[2]
ダーツパブではバイナリの配布は行っておりませんpub.
手動でビルドすることもできます[3]
ビルドは可能ですが、Windows ノード/dart libs
とのリンクが必要でした。
[4]
移植中 #273
Linux
、 macOS
、 FreeBSD
でテスト済み。 Windows サポートが限定されているEJDBを使用していますか?お知らせ下さい!
EJDB2 コードが移植され、 High Sierra
/ Mojave
/ Catalina
でテストされました
MacOS、iOS、Linux 用の EJDB2 Swift バインディング。 Swift バインディングは現在では時代遅れです。寄稿者を募集しています。
brew install ejdb
cmake v3.24 以降が必要
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 は Windows 用にクロスコンパイル可能
注: HTTP/Websocket ネットワーク API は無効になっており、まだサポートされていません
Nodejs/Dart バインディングはまだ Windows に移植されていません。
Windows 用クロスコンパイル ガイド
IWSTART は、iowow / iwnet / ejdb2 ライブラリに基づく C プロジェクト用の自動 CMake 初期プロジェクト ジェネレーターです。
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
XPath および Unix シェル パイプの背後にあるアイデアからインスピレーションを得た EJDB クエリ言語 (JQL) 構文。 JSON ドキュメントのセットを簡単にクエリおよび更新できるように設計されています。
peg/leg によって作成された JQL パーサー — C 用の再帰降下パーサー ジェネレーター 正式なパーサー文法は次のとおりです: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg
以下で使用される表記は、SQL 構文の説明に基づいています。
ルール | 説明 |
---|---|
' ' | 一重引用符で囲まれた文字列は、クエリの一部として引用符で囲まれていない文字列リテラルを示します。 |
{ a | b } | 中括弧は、縦棒で区切られた 2 つ以上の必須の選択肢を囲みます。 |
[ ] | 角括弧は、オプションの要素または句を示します。複数の要素または句は縦線で区切られます。 |
| | 垂直バーは 2 つ以上の代替構文要素を区切ります。 |
... | 省略記号は、前の要素を繰り返すことができることを示します。特に指定がない限り、繰り返しは無制限です。 |
( ) | 括弧はグループ化記号です。 |
引用符で囲まれていない単語を小文字で表記 | 何らかのクエリ部分のセマンティクスを示します。例: placeholder_name - 任意のプレースホルダーの名前。 |
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
: 任意の有効な JSON 値: オブジェクト、配列、文字列、ブール値、数値。json_path
: 簡略化された JSON ポインター。例: /foo/bar
または/foo/"bar with spaces"/
*
NODE
のコンテキスト内: 特定のネスト レベルの任意の JSON オブジェクト キー名。**
NODE
のコンテキスト内: 任意のネスト レベルの任意の JSON オブジェクト キー名。*
NODE_EXPR_LEFT
のコンテキスト内: 特定のレベルのキー名。**
NODE_EXPR_LEFT
のコンテキスト内: 特定のキーの下にある配列要素のネストされた配列値。 非常に基本的なデータとクエリを試してみましょう。簡単にするために、一種の対話型 CLI を提供する ejdb websocket ネットワーク API を使用します。純粋なC
API ( ejdb2.h jql.h
) を使用しても同じジョブを実行できます。
注: 他の例については、JQL テスト ケースを参照してください。
{
"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 " ]}
]
}
json をsample.json
として保存し、 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
サーバーには、HTTP または Websocket エンドポイントを使用してアクセスできます。詳細情報
curl -d ' @sample.json ' -H ' X-Access-Token:myaccess01 ' -X POST http://localhost:9191/family
インタラクティブな wscat Websocket クライアントを使用して遊んでみましょう。
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 "
]
}
]
}
すべてのコマンドの前にあるk
接頭辞に注意してください。これはクライアントによって選択され、特定の WebSocket リクエストを識別するために指定された任意のキーです。このキーはリクエストへの応答とともに返され、クライアントがその特定のリクエストに対する応答を識別できるようになります。詳細情報
WebSocket を介したクエリ コマンドの形式は次のとおりです。
<key> query <collection> <query>
したがって、このドキュメントでは<query>
部分のみを考慮します。
k query family /*
または
k query family /**
またはクエリでコレクション名を明示的に指定します
k @family/*
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
インデックス1
の要素は、 pets
サブオブジェクト内のlikes
配列に存在します
> k query family /pets/*/likes/1
< k 1 {"firstName":"John"...
インデックス1
の要素は、任意のlikes
ネスト レベルのlikes
配列に存在します
> k query family /**/likes/1
< k 1 {"firstName":"John"...
これ以降、WebSocket 固有のプレフィックスk query family
を省略し、JQL クエリのみを考慮します。
主キーによってドキュメントを取得するには、次のオプションを使用できます。
API 呼び出しejdb_get()
を使用する
const doc = await db . get ( 'users' , 112 ) ;
特別なクエリ構造を使用します: /=:?
または@collection/=:?
主キー112
を使用してusers
コレクションからドキュメントを取得します
> k @users/=112
jobs
コレクション内のドキュメントのタグ配列を更新します (TypeScript):
await db . createQuery ( '@jobs/ = :? | apply :? | count' )
. setNumber ( 0 , id )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
主キーの配列も照合に使用できます。
await db . createQuery ( '@jobs/ = :?| apply :? | count' )
. setJSON ( 0 , [ 23 , 1 , 2 ] )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
以下は、自己説明型のクエリのセットです。
/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]
スペースを含む単語の前後の引用符に注意してください。
飼い主のage
20
以上で、 bones
やtoys
好きなペットを飼っている場合は、すべての書類を入手してください。
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
ここで**
は、 likes
配列内の要素を表します。
ni
in
の逆演算子です。 bones
likes
配列のどこかにあるドキュメントを取得します。
/pets/*/[likes ni "bones"]
より複雑なフィルターを作成できます
( /[age <= 20] or /[lastName re "Do.*"] )
and /pets/*/likes/[** in ["bones", "toys"]]
re
演算子を使用した括弧のグループ化と正規表現の一致について注意してください。
~
接頭辞一致演算子です ( ejdb v2.0.53
以降)。プレフィックス マッチングはインデックスを使用するとメリットが得られます。
/lastName
"Do"
で始まるドキュメントを取得します。
/[lastName ~ Do]
["bones","jumping","toys"]
に完全に一致するlikes
配列を含むドキュメントをフィルターします。
/**/[likes = ["bones","jumping","toys"]]
配列とマップのマッチング アルゴリズムは異なります。
{"f":"d","e":"j"}
と{"e":"j","f":"d"}
等しいマップです。ルートレベルでfirstName
キーを持つ JSON ドキュメントを検索します。
/[* = "firstName"]
このコンテキスト*
はキー名を示します。
キー名とキー値の条件を同時に使用できます。
/[[* = "firstName"] = John]
キー名はfirstName
またはlastName
のいずれかにすることができますが、いずれの場合もJohn
値を含む必要があります。
/[[* in ["firstName", "lastName"]] = John]
これは、動的プレースホルダーを使用したクエリ (C API) で役立つ場合があります。
/[[* = :keyName] = :keyValue]
APPLY
セクションはドキュメントの内容の変更を担当します。
APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del'
JSON パッチ仕様は、 apply
キーワードの後に続くrfc7386
またはrfc6902
仕様に準拠しています。
一致したすべてのドキュメントにaddress
オブジェクトを追加しましょう
/[firstName = John] | apply {"address":{"city":"New York", "street":""}}
JSON オブジェクトがapply
セクションの引数である場合、マージ マッチ ( rfc7386
) として扱われます。それ以外の場合は、 rfc6902
JSON パッチを示す配列である必要があります。プレースホルダーはapply
セクションでもサポートされています。
/* | apply :?
address
に番地名を設定します
/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]
ジョンのpets
のセットにNeo
フィッシュを追加
/[firstName = John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]
upsert
マージ パッチとして使用される指定された json 引数によって既存のドキュメントを更新するか、指定された json 引数を新しいドキュメント インスタンスとして挿入します。
/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}
JSON パスで識別される数値を、指定された値だけインクリメントします。
例:
Document: {"foo": 1}
Patch: [{"op": "increment", "path": "/foo", "value": 2}]
Result: {"foo": 3}
JSON パッチのadd
と同じですが、欠落している JSON パス セグメントに対して中間オブジェクト ノードを作成します。
例:
Document: {"foo": {"bar": 1}}
Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}]
Result: {"foo":{"bar":1,"zaz":{"gaz":22}}}
例:
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
JSON ドキュメントの 2 つの値をパスfrom
開始して交換します。
スワッピングルール
from
が指す値が存在しない場合、エラーが発生します。path
によって指定された値が存在しない場合は、パスfrom
の値によって設定され、パスfrom
指定されたオブジェクトは削除されます。from
とpath
が指す両方の値が指定された場合、それらは交換されます。例:
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]
Result: {"foo": [11], "baz": {"gaz": "bar"}}
例 (ルール 2 のデモ):
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]
Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}
del
キーワードを使用して、一致した要素をコレクションから削除します。
/FILTERS | del
例:
> 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
プロジェクションを使用すると、不要なデータを除いた JSON ドキュメントのサブセットのみを取得できます。
クエリ プレースホルダ API はプロジェクションでサポートされています。
コレクションにドキュメントをもう 1 つ追加しましょう。
$ 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
ここで、コレクションからペットの所有者の姓と名のみをクエリします。
> k query family /* | /{firstName,lastName}
< k 3 {"firstName":"Jack","lastName":"Parker"}
< k 1 {"firstName":"John","lastName":"Doe"}
< k
すべてのドキュメントにpets
配列を追加する
> k query family /* | /{firstName,lastName} + /pets
< k 3 {"firstName":"Jack","lastName":"Parker","pets":[...
< k 1 {"firstName":"John","lastName":"Doe","pets":[...
pets
フィールドのみを文書から除外する
> 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
ここで使用されるall
キーワードは文書全体を示します。
pets
配列のage
と最初のペットを取得します。
> 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
結合は、ドキュメントへの参照を実際のドキュメント オブジェクトに具体化し、参照をインプレースで置き換えます。
ドキュメントは主キーのみで結合されます。
参照キーは参照ドキュメントに数値または文字列フィールドとして保存する必要があります。
結合は、次の形式で射影式の一部として指定できます。
/.../field<collection
どこ
field
- JSON フィールドには、結合されたドキュメントの主キーが含まれます。<
‐ EJDB エンジンにfield
キーを結合されたドキュメントの本文で置き換えるよう指示する特別なマーク シンボル。collection
- 結合されたドキュメントが存在する DB コレクションの名前。関連するドキュメントが見つからない場合、参照元ドキュメントは変更されません。
インタラクティブな WebSocket シェルでのコレクション結合の簡単なデモンストレーションを次に示します。
> 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
無効な参照:
> 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)...
さらにドキュメントを 1 つ追加し、 firstName
昇順とage
の降順に従ってコレクション内のドキュメントを並べ替えてみましょう。
> 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
命令は、別個のドキュメントの並べ替え段階を避けるために、コレクション用に定義されたインデックスを使用する場合があります。
OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
skip n
結果セットの最初の要素の前にある最初のn
レコードをスキップしますlimit n
結果セット内のドキュメントの最大数を設定しますcount
一致したドキュメントのcount
のみを返します。 > k query family /* | count
< k 3
< k
noidx
クエリの実行にインデックスを使用しません。inverse
デフォルトでは、クエリは最近追加されたドキュメントから古いドキュメントまでスキャンします。このオプションはスキャン方向を反対に反転し、 noidx
モードをアクティブにします。クエリにasc/desc
ソート句がある場合は効果がありません。 データベース インデックスは、数値または文字列型の値を含む任意の JSON フィールド パスに対して構築できます。インデックスは、値の重複を許可しないunique
、またはnon unique
にすることができます。次のインデックス モード ビット マスク フラグが使用されます ( ejdb2.h
で定義)。
インデックスモード | 説明 |
---|---|
0x01 EJDB_IDX_UNIQUE | インデックスは一意です |
0x04 EJDB_IDX_STR | JSON string フィールド値タイプのインデックス |
0x08 EJDB_IDX_I64 | 8 bytes width 符号付き整数フィールド値のインデックス |
0x10 EJDB_IDX_F64 | 8 bytes width 符号付き浮動小数点フィールド値のインデックス。 |
たとえば、文字列型の一意のインデックスはEJDB_IDX_UNIQUE | EJDB_IDX_STR
によって指定されます。 EJDB_IDX_UNIQUE | EJDB_IDX_STR
= 0x05
。インデックスは、JSON ドキュメント内の特定のパスの下にある 1 つの値の型に対してのみ定義できます。
/lastName
パスの一意でない文字列インデックスを定義してみましょう。
> k idx family 4 /lastName
< k
一連のヒューリスティック ルールに基づくクエリのインデックス選択。
WS API でexplain
コマンドを発行すると、いつでもインデックスの使用状況を確認できます。
> 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
EJDB2 インデックスを使用する場合は、次のステートメントが考慮されます。
特定のクエリの実行に使用できるインデックスは 1 つだけです
クエリがトップレベルのor
部分で構成されている場合、またはクエリ式のトップレベルにnegated
式が含まれている場合、インデックスはまったく使用されません。したがって、以下のインデックスはありません。
/[lastName != Andy]
/[lastName = "John"] or /[lastName = Peter]
ただし、上で定義した/lastName
インデックスが使用されます
/[lastName = Doe]
/[lastName = Doe] and /[age = 28]
/[lastName = Doe] and not /[age = 28]
/[lastName = Doe] and /[age != 28]
次の演算子はインデックス (ejdb 2.0.x) でサポートされています。
eq, =
gt, >
gte, >=
lt, <
lte, <=
in
~
(ejdb 2.0.53 以降のプレフィックス マッチング) ORDERBY
句では、結果セットの並べ替えを回避するためにインデックスを使用する場合があります。
配列フィールドにもインデックスを付けることができます。いくつかのエンティティ タグのインデックス作成という典型的な使用例の概要を説明します。
> 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
/tags
の文字列インデックスを作成する
> k idx books 4 /tags
< k
bestseller
タグで書籍をフィルターし、クエリでのインデックスの使用状況を表示します。
> 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
コレクション内のすべてのドキュメントは、主キーによってdescending
に並べ替えられます。そのため、自動生成キー ( ejdb_put_new
) を使用すると、クエリの並べ替え、インデックス、またはinverse
キーワードを使用しない限り、フル スキャン クエリの結果としてフェッチされたドキュメントが挿入時間に従って子孫順に並べられることが確実になる場合があります。
多くの場合、インデックスを使用すると全体的なクエリのパフォーマンスが低下する可能性があります。インデックス コレクションにはドキュメント参照 ( id
) のみが含まれており、エンジンはクエリの照合を完了するために主キーによって追加のドキュメント フェッチを実行する可能性があるためです。したがって、それほど大きくないコレクションの場合は、インデックスを使用したスキャンよりもブルート スキャンの方がパフォーマンスが向上する可能性があります。ただし、完全一致操作: eq
、 in
、および自然なインデックス順序によるsorting
、ほとんどの場合、インデックスの利点が得られます。
apply
またはdel
操作を使用して一部のドキュメント セットを更新したいが、クエリの結果としてすべてのドキュメントを取得したくない場合は、クエリにcount
修飾子を追加するだけで、不必要なデータ転送と JSON データ変換を取り除くことができます。
EJDB エンジンは、クエリとデータ変更のためのネットワーク API を公開する別の HTTP/Websocket エンドポイント ワーカーを開始する機能を提供します。 jbs
サーバーでは SSL (TLS 1.2) がサポートされています。
ネットワーク経由でデータベースを公開する最も簡単な方法は、スタンドアロンのjbs
サーバーを使用することです。 (もちろん、 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.
HTTP エンドポイントは、 --access
フラグまたは C API EJDB_HTTP
構造体で指定されたトークンによって保護される場合があります。アクセス トークンが設定されている場合、クライアントはX-Access-Token
HTTP ヘッダーを提供する必要があります。トークンが必要であるがクライアントによって提供されない場合は、 401
HTTP コードが報告されます。アクセス トークンがクライアントから提供されたトークンと一致しない場合、サーバーは403
HTTP コードで応答します。
新しいドキュメントをcollection
に追加します。
200
成功。本文: int64
数値としての新しいドキュメント識別子特定の数値id
でドキュメントを置換/保存します
200
。空の体 id
で識別されたドキュメントをcollection
から削除します
200
。空の体404
ドキュメントが見つかりません rfc7396、rfc6902 データのid
で識別されるドキュメントにパッチを適用します。
200
。空の体 id
で識別されるドキュメントをcollection
から取得します。
200
。本文: JSON ドキュメント テキスト。content-type:application/json
content-length:
404
ドキュメントが見つかりません POST 本文として指定されたクエリによってコレクションをクエリします。クエリの本文には、最初のフィルター要素で使用されているコレクション名が含まれている必要があります: @collection_name/...
リクエスト ヘッダー:
X-Hints
カンマ区切りの ejdb2 データベース エンジンへの追加ヒント。explain
--------------------
行で区切られた結果セットの最初の要素の前にクエリ実行プランを表示します。応答:200
。n
で区切られた JSON ドキュメント: rn<document id>t<document JSON body>
...
例:
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}
ejdb JSON メタデータと、 Allow
応答ヘッダーで使用可能な HTTP メソッドを取得します。例:
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 は、HTTP WebSocket プロトコルを介した単純なテキストベースのプロトコルをサポートします。インタラクティブな WebSocket CLI ツール wscat を使用して、手動でサーバーと通信できます。
次のヘルプ テキスト メッセージで応答します。
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>
>
すべてのコマンドの前の<key>
プレフィックスに注意してください。これはクライアントによって選択され、特定の WebSocket リクエストを識別するために指定された任意のキーです。このキーはリクエストへの応答とともに返され、クライアントがその特定のリクエストに対する応答を識別できるようになります。
エラーは次の形式で返されます。
<key> ERROR: <error description>
<key> info
データベースのメタデータを JSON ドキュメントとして取得します。
<key> get <collection> <id>
id
で識別されるドキュメントをcollection
から取得します。ドキュメントが見つからない場合は、 IWKV_ERROR_NOTFOUND
が返されます。
例:
> k get family 3
< k 3 {
"firstName": "Jack",
"lastName": "Parker",
"age": 35,
"pets": [
{
"name": "Sonic",
"kind": "mouse",
"likes": []
}
]
}
ドキュメントが見つからない場合は、次のエラーが発生します。
> k get family 55
< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND)
>
<key> set <collection> <id> <document json>
特定の数値id
の下にあるドキュメントを置換/追加します。 Collection
が存在しない場合は、自動的に作成されます。
<key> add <collection> <document json>
新しいドキュメントを<collection>
に追加します。ドキュメントの新しいid
が生成され、応答として返されます。 `Collection> が存在しない場合は自動的に作成されます。
例:
> k add mycollection {"foo":"bar"}
< k 1
> k add mycollection {"foo":"bar"}
< k 2
>
<key> del <collection> <id>
id
で識別されるドキュメントをcollection
から削除します。ドキュメントが見つからない場合は、 IWKV_ERROR_NOTFOUND
が返されます。
<key> patch <collection> <id> <patch json>
id
で識別されるドキュメントに rfc7396 または rfc6902 パッチを適用します。ドキュメントが見つからない場合は、 IWKV_ERROR_NOTFOUND
が返されます。
<key> query <collection> <query>
指定されたcollection
内のドキュメントに対してクエリを実行します。応答:ドキュメント本体を含む一連の WS メッセージは、本体が空の最後のメッセージで終了します。
> k query family /* | /firstName
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
最後のメッセージに関する注意: <key>
に本文がありません。
<key> explain <collection> <query>
<key> query <collection> <query>
と同じですが、最初の応答メッセージには<key> explain
プレフィックスが付き、クエリ実行プランが含まれます。
例:
> k explain family /* | /firstName
< k explain [INDEX] NO [COLLECTOR] PLAIN
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
クエリテキストを実行します。クエリの本文には、最初のフィルター要素で使用されているコレクション名が含まれている必要があります: @collection_name/...
。動作は次の場合と同じです: <key> query <collection> <query>
<key> idx <collection> <mode> <path>
指定された json path
とcollection
に対して、指定されたmode
(ビットマスク フラグ) でインデックスを確保します。コレクションが存在しない場合は作成されます。
インデックスモード | 説明 |
---|---|
0x01 EJDB_IDX_UNIQUE | インデックスは一意です |
0x04 EJDB_IDX_STR | JSON string フィールド値タイプのインデックス |
0x08 EJDB_IDX_I64 | 8 bytes width 符号付き整数フィールド値のインデックス |
0x10 EJDB_IDX_F64 | 8 bytes width 符号付き浮動小数点フィールド値のインデックス。 |
/name
JSON フィールドに一意の文字列インデックス(0x01 & 0x04) = 5
設定します。
k idx mycollection 5 /name
<key> rmi <collection> <mode> <path>
指定された json path
およびcollection
の指定されたmode
(ビットマスク フラグ) を持つインデックスを削除します。指定されたインデックスが見つからない場合はエラーを返します。
<key> rmc <collection>
コレクションとそのすべてのデータを削除します。注: collection
が見つからない場合でも、エラーは報告されません。
Docker がインストールされている場合は、Docker イメージを構築してコンテナーで実行できます。
cd docker
docker build -t ejdb2 .
docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey
または、Docker Hub から直接ejdb2
のイメージを取得します。
docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey
EJDB は、任意のC/C++
アプリケーションに組み込むことができます。 C API
次のヘッダーに文書化されています。
アプリケーション例:
#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 ;
}
コンパイルして実行します。
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.