NOTA: O rastreador de problemas está desativado. Você está convidado a contribuir, solicitações pull aceitas.
EJDB2 é um mecanismo de banco de dados JSON incorporável publicado sob licença do MIT.
A história da depressão de TI, pássaros e EJDB 2.0
Linux | macOS | iOS | Android | Windows | |
---|---|---|---|---|---|
Biblioteca C | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ 1 |
NodeJS | ✔️ | ✔️ | 3 | ||
Java | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
DartVM5 | ✔️ | ✔️ 2 | 3 | ||
Vibração 5 | ✔️ | ✔️ | |||
Reagir Nativo 5 | 4 | ✔️ | |||
Rápido 5 | ✔️ | ✔️ | ✔️ |
[5]
As vinculações são necessárias para colaboradores não mantidos .
[1]
Sem suporte HTTP/Websocket #257
[2]
Os binários não são distribuídos com o dart pub.
Você pode construí-lo manualmente
[3]
Pode ser compilado, mas precisava de uma ligação com windows node/dart libs
.
[4]
Portabilidade em andamento #273
Linux
, macOS
e FreeBSD
. Tem suporte limitado ao WindowsVocê está usando EJDB? Avise!
Código EJDB2 portado e testado em High Sierra
/ Mojave
/ Catalina
Ligação EJDB2 Swift para MacOS, iOS e Linux. A vinculação rápida está desatualizada no momento. Procurando colaboradores.
brew install ejdb
cmake v3.24 ou superior necessário
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 pode ser compilado cruzadamente para Windows
Observação: a API de rede HTTP/Websocket está desativada e ainda não é compatível
Vinculações Nodejs/Dart ainda não portadas para Windows.
Guia de compilação cruzada para Windows
IWSTART é um gerador automático de projeto inicial CMake para projetos C baseado em bibliotecas iowow/iwnet/ejdb2.
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
Sintaxe da linguagem de consulta EJDB (JQL) inspirada nas ideias por trás dos shell pipes XPath e Unix. Ele foi projetado para facilitar a consulta e atualização de conjuntos de documentos JSON.
Analisador JQL criado por peg/leg - geradores de analisador descendente recursivo para C Aqui está a gramática formal do analisador: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg
A notação usada abaixo é baseada na descrição da sintaxe SQL:
Regra | Descrição |
---|---|
' ' | String entre aspas simples denota literal de string sem aspas como parte da consulta. |
{ a | b } | As chaves incluem duas ou mais opções alternativas obrigatórias, separadas por barras verticais. |
[ ] | Colchetes indicam um elemento ou cláusula opcional. Vários elementos ou cláusulas são separados por barras verticais. |
| | Barras verticais separam dois ou mais elementos de sintaxe alternativos. |
... | As reticências indicam que o elemento anterior pode ser repetido. A repetição é ilimitada, salvo indicação em contrário. |
( ) | Parênteses são símbolos de agrupamento. |
Palavra sem aspas em minúsculas | Denota a semântica de alguma parte da consulta. Por exemplo: placeholder_name - nome de qualquer espaço reservado. |
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
: Qualquer valor JSON válido: objeto, array, string, bool, número.json_path
: ponteiro JSON simplificado. Ex.: /foo/bar
ou /foo/"bar with spaces"/
*
no contexto de NODE
: qualquer nome de chave de objeto JSON em um nível de aninhamento específico.**
no contexto de NODE
: qualquer nome de chave de objeto JSON em nível de aninhamento arbitrário.*
no contexto de NODE_EXPR_LEFT
: Nome da chave em nível específico.**
no contexto de NODE_EXPR_LEFT
: valor da matriz aninhada do elemento da matriz sob uma chave específica. Vamos brincar com alguns dados e consultas muito básicos. Para simplificar, usaremos a API de rede ejdb websocket, que nos fornece uma espécie de CLI interativo. O mesmo trabalho também pode ser feito usando API C
pura ( ejdb2.h jql.h
).
NOTA: Dê uma olhada nos casos de teste JQL para obter mais exemplos.
{
"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 " ]}
]
}
Salve json como sample.json
e carregue-o na coleção 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
O servidor pode ser acessado usando HTTP ou endpoint Websocket. Mais informações
curl -d ' @sample.json ' -H ' X-Access-Token:myaccess01 ' -X POST http://localhost:9191/family
Podemos brincar usando o cliente websocket wscat interativo.
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 "
]
}
]
}
Nota sobre o prefixo k
antes de cada comando; É uma chave arbitrária escolhida pelo cliente e designada para identificar uma solicitação específica de websocket, esta chave será retornada com a resposta à solicitação e permite que o cliente identifique essa resposta para sua solicitação específica. Mais informações
O comando de consulta pelo websocket tem o seguinte formato:
<key> query <collection> <query>
Portanto, consideraremos apenas a parte <query>
neste documento.
k query family /*
ou
k query family /**
ou especifique explicitamente o nome da coleção na consulta
k @family/*
Podemos executar consulta por solicitação 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
O elemento no índice 1
existe na matriz likes
dentro de um subobjeto pets
> k query family /pets/*/likes/1
< k 1 {"firstName":"John"...
O elemento no índice 1
existe na matriz likes
em qualquer nível de aninhamento likes
> k query family /**/likes/1
< k 1 {"firstName":"John"...
A partir deste ponto e abaixo, omitirei k query family
específico do websocket e considerarei apenas as consultas JQL.
Para obter documentos por chave primária estão disponíveis as seguintes opções:
Use a chamada de API ejdb_get()
const doc = await db . get ( 'users' , 112 ) ;
Use a construção de consulta especial: /=:?
ou @collection/=:?
Obtenha documento da coleção users
com chave primária 112
> k @users/=112
Atualizar matriz de tags para documento na coleção jobs
(TypeScript):
await db . createQuery ( '@jobs/ = :? | apply :? | count' )
. setNumber ( 0 , id )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Matriz de chaves primárias também pode ser usada para correspondência:
await db . createQuery ( '@jobs/ = :?| apply :? | count' )
. setJSON ( 0 , [ 23 , 1 , 2 ] )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Abaixo está um conjunto de perguntas autoexplicativas:
/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]
Nota sobre citações em torno de palavras com espaços.
Obtenha todos os documentos de proprietários com age
superior a 20
e algum animal de estimação que goste bones
ou toys
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
Aqui **
denota algum elemento na matriz likes
.
ni
é o operador inverso de in
. Obtenha documentos onde bones
estão em algum lugar na matriz likes
.
/pets/*/[likes ni "bones"]
Podemos criar filtros mais complicados
( /[age <= 20] or /[lastName re "Do.*"] )
and /pets/*/likes/[** in ["bones", "toys"]]
Nota sobre agrupamento de parênteses e correspondência de expressões regulares usando o operador re
.
~
é um operador de correspondência de prefixo (desde ejdb v2.0.53
). A correspondência de prefixos pode se beneficiar do uso de índices.
Obtenha documentos onde /lastName
começa com "Do"
.
/[lastName ~ Do]
Filtre documentos com matriz likes
que corresponda exatamente a ["bones","jumping","toys"]
/**/[likes = ["bones","jumping","toys"]]
Os algoritmos de correspondência para matrizes e mapas são diferentes:
{"f":"d","e":"j"}
e {"e":"j","f":"d"}
são mapas iguais. Encontre o documento JSON com a chave firstName
no nível raiz.
/[* = "firstName"]
Neste contexto *
denota um nome de chave.
Você pode usar condições no nome e no valor da chave ao mesmo tempo:
/[[* = "firstName"] = John]
O nome da chave pode ser firstName
ou lastName
mas deve ter o valor John
em qualquer caso.
/[[* in ["firstName", "lastName"]] = John]
Pode ser útil em consultas com espaços reservados dinâmicos (API C):
/[[* = :keyName] = :keyValue]
Seção APPLY
responsável pela modificação do conteúdo dos documentos.
APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del'
As especificações do patch JSON estão em conformidade com as especificações rfc7386
ou rfc6902
seguidas após a palavra-chave apply
.
Vamos adicionar o objeto address
a todos os documentos correspondentes
/[firstName = John] | apply {"address":{"city":"New York", "street":""}}
Se o objeto JSON for um argumento da seção apply
, ele será tratado como correspondência de mesclagem ( rfc7386
), caso contrário, deverá ser um array que denota o patch JSON rfc6902
. Espaços reservados também são suportados pela seção apply
.
/* | apply :?
Defina o nome da rua no address
/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]
Adicione o peixe Neo
ao conjunto de pets
de John
/[firstName = John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]
upsert
atualiza o documento existente por determinado argumento json usado como patch de mesclagem ou insere o argumento json fornecido como nova instância de documento.
/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}
Incrementa o valor numérico identificado pelo caminho JSON pelo valor especificado.
Exemplo:
Document: {"foo": 1}
Patch: [{"op": "increment", "path": "/foo", "value": 2}]
Result: {"foo": 3}
Igual à add
patch JSON, mas cria nós de objetos intermediários para segmentos de caminho JSON ausentes.
Exemplo:
Document: {"foo": {"bar": 1}}
Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}]
Result: {"foo":{"bar":1,"zaz":{"gaz":22}}}
Exemplo:
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
Troca dois valores do documento JSON começando from
caminho.
Trocando regras
from
não existir, um erro será gerado.path
não existir, ele será definido pelo valor from
caminho, então o objeto apontado from
caminho será removido.from
e path
forem apresentados, eles serão trocados.Exemplo:
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]
Result: {"foo": [11], "baz": {"gaz": "bar"}}
Exemplo (demonstração da regra 2):
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]
Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}
Use a palavra-chave del
para remover elementos correspondentes da coleção:
/FILTERS | del
Exemplo:
> 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
A projeção permite obter apenas um subconjunto do documento JSON, excluindo dados desnecessários.
A API de marcadores de consulta é suportada em projeções.
Vamos adicionar mais um documento à nossa coleção:
$ 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
Agora consulte apenas os proprietários de animais de estimação firstName e lastName da coleção.
> k query family /* | /{firstName,lastName}
< k 3 {"firstName":"Jack","lastName":"Parker"}
< k 1 {"firstName":"John","lastName":"Doe"}
< k
Adicione uma matriz pets
para cada documento
> k query family /* | /{firstName,lastName} + /pets
< k 3 {"firstName":"Jack","lastName":"Parker","pets":[...
< k 1 {"firstName":"John","lastName":"Doe","pets":[...
Excluir apenas o campo pets
dos documentos
> 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
Aqui all
as palavras-chave usadas denotam o documento inteiro.
Obtenha age
e o primeiro animal de estimação na lista de 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
Join materializa a referência ao documento em objetos de documento reais que substituirão a referência no local.
Os documentos são unidos apenas pelas suas chaves primárias.
As chaves de referência devem ser armazenadas no documento referenciador como campo de número ou string.
As junções podem ser especificadas como parte da expressão de projeção no seguinte formato:
/.../field<collection
Onde
field
- O campo JSON contém a chave primária do documento unido.<
‐ O símbolo de marca especial que instrui o mecanismo EJDB a substituir a chave field
pelo corpo do documento unido.collection
– nome da coleção do banco de dados onde os documentos unidos estão localizados.Um documento de referência permanecerá intacto se o documento associado não for encontrado.
Aqui está uma demonstração simples de junções de coleção em nosso shell de websocket interativo:
> 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
Referências inválidas:
> 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)...
Vamos adicionar mais um documento e, em seguida, classificar os documentos na coleção de acordo com a ordem crescente e age
firstName
.
> 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
As instruções asc, desc
podem usar índices definidos para coleta para evitar um estágio separado de classificação de documentos.
OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
skip n
Pular os primeiros n
registros antes do primeiro elemento no conjunto de resultadoslimit n
Define o número máximo de documentos no conjunto de resultadoscount
Retorna apenas count
de documentos correspondentes > k query family /* | count
< k 3
< k
noidx
Não use nenhum índice para execução de consulta.inverse
Por padrão, a consulta verifica os documentos adicionados mais recentemente aos mais antigos. Esta opção inverte a direção da varredura para o oposto e ativa o modo noidx
. Não tem efeito se a consulta tiver cláusulas de classificação asc/desc
. O índice do banco de dados pode ser criado para qualquer caminho de campo JSON contendo valores de número ou tipo de string. O índice pode ser unique
- não permitindo duplicação de valor e non unique
. Os seguintes sinalizadores de máscara de bits de modo de índice são usados (definidos em ejdb2.h
):
Modo de índice | Descrição |
---|---|
0x01 EJDB_IDX_UNIQUE | O índice é único |
0x04 EJDB_IDX_STR | Índice para tipo de valor de campo string JSON |
0x08 EJDB_IDX_I64 | Índice para valores de campos inteiros assinados com 8 bytes width |
0x10 EJDB_IDX_F64 | Índice para valores de campo de ponto flutuante assinados com 8 bytes width . |
Por exemplo, o índice exclusivo do tipo string será especificado por EJDB_IDX_UNIQUE | EJDB_IDX_STR
= 0x05
. O índice pode ser definido para apenas um tipo de valor localizado em um caminho específico no documento JSON.
Vamos definir um índice de string não exclusivo para o caminho /lastName
:
> k idx family 4 /lastName
< k
Seleção de índice para consultas com base em um conjunto de regras heurísticas.
Você sempre pode verificar o uso do índice emitindo o comando explain
na API do WS:
> 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
As seguintes declarações são levadas em consideração ao usar índices EJDB2:
Apenas um índice pode ser usado para execução de consulta específica
Se a consulta consistir em or
parte unida no nível superior ou contiver expressões negated
no nível superior da expressão de consulta - os índices não estarão em uso. Portanto, não há índices abaixo:
/[lastName != Andy]
/[lastName = "John"] or /[lastName = Peter]
Mas será usado o índice /lastName
definido acima
/[lastName = Doe]
/[lastName = Doe] and /[age = 28]
/[lastName = Doe] and not /[age = 28]
/[lastName = Doe] and /[age != 28]
Os seguintes operadores são suportados por índices (ejdb 2.0.x):
eq, =
gt, >
gte, >=
lt, <
lte, <=
in
~
(correspondência de prefixo desde ejdb 2.0.53) As cláusulas ORDERBY
podem usar índices para evitar a classificação do conjunto de resultados.
Os campos da matriz também podem ser indexados. Vamos descrever um caso de uso típico: indexação de algumas tags de entidade:
> 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
Crie um índice de string para /tags
> k idx books 4 /tags
< k
Filtre livros por tag bestseller
e mostre o uso do índice na consulta:
> 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
Todos os documentos da coleção são classificados por sua chave primária em ordem descending
. Portanto, se você usar chaves geradas automaticamente ( ejdb_put_new
), poderá ter certeza de quais documentos obtidos como resultado da consulta de verificação completa serão ordenados de acordo com o momento da inserção em ordem descendente, a menos que você não use classificação de consulta, índices ou palavra-chave inverse
.
Em muitos casos, o uso do índice pode diminuir o desempenho geral da consulta. Como a coleção de índices contém apenas referências de documentos ( id
) e o mecanismo pode executar uma busca adicional de documentos por sua chave primária para finalizar a correspondência da consulta. Portanto, para coleções não tão grandes, uma varredura bruta pode ter melhor desempenho do que a varredura usando índices. No entanto, as operações de correspondência exata: eq
, in
e sorting
por ordem natural do índice se beneficiarão do índice na maioria dos casos.
Se você deseja atualizar algum conjunto de documentos com operações apply
ou del
, mas não deseja buscá-los todos como resultado da consulta - basta adicionar o modificador count
à consulta para se livrar da transferência desnecessária de dados e da conversão de dados JSON.
O mecanismo EJDB fornece a capacidade de iniciar um trabalhador de endpoint HTTP/Websocket separado, expondo a API da rede para consultas e modificações de dados. SSL (TLS 1.2) é suportado pelo servidor jbs
.
A maneira mais fácil de expor o banco de dados na rede é usar o servidor jbs
independente. (Claro, se você quiser evitar a integração 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.
O endpoint HTTP pode ser protegido por um token especificado com o sinalizador --access
ou estrutura C API EJDB_HTTP
. Se o token de acesso foi definido, o cliente deverá fornecer o cabeçalho HTTP X-Access-Token
. Se o token for necessário, mas não fornecido pelo cliente, o código HTTP 401
será relatado. Se o token de acesso não corresponder ao token fornecido pelo servidor cliente, responderá com o código HTTP 403
.
Adicione um novo documento à collection
.
200
sucesso. Corpo: um novo identificador de documento como número int64
Substitui/armazena documento sob id
numérico específico
200
em sucesso. Corpo vazio Remove documento identificado por id
de uma collection
200
em sucesso. Corpo vazio404
não encontrado Corrija um documento identificado por id
por dados rfc7396, rfc6902.
200
em sucesso. Corpo vazio Recuperar documento identificado por id
de uma collection
.
200
em sucesso. Corpo: texto do documento JSON.content-type:application/json
content-length:
404
não encontrado Consulte uma coleção por consulta fornecida como corpo POST. O corpo da consulta deve conter o nome da coleção em uso no primeiro elemento de filtro: @collection_name/...
Cabeçalhos da solicitação:
X-Hints
separava dicas extras por vírgula para o mecanismo de banco de dados ejdb2.explain
Mostrar plano de execução da consulta antes do primeiro elemento no conjunto de resultados separado pela linha --------------------
. Resposta:200
em sucesso.n
no seguinte formato: rn<document id>t<document JSON body>
...
Exemplo:
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}
Busque metadados JSON ejdb e métodos HTTP disponíveis em Allow
cabeçalho de resposta. Exemplo:
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 suporta protocolo baseado em texto simples sobre protocolo websocket HTTP. Você pode usar a ferramenta CLI interativa do websocket wscat para se comunicar com o servidor manualmente.
Responderá com a seguinte mensagem de texto de ajuda:
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>
>
Nota sobre o prefixo <key>
antes de cada comando; É uma chave arbitrária escolhida pelo cliente e designada para identificar uma solicitação específica de websocket, esta chave será retornada com a resposta à solicitação e permite que o cliente identifique essa resposta para sua solicitação específica.
Os erros são retornados no seguinte formato:
<key> ERROR: <error description>
<key> info
Obtenha metadados de banco de dados como documento JSON.
<key> get <collection> <id>
Recuperar documento identificado por id
de uma collection
. Se o documento não for encontrado IWKV_ERROR_NOTFOUND
será retornado.
Exemplo:
> k get family 3
< k 3 {
"firstName": "Jack",
"lastName": "Parker",
"age": 35,
"pets": [
{
"name": "Sonic",
"kind": "mouse",
"likes": []
}
]
}
Se o documento não for encontrado, obteremos o erro:
> k get family 55
< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND)
>
<key> set <collection> <id> <document json>
Substitui/adiciona documento sob id
numérico específico. Collection
será criada automaticamente se não existir.
<key> add <collection> <document json>
Adicionar novo documento a <collection>
O novo id
do documento será gerado e retornado como resposta. `Collection> será criada automaticamente se não existir.
Exemplo:
> k add mycollection {"foo":"bar"}
< k 1
> k add mycollection {"foo":"bar"}
< k 2
>
<key> del <collection> <id>
Remova o documento identificado por id
da collection
. Se o documento não for encontrado IWKV_ERROR_NOTFOUND
será retornado.
<key> patch <collection> <id> <patch json>
Aplique o patch rfc7396 ou rfc6902 ao documento identificado por id
. Se o documento não for encontrado IWKV_ERROR_NOTFOUND
será retornado.
<key> query <collection> <query>
Execute consulta em documentos na collection
especificada. Resposta: Um conjunto de mensagens WS com corpos de documento finalizados pela última mensagem com corpo vazio.
> k query family /* | /firstName
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Nota sobre a última mensagem: <key>
sem corpo.
<key> explain <collection> <query>
O mesmo que <key> query <collection> <query>
mas a primeira mensagem de resposta será prefixada por <key> explain
e contém o plano de execução da consulta.
Exemplo:
> k explain family /* | /firstName
< k explain [INDEX] NO [COLLECTOR] PLAIN
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Execute o texto da consulta. O corpo da consulta deve conter o nome da coleção em uso no primeiro elemento de filtro: @collection_name/...
. O comportamento é o mesmo de: <key> query <collection> <query>
<key> idx <collection> <mode> <path>
Garanta o índice com mode
especificado (sinalizador de máscara de bits) para determinado path
e collection
json. A coleção será criada se não existir.
Modo de índice | Descrição |
---|---|
0x01 EJDB_IDX_UNIQUE | O índice é único |
0x04 EJDB_IDX_STR | Índice para tipo de valor de campo string JSON |
0x08 EJDB_IDX_I64 | Índice para valores de campos inteiros assinados com 8 bytes width |
0x10 EJDB_IDX_F64 | Índice para valores de campo de ponto flutuante assinados com 8 bytes width . |
Defina o índice de string exclusivo (0x01 & 0x04) = 5
no campo JSON /name
:
k idx mycollection 5 /name
<key> rmi <collection> <mode> <path>
Remova o índice com mode
especificado (sinalizador de máscara de bits) para determinado path
e collection
json. Retornar erro se o índice fornecido não for encontrado.
<key> rmc <collection>
Remova a coleção e todos os seus dados. Nota: Se collection
não for encontrada, nenhum erro será relatado.
Se você tiver o Docker instalado, poderá criar uma imagem do Docker e executá-la em um contêiner
cd docker
docker build -t ejdb2 .
docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey
ou obtenha uma imagem do ejdb2
diretamente do Docker Hub
docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey
EJDB pode ser incorporado em qualquer aplicativo C/C++
. C API
documentada nos seguintes cabeçalhos:
Exemplo de aplicação:
#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 ;
}
Compile e execute:
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.