참고: 문제 추적기는 비활성화됩니다. 기여하실 수 있으며 풀 요청이 허용됩니다.
EJDB2는 MIT 라이센스에 따라 게시된 내장형 JSON 데이터베이스 엔진입니다.
IT 불황 이야기, 새와 EJDB 2.0
리눅스 | macOS | iOS | 기계적 인조 인간 | 윈도우 | |
---|---|---|---|---|---|
C 라이브러리 | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ 1 |
NodeJS | ✔️ | ✔️ | 3 | ||
자바 | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
다트VM 5 | ✔️ | ✔️ 2 | 3 | ||
플러터 5 | ✔️ | ✔️ | |||
리액트 네이티브 5 | 4 | ✔️ | |||
스위프트 5 | ✔️ | ✔️ | ✔️ |
[5]
바인딩은 유지 관리되지 않는 기여자가 필요합니다.
[1]
HTTP/웹소켓 지원 없음 #257
[2]
dart pub.
수동으로 빌드할 수 있습니다.
[3]
빌드할 수 있지만 Windows 노드/dart libs
와의 연결이 필요합니다.
[4]
포팅 진행 중 #273
Linux
, macOS
및 FreeBSD
에서 테스트되었습니다. 제한된 Windows 지원EJDB를 사용하고 있습니까? 알려줘요!
High Sierra
/ Mojave
/ Catalina
에서 EJDB2 코드 포팅 및 테스트됨
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 libs를 기반으로 하는 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 셸 파이프의 아이디어에서 영감을 받은 JQL(EJDB 쿼리 언어) 구문입니다. 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
맥락에서: 특정 키 아래 배열 요소의 중첩된 배열 값입니다. 아주 기본적인 데이터와 쿼리를 사용해 보겠습니다. 단순화를 위해 일종의 대화형 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을 통한 쿼리 명령의 형식은 다음과 같습니다.
<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"...
이 시점 이하에서는 웹소켓 관련 접두사 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]
공백이 있는 단어 주위의 따옴표에 유의하세요.
소유자가 20
age
이상이고 bones
toys
좋아하는 애완동물이 있는 모든 문서를 확보하세요.
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
여기서 **
likes
배열의 일부 요소를 나타냅니다.
ni
in
의 역연산자입니다. likes
배열 어딘가에 bones
있는 문서를 가져옵니다.
/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'
rfc7386
또는 rfc6902
사양을 준수하는 JSON 패치 사양은 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"}]
존의 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
경로 from
시작하여 JSON 문서의 두 값을 교환합니다.
교환 규칙
from
값이 가리키는 값이 있으면 오류가 발생합니다.path
가 가리키는 값이 없으면 path from
값으로 설정되고 path 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
이제 컬렉션에서 애완동물 소유자인 firstName 및 lastName만 쿼리합니다.
> 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
‐ 결합된 문서가 있는 DB 컬렉션의 이름입니다.관련 문서가 없으면 참조 문서는 그대로 유지됩니다.
다음은 대화형 웹소켓 셸의 컬렉션 조인에 대한 간단한 데모입니다.
> 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
= 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 엔진은 쿼리 및 데이터 수정을 위해 네트워크 API를 노출하는 별도의 HTTP/Websocket 엔드포인트 작업자를 시작하는 기능을 제공합니다. SSL(TLS 1.2)은 jbs
서버에서 지원됩니다.
네트워크를 통해 데이터베이스를 노출하는 가장 쉬운 방법은 독립형 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
. 빈 몸 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
으로 구분된 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}
응답 헤더 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 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>
접두사에 유의하세요. 클라이언트가 선택하고 특정 웹소켓 요청을 식별하도록 지정된 임의의 키입니다. 이 키는 요청에 대한 응답으로 반환되며 클라이언트가 특정 요청에 대한 응답을 식별할 수 있도록 합니다.
오류는 다음 형식으로 반환됩니다.
<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
생성되어 응답으로 반환됩니다. `컬렉션>이 없으면 자동으로 생성됩니다.
예:
> 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>
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.