注意:問題追蹤器已停用。歡迎您貢獻,接受拉取請求。
EJDB2 是在 MIT 授權下發布的嵌入式 JSON 資料庫引擎。
IT 的故事——蕭條、鳥類和 EJDB 2.0
Linux | macOS | iOS系統 | 安卓 | 視窗 | |
---|---|---|---|---|---|
C庫 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️1 |
NodeJS | ✔️ | ✔️ | 3 | ||
爪哇 | ✔️ | ✔️ | ✔️ | ✔️2 | |
達特VM 5 | ✔️ | ✔️2 | 3 | ||
顫振5 | ✔️ | ✔️ | |||
反應本機5 | 4 | ✔️ | |||
雨燕5 | ✔️ | ✔️ | ✔️ |
[5]
綁定未維護,需要貢獻者。
[1]
不支援 HTTP/Websocket #257
[2]
二進位檔案不隨 dart pub.
您可以手動建立它[3]
可以構建,但需要與 windows node/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
EJDB 查詢語言 (JQL) 語法的靈感來自 XPath 和 Unix shell 管道背後的想法。它旨在輕鬆查詢和更新 JSON 文件集。
由 peg/leg 建立的 JQL 解析器 — C 的遞歸下降解析器產生器 這是正式的解析器語法:https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg
以下使用的符號是基於 SQL 語法描述:
規則 | 描述 |
---|---|
' ' | 單引號中的字串表示作為查詢一部分的不含引號的字串文字。 |
{ a | b } | 大括號括起兩個或多個所需的替代選項,並以垂直線分隔。 |
[ ] | 方括號表示可選元素或子句。多個元素或子句由垂直線分隔。 |
| | 豎線分隔兩個或多個替代語法元素。 |
... | 省略號表示前面的元素可以重複。除非另有說明,重複次數不受限。 |
( ) | 括號是分組符號。 |
未引用的小寫單字 | 表示某些查詢部分的語意。例如: 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
的上下文中:特定鍵下數組元素的嵌套數組值。 讓我們玩一些非常基本的數據和查詢。為了簡單起見,我們將使用 ejdb websocket 網路 API,它為我們提供了一個互動式 CLI。使用純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/=:?
從users
集合中取得主鍵為112
文檔
> 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]
過濾likes
數組與["bones","jumping","toys"]
完全匹配的文檔
/**/[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 補丁規範符合rfc7386
或rfc6902
規範,後面跟著apply
關鍵字。
讓我們將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"}]
將Neo
魚加入約翰的pets
集中
/[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 patch 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
交換from
路徑開始的 JSON 文件的兩個值。
交換規則
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。
讓我們在我們的集合中再新增一份文件:
$ 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
關鍵字表示整個文件。
取得age
和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
連接將對文檔的引用具體化為真實的文檔對象,該對象將就地替換引用。
文件僅透過其主鍵連接。
引用鍵應作為數字或字串欄位儲存在引用者文件中。
連接可以以下形式指定為投影表達式的一部分:
/.../field<collection
在哪裡
field
- JSON 字段包含連接文檔的主鍵。<
‐ 特殊標記符號,指示 EJDB 引擎以連接文件的正文取代field
位鍵。collection
- 連接文件所在的資料庫集合的名稱。如果未找到關聯文檔,則引用者文件將保持不變。
以下是我們的互動式 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
無效參考:
> 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)...
讓我們再增加一個文檔,然後根據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 文件中特定路徑下的一種值類型定義索引。
讓我們為/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 索引時要考慮以下語句:
特定查詢執行只能使用一個索引
如果查詢由頂層的部分組成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 引擎提供了啟動單獨的 HTTP/Websocket 端點工作程序的能力,該工作程序公開網路 API 以進行查詢和資料修改。 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
成功。 Body:一個新的文檔標識符,為int64
數字替換/儲存特定數字id
下的文檔
200
成功。身體空虛從collection
中刪除由id
標識的文檔
200
成功。身體空虛404
未找到文檔修補 rfc7396、rfc6902 資料的id
標識的文件。
200
成功。身體空虛從collection
中檢索由id
標識的文件。
200
成功。正文:JSON 文檔文本。content-type:application/json
content-length:
404
未找到文檔透過提供的查詢作為 POST 正文來查詢集合。查詢正文應包含第一個篩選器元素中使用的集合名稱: @collection_name/...
請求標頭:
X-Hints
以逗號分隔 ejdb2 資料庫引擎的額外提示。explain
在結果集中第一個元素之前顯示查詢執行計劃,以--------------------
行分隔。回覆:200
成功。n
分隔,格式如下: 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}
在Allow
回應標頭中取得ejdb JSON元資料和可用的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>
從collection
中檢索由id
標識的文件。如果未找到文檔,將返回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>
從collection
中刪除由id
標識的文件。如果未找到文檔,將返回IWKV_ERROR_NOTFOUND
。
<key> patch <collection> <id> <patch json>
將 rfc7396 或 rfc6902 補丁套用至id
標識的文件。如果未找到文檔,將返回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.