NOTA: El rastreador de problemas está deshabilitado. Eres bienvenido a contribuir, se aceptan solicitudes de extracción.
EJDB2 es un motor de base de datos JSON integrable publicado bajo licencia MIT.
La historia de la depresión informática, los pájaros y EJDB 2.0
linux | macos | iOS | Androide | ventanas | |
---|---|---|---|---|---|
biblioteca C | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ 1 |
NodoJS | ✔️ | ✔️ | 3 | ||
Java | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
DardoVM 5 | ✔️ | ✔️ 2 | 3 | ||
Aleteo 5 | ✔️ | ✔️ | |||
Reaccionar nativo 5 | 4 | ✔️ | |||
veloz 5 | ✔️ | ✔️ | ✔️ |
[5]
Los enlaces no se mantienen . Se necesitan contribuyentes.
[1]
No hay soporte para HTTP/Websocket #257
[2]
Los binarios no se distribuyen con dart pub.
Puedes construirlo manualmente.
[3]
Se puede compilar, pero se necesita un vínculo con libs
de nodos/dardos de Windows.
[4]
Portación en curso #273
Linux
, macOS
y FreeBSD
. Tiene soporte limitado para Windows¿Estás usando EJDB? ¡Hágamelo saber!
Código EJDB2 portado y probado en High Sierra
/ Mojave
/ Catalina
Enlace EJDB2 Swift para MacOS, iOS y Linux. El enlace rápido está desactualizado en este momento. Buscando colaboradores.
brew install ejdb
Se requiere cmake v3.24 o superior
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 se puede compilar de forma cruzada para Windows
Nota: La API de red HTTP/Websocket está deshabilitada y aún no es compatible
Los enlaces de Nodejs/Dart aún no se han portado a Windows.
Guía de compilación cruzada para Windows
IWSTART es un generador automático de proyectos iniciales de CMake para proyectos C basados en 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
Sintaxis del lenguaje de consulta EJDB (JQL) inspirada en las ideas detrás de XPath y los shell pipes de Unix. Está diseñado para consultar y actualizar fácilmente conjuntos de documentos JSON.
Analizador JQL creado por peg/leg - generadores de analizadores de descenso recursivo para C Aquí está la gramática formal del analizador: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg
La notación utilizada a continuación se basa en la descripción de la sintaxis SQL:
Regla | Descripción |
---|---|
' ' | La cadena entre comillas simples indica una cadena literal sin comillas como parte de la consulta. |
{ a | b } | Las llaves encierran dos o más opciones alternativas requeridas, separadas por barras verticales. |
[ ] | Los corchetes indican un elemento o cláusula opcional. Varios elementos o cláusulas están separados por barras verticales. |
| | Las barras verticales separan dos o más elementos de sintaxis alternativos. |
... | Las elipses indican que el elemento anterior se puede repetir. La repetición es ilimitada salvo que se indique lo contrario. |
( ) | Los paréntesis son símbolos de agrupación. |
Palabra sin comillas en minúscula | Denota semántica de alguna parte de la consulta. Por ejemplo: placeholder_name : nombre de cualquier marcador de posición. |
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
: cualquier valor JSON válido: objeto, matriz, cadena, bool, número.json_path
: puntero JSON simplificado. Ej.: /foo/bar
o /foo/"bar with spaces"/
*
en el contexto de NODE
: cualquier nombre de clave de objeto JSON en un nivel de anidamiento particular.**
en el contexto de NODE
: cualquier nombre de clave de objeto JSON en un nivel de anidamiento arbitrario.*
en el contexto de NODE_EXPR_LEFT
: Nombre de clave en un nivel específico.**
en el contexto de NODE_EXPR_LEFT
: valor de matriz anidado del elemento de matriz bajo una clave específica. Juguemos con algunos datos y consultas muy básicos. Para simplificar, utilizaremos la API de red ejdb websocket que nos proporciona una especie de CLI interactiva. También se puede realizar el mismo trabajo utilizando la API C
pura ( ejdb2.h jql.h
).
NOTA: Eche un vistazo a los casos de prueba de JQL para ver más ejemplos.
{
"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 " ]}
]
}
Guarde json como sample.json
y luego cárguelo en la colección 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
Se puede acceder al servidor mediante HTTP o punto final Websocket. Más información
curl -d ' @sample.json ' -H ' X-Access-Token:myaccess01 ' -X POST http://localhost:9191/family
Podemos jugar usando el cliente websocket interactivo wscat.
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 el prefijo k
antes de cada comando; Es una clave arbitraria elegida por el cliente y designada para identificar una solicitud de websocket particular; esta clave se devolverá con la respuesta a la solicitud y permite al cliente identificar esa respuesta para su solicitud particular. Más información
El comando de consulta a través de websocket tiene el siguiente formato:
<key> query <collection> <query>
Por lo tanto, consideraremos solo la parte <query>
en este documento.
k query family /*
o
k query family /**
o especificar el nombre de la colección en la consulta explícitamente
k @family/*
Podemos ejecutar la consulta mediante solicitud 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
El elemento en el índice 1
existe en la matriz likes
dentro de un subobjeto pets
> k query family /pets/*/likes/1
< k 1 {"firstName":"John"...
El elemento en el índice 1
existe en la matriz likes
en cualquier nivel de anidamiento likes
.
> k query family /**/likes/1
< k 1 {"firstName":"John"...
A partir de este punto y a continuación, omitiré k query family
específico de websocket y consideraré solo consultas JQL.
Para obtener documentos por clave principal están disponibles las siguientes opciones:
Utilice la llamada API ejdb_get()
const doc = await db . get ( 'users' , 112 ) ;
Utilice la construcción de consulta especial: /=:?
o @collection/=:?
Obtener documento de la colección users
con clave principal 112
> k @users/=112
Actualizar la matriz de etiquetas para el documento en la colección jobs
(TypeScript):
await db . createQuery ( '@jobs/ = :? | apply :? | count' )
. setNumber ( 0 , id )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
También se puede utilizar una matriz de claves primarias para hacer coincidir:
await db . createQuery ( '@jobs/ = :?| apply :? | count' )
. setJSON ( 0 , [ 23 , 1 , 2 ] )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
A continuación se muestra un conjunto de consultas autoexplicativas:
/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]
Nota sobre las comillas alrededor de palabras con espacios.
Obtenga todos los documentos donde el propietario age
más de 20
años y tenga alguna mascota a la que le gusten bones
o toys
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
Aquí **
denota algún elemento en la matriz likes
.
ni
es el operador inverso de in
. Obtenga documentos donde bones
en algún lugar de la matriz likes
.
/pets/*/[likes ni "bones"]
Podemos crear filtros más complicados.
( /[age <= 20] or /[lastName re "Do.*"] )
and /pets/*/likes/[** in ["bones", "toys"]]
Nota sobre la agrupación de paréntesis y la coincidencia de expresiones regulares mediante el operador re
.
~
es un operador de coincidencia de prefijos (desde ejdb v2.0.53
). La coincidencia de prefijos puede beneficiarse del uso de índices.
Obtenga documentos donde /lastName
comience con "Do"
.
/[lastName ~ Do]
Filtre documentos con una matriz likes
que coincida exactamente con ["bones","jumping","toys"]
/**/[likes = ["bones","jumping","toys"]]
Los algoritmos de coincidencia para matrices y mapas son diferentes:
{"f":"d","e":"j"}
y {"e":"j","f":"d"}
son mapas iguales. Busque el documento JSON que tenga la clave firstName
en el nivel raíz.
/[* = "firstName"]
En este contexto *
denota un nombre clave.
Puede utilizar condiciones sobre el nombre de la clave y el valor de la clave al mismo tiempo:
/[[* = "firstName"] = John]
El nombre de la clave puede ser firstName
o lastName
, pero en cualquier caso debe tener el valor John
.
/[[* in ["firstName", "lastName"]] = John]
Puede resultar útil en consultas con marcadores de posición dinámicos (C API):
/[[* = :keyName] = :keyValue]
Sección APPLY
responsable de la modificación del contenido de los documentos.
APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del'
Las especificaciones del parche JSON se ajustaban a las especificaciones rfc7386
o rfc6902
seguidas de la palabra clave apply
.
Agreguemos el objeto address
a todos los documentos coincidentes.
/[firstName = John] | apply {"address":{"city":"New York", "street":""}}
Si el objeto JSON es un argumento de la sección apply
, se tratará como una coincidencia de combinación ( rfc7386
); de lo contrario, debería ser una matriz que denota el parche JSON rfc6902
. Los marcadores de posición también son compatibles con la sección apply
.
/* | apply :?
Establecer el nombre de la calle en address
/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]
Añade el pez Neo
al conjunto de pets
de John.
/[firstName = John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]
upsert
actualiza el documento existente según el argumento json dado utilizado como parche de combinación o inserta el argumento json proporcionado como nueva instancia de documento.
/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}
Incrementa el valor numérico identificado por la ruta JSON por el valor especificado.
Ejemplo:
Document: {"foo": 1}
Patch: [{"op": "increment", "path": "/foo", "value": 2}]
Result: {"foo": 3}
Igual que add
el parche JSON, pero crea nodos de objetos intermedios para los segmentos de ruta JSON que faltan.
Ejemplo:
Document: {"foo": {"bar": 1}}
Patch: [{"op": "add_create", "path": "/foo/zaz/gaz", "value": 22}]
Result: {"foo":{"bar":1,"zaz":{"gaz":22}}}
Ejemplo:
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
Intercambia dos valores del documento JSON a partir from
la ruta.
reglas de intercambio
from
no existe, se generará un error.path
no existe, se establecerá por el valor from
la ruta y luego se eliminará el objeto señalado from
la ruta.from
y path
, se intercambiarán.Ejemplo:
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]
Result: {"foo": [11], "baz": {"gaz": "bar"}}
Ejemplo (Demostración de la regla 2):
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]
Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}
Utilice la palabra clave del
para eliminar elementos coincidentes de la colección:
/FILTERS | del
Ejemplo:
> 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
La proyección permite obtener solo un subconjunto del documento JSON excluyendo los datos no necesarios.
La API de marcadores de posición de consulta se admite en proyecciones.
Agreguemos un documento más a nuestra colección:
$ 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
Ahora consulte solo el nombre y apellido de los dueños de mascotas de la colección.
> k query family /* | /{firstName,lastName}
< k 3 {"firstName":"Jack","lastName":"Parker"}
< k 1 {"firstName":"John","lastName":"Doe"}
< k
Agregue una 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 solo el campo pets
de los 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
Aquí se utilizan all
las palabras clave que denotan el documento completo.
Obtenga age
y la primera mascota del conjunto 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
La unión materializa la referencia del documento a un objeto de documento real que reemplazará la referencia en el lugar.
Los documentos se unen únicamente por sus claves principales.
Las claves de referencia deben almacenarse en el documento de referencia como número o campo de cadena.
Las uniones se pueden especificar como parte de la expresión de proyección de la siguiente forma:
/.../field<collection
Dónde
field
: el campo JSON contiene la clave principal del documento unido.<
- El símbolo de marca especial que indica al motor EJDB que reemplace la clave field
por el cuerpo del documento unido.collection
: nombre de la colección de base de datos donde se encuentran los documentos unidos.Un documento de referencia no se modificará si no se encuentra el documento asociado.
Aquí está una demostración simple de uniones de colecciones en nuestro shell websocket interactivo:
> 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
Referencias no vá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)...
Agreguemos un documento más y luego ordenemos los documentos de la colección según el orden ascendente y descendente 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
Las instrucciones asc, desc
pueden utilizar índices definidos para la recopilación para evitar una etapa de clasificación de documentos por separado.
OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
skip n
Salta los primeros n
registros antes del primer elemento en el conjunto de resultadoslimit n
Establecer el número máximo de documentos en el conjunto de resultadoscount
Devuelve sólo count
de documentos coincidentes > k query family /* | count
< k 3
< k
noidx
No utilice ningún índice para la ejecución de consultas.inverse
De forma predeterminada, la consulta escanea los documentos agregados más recientemente a los más antiguos. Esta opción invierte la dirección de escaneo al opuesto y activa el modo noidx
. No tiene ningún efecto si la consulta tiene cláusulas de clasificación asc/desc
. El índice de la base de datos se puede crear para cualquier ruta de campo JSON que contenga valores de tipo numérico o cadena. El índice puede ser unique
: no permite la duplicación de valores y non unique
. Se utilizan los siguientes indicadores de máscara de bits del modo de índice (definidos en ejdb2.h
):
Modo de índice | Descripción |
---|---|
0x01 EJDB_IDX_UNIQUE | El índice es único. |
0x04 EJDB_IDX_STR | Índice para el tipo de valor de campo string JSON |
0x08 EJDB_IDX_I64 | Índice para valores de campo enteros con signo de 8 bytes width |
0x10 EJDB_IDX_F64 | Índice para valores de campo de coma flotante firmados 8 bytes width . |
Por ejemplo, el índice único del tipo de cadena se especificará mediante EJDB_IDX_UNIQUE | EJDB_IDX_STR
= 0x05
. El índice se puede definir solo para un tipo de valor ubicado en una ruta específica en el documento json.
Definamos un índice de cadena no único para la ruta /lastName
:
> k idx family 4 /lastName
< k
Selección de índice para consultas basada en un conjunto de reglas heurísticas.
Siempre puede verificar el uso del índice emitiendo el comando explain
en la API de 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
Se tienen en cuenta las siguientes declaraciones al utilizar índices EJDB2:
Sólo se puede utilizar un índice para la ejecución de una consulta concreta.
Si la consulta consta de or
parte unida en el nivel superior o contiene expresiones negated
en el nivel superior de la expresión de consulta, los índices no se utilizarán en absoluto. Entonces no hay índices a continuación:
/[lastName != Andy]
/[lastName = "John"] or /[lastName = Peter]
Pero se utilizará el índice /lastName
definido anteriormente
/[lastName = Doe]
/[lastName = Doe] and /[age = 28]
/[lastName = Doe] and not /[age = 28]
/[lastName = Doe] and /[age != 28]
Los índices (ejdb 2.0.x) admiten los siguientes operadores:
eq, =
gt, >
gte, >=
lt, <
lte, <=
in
~
(Coincidencia de prefijos desde ejdb 2.0.53) Las cláusulas ORDERBY
pueden utilizar índices para evitar la clasificación del conjunto de resultados.
Los campos de matriz también se pueden indexar. Resumamos un caso de uso típico: indexación de algunas etiquetas de entidad:
> 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
Crear índice de cadena para /tags
> k idx books 4 /tags
< k
Filtre libros por etiqueta bestseller
y muestre el uso del índice en la 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 los documentos de la colección están ordenados por clave principal en orden descending
. Entonces, si usa claves generadas automáticamente ( ejdb_put_new
), puede estar seguro de que los documentos obtenidos como resultado de la consulta de escaneo completo se ordenarán según el momento de la inserción en orden descendiente, a menos que no use clasificación de consultas, índices o palabras clave inverse
.
En muchos casos, el uso del índice puede reducir el rendimiento general de la consulta. Debido a que la colección de índices contiene solo referencias de documentos ( id
) y el motor puede realizar una búsqueda adicional de documentos mediante su clave principal para finalizar la coincidencia de consultas. Entonces, para colecciones no tan grandes, un escaneo bruto puede funcionar mejor que un escaneo usando índices. Sin embargo, las operaciones de coincidencia exacta: eq
, in
y sorting
por orden de índice natural se beneficiarán del índice en la mayoría de los casos.
Si desea actualizar algún conjunto de documentos con operaciones apply
o del
, pero no desea recuperarlos todos como resultado de la consulta, simplemente agregue el modificador count
a la consulta para deshacerse de la transferencia de datos innecesaria y la conversión de datos json.
El motor EJDB proporciona la capacidad de iniciar un trabajador de punto final HTTP/Websocket independiente que expone la API de red para consultas y modificaciones de datos. SSL (TLS 1.2) es compatible con el servidor jbs
.
La forma más sencilla de exponer la base de datos a través de la red es utilizar el servidor jbs
independiente. (Por supuesto, si desea evitar la integración 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.
El punto final HTTP puede estar protegido por un token especificado con el indicador --access
o la estructura C API EJDB_HTTP
. Si se configuró el token de acceso, el cliente debe proporcionar el encabezado HTTP X-Access-Token
. Si el cliente requiere un token pero no lo proporciona, se informará el código HTTP 401
. Si el token de acceso no coincide con el token proporcionado por el servidor del cliente, responderá con el código HTTP 403
.
Añade un nuevo documento a la collection
.
200
éxitos. Cuerpo: un nuevo identificador de documento como número int64
Reemplaza/almacena el documento con id
numérica específica
200
por el éxito. cuerpo vacio Elimina el documento identificado por id
de una collection
200
por el éxito. cuerpo vacio404
no encontrado Parchear un documento identificado por id
mediante datos rfc7396, rfc6902.
200
por el éxito. cuerpo vacio Recuperar documento identificado por id
de una collection
.
200
por el éxito. Cuerpo: texto del documento JSON.content-type:application/json
content-length:
404
no encontrado Consulta una colección mediante la consulta proporcionada como cuerpo POST. El cuerpo de la consulta debe contener el nombre de la colección en uso en el primer elemento de filtro: @collection_name/...
Encabezados de solicitud:
X-Hints
sugerencias adicionales separadas por comas para el motor de base de datos ejdb2.explain
Mostrar el plan de ejecución de la consulta antes del primer elemento del conjunto de resultados separado por --------------------
línea. Respuesta:200
por el éxito.n
en el siguiente formato: rn<document id>t<document JSON body>
...
Ejemplo:
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}
Obtenga metadatos JSON de ejdb y métodos HTTP disponibles en Allow
encabezado de respuesta. Ejemplo:
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 admite un protocolo simple basado en texto sobre el protocolo websocket HTTP. Puede utilizar la herramienta CLI websocket interactiva wscat para comunicarse con el servidor manualmente.
Responderá con el siguiente mensaje de texto de ayuda:
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 el prefijo <key>
antes de cada comando; Es una clave arbitraria elegida por el cliente y designada para identificar una solicitud de websocket particular; esta clave se devolverá con la respuesta a la solicitud y permite al cliente identificar esa respuesta para su solicitud particular.
Los errores se devuelven en el siguiente formato:
<key> ERROR: <error description>
<key> info
Obtenga metadatos de la base de datos como documento JSON.
<key> get <collection> <id>
Recuperar documento identificado por id
de una collection
. Si no se encuentra el documento, se devolverá IWKV_ERROR_NOTFOUND
.
Ejemplo:
> k get family 3
< k 3 {
"firstName": "Jack",
"lastName": "Parker",
"age": 35,
"pets": [
{
"name": "Sonic",
"kind": "mouse",
"likes": []
}
]
}
Si no se encuentra el documento, obtendremos un error:
> k get family 55
< k ERROR: Key not found. (IWKV_ERROR_NOTFOUND)
>
<key> set <collection> <id> <document json>
Reemplaza/agrega documento bajo id
numérica específica. Collection
se creará automáticamente si no existe.
<key> add <collection> <document json>
Agregar nuevo documento a <collection>
Se generará una nueva id
del documento y se devolverá como respuesta. `Colección> se creará automáticamente si no existe.
Ejemplo:
> k add mycollection {"foo":"bar"}
< k 1
> k add mycollection {"foo":"bar"}
< k 2
>
<key> del <collection> <id>
Eliminar de la collection
el documento identificado por id
. Si no se encuentra el documento, se devolverá IWKV_ERROR_NOTFOUND
.
<key> patch <collection> <id> <patch json>
Aplique el parche rfc7396 o rfc6902 al documento identificado por id
. Si no se encuentra el documento, se devolverá IWKV_ERROR_NOTFOUND
.
<key> query <collection> <query>
Ejecutar consulta sobre documentos en collection
especificada. Respuesta: Un conjunto de mensajes WS con cuerpos de documentos terminados por el último mensaje con cuerpo vacío.
> k query family /* | /firstName
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Nota sobre el último mensaje: <key>
sin cuerpo.
<key> explain <collection> <query>
Igual que <key> query <collection> <query>
pero el primer mensaje de respuesta tendrá el prefijo <key> explain
y contiene el plan de ejecución de la consulta.
Ejemplo:
> k explain family /* | /firstName
< k explain [INDEX] NO [COLLECTOR] PLAIN
< k 4 {"firstName":"John"}
< k 3 {"firstName":"Jack"}
< k 1 {"firstName":"John"}
< k
Ejecutar texto de consulta. El cuerpo de la consulta debe contener el nombre de la colección en uso en el primer elemento de filtro: @collection_name/...
. El comportamiento es el mismo que para: <key> query <collection> <query>
<key> idx <collection> <mode> <path>
Asegúrese de índice con mode
especificado (indicador de máscara de bits) para path
y collection
json dadas. La colección se creará si no existe.
Modo de índice | Descripción |
---|---|
0x01 EJDB_IDX_UNIQUE | El índice es único. |
0x04 EJDB_IDX_STR | Índice para el tipo de valor de campo string JSON |
0x08 EJDB_IDX_I64 | Índice para valores de campo enteros con signo de 8 bytes width |
0x10 EJDB_IDX_F64 | Índice para valores de campo de coma flotante firmados 8 bytes width . |
Establezca un índice de cadena único (0x01 & 0x04) = 5
en el campo JSON /name
:
k idx mycollection 5 /name
<key> rmi <collection> <mode> <path>
Elimine el índice con mode
especificado (indicador de máscara de bits) para path
y collection
json dadas. Error de devolución si no se encuentra el índice dado.
<key> rmc <collection>
Elimina la colección y todos sus datos. Nota: Si no se encuentra collection
, no se informarán errores.
Si tiene Docker instalado, puede crear una imagen de Docker y ejecutarla en un contenedor.
cd docker
docker build -t ejdb2 .
docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey
u obtenga una imagen de ejdb2
directamente desde Docker Hub
docker run -d -p 9191:9191 --name myEJDB softmotions/ejdb2 --access myAccessKey
EJDB se puede integrar en cualquier aplicación C/C++
. C API
documentada en los siguientes encabezados:
Aplicación de ejemplo:
#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 ;
}
Compilar y ejecutar:
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.