注意:问题跟踪器已禁用。欢迎您贡献,接受拉取请求。
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.