ПРИМЕЧАНИЕ. Отслеживание проблем отключено. Вы можете внести свой вклад, запросы на включение принимаются.
EJDB2 — это встраиваемый механизм базы данных JSON, опубликованный под лицензией MIT.
История IT-депрессии, птиц и EJDB 2.0
Линукс | macOS | iOS | Андроид | Окна | |
---|---|---|---|---|---|
библиотека C | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ 1 |
NodeJS | ✔️ | ✔️ | 3 | ||
Ява | ✔️ | ✔️ | ✔️ | ✔️ 2 | |
ДартВМ 5 | ✔️ | ✔️ 2 | 3 | ||
Флаттер 5 | ✔️ | ✔️ | |||
Реагировать Нативный 5 | 4 | ✔️ | |||
Свифт 5 | ✔️ | ✔️ | ✔️ |
[5]
Привязки не поддерживаются . Требуются участники.
[1]
Нет поддержки HTTP/Websocket #257.
[2]
Двоичные файлы не распространяются через dart pub.
Вы можете построить его вручную
[3]
Можно построить, но необходима связь с libs
Windows node/dart.
[4]
Выполняется портирование #273
Linux
, macOS
и FreeBSD
. Имеет ограниченную поддержку WindowsВы используете EJDB? Дайте мне знать!
Код EJDB2 портирован и протестирован на High Sierra
/ Mojave
/ Catalina
Привязка EJDB2 Swift для MacOS, iOS и Linux. Привязка 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.
Примечание. Сетевой API HTTP/Websocket отключен и еще не поддерживается.
Привязки Nodejs/Dart еще не перенесены в Windows.
Руководство по кросс-компиляции для Windows
IWSTART — это автоматический генератор начальных проектов CMake для проектов C на основе библиотек 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
Синтаксис языка запросов EJDB (JQL), вдохновленный идеями, лежащими в основе XPath и каналов оболочки Unix. Он предназначен для удобного запроса и обновления наборов документов JSON.
JQL-парсер, созданный с помощью peg/leg — генераторы рекурсивного синтаксического анализатора для 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
: значение вложенного массива элемента массива под определенным ключом. Давайте поиграем с некоторыми очень простыми данными и запросами. Для простоты мы будем использовать сетевой API ejdb websocket, который предоставляет нам своего рода интерактивный интерфейс командной строки. Ту же работу можно выполнить и с использованием чистого 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.
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
перед каждой командой; Это произвольный ключ, выбранный клиентом и предназначенный для идентификации конкретного запроса веб-сокета. Этот ключ будет возвращен вместе с ответом на запрос и позволит клиенту идентифицировать этот ответ для его конкретного запроса. Дополнительная информация
Команда запроса через веб-сокет имеет следующий формат:
<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
существует в массиве likes
в подобъекте pets
.
> 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
с префиксом k, специфичным для веб-сокетов, и рассматривать только запросы JQL.
Для получения документов по первичному ключу доступны следующие варианты:
Используйте вызов API ejdb_get()
const doc = await db . get ( 'users' , 112 ) ;
Используйте специальную конструкцию запроса: /=:?
или @collection/=:?
Получить документ из коллекции users
с первичным ключом 112
> k @users/=112
Обновить массив тегов для документа в коллекции jobs
(TypeScript):
await db . createQuery ( '@jobs/ = :? | apply :? | count' )
. setNumber ( 0 , id )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Массив первичных ключей также можно использовать для сопоставления:
await db . createQuery ( '@jobs/ = :?| apply :? | count' )
. setJSON ( 0 , [ 23 , 1 , 2 ] )
. setJSON ( 1 , { tags } )
. completionPromise ( ) ;
Ниже приведен набор самообъясняющих вопросов:
/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]
Обратите внимание на кавычки вокруг слов с пробелами.
Получите все документы, если age
владельца старше 20
и у него есть домашнее животное, которое любит bones
или toys
/[age > 20] and /pets/*/likes/[** in ["bones", "toys"]]
Здесь **
обозначает некоторый элемент массива likes
.
ni
— обратный оператор к in
. Получите документы, в которых bones
находятся где-то в массиве likes
.
/pets/*/[likes ni "bones"]
Мы можем создавать более сложные фильтры
( /[age <= 20] or /[lastName re "Do.*"] )
and /pets/*/likes/[** in ["bones", "toys"]]
Обратите внимание на группировку круглых скобок и сопоставление регулярных выражений с использованием оператора re
.
~
— оператор сопоставления префиксов (начиная с ejdb v2.0.53
). Сопоставление префиксов может выиграть от использования индексов.
Получите документы, где /lastName
начинается с "Do"
.
/[lastName ~ Do]
Отфильтровать документы с массивом likes
, точно соответствующим ["bones","jumping","toys"]
/**/[likes = ["bones","jumping","toys"]]
Алгоритмы сопоставления массивов и карт различны:
{"f":"d","e":"j"}
и {"e":"j","f":"d"}
— равные карты. Найдите документ JSON, имеющий ключ firstName
на корневом уровне.
/[* = "firstName"]
В этом контексте *
обозначает имя ключа.
Вы можете использовать условия для имени ключа и значения ключа одновременно:
/[[* = "firstName"] = John]
Имя ключа может быть либо firstName
, либо lastName
, но в любом случае оно должно иметь значение John
.
/[[* in ["firstName", "lastName"]] = John]
Это может быть полезно в запросах с динамическими заполнителями (C API):
/[[* = :keyName] = :keyValue]
Раздел APPLY
отвечающий за изменение содержимого документов.
APPLY = ({'apply' | `upsert`} { PLACEHOLDER | json_object | json_array }) | 'del'
Спецификации исправлений JSON соответствуют спецификациям rfc7386
или rfc6902
, указанным после ключевого слова apply
.
Давайте добавим объект address
ко всем совпадающим документам.
/[firstName = John] | apply {"address":{"city":"New York", "street":""}}
Если объект JSON является аргументом раздела apply
, он будет рассматриваться как совпадение слияния ( rfc7386
), в противном случае это должен быть массив, обозначающий патч rfc6902
JSON. Заполнители также поддерживаются разделом apply
.
/* | apply :?
Установить название улицы в address
/[firstName = John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]
Добавьте рыбку Neo
в набор pets
Джона.
/[firstName = John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]
upsert
обновляет существующий документ по заданному аргументу json, используемому в качестве патча слияния, или вставляет предоставленный аргумент json в качестве экземпляра нового документа.
/[firstName = John] | upsert {"firstName": "John", "address":{"city":"New York"}}
Увеличивает числовое значение, определенное путем JSON, на указанное значение.
Пример:
Document: {"foo": 1}
Patch: [{"op": "increment", "path": "/foo", "value": 2}]
Result: {"foo": 3}
То же, что и add
исправления JSON, но создает промежуточные узлы объектов для отсутствующих сегментов пути 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
Меняет местами два значения документа JSON, начиная from
пути.
Правила обмена
from
«Не существует», возникнет ошибка.path
не существует, оно будет установлено по значению from
пути, тогда объект, указанный from
пути, будет удален.from
и path
, они будут заменены местами.Пример:
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/gaz"}]
Result: {"foo": [11], "baz": {"gaz": "bar"}}
Пример (Демо правила 2):
Document: {"foo": ["bar"], "baz": {"gaz": 11}}
Patch: [{"op": "swap", "from": "/foo/0", "path": "/baz/zaz"}]
Result: {"foo":[],"baz":{"gaz":11,"zaz":"bar"}}
Используйте ключевое слово del
, чтобы удалить соответствующие элементы из коллекции:
/FILTERS | del
Пример:
> k add family {"firstName":"Jack"}
< k 2
> k query family /[firstName re "Ja.*"]
< k 2 {"firstName":"Jack"}
# Remove selected elements from collection
> k query family /[firstName=Jack] | del
< k 2 {"firstName":"Jack"}
PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ]
PROJECTION = 'all' | json_path | join_clause
Проекция позволяет получить только часть документа JSON, исключая ненужные данные.
API заполнителей запросов поддерживается в проекциях.
Добавим в нашу коллекцию еще один документ:
$ cat << EOF | curl -d @- -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family
{
"firstName":"Jack",
"lastName":"Parker",
"age":35,
"pets":[{"name":"Sonic", "kind":"mouse", "likes":[]}]
}
EOF
Теперь запросите из коллекции только владельцев домашних животных по имени и фамилии.
> k query family /* | /{firstName,lastName}
< k 3 {"firstName":"Jack","lastName":"Parker"}
< k 1 {"firstName":"John","lastName":"Doe"}
< k
Добавить массив pets
для каждого документа
> k query family /* | /{firstName,lastName} + /pets
< k 3 {"firstName":"Jack","lastName":"Parker","pets":[...
< k 1 {"firstName":"John","lastName":"Doe","pets":[...
Исключить из документов только поле pets
> k query family /* | all - /pets
< k 3 {"firstName":"Jack","lastName":"Parker","age":35}
< k 1 {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}}
< k
Здесь используется ключевое слово all
, обозначающее весь документ.
Получите age
и первое домашнее животное в массиве pets
.
> k query family /[age > 20] | /age + /pets/0
< k 3 {"age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]}
< k 1 {"age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]}]}
< k
Соединение материализует ссылку на документ на объекты реального документа, которые заменят ссылку на месте.
Документы объединяются только по их первичным ключам.
Ссылочные ключи должны храниться в ссылающемся документе как числовое или строковое поле.
Объединения можно указать как часть выражения проекции в следующей форме:
/.../field<collection
Где
field
— поле JSON содержит первичный ключ объединенного документа.<
- специальный символ отметки, который указывает механизму EJDB заменить ключ field
телом присоединенного документа.collection
— имя коллекции БД, в которой расположены объединенные документы.Ссылающийся документ останется нетронутым, если связанный документ не найден.
Вот простая демонстрация объединения коллекций в нашей интерактивной оболочке веб-сокета:
> 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 | Индекс для типа значения string поля JSON |
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
Выбор индекса для запросов на основе набора эвристических правил.
Вы всегда можете проверить использование индекса, введя команду explain
в WS API:
> k explain family /[lastName=Doe] and /[age!=27]
< k explain [INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ
[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = Doe' INIT: IWKV_CURSOR_EQ
[COLLECTOR] PLAIN
При использовании индексов EJDB2 учитываются следующие утверждения:
Для выполнения конкретного запроса можно использовать только один индекс.
Если запрос состоит из or
объединенной части на верхнем уровне или содержит negated
выражения на верхнем уровне выражения запроса, индексы вообще не будут использоваться. Поэтому никаких индексов ниже:
/[lastName != Andy]
/[lastName = "John"] or /[lastName = Peter]
Но будет использоваться индекс /lastName
, определенный выше.
/[lastName = Doe]
/[lastName = Doe] and /[age = 28]
/[lastName = Doe] and not /[age = 28]
/[lastName = Doe] and /[age != 28]
Индексы поддерживают следующие операторы (ejdb 2.0.x):
eq, =
gt, >
gte, >=
lt, <
lte, <=
in
~
(Соответствие префиксов начиная с ejdb 2.0.53) Предложения ORDERBY
могут использовать индексы, чтобы избежать сортировки набора результатов.
Поля массива также можно индексировать. Давайте обрисуем типичный вариант использования: индексирование некоторых тегов сущностей:
> k add books {"name":"Mastering Ultra", "tags":["ultra", "language", "bestseller"]}
< k 1
> k add books {"name":"Learn something in 24 hours", "tags":["bestseller"]}
< k 2
> k query books /*
< k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]}
< k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]}
< k
Создать строковый индекс для /tags
> k idx books 4 /tags
< k
Отфильтруйте книги по тегу bestseller
и отобразите использование индекса в запросе:
> k explain books /tags/[** in ["bestseller"]]
< k explain [INDEX] MATCHED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ
[INDEX] SELECTED STR|4 /tags EXPR1: '** in ["bestseller"]' INIT: IWKV_CURSOR_EQ
[COLLECTOR] PLAIN
< k 1 {"name":"Mastering Ultra","tags":["ultra","language","bestseller"]}
< k 2 {"name":"Learn something in 24 hours","tags":["bestseller"]}
< k
Все документы в коллекции сортируются по первичному ключу в порядке descending
. Поэтому, если вы используете автоматически сгенерированные ключи ( ejdb_put_new
), вы можете быть уверены, что документы, полученные в результате запроса полного сканирования, будут упорядочены в соответствии со временем вставки в порядке потомков, если только вы не используете сортировку запросов, индексы или inverse
ключевое слово.
Во многих случаях использование индекса может снизить общую производительность запросов. Поскольку коллекция индексов содержит только ссылки на документы ( id
), и механизм может выполнить дополнительную выборку документа по его первичному ключу для завершения сопоставления запроса. Таким образом, для не очень больших коллекций полное сканирование может работать лучше, чем сканирование с использованием индексов. Однако операции точного сопоставления: eq
, in
и sorting
по естественному порядку индекса в большинстве случаев выиграют от индекса.
Если вы хотите обновить некоторый набор документов с помощью операций apply
или del
, но не хотите получать их все в результате запроса, просто добавьте модификатор count
в запрос, чтобы избавиться от ненужной передачи данных и преобразования данных JSON.
Механизм EJDB обеспечивает возможность запуска отдельного работника конечной точки HTTP/Websocket, предоставляющего сетевой API для запросов и модификации данных. 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
. Если токен доступа был установлен, клиент должен предоставить HTTP-заголовок X-Access-Token
. Если токен требуется, но не предоставлен клиентом, будет сообщен HTTP-код 401
. Если токен доступа не соответствует токену, предоставленному клиентом, сервер ответит HTTP-кодом 403
.
Добавьте новый документ в collection
.
200
успехов. Тело: новый идентификатор документа как номер int64
Заменяет/сохраняет документ под определенным числовым id
200
за успех. Пустое тело Удаляет документ, идентифицированный по id
, из collection
200
за успех. Пустое тело404
документ не найден Исправьте документ, идентифицированный по id
rfc7396, данным rfc6902.
200
за успех. Пустое тело Получить документ, идентифицированный по id
, из collection
.
200
за успех. Тело: текст документа в формате JSON.content-type:application/json
content-length:
404
документ не найден Запросите коллекцию с помощью предоставленного запроса в виде тела POST. Тело запроса должно содержать имя коллекции, используемое в первом элементе фильтра: @collection_name/...
Заголовки запроса:
X-Hints
.explain
Показывать план выполнения запроса перед первым элементом в наборе результатов, разделенным строкой --------------------
. Ответ:200
за успех.n
в следующем формате: rn<document id>t<document JSON body>
...
Пример:
curl -v --data-raw '@family/[age > 18]' -H 'X-Access-Token:myaccess01' http://localhost:9191
* Rebuilt URL to: http://localhost:9191/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 9191 (#0)
> POST / HTTP/1.1
> Host: localhost:9191
> User-Agent: curl/7.58.0
> Accept: */*
> X-Access-Token:myaccess01
> Content-Length: 18
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 18 out of 18 bytes
< HTTP/1.1 200 OK
< connection:keep-alive
< content-type:application/json
< transfer-encoding:chunked
<
4 {"firstName":"John","lastName":"Ryan","age":39}
3 {"firstName":"Jack","lastName":"Parker","age":35,"pets":[{"name":"Sonic","kind":"mouse","likes":[]}]}
1 {"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}],"address":{"city":"New York","street":"Fifth Avenue"}}
* Connection #0 to host localhost left intact
curl --data-raw '@family/[lastName = "Ryan"]' -H 'X-Access-Token:myaccess01' -H 'X-Hints:explain' http://localhost:9191
[INDEX] MATCHED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ
[INDEX] SELECTED STR|3 /lastName EXPR1: 'lastName = "Ryan"' INIT: IWKV_CURSOR_EQ
[COLLECTOR] PLAIN
--------------------
4 {"firstName":"John","lastName":"Ryan","age":39}
Получите метаданные ejdb JSON и доступные методы HTTP в заголовке ответа Allow
. Пример:
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. Вы можете использовать интерактивный инструмент 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>
Получить документ, идентифицированный по id
, из collection
. Если документ не найден, будет возвращен 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>
Удалить документ, идентифицированный по id
, из collection
. Если документ не найден, будет возвращен IWKV_ERROR_NOTFOUND
.
<key> patch <collection> <id> <patch json>
Примените исправление rfc7396 или rfc6902 к документу, указанному по id
. Если документ не найден, будет возвращен IWKV_ERROR_NOTFOUND
.
<key> query <collection> <query>
Выполнить запрос к документам в указанной collection
. Ответ: набор WS-сообщений с boidies документа, завершающийся последним сообщением с пустым телом.
> 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>
Обеспечьте индекс с указанным mode
(флагом битовой маски) для данного path
json и collection
. Коллекция будет создана, если она не существует.
Индексный режим | Описание |
---|---|
0x01 EJDB_IDX_UNIQUE | Индекс уникален |
0x04 EJDB_IDX_STR | Индекс для типа значения string поля JSON |
0x08 EJDB_IDX_I64 | Индекс для значений целочисленных полей со знаком 8 bytes width |
0x10 EJDB_IDX_F64 | Индекс для значений полей с плавающей запятой 8 bytes width со знаком. |
Установите уникальный индекс строки (0x01 & 0x04) = 5
в поле /name
JSON:
k idx mycollection 5 /name
<key> rmi <collection> <mode> <path>
Удалить индекс с указанным mode
(флагом битовой маски) для заданного path
и collection
json. Ошибка возврата, если указанный индекс не найден.
<key> rmc <collection>
Удалить коллекцию и все ее данные. Примечание. Если collection
не найдена, об ошибках не будет сообщено.
Если у вас установлен Docker, вы можете создать образ Docker и запустить его в контейнере.
cd docker
docker build -t ejdb2 .
docker run -d -p 9191:9191 --name myEJDB ejdb2 --access myAccessKey
или получите образ ejdb2
прямо из Docker Hub.
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.