EJDB — различия между версиями

Материал из Национальной библиотеки им. Н. Э. Баумана
Строка 746: Строка 746:
 
'''PATCH /{collection}/{id}'''
 
'''PATCH /{collection}/{id}'''
  
'''ПРОДОЛЖАЮ РЕДАКТИРОВАНИЕ осталось немного закончу в понедельник 15 июня'''  
+
'''ПРОДОЛЖАЮ РЕДАКТИРОВАНИЕ осталось немного, закончу в понедельник 15 июня'''  
 
== Применение ==
 
== Применение ==
 
Существует определенный класс приложений, где удобно использовать встраиваемые СУБД,
 
Существует определенный класс приложений, где удобно использовать встраиваемые СУБД,

Версия 20:53, 14 июня 2020

AppDetectivePRO
Ejdb-2.png
Создатели: [https://github.com/Softmotions/ejdb
Разработчики: Антон Адаманский
Постоянный выпуск: 2.0 / середина 2018
Состояние разработки: Active
Операционная система: Windows, Linux
Платформа: Github.com[1]]
Веб-сайт ejdb.org

EJDB (Embedded JSON DataBase engine) - это встроенная база данных JSON. Она нацелена на создание быстрой библиотеки, подобной MongoDB, которая может быть встроена в приложения C / C ++. Он включает блокировку записи на уровне коллекции, транзакции на уровне коллекции, запросы на сопоставление с токеном строки и привязку Node.js. EJDB[1] библиотека C, основанная на модифицированной версии Tokyo Cabinet. JSON представление запросов и данных, реализованных с помощью API на основе C BSON.

Содержание

История

Основателем проекта стал разработчик и преподаватель НГУ[2] Антон Адаманский[3]. Всё началось в 2011 году, изначально возникла идея о создании удобной системы хранения информации и поиска метатегов аудиофайлов для небольшого медиаплеера, написанной на C++. Но во время осуществления задуманного возникли следующие проблемы: реализация была слишком громоздкой ( SQLite ) или имела проблемы со стабильностью ( GigaBASE ); размер всего проекта составлял одну десятую от одного файла sqlite.c, тогда Антон не сравнивал размер проекта с ядром системы. Как вспоминал он сам : "2011 год был золотым веком различных решений NoSQL". Помимо этого рассматривалась MongoDB, как возможное решение, но в процессе изучения выявились два существенными ограничения:

  • Трудность использования MongoDB в качестве дополнительной DLL, потому что ее разработчики изначально не планировали такой вариант применения, а MongoDB технически не был готов к этому.
  • MongoDB был под лицензией AGPL, что,на тот момент только вредило проекту. По словам Антона Адаманского AGPL ничего не приносит в мир открытого программного обеспечения и, как правило, просто удобное прикрытие для коммерчески лицензированных продуктов с условно-бесплатным кодом.

Тогда в поисках лучшего варианта облегченной системы хранения данных для небольших проектов были определены ключевые качества продукта:

  • Документно-ориентированный с возможностью хранения иерархии документов с произвольной структурой.
  • Поддержка коллекций документов JSON.
  • Реализовано на языке C99.
  • Бесплатная лицензия, которая позволяет использовать продукты с закрытым исходным кодом.

Вдохновившись MongoDB была создана система управления базами данных(в дальнейшем СУБД), реализованная в виде разделяемой библиотеки ( EJDB 1.0 ). EJDB 1.0 основан на модифицированной версии проекта TokyoCabinet,который на данный момент уже заброшен. EJDB стал довольно популярным на github[4] среди разработчиков со всего мира. В то время один из менеджеров 10gen связался с разработчиками, чтобы обсудить совместимость запросов между EJDB и MongoDB, но дальше обсуждения это не пошло.Однако разработчики взяли слишком много у MongoDB, и это стало проблемой, так к примеру, громоздкий формат поисковых запросов. Другим спорным моментом, взятым из MongoDB, стала функция, которая сохраняла первичный ключ документа в самом документе, как специальное поле _id, которое смешивает исходную структуру хранимого документа с реализацией системы хранения. Позднее TokyoCabinet, наложил лицензию LGPL 1.x на EJDB, которая является более свободной, чем AGPL, но все же ограничивает использование EJDB во многих проектах.Будучи немного разочарованными, разработчики выпустили несколько версий EJDB, а затем оставили его неактивным в течение нескольких лет. В 2018 году проанализировав современный IT рынок и технологии было принято решение создать о создание второй версии EJDB, главным девизом которой стал лозунг "Хватит Усложнять!". EJDB 2.0 полностью избавились от кода LGPL из TokyoCabinet. Исходный код стал полностью открытым благодаря использованию лицензии MIT для всех компонентов проекта. Для EJDB2 было разработано хранилище данных Key-value под лицензией MIT[5]. Описание iowow ниже.

Архитектура EJDB

Как говорилось ранее EJDB является встраиваемой кроссплатформенной документо-ориентированной (иерархической, в англоязычной литературе document-oriented)[6] СУБД для JSON данных, которая представлет собой разделяемую библиотеку для windows/linux платформ. Самые популярные СУБД для работы с JSON данными – это mongodb и couchdb, но они работают с использованием протокола TCP/IP. Такой подход очень удобен в реализации серверных приложений, однако не слишком пригоден для встраивания базы в легкие приложения, такие как мобильные приложения и проч., по следующим причинам:

  • скорость общения клиента с базой данных через протокол TCP/IP значительно медленнее, чем скорость того же общения, работающего через разделяемую память в разделяемом адресном пространстве запущенного процесса;
  • известные препятствия возникают во время развертывания программного продукта вконечной среде. Эти трудности приходят из необходимости одновременного развертывания продукта с СУБД. Таким образом, мы должны установить систему управления базой данных как отдельный сетевой сервис. Эта конфигурация более сложная для поддержки, а также может создавать уязвимости в безопасности программы.

Основой EJDB является общая философия MongoDB и язык запросов этой СУБД. Реализация EJDB технически основывается на tokyocabinet 2 – хранилище данных типа ключ-значение, которое распространяется под лицензией LGPL (Lesser GNU Public License). Система хранения EJDB построена на низкоуровневых структурах данных, которые предоставляет tokyocabinet:

  • B+ дерево (tcbdb);
  • хеш-таблица, которая хранит записи вида ключ-значение (tchdb);
  • табличная база данных (tctdb)

Внутри EJDB JSON документы представлены как BSON (Binary JSON) объекты. Этот формат достаточно компактный и эффективный для хранения и обработки данных. EJDB хранит набор коллекций, где каждая коллекция содержит набор логически связанных документов JSON. Логически каждая коллекция EJDB – это табличная база данных tokyocabinet (tctdb), которая явлется хранилищем таблиц, не зависимых от схемы с записаннымданными в строки, разделенные на набор определенных пользователем колонок.

Структура документа JSON

Каждая строка таблицы разделена на три части: 24-битный уникальный идентификатор, тело документа в формате BSON и дополнительные метаданные документа. Если документы не содержат проиндексированные поля в процессе выполнения запроса, вся коллекция документов будет просканирована, и BSON-документы, чьи поля подошли к запросу, будут выбраны. Выбор документа по первичному ключу сводится к выборке записей данных с диска при помощи хэш-таблицы. Эта операция может быть выполнена достаточно быстро. EJDB-запросы определены как набор CRUD-операций с документами, хранящимися в коллекциях, т. е. создание, чтение, обновление и удаление. Правила CRUD представлены в виде набора BSON-документов, состав которых похож на запросы MongoDB. CRUD-операции чтения определяют набор ограничений на поля, применяемых к коллекции документов. Так как структура JSON-документов может быть иерархичной, то fieldpath используется для идентификации документа. Например, условие запроса на выбор книг с определенным издателем может быть таким: {"publisher.name" : "some publisher"}. EJDB стремится быть совместимой с MongoDB в плане запросов, так как это обеспечит возможность легкой миграции приложений из/в MongoDB. На данный момент около 70 % запросов mongodb реализованы в EJDB, планируется достигнуть совместимости по запросам в 90 %. Кроме того, EJDB расширяет возможности запросов MongoDB и предоставляет следующие возможности извлечения данных:

  • оптимизированное сравнение строк, не зависящее от регистра;
  • быстрое сравнение строк, например, запрос {"words" : {"$strand" : ["one", "two"]}} находит докумнты, где строковое поле words содержит слова «one» и «two» в наборе из слов,разделенных пробелами.
  • оптимизированный строковый префикс matching with "$begin";
  • соединение набора документов. Вхождения документов в различные коллекции могут

быть объединены с помощью первичного ключа, как результат одного запроса. Если разработчик захочет получить объединение объектов из связанных коллекций, ему придется исполнить как минимум N + 1 запрос, где N – это количество элементов основной коллекции. Для того чтобы ликвидировать эти тривиальные раунды, EJDB предлагает способ указатьобъединение как часть исполнения одного запроса в следующей формой: "{$do : {<fpath> :{$join : <collection name>}}}", где <fpath> – это путь к полю документа, содержащего идентификаторы объектов, которые будут объединены с другой коллекцией.

Iowow

Iowow может работать со многими базами данных «ключ-значение», хранящимися в одном файле, что упрощает передачу данных между устройствами и создание резервных копий, а также снижает вероятность несоответствий в данных хранилища. Iowow основан на простой структуре данных - списке пропусков , что позволило нам создать гораздо более понятную реализацию постоянного хранилища с гораздо меньшей базой кода (по сравнению с деревом B + и деревом LSM) с высокой производительностью (в соответствии с тестами). Максимальный объем файла базы данных составляет 512 ГБ, что является следствием компромиссов при реализации однофайловой базы данных в списках пропуска. Документы, хранящиеся в коллекциях EJDB2, сериализуются в простом двоичном формате - Binn.

Структура данных EJDB

Физически EJDB это:

  • Мета-файл в котором хранится информация об доступных коллекциях. Это табличная база tctdb.
  • Для каждой коллекции:
  • Мета-файл с данными об индексах коллекции и настройках коллекции (tctdb)
  • Файл c данными JSON объектов коллекции (tctdb)
  • Набор B-Tree индексов для полей JSON объектов (tcbdb)

JSON объекты в EJDB представлены в формате BSON (Binary JSON). Для работы с BSON используется несколько модифицированная версия BSON API из C драйвера mongodb (ссылка). Замечу, что BSON, по моему мнению, очень эффективный формат для представления любых иерархических данных со строковыми ключами и типизированными значениями как с точки зрения удобства навигации так и производительности, и отлично может подойти для внутреннего представления данных в обычных С/C++ программах.

Записи в коллекции EJDB хранятся в табличной базе tctdb. Коротко, база tctcdb является хеш-таблицей, где значение каждой записи это словарь в котором ключ (c1..cN) это имя колонки в таблице, и соответствующие им данные (v1..vN). Каждая запись в табличной базе может иметь свой набор колонок. Данные разных записей принадлежащие к колонкам с одинаковым именами могут быть проиндексированы в B+ дереве tcbdb ссылаясь на первичные ключи основной хеш таблицы.

Где в качестве первичных ключей используются уникальные двенадцатибайтные идентификаторы UUID для JSON объектов. Объекты сериализованные в BSON формат хранятся в колонке с кратким и звучным именем $. Подобная модель допускает возможность связи произвольной мета информации с записями коллекции, путем добавления новых колонок, например списков доступа (ACL) на уровне записей, или данных статистики доступа к записям, но оставим это для следующих релизов.

Какая требовалась функциональность от этой библиотеки:

  • Хранение коллекций JSON объектов
  • Mongodb-like запросы относительно коллекций
  • Поддержка транзакций на уровне коллекций
  • Связка с NodeJS

Что было использовано из tokyocabinet:

  • B+ деревья (tcbdb.h)
  • Хеш таблицы (tchdb.h)
  • Табличная база (tctdb.h)
  • Структуры данных и утилиты (tcutil.h)

В этом списке в скобках приведены ссылки на элементы api tokyocabinet.

Работа с EJDB

Чтобы начать работать с EJDB нам нужно перейти на сайт:https://ejdb.org. Он полостью англоязычный, на нем есть необходимая информация о проекте. На сайте регистрироваться не нужно!

Знакомство с EJDB

Далее мы видим что на сайте уже есть разбиение по необходимым разделам. При желании можно нажать на необходимый раздел и сразу перейти на этот раздел на GitHub. Или переходя сразу по ссылке на весь GitHub. Перейдя по ссылке мы увидим всю необходимую информацию для работы с библиотекой:

GitHub EJDB2.0

Информация и сама версия EJDB2.0. постоянно обновляется автором.


EJDB 2.0

EJDB2 - это встраиваемый движок базы данных JSON, опубликованный под лицензией MIT.

  • C11 API
  • Однофайловая база данных
  • Онлайн поддержка резервного копирования
  • Размер библиотеки 500K для Android
  • iOS / Android / React Native / Flutter интеграция
  • Простой, но мощный язык запросов (JQL), а также поддержка следующих стандартов:
  1. rfc6902 JSON Patch
  2. rfc7386 JSON Merge patch
  3. rfc6901 JSON Path
  • Поддержка коллекционных объединений
  • Работает на owow.io - механизм постоянного хранения ключей / значений
  • Предоставляет конечные точки сети HTTP REST / Websockets с помощью facil.io
  • Документы JSON хранятся в быстром и компактном двоичном формате
  • Привязки родного языка

Матрица платформ EJDB2

Linux macOS iOS Android Windows
Библиотека С ✔️ ✔️ ✔️ ✔️ ✔️1
NodeJS ✔️ ✔️ ❌3
DartVM ✔️ ✔️2 ❌3
Flutter ✔️ ✔️
React Native ❌4 ✔️
Swift ✔️ ✔️ ✔️
Java ✔️ ✔️ ✔️ ✔️2
  1. Нет поддержки HTTP / Websocket # 257
  2. Двоичные файлы не распространяются с dart pub. Вы можете построить его вручную
  3. Можно построить, но нужна связь с windows node / dart libs.
  4. Выполняется перенос # 273

Привязки родного языка

  • NodeJS
  • Dart
  • Java
  • Android support
  • Swift | iOS
  • React Native
  • Flutter

Неофициальные привязки к родному языку

  • .Net
  1. https://github.com/kmvi/ejdb2-csharp
  • Haskell
  1. https://github.com/cescobaz/ejdb2haskell
  2. https://hackage.haskell.org/package/ejdb2-binding
  • Pharo
  1. https://github.com/pharo-nosql/pharo-ejdb

Статус

  • Ядро EJDB 2.0 хорошо протестировано и используется в различных сильно загруженных средах.
  • Протестировано на платформах Linux и OSX. Ограниченная поддержка Windows
  • Старая версия EJDB 1.x находится в отдельной ветке ejdb_1.x Её не поддерживают.

Случаи использования

  • Softmotions платформа для торговых роботов
  • Gimme - социальная игрушка для обмена токенами мобильного приложения. EJDB2 используется как на стороне мобильного телефона, так и на стороне сервера.

macOS / OSX

Код EJDB2 перенесен и протестирован на High Sierra / Mojave / Catalina

См. Также EJDB2 Swift для OSX, iOS и Linux.

brew install ejdb

или

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make install

Linux

Ubuntu/Debian

PPA хранилище

sudo add-apt-repository ppa:adamansky/ejdb2
sudo apt-get update
sudo apt-get install ejdb2

Сборка пакетов Debian требуется cmake v3.15 или выше

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_DEB=ON
make package

Дистрибутивы Linux на основе RPM

mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DPACKAGE_RPM=ON
make package

Windows

EJDB2 может быть скомпилирован для Windows Примечание. Сетевой API HTTP / Websocket отключен и не поддерживается в Windows для порта библиотеки http://facil.io (# 257) Привязки Nodejs / Dart еще не перенесены в Windows.

Руководство по кросс-компиляции для Windows.[7]

Android

  • Flutter binding[8]
  • React Native binding[9]

Пример приложения для Android

JQL

Синтаксис языка запросов EJDB (JQL), основанный на идеях, лежащих в основе оболочек XPath и Unix. Он предназначен для простого запроса и обновления наборов документов JSON.

Грамматика JQL

Парсер JQL создан с помощью peg / leg - генераторов парсеров рекурсивного спуска для C Вот формальная грамматика парсера: https://github.com/Softmotions/ejdb/blob/master/src/jql/jqp.leg

Неформальная грамматика JQL адаптирована для краткого обзора

Используемая ниже запись основана на описании синтаксиса SQL:

Правила описание
' ' Строка в одинарных кавычках обозначает строковый литерал без кавычек как часть запроса.
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' { 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: объект, массив, строка, bool, число.
  • json_path: упрощенный указатель JSON. Например: / foo / bar или / foo / "бар с пробелами" /
  • * в контексте NODE: любое имя ключа объекта JSON на определенном уровне вложенности.
  • ** в контексте NODE: любое имя ключа объекта JSON на произвольном уровне вложенности.
  • * в контексте NODE_EXPR_LEFT: имя ключа на определенном уровне.
  • ** в контексте NODE_EXPR_LEFT: значение вложенного массива элемента массива под конкретным ключом.

Краткое описание JQL

Для простоты мы будем использовать сетевой API ejdb websocket, который предоставляет нам своего рода интерактивный CLI. То же самое можно сделать и с использованием чистого C API (ejdb2.h jql.h).

ПРИМЕЧАНИЕ. Просмотрите примеры тестов JQL для получения дополнительных примеров.[10]

{
  "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 и загрузите семейную коллекцию:

# Start HTTP/WS server protected by some access token
./jbs -a 'myaccess01'
8 Mar 16:15:58.601 INFO: HTTP/WS endpoint at localhost:9191

Доступ к серверу можно получить через конечную точку HTTP или Websocket. Больше информации

curl -d '@sample.json' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191/family


Мы можем поиграть, используя интерактивный клиент wscat websocket.

wscat  -H 'X-Access-Token:myaccess01' -q -c http://localhost:9191
connected (press CTRL+C to quit)
> k info
< k     {
 "version": "2.0.0",
 "file": "db.jb",
 "size": 8192,
 "collections": [
  {
   "name": "family",
   "dbid": 3,
   "rnum": 1,
   "indexes": []
  }
 ]
}

> k get family 1
< k     1       {
 "firstName": "John",
 "lastName": "Doe",
 "age": 28,
 "pets": [
  {
   "name": "Rexy rex",
   "kind": "dog",
   "likes": [
    "bones",
    "jumping",
    "toys"
   ]
  },
  {
   "name": "Grenny",
   "kind": "parrot",
   "likes": [
    "green color",
    "night",
    "toys"
   ]
  }
 ]
}

Примечание о префиксе k перед каждой командой; Это произвольный ключ, выбранный клиентом и предназначенный для идентификации конкретного запроса веб-сокета. Этот ключ будет возвращен в ответ на запрос и позволяет клиенту идентифицировать этот ответ для своего конкретного запроса.

Команда запроса через websocket имеет следующий формат:

<key> query <collection> <query>

Поэтому мы рассмотрим только часть <query> в этом документе.

Получение всех элементов в коллекции

k query family /*

или

k query family /**

или указать имя коллекции в запросе явно

k @family/*

Мы можем выполнить запрос по запросу HTTP POST

curl --data-raw '@family/[firstName=John]' -H'X-Access-Token:myaccess01' -X POST http://localhost:9191
1	{"firstName":"John","lastName":"Doe","age":28,"pets":[{"name":"Rexy rex","kind":"dog","likes":["bones","jumping","toys"]},{"name":"Grenny","kind":"parrot","likes":["green color","night","toys"]}]}

Установка максимального количества элементов в наборе результатов

k @family/* | limit 10

Получение документов, в которых указан указанный путь json

Элемент с индексом 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 для веб-сокетов и рассмотрю только JQL-запросы.

Получение документов по первичному ключу

Для получения документов по первичному ключу доступны следующие опции:

  1. Использовать вызов API ejdb_get ()
const doc = await db.get('users', 112);
  1. Используйте специальную конструкцию запроса: / = :? или @ collection / = :?

Получение документов из коллекции пользователей с первичным ключом 112

> k @users/=112

Обновление массива тегов для документа в коллекции заданий (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();

Соответствующие значения записи JSON

Ниже приведен набор самоочевидных запросов:

/pets/*/[name = "Rexy rex"]
/pets/*/[name eq "Rexy rex"]
/pets/*/[name = "Rexy rex" or name = Grenny]

Обратите внимание на кавычки вокруг слов с пробелами.

Получить все документы, где владелец старше 20 лет, и есть домашнее животное, которое like 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"]]

Массивы и карты могут быть сопоставлены как есть

Фильтрация документов по массиву likes, точно совпадающему с ["bones","jumping","toys"]

/**/[likes = ["bones","jumping","toys"]]

Алгоритмы сопоставления для массивов и карт различны:

  • Элементы массива сопоставляются от начала до конца. В равных массивах все значения с одинаковым индексом должны быть равны.
  • Сопоставление карт объектов состоит из следующих этапов:
  1. Лексикографически сортируйте ключи объекта на обеих картах.
  2. Выполните сопоставление ключей и их значений, начиная с самого низкого ключа.
  3. Если все соответствующие ключи и значения в одной карте полностью совпадают с ключами в другой и наоборот, карты считаются равными. Например: {"f": "d", "e": "j"} и {"e": "j", "f": "d"} являются равными картами.

Условия по ключевым именам

Найти документ JSON с ключом firstName на корневом уровне.

/[* = "firstName"]

В этом контексте * обозначает имя ключа.

Вы можете использовать условия для имени ключа и значения ключа одновременно:

/[[* = "firstName"] = John]

Имя ключа может быть firstName или lastName, но в любом случае должно иметь значение John.

/[[* in ["firstName", "lastName"]] = John]

Это может быть полезно в запросах с динамическими заполнителями (C API):

/[[* = :keyName] = :keyValue]

Модификация данных JQL

APPLY раздел, отвечающий за изменение содержания документов.

APPLY = ('apply' { PLACEHOLDER | json_object | json_array  }) | 'del'


Спецификации патча JSON соответствуют спецификациям rfc7386 или rfc6902, после которых следует ключевое слово apply.

Давайте добавим объект address ко всему найденному документу /[firstName=John] | apply {"address":{"city":"New York", "street":""}} Если объект JSON является аргументом секции apply, он будет рассматриваться как совпадение слиянием (rfc7386), в противном случае это должен быть массив, обозначающий патч JSON rfc6902. Заполнители также поддерживаются разделом заявки.

/* | apply :?

Установите название улицы в address

/[firstName=John] | apply [{"op":"replace", "path":"/address/street", "value":"Fifth Avenue"}]

Добавьте Neo fish в набор pets Джона

/[firstName=John]
| apply [{"op":"add", "path":"/pets/-", "value": {"name":"Neo", "kind":"fish"}}]

Нестандартные расширения патча JSON

increment

Увеличивает числовое значение, определяемое путем JSON, на указанное значение.

Пример:

Document:  {"foo": 1}
Patch:     [{"op": "increment", "path": "/foo", "value": 2}]
Result:    {"foo": 3}

add_create

То же, что и добавление исправления 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

Удаление документов

Используйте ключевое слово 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"}

JQL проекции

PROJECTIONS = PROJECTION [ {'+' | '-'} PROJECTION ]
PROJECTION = 'all' | json_path | join_clause

Проекция позволяет получить только подмножество JSON-документа, исключая ненужные данные.

Добавим еще один документ в нашу коллекцию:

$ 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

Теперь запрашивайте только владельцев pet firstName и lastName из коллекции.

> k query family /* | /{firstName,lastName}
< k     3       {"firstName":"Jack","lastName":"Parker"}
< k     1       {"firstName":"John","lastName":"Doe"}
< k

Добавить массив pets для каждого документа

> k query family /* | /{firstName,lastName} + /pets
< k     3       {"firstName":"Jack","lastName":"Parker","pets":[...
< k     1       {"firstName":"John","lastName":"Doe","pets":[...


Исключить только документы pets из документов

> k query family /* | all - /pets
< k     3       {"firstName":"Jack","lastName":"Parker","age":35}
< k     1       {"firstName":"John","lastName":"Doe","age":28,"address":{"city":"New York","street":"Fifth Avenue"}}
< k

Здесь используются все ключевые слова, обозначающие весь документ.

Получить age и первый pet в массиве 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

JQL коллекция присоединяется

Join материализует ссылку на документ к реальному объекту документа, который заменит ссылку на месте.

Документы объединяются только по их первичным ключам.

Ссылочные ключи должны храниться в документе реферера в виде числового или строкового поля.

Соединения могут быть указаны как часть проекционного выражения в следующей форме:

/.../field<collection

Где:

  • field - поле JSON содержит первичный ключ присоединяемого документа.
  • <- Специальный символ метки, который инструктирует движку EJDB заменять ключ поля телом присоединенного документа.
  • collection - имя коллекции БД, в которой находятся объединенные документы.

Документ-реферер не будет затронут, если соответствующий документ не найден.

Вот простая демонстрация объединений коллекций в нашей интерактивной оболочке websocket:

> 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

Порядок результатов JQL

ORDERBY = ({ 'asc' | 'desc' } PLACEHOLDER | json_path)...

Добавим еще один документ, затем отсортируем документы в коллекции по возрастанию 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

asc, desc инструкции могут использовать индексы, определенные для сбора, чтобы избежать отдельной стадии сортировки документов.

Параметры JQL

OPTS = { 'skip' n | 'limit' n | 'count' | 'noidx' | 'inverse' | ORDERBY }...
  • skip n пропустить первые n записей перед первым элементом в наборе результатов
  • limit n установить максимальное количество документов в наборе результатов
  • count возвращает только количество найденных документов
> k query family /* | count
< k     3
< k
  • noidx не используйте индексы для выполнения запроса.
  • inverse по умолчанию запрос сканирует документы из самых последних, добавленных в более старые. Эта опция инвертирует направление сканирования в противоположное и активирует режим noidx. Не имеет никакого эффекта, если запрос имеет условия сортировки asc / desc.

JQL Indexes and performance tips

Индекс базы данных можно построить для любого пути к полю JSON, содержащего значения числа или типа строки. Индекс может быть уникальным - не допускающим дублирования значений и non unique. Используются следующие флаги битовой маски индексного режима (определенные в ejdb2.h):

Индексный режим Описание
0x01 EJDB_IDX_UNIQUE Уникальный индекс
0x04 EJDB_IDX_STR Индекс для JSON string типа значения поля
0x08 EJDB_IDX_I64 Индекс для 8 bytes width целочисленного значения полей со знаком
0x10 EJDB_IDX_F64 Индекс для 8 bytes width значения полей с плавающей запятой со знаком

Например, уникальный индекс строкового типа будет указан EJDB_IDX_UNIQUE | EJDB_IDX_STR = 0x05. Индекс может быть определен только для одного типа значения, расположенного по определенному пути в документе json.

Давайте определим неуникальный строковый индекс для пути / lastName:

> k idx family 4 /lastName
< k

Выбор индекса для запросов на основе набора эвристических правил.

Вы всегда можете проверить использование индекса, введя команду объяснения в WS API:

> 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 объединяет часть на верхнем уровне или содержит отрицательные выражения на верхнем уровне выражения запроса - индексы вообще не будут использоваться. Так что никаких индексов ниже:
/[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
  • 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.

HTTP REST / конечная точка API Websocket

Механизм EJDB обеспечивает возможность запуска отдельного работника конечной точки HTTP / Websocket, предоставляя сетевой API для запросов и модификаций данных.

Самый простой способ выставить базу данных по сети - использовать автономный сервер JBS. (Конечно, если вы планируете избегать интеграции C API).

JBS-сервер

jbs -h
EJDB 2.0.0 standalone REST/Websocket server. http://ejdb.org
 --file <>	Database file path. Default: db.jb
 -f <>    	(same as --file)
 --port ##	HTTP port number listen to. Default: 9191
 -p ##    	(same as --port)
 --bind <>	Address server listen. Default: localhost
 -b <>    	(same as --bind)
 --access <>	Server access token matched to 'X-Access-Token' HTTP header value
 -a <>      	(same as --access)
 --trunc   	Cleanup existing database file on open
 -t        	(same as --trunc)
 --wal   	Use write ahead logging (WAL). Must be set for data durability.
 -w      	(same as --wal)
 Advanced options
 --sbz ##	Max sorting buffer size. If exceeded, an overflow temp file for data will be created. Default: 16777216, min: 1048576
 --dsz ##	Initial size of buffer to process/store document on queries. Preferable average size of document. Default: 65536, min: 16384
 --bsz ##	Max HTTP/WS API document body size. Default: 67108864, min: 524288
Use any of the following input formats:
 -arg <value>	-arg=<value>	-arg<value>
Use the -h, -help or -? to get this information again.

HTTP API

Доступ к конечной точке HTTP может быть защищен токеном, указанным с помощью флага команды --access, или параметрами C API EJDB_HTTP. Если токен доступа указан на сервере, клиент должен предоставить значение HTTP-заголовка X-Access-Token. Если токен требуется и не предоставлен клиентом, сообщается код HTTP 401. Если токен доступа не соответствует токену при условии, что будет возвращен код HTTP 403. За любые другие ошибки сервер ответит 500 кодом ошибки.

REST API

POST /{collection} Добавить новый документ в collection.

  • 200 success. Body: новый идентификатор документа как номер int64

PUT /{collection}/{id} Заменяет / сохраняет документ под определенным числовым идентификатором

  • 200 on success.  Пустое тело

DELETE /{collection}/{id} Удаляет документ, идентифицированный по идентификатору, из collection

  • 200 on success.  Пустое тело
  • 404 если документ не найден

PATCH /{collection}/{id}

ПРОДОЛЖАЮ РЕДАКТИРОВАНИЕ осталось немного, закончу в понедельник 15 июня

Применение

Существует определенный класс приложений, где удобно использовать встраиваемые СУБД, это: легковесные приложения, использующие СУБД для доступа к данным, хранимых на сменных носителях (DVD и проч.) и которые сами хранятся на этих носителях; компьютерные игры, хранящие данные игрового процесса и самой игры; большой класс приложений, предназначенных для работы на мобильных устройствах. Достаточно часто требуется соблюдение конфиденциальности хранимых данных от неавторизованного доступа, в частности, это очень актуально для приложений игровой индустрии и мобильных приложений, хранящих персональные данные пользователя. В данном контексте рассматривается задача эффективной реализации шифрования данных во встраиваемой СУБД EJDB.

Источники

  1. [2], встроенный JSON Database Engine.
  2. [3],Новосибирский Государственный Университет, НГУ.
  3. [4], История IT-депрессии, птиц и EJDB 2.0.
  4. [5], EJDB 2.0 - Встраиваемая библиотека C для JSON Database Engine..
  5. [6], C11 key/value database engine.
  6. [7], РЕАЛИЗАЦИЯ ШИФРОВАНИЯ ДАННЫХ ВО ВСТРАИВАЕМОЙ СУБД EJDB.
  7. [8], руководство.
  8. [9], ссылка на GitHub.
  9. [10], ссылка на GitHub.
  10. [11], пример тестов