Skip to main content

Сервіс і плагіни зберігання

Компонент зберігання забезпечує рівень абстракції рівня бази даних, що використовується у Fledge. Абстракція зберігання явно не є рівнем SQL, і інтерфейс, який він пропонує клієнтам рівня зберігання; сервіс пристрою, API і процес надсилання, навмисно не є інтерфейсом SQL, щоб полегшити заміну базового зберігання будь-яким механізмом зберігання без використання SQL або навіть простим механізмом зберігання файлів. Для структурованих і неструктурованих даних, які зберігаються на рівні зберігання, можна використовувати різні плагіни.

Три вимоги, які призвели до архітектури плагінів та відокремлення доступу до бази даних у мікросервіс у Fledge, є наступними:

  • Бажання мати можливість підтримувати різні механізми зберігання даних відповідно до вимог розгортання та клієнтів. Наприклад, SQL, no-SQL, в пам'яті, резервне сховище (диск, SD-карта тощо) або прості файлові механізми.
  • Можливість відокремити сховище від південного та північног сервісу Fledge і дозволити розподілення Fledge між кількома фізичними апаратними компонентами.
  • Забезпечити гнучкість, щоб дозволити вилучати компоненти з розгортання Fledge, наприклад, вилучити буферизацію і мати просту реалізацію Fledge як маршрутизатора переадресації без зберігання.

Використання JSON

Існує три різні причини, чому JSON використовується на рівні зберігання даних, а саме

  • REST API використовує JSON для кодування корисного навантаження в кожній точці входу в API. Це найкращий тип корисного навантаження для всіх REST-інтерфейсів у Fledge. Варіант використання XML був розглянутий і відхилений, оскільки переважна більшість REST-інтерфейсів зараз використовують JSON, а не XML. JSON, як правило, більш компактний і легший для читання, ніж XML.
  • Інтерфейс між загальним рівнем зберігання і плагіном також передає запити і результати у форматі JSON. Частково це зроблено для того, щоб зробити його сумісним з REST-навантаженнями, а частково для того, щоб надати розробнику плагіна гнучкість і можливість перенести функціональність на рівень плагіна, щоб мати змогу використовувати специфічні особливості системи зберігання даних з найбільшою ефективністю.
  • Деякі структури, які зберігаються, самі є документами у форматі JSON. Передбачається, що в цьому випадку вони залишаться у форматі JSON на всьому шляху до самої системи зберігання і будуть збережені як JSON, а не перекладені. Ці JSON-структури транспортуються в JSON-структурі корисного навантаження запиту (або відповіді) і будуть відправлені як об'єкти в межах цього корисного навантаження, хоча вони не інтерпретуються як щось інше, ніж дані, які будуть зберігатися на рівні зберігання.

Вимоги

Рівень зберігання являє собою інтерфейс для збереження даних для пристрою Fledge, всі збережені дані будуть зчитуватися або записуватися через цей рівень зберігання. Сюди входять

  • Дані конфігурації - це набір JSON-документів, проіндексованих за ключем.
  • Дані зчитування - показання, що надходять з пристрою, які буферизуються протягом певного періоду часу.
  • Дані користувача та облікові дані - це ім'я користувача, паролі та сертифікати, пов'язані з користувачами Fledge API.
  • Дані журналу аудиту - це журнал значущих подій за час роботи Fledge.
  • Метрики - різні модулі будуть зберігати метрики продуктивності, такі як кількість введених, виведених даних тощо. Вони будуть періодично записуватися цими моделями у вигляді кумулятивних підсумків. Вони будуть збиратися збирачем статистики, а інтервальна статистика значень буде записуватися в постійне сховище.
  • Записи завдань - статус та історія завдань, які були заплановані у Fledge.
  • Гнучкі схеми - на рівні зберігання повинно бути написано, що схема, за умови наявності базового механізму зберігання, фіксується не самим рівнем зберігання, а реалізацією зберігання і додатком (Fledge). Зокрема, набір таблиць і стовпців у цих таблицях не є попередньо сконфігурованим у компоненті рівня зберігання (якщо припустити, що базове сховище даних базується на схемі).

Мова реалізації

Ядро платформи Fledge до цього часу було написано з використанням Python, але для рівня зберігання було прийнято рішення реалізувати його на C/C++. Існує ряд факторів, які необхідно взяти до уваги в результаті цього рішення.

  • Вибір бібліотеки, зроблений для реалізації на Python, більше не є дійсним, і необхідно зробити вибір на користь C/C++.
  • Загальний код, такий як API управління мікросервісами, не може бути використаний повторно, тому необхідна реалізація на C/C++.

Сервіс зберігання відрізняється від інших сервісів Fledge тим, що він підтримує лише плагіни, скомпільовані у спільні об'єкти, які мають визначений інтерфейс C. Сам код плагіна може бути написаний іншими мовами, але він має бути скомпільований у C-сумісний спільний об'єкт з використанням угод виклику C.

Причини вибору мови

Спочатку передбачалося, що весь продукт Fledge буде написаний на Python, але після першої демонстраційної версії почали з'являтися проблеми з обґрунтованістю цього вибору для реалізації такого продукту, як Fledge. Ці проблеми такі;

  • Масштабованість - Python по суті є однопотоковою мовою через глобальне блокування інтерпретатора (GIL), яке дозволяє виконувати лише один оператор Python в будь-який момент часу.
  • Переносимість - Коли ми почали більше працювати з OSIsoft та ARM, стало зрозуміло, що можливість перенесення Fledge або деяких його компонентів на вбудоване обладнання стане для нас все більш необхідною. Зокрема, обговорювалася платформа ARM mbed. Python не доступний на цій платформі та багатьох інших вбудованих платформах.

Якщо Python не буде мовою для реалізації в майбутньому, то було вирішено, що рівень зберігання даних, як щось, що ще не розпочато, може бути краще реалізовано в інший спосіб. Оскільки проект базується на мікросервісах з REST API між ними, то можна змішувати та поєднувати реалізацію різних компонентів між різними мовами.

Рівень зберігання є окремим мікросервісом і не пов'язаний безпосередньо з будь-яким кодом Python, зв'язок здійснюється лише через REST API. Тому рівень зберігання може реалізовувати модель потоків, яка найкраще йому підходить і не прив'язана до моделі потоків Python, що використовується в інших мікросервісах.

Вибір мови C/C++ ґрунтується на тому, що вона є загальнодоступною на всіх платформах, на яких, за нашими прогнозами, Fledge може працювати в осяжному майбутньому, а також на досвіді, який має команда.

Вибір бібліотеки

Однією з ключових бібліотек, яку необхідно вибрати для C/C++, є бібліотека JSON, оскільки в мові немає власної підтримки для цього. Для цього існує багато бібліотек, наприклад, rapidjson, Jansson та багато інших. Щоб знайти найбільш підходящу, потрібно провести певне дослідження. Фактори, які слід враховувати при виборі бібліотеки, наведені нижче у порядку важливості;

  • Функціональність - очевидно, що будь-яка обрана бібліотека має надавати потрібну нам функцію.
  • Займана площа - Займана площа є основною проблемою для Fledge, оскільки ми хочемо працювати на обмежених пристроях з ймовірністю того, що в майбутньому пристрій, на якому ми хочемо працювати, може стати ще меншим, ніж ми розглядаємо сьогодні.
  • Безпека потоків - Передбачається, що з причин масштабованості та природи REST-інтерфейсу у реалізації буде використано декілька потоків, тому безпека потоків є основною проблемою при виборі бібліотеки.
  • Продуктивність - Будь-яка обрана бібліотека повинна бути достатньо продуктивною у виконанні роботи, яку вона виконує, щоб бути розглянутою. Ми повинні уникати вибору повільних або роздутих бібліотек, якщо ми прагнемо працювати на апаратному забезпеченні з обмеженими можливостями.

Вибір бібліотеки JSON також варто розглянути; оскільки об'єкти JSON передаються через інтерфейс плагіна, вибір бібліотеки C++ обмежить використання C++ як мікросервісом, так і плагінами. Можливо, краще використовувати бібліотеку на основі C і, таким чином, мати гнучкість у реалізації на C або C++ як для самого сервісу, так і для плагіна.

Іншим ключовим вибором бібліотеки для підтримки REST-інтерфейсу є бібліотека HTTP, яка може бути використана для підтримки розробки REST-інтерфейсу і здатна підтримувати кастомні поля заголовків і HTTPS. Знову ж таки, їх багато: libmicrohttpd, Simple-Web-Server, Proxygen. Вибір тут також має бути зроблений за тими ж критеріями, що описані вище.

Безпека потоків, ймовірно, також буде важливою, оскільки передбачається, що рівень зберігання буде багатопотоковим і майже напевно використовуватиме асинхронні операції вводу/виводу.

Класи збережених даних

Існує два класи даних, які Fledge має зберігати:

  • Внутрішньо згенеровані дані
  • Дані, що надходять від датчиків

Перші з них - це, по суті, дані про конфігурацію, стан та пошук Fledges, необхідні для його функціонування. Схема доступу до цих даних - це класичні операції створення, отримання, оновлення та видалення, які є загальними для більшості баз даних. Доступ є випадковим за своєю природою і зазвичай здійснюється за допомогою певних індексів та ключів.

Другий клас даних, які зберігаються, і який є основною функцією Fledge - це дані, які він отримує з датчиків. Тут схема доступу зовсім інша;

  • Нові дані завжди додаються до збережених даних
  • Оновлення цих даних не підтримуються
  • Дані зчитуються переважно послідовними блоками (основний варіант використання)
  • Випадковий доступ є рідкісним і обмежується відображенням та аналітикою в інтерфейсі користувача або клієнтами публічного API
  • Видалення даних відбувається виключно на основі віку, і записи не будуть видалятися інакше, ніж у хронологічному порядку.

Враховуючи різницю в природі цих двох класів даних і можливість того, що це призведе до різних реалізацій зберігання для них, інтерфейс розділений між цими двома класами даних. Це дозволяє

  • Використовувати різні плагіни для кожного типу, можливо, базу даних SQL для внутрішнього зберігання даних і спеціалізовану базу даних часових рядів або сховище документів для показань датчиків.
  • Один плагін може реалізовувати лише підмножину API плагіна, наприклад, загальні методи доступу до даних або методи зчитування. Або обидва.
  • Плагіни можуть вибирати, де і як вони зберігають дані, щоб оптимізувати реалізацію. Наприклад, дані SQL можуть зберігатися у форматі JSON у таблиці або серії таблиць, якщо бажано.
  • Плагіни не змушені зберігати дані JSON певним чином. Наприклад, база даних SQL не зобов'язана використовувати типи даних JSON в одному стовпці, якщо вона їх не підтримує.

Ці два класи даних у цій документації називаються "загальний доступ до даних" і "дані для читання".

Загальні методи доступу до даних

До більшості з цих типів даних можна отримати доступ за допомогою класичних методів створення, оновлення, отримання та видалення, і вони складаються з даних у форматі JSON з відповідним ключем і міткою часу. У цьому випадку достатньо простого створення з ключем і значенням JSON, оновлення з тим же ключем і значенням, отримання з необов'язковим ключем (який повертає масив об'єктів JSON) і видалення з ключем - це все, що потрібно. Конфігурація, метрики, записи завдань, контрольний журнал і дані користувачів - все це підпадає під цю категорію. Однак читання не підпадають, і до них потрібно ставитися по-іншому.

Доступ до даних зчитувань

Зчитування працюють інакше, ніж інші дані, як у способі їх створення, так і у способі їх отримання та видалення. Наразі для зчитувань не передбачена функція оновлення, зокрема, не існує методу оновлення даних зчитувань.

Інша відмінність даних зчитування від інших даних, якими керує рівень зберігання, пов'язана з обсягом та використанням даних. Дані зчитування - це, безумовно, найбільший обсяг даних, якими керує Fledge, і вони мають дещо інший життєвий цикл та використання. Дані надходять із зовнішніх пристроїв, певний час зберігаються на рівні зберігання, а потім видаляються. Вони також можуть бути отримані іншими процесами протягом періоду часу, поки існують у буфері.

Ще однією характеристикою даних зчитування є здатність запускати обробку на основі надходження нових даних. Це може бути пов'язано з процесом, який блокується, очікуючи на надходження даних, або з оптимізацією, коли процес бажає обробляти нові дані в міру їх надходження, а не отримувати їх безпосередньо зі зберігання. В останньому випадку дані все одно буферизуються на рівні зберігання, використовуючи звичайні правила зберігання та очищення цих даних.

Створення зчитування

Зчитування надходять з компонента пристрою Fledge і являють собою потік часових рядів JSON-документів. Вони мають бути додані до пристрою зберігання з унікальними ключами та міткою часу. Додавання зчитувань можна розглядати як механізм черги до рівня зберігання.

Керування заблокованими пошуками

Різні компоненти, передусім процес відправлення та північний сервіс, зчитують блоки даних з рівня зберігання. Ці компоненти можуть запитувати сповіщення, коли з'являються нові дані, наприклад, процес відправлення може запитувати новий блок даних, коли більше немає доступних блоків. Це буде зареєстровано на рівні зберігання, і рівень зберігання повідомить процес відправлення про те, що нові дані доступні, і що наступний виклик поверне новий блок даних.

Це переважна функція, яка може бути відсутня у першій версії. Вона призначена для того, щоб процес, який отримує та обробляє дані зчитування, мав ефективний спосіб дізнатися, що нові дані доступні для обробки. Одним із сценаріїв може бути процес надсилання, який надіслав усі наявні дані; він бажає бути поінформованим, коли нові дані будуть доступні для надсилання. Замість того, щоб опитувати рівень зберігання, запитуючи нові дані, він може попросити рівень зберігання викликати його, коли з'являться дані, що перевищують ідентифікатор, отриманий процесом востаннє.

Обхід зберігання бази даних

Однією з потенційних оптимізацій, яку слід створити на рівні зберігання в якості майбутньої оптимізації, є створення архітектури рівня зберігання таким чином, щоб можна було використовувати механізм публікації/підписки, щоб дані, які надходять на рівень зберігання, спрямовувалися як до плагіна зберігання, сам, а також надіслати його до інших сервісів, таких як процес надсилання.

Відновлення зчитування

Зчитування можуть бути отримані за допомогою одного з двох механізмів

  • Процесом надсилання, який запитує зчитування протягом певного часового інтервалу
  • З рівня API для аналізу на периферійному пристрої або зовнішньому об'єкті, який отримує дані через користувацький REST API Fledge.

Процес відправлення та північний сервіс можуть вимагати надсилання великих обсягів даних, тому для зменшення необхідного обсягу пам'яті та підвищення надійності модуль відправлення вимагатиме показання контрольованими "шматками", тому він запитуватиме показання між двома часовими мітками блоками по x показань, а потім запитуватиме кожний блок послідовно. Відповідальність процесу-відправника полягає в тому, щоб забезпечити запит блоків розумного розміру. Оскільки інтерфейс REST за визначенням не має стану, рівень зберігання не повинен зберігати інформацію про попередні вибірки даних.

Доступ API до даних буде аналогічним, за винятком того, що він матиме обмеження на кількість зчитувань, він запитуватиме впорядковані зчитування між часовими мітками і запитуватиме зчитування між n-им і m-им зчитуванням. Наприклад, повернення показань між 21:00 10 червня 2017 року та 21:00 11 червня обмежене 100-м та 150-м зчитуванням за цей час. Рівень API контролюватиме максимальну кількість зчитувань, які можуть бути повернуті, щоб переконатися, що набори результатів є невеликими.

Видалення зчитування

Видалення зчитувань відбувається за допомогою процесу очищення, який запитує зчитування, зроблені до певного часу, для видалення з пристрою зберігання на основі часової мітки кожного зчитування. Введення рівня зберігання і видалення чистого інтерфейсу SQL змінить природу процесу очищення і, по суті, перенесе логіку процесу очищення на рівень зберігання.

Плагін Зберігання

Однією з вимог, яка зумовлює бажання мати рівень зберігання, є ізоляція інших сервісів і користувачів рівня зберігання від технології, яка забезпечує це зберігання. Верхній рівень сервісу зберігання пропонує узгоджений API для клієнтів сервісу зберігання і забезпечує загальну інфраструктуру для зв'язку з іншими сервісами в рамках Fledge, в той час як нижній рівень забезпечує інтерфейс до технології зберігання, яка фактично буде зберігати дані. Оскільки ми хочемо мати можливість перемикатися між різними рівнями зберігання, цей нижній рівень буде використовувати механізм плагінів, який дозволить загальному сервісу зберігання динамічно завантажувати один або декілька плагінів зберігання.

Можливість використання декількох плагінів в межах одного рівня зберігання дозволить використовувати різні плагіни для кожного класу даних, див. Класи даних, що зберігаються. Це дало б можливість зберігати внутрішні дані Fledges у загальній базі даних, а дані зчитування зберігати у спеціальному сховищі, пристосованому для часових рядів або даних JSON. Немає вимоги мати кілька плагінів у будь-якому конкретному розгортанні, однак, якщо така можливість буде доступна, код, який розробляється на початковому етапі, повинен знати про цю майбутню вимогу і бути реалізованим належним чином. Передбачається, що перша версія матиме один плагін для обох класів даних. Додаткові зусилля для підтримки більш ніж одного плагіна практично дорівнюють нулю, тому ми включили його сюди.

Точки входу

Плагін зберігання відкриває ряд точок входу подібно до плагінів Python, що використовуються для інтерфейсу перекладача та інтерфейсу пристрою. У середовищі C/C++ цей механізм дещо відрізняється від механізму Python. Плагін - це спільна бібліотека, яка входить до складу інсталяції або може бути встановлена пізніше у відоме місце. Бібліотека використовується за допомогою бібліотечної функції C dlopen(), а кожна точка входу отримується за допомогою виклику dlsym().

Інтерфейс плагіна змодельовано як набір функцій C, а не як клас C++, щоб надати автору плагіна гнучкість у реалізації плагіна на C або C++ за власним бажанням.

Точка входуПідсумок
plugin_infoПовернути інформацію про плагін.
plugin_initІніціалізуйте плагін.
plugin_common_insertВставити рядок у набір даних (таблицю).
plugin_common_retrieveОтримати набір результатів із таблиці.
plugin_common_updateОновити дані в наборі даних.
plugin_common_deleteВидалити дані з набору даних.
plugin_reading_appendДодайте одне чи кілька зчитувань або таблицю зчитувань.
plugin_reading_fetchОтримати блок зчитувань із таблиці зчитувань.
plugin_reading_retrieveЗагальне отримання для отримання даних із таблиці зчитувань на основі параметрів запиту.
plugin_reading_purgeОчистити зчитування з таблиці зчитувань.
plugin_releaseВивільніть набір результатів, попередньо повернутий плагіном, до плагіна, щоб його можна було звільнити.
plugin_last_errorПовернути інформацію про останню помилку, яка сталася в плагіні.
plugin_shutdownВикликається перед вимкненням сервісу пристрою.

Обробка помилок плагіна

Помилки, які виникають у плагіні, повинні передаватися на загальний рівень зберігання з достатньою інформацією, щоб загальний рівень міг повідомити про ці помилки і вжити відповідних заходів для їх виправлення. Інтерфейс плагіна навмисно не використовує класи або інтерфейси C++, щоб не змушувати розробників плагінів реалізовувати плагіни на C++. Тому механізм розповсюдження помилок не може бути виключенням C++, а має бути набагато простіший, мовно-агностичний підхід. З цією метою помилки будуть позначатися статусом повернення кожного виклику інтерфейсу, а для отримання більш детальної інформації про помилки, що виникають, буде використовуватися певна точка входу до плагіна.

Plugin API Header File

#ifndef _PLUGIN_API
#define _PLUGIN_API

typedef struct {
char *name;
char *version;
unsigned int options;
char *type;
char *interface;
char *config;
} PLUGIN_INFORMATION;

typedef struct {
char *message;
char *entryPoint;
boolean retryable;
} PLUGIN_ERROR;

typedef void * PLUGIN_HANDLE;

/**
* Plugin options bitmask values
*/
#define SP_COMMON 0x0001
#define SP_READINGS 0x0002

/**
* Plugin types
*/
#define PLUGIN_TYPE_STORAGE "storage"

/**
* Readings purge flags
*/
#define PLUGIN_PURGE_UNSENT 0x0001

extern PLUGIN_INFORMATION *plugin_info();
extern PLUGIN_HANDLE plugin_init();
extern boolean plugin_common_insert(PLUGIN_HANDLE handle, char *table, JSON *data);
extern JSON *plugin_common_retrieve(PLUGIN_HANDLE handle, char *table, JSON *query);
extern boolean plugin_common_update(PLUGIN_HANDLE handle, char *table, JSON *data);
extern boolean plugin_common_delete(PLUGIN_HANDLE handle, char *table, JSON *condition);
extern boolean plugin_reading_append(PLUGIN_HANDLE handle, JSON *reading);
extern JSON *plugin_reading_fetch(PLUGIN_HANDLE handle, unsigned long id, unsigned int blksize);
extern JSON *plugin_reading_retrieve(PLUGIN_HANDLE handle, JSON *condition);
extern unsigned int plugin_reading_purge(PLUGIN_HANDLE handle, unsigned long age, unsigned int flags, unsigned long sent);
extern plugin_release(PLUGIN_HANDLE handle, JSON *results);
extern PLUGIN_ERROR *plugin_last_error(PLUGIN_HANDLE);
extern boolean plugin_shutdown(PLUGIN_HANDLE handle)
#endif

Підтримка Плагіну

Плагін зберігання може підтримувати один або обидва з двох методів доступу до даних: загальні методи доступу до даних та методи доступу до зчитування. Сервіс зберігання може використовувати механізм одного плагіна для загальних методів доступу до даних, а отже, систему зберігання загальних таблиць та інформації про конфігурацію. Потім він може завантажити другий плагін для підтримки зберігання та отримання даних.

Інформація про плагін

Точка входу інформації про плагін, plugin_info(), дозволяє сервісу пристрою отримувати інформацію з плагіна. Ця інформація повертається у вигляді структури C (PLUGIN_INFORMATION). PLUGIN_INFORMATION міститиме низку полів з інформацією, яку буде використано сервісом зберігання.

ВластивістьОписПриклад
nameІм’я для друку, яке можна використовувати для ідентифікації плагіна.Postgres Plugin
versionНомер версії плагіна, який знову використовується для діагностики та звітування про стан1.0.2
optionsБітова маска параметрів, яка описує рівень підтримки, яку пропонує цей плагін. Наразі доступні два варіанти; SP_COMMON і SP_READINGS. Кожен із цих бітів представляє підтримку набору загальних методів доступу до даних і методу доступу до читання. Додаткову інформацію див. у розділі Підтримка плагінів.SP_COMMON|SP_READINGS
typeТип плагіна, використовується для того, щоб відрізнити плагін API зберігання від будь-якого іншого типу плагіна у Fledge. Це завжди має бути рядок «storage».storage
interfaceВерсія інтерфейсу, яку реалізує плагін. Наразі версія 1.0.1.0

Це перший виклик, який буде зроблено до плагіна після його завантаження, він призначений для того, щоб надати завантажувачу достатньо інформації про те, як взаємодіяти з плагіном, і дозволити йому підтвердити, що плагін має правильний тип.

Ініціалізація плагіна

extern PLUGIN_HANDLE plugin_init();

Викликається після завантаження плагіна та успішного отримання інформації про плагін. Викликається лише один раз і має виконати ініціалізацію, необхідну для зв'язку з датчиком.

Виклик ініціалізації плагіна повертає дескриптор типу void *, який буде використано у наступних викликах плагіна. Це може бути використано для зберігання інформації про екземпляр або стан, яка може знадобитися для будь-яких майбутніх викликів. Дескриптор слід використовувати замість глобальних змінних у плагіні.

Якщо ініціалізація не вдалася, процедура повинна згенерувати виключення. Після цього виключення плагін не буде використовуватися далі.

Плагін Загальна вставка

extern boolean plugin_common_insert(PLUGIN_HANDLE handle, char *table, JSON *data);

Вставити дані, які представлені структурою JSON, що передається у виклик до вказаної таблиці.

Дескриптор - це значення, яке повертається при виклику plugin_init().

Таблиця - це назва таблиці або набору даних, до якого потрібно вставити дані.

Дані - це JSON-документ з кількома парами ім'я/значення властивості. Наприклад, якщо плагін зберігає дані в базі даних SQL, імена - це імена стовпців в еквівалентній базі даних SQL, а значення - це значення, які потрібно записати до цього стовпця. Плагіни для не-SQL, наприклад, бази даних документів, можуть зберігати дані так, як вони представлені в JSON-документі, або в зовсім іншій структурі. Зауважте, що значення можуть бути різних типів, представлені у форматі JSON, і можуть бути самими об'єктами JSON. Плагін повинен виконати будь-яку розмову, необхідну для конкретного рівня зберігання на основі типу JSON.

Значення, що повертається цим викликом, є логічним, яке представляє успіх або значення вставки.

Плагін загального пошуку

extern JSON *plugin_common_retrieve(PLUGIN_HANDLE handle, char *table, JSON *query);

Отримати набір даних з іменованої таблиці.

Дескриптор (handle) - це значення, яке повертається при виклику plugin_init().

Таблиця (table) - це назва таблиці або набору даних, з якого потрібно отримати дані.

Запит (query) - це JSON-документ, який кодує предикати запиту, умову where у випадку рівня SQL. Дивіться Кодування предикатів запиту в JSON для отримання детальної інформації про те, як кодується цей JSON.

Значення, що повертається (return value) - це набір результатів запиту, закодований як структура JSON. Це кодування має вигляд масиву об'єктів JSON, по одному на рядок у наборі результатів. Кожен об'єкт представляє рядок, закодований у вигляді пари властивостей ім'я/значення. Крім того, включено лічильник властивостей, який повертає кількість рядків у результуючому наборі.

Запит, який повертає два рядки зі стовпчиками з іменами "c1", "c2" і "c3", буде представлено так

{
"count" : 2,
"rows" : [
{
"c1" : 1,
"c2" : 5,
"c3" : 9
},
{
"c1" : 8,
"c2" : 2,
"c3" : 15
}
]
}

Вказівник, що повертається викликаючому користувачеві, має бути звільнений, коли викликаючий користувач завершить роботу з набором результатів. Це робиться шляхом виклику виклику plugin_release() з дескриптором plugin_handle та вказівником, що повертається з цього виклику.

Плагін Загальне оновлення

extern boolean plugin_common_update(PLUGIN_HANDLE handle, char *table, JSON *data);

Update вміст набору рядків у заданій таблиці.

Дескриптор - це значення, яке повертається при виклику plugin_init().

Таблиця - це назва таблиці або набору даних, до якого потрібно оновити дані.

Об'єкт даних - це JSON-документ, який кодує лише значення, які потрібно встановити в таблиці, та умову, що використовується для відбору даних. Об'єкт містить дві властивості, умову, значенням якої є JSON, закодований де клаузула, як визначено в Кодування предикатів запиту в JSON, і об'єкт значень. Об'єкт values - це набір пар ім'я/значення, де ім'я збігається з іменами стовпців у даних, а значення визначає значення, яке потрібно встановити для цього стовпця.

Наступний приклад JSON

{
"condition" : {
"column" : "c1",
"condition" : "=",
"value" : 15
},
"values" : {
"c2" : 20,
"c3" : "Updated"
}
}

відобразиться в оператор оновлення SQL

UPDATE <table> SET c2 = 20, c3 = "Updated" where c1 = 15;

Плагін Загальне видалення

extern boolean plugin_common_delete(PLUGIN_HANDLE handle, char *table, JSON *condition);

Оновити вміст набору рядків у заданій таблиці.

Дескриптор (handle) - це значення, яке повертається при виклику plugin_init().

Таблиця (table) - це ім'я таблиці або набору даних, до якого потрібно видалити дані. JSON-елемент condition визначає умову, за якою будуть вибрані рядки даних, які потрібно видалити. Цей об'єкт умови відповідає тій самій схемі кодування JSON, що описана в розділі Кодування предикатів запиту в JSON. Об'єкт умови, що містить

{
"column" : "c1",
"condition" : "=",
"value" : 15
}

видалить усі рядки, де значення c1 дорівнює 15.

Плагін Додавання зчитувань

extern boolean plugin_reading_append(PLUGIN_HANDLE handle, JSON *reading);

Дескриптор - це значення, яке повертається при виклику plugin_init().

Об'єкт читання JSON - це масив з одного або декількох об'єктів читання, які слід додати до пристрою зберігання даних.

Статус, що повертається, вказує на те, чи було успішно додано дані до зберігання, чи ні.

Плагін Вибірка зчитувань

extern JSON *plugin_reading_fetch(PLUGIN_HANDLE handle, unsigned long id, unsigned int blksize);

Отримати блок зчитувань, починаючи з заданого ідентифікатора, і повернути їх у вигляді JSON-об'єкта.

Цей виклик буде використано процесом надсилання, щоб отримати дані, які було буферизовано, і надіслати їх історику. Процес надсилання зчитуватиме з бази даних набір послідовних зчитувань і надсилатиме їх блоком, а не надсилатиме всі зчитування за одну транзакцію з істориком. Це дозволяє процесу надсилання обмежити швидкість надсилання, а також забезпечити покращене відновлення помилок у випадку збою передачі.

Дескриптор - це значення, що повертається викликом plugin_init().

Переданий ідентифікатор - це ідентифікатор першого запису, який повертається у блоці.

Blksize - максимальна кількість записів, що повертається у блоці. Якщо немає достатньої кількості даних для повернення повного блоку даних, то буде повернуто меншу кількість даних. Якщо жодне зчитування не може бути повернуто, то повертається вказівник NULL. Цей виклик не блокує очікування нових значень.

Плагін Отримання зчитувань

extern JSON *plugin_reading_retrieve(PLUGIN_HANDLE handle, JSON *condition);

Повертає набір показників як JSON-об'єкт на основі запиту на вибірку цих показників.

Дескриптором є значення, повернуте викликом plugin_init().

Умова - це запит, закодований у JSON з використанням тих самих механізмів, що описані в розділі Кодування предикатів запитів у JSON. У цьому випадку очікується, що умова JSON включатиме не лише критерії відбору, але й параметри групування та агрегації.

Плагін Чистка зчитувань

extern unsigned int plugin_reading_purge(PLUGIN_HANDLE handle, unsigned long age, unsigned int flags, unsigned long sent);

Видалення даних зчитування на основі віку даних з необов'язковим обмеженням, щоб запобігти видаленню даних, які не були відправлені з пристрою Fledge на зовнішнє зберігання/обробку.

Дескриптор - це значення, яке повертається викликом plugin_init().

Вік визначає максимальний вік даних, які будуть зберігатися

Прапори визначають, чи слід враховувати статус відправлених або невідправлених даних. Якщо прапори вказують, що невідправлені дані не слід видаляти, то значення параметра sent використовується для визначення того, які дані не було відправлено, і дані з ідентифікатором, більшим за ідентифікатор відправлених даних, не буде видалено.

Плагін Звільнення

extern boolean plugin_release(PLUGIN_HANDLE handle, JSON *json)

Цей виклик використовується сервісом зберігання для звільнення набору результатів або іншого JSON-об'єкта, який був повернутий раніше від плагіна до сервісу зберігання. JSON-структури слід віддавати плагіну лише тоді, коли сервіс зберігання завершив роботу з ними, оскільки плагін, найімовірніше, звільнить ресурси пам'яті, пов'язані з JSON-структурою.

Плагін Відновлення помилок

extern PLUGIN_ERROR *plugin_last_error(PLUGIN_HANDLE)

Повертає детальну інформацію про останню помилку, що сталася у цьому екземплярі плагіна. Повернутий вказівник вказує на статичну ділянку пам'яті, яка буде перезаписана, коли у плагіні виникне наступна помилка. Викликувач не зобов'язаний звільняти повернуту пам'ять.

Вимкнення плагіна

extern boolean plugin_shutdown(PLUGIN_HANDLE handle)

Вимкнення плагіна, викликається за допомогою дескриптора плагіна, повернутого з plugin_init, і є останньою операцією, яка буде виконана над плагіном. Вона призначена для того, щоб дозволити плагіну завершити будь-які незавершені операції, які він може мати, закрити з'єднання з рівнями зберігання і загалом звільнити ресурси.

Після завершення цього виклику дескриптор плагіна, який було видано плагіном раніше, слід вважати недійсним, і будь-які подальші виклики з використанням цього дескриптора будуть помилковими.

Кодування предикатів запиту в JSON

Однією з особливих проблем API рівня зберігання є те, як кодувати предикати запиту в структурі JSON, які є такими ж виразами, як і предикати SQL, не роблячи при цьому JSON-документ занадто складним і зберігаючи при цьому гнучкість, необхідну для реалізації плагінів зберігання, які не базуються на базах даних SQL. У традиційних REST API для отримання даних слід використовувати операцію HTTP GET, однак операція GET не підтримує вміст тіла запиту, тому будь-які модифікатори або запити повинні бути закодовані в URL-адресі. Кодування складних предикатів запиту в URL-адресі швидко стає проблемою, тому цей рівень API не буде використовувати цей підхід, він дозволить прості предикати в URL-адресі, але буде використовувати документи JSON і операції PUT для кодування більш складних предикатів в тілі операції PUT.

Таке ж кодування JSON буде використовуватися на рівні зберігання в інтерфейсі плагіна для всіх операцій пошуку.

Предикати будуть закодовані в JSON-об'єкті, який містить речення where, інші необов'язкові властивості можуть бути додані для управління агрегацією, групуванням і сортуванням вибраних даних.

Об'єкт where містить ім'я стовпця, операцію та значення, яке потрібно зіставити, він також може додатково містити властивість and та властивість or. Значення властивостей and та or, якщо вони існують, самі є об'єктами where.

Для прикладу розглянемо наступний JSON-об'єкт

{
"where" : {
"column" : "c1",
"condition" : "=",
"value" : "mine",
"and" : {
"column" : "c2",
"condition" : "<",
"value" : 20
}
}
}

призведе до результату в SQL де пункт форми

WHERE c1 = “mine” AND c2 < 20

Прикладом більш складного прикладу з використанням і та умови або може бути

{
"where" : {
"column" : "id",
"condition" : "<",
"value" : "3",
"or" : {
"column" : "id",
"condition" : ">",
"value" : "7",
"and" : {
"column" : "description",
"condition" : "=",
"value" : "A test row"
}
}
}
}

Що дасть традиційний SQL-запит

WHERE id < 3 OR id > 7 AND description = “A test row”

Note

Наразі неможливо ввести умови в дужках.

Об'єднання

У деяких випадках також потрібно додати об'єднання результатів вибірки записів. У JSON це представлено за допомогою необов'язкового об'єкта aggregate.

"aggregate" : {
"operation" : "<operation>"
"column" : "<column name>"
}

Допустимими операціями для об'єднань є: min, max, avg, sum та count.

Як приклад розглянемо наступний JSON об'єкт

{
"where" : {
"column" : "room",
"condition" : "=",
"value" : "kitchen"
},
"aggregate" : {
"operation" : "avg",
"column" : "temperature"
}
}

Можна застосувати декілька сукупностей, у цьому випадку властивість сукупності стає масивом об'єктів, а не одним об'єктом.

{
"where" : {
"column" : "room",
"condition" : "=",
"value" : "kitchen"
},
"aggregate" : [
{
"operation" : "avg",
"column" : "temperature"
},
{
"operation" : "min",
"column" : "temperature"
},
{
"operation" : "max",
"column" : "temperature"
}
]
}

Набір результатів JSON, створений для сукупностей, матиме властивості з назвами, що є конкатенацією стовпця та операції. Наприклад, де пункт, визначений вище, призведе до відповіді, подібної до наведеної нижче.

{
"count": 1,
"rows" : [
{
"avg_temperature" : 21.8,
"min_temperature" : 18.4,
"max_temperature" : 22.6
}
]
}

Крім того, до сукупностей можна додати властивість "псевдонім", щоб контролювати назву властивості у створеному JSON-документі.

{
"where" : {
"column" : "room",
"condition" : "=",
"value" : "kitchen"
},
"aggregate" : [
{
"operation" : "avg",
"column" : "temperature",
"alias" : "Average"
},
{
"operation" : "min",
"column" : "temperature",
"alias" : "Minimum"
},
{
"operation" : "max",
"column" : "temperature",
"alias" : "Maximum"
}
]
}

Це призведе до наступного результату

{
"count": 1,
"rows" : [
{
"Average" : 21.8,
"Minimum" : 18.4,
"Maximum" : 22.6
}
]
}

Якщо стовпець, який агрегується, містить JSON-документ, а не просте значення, тоді властивість стовпця замінюється на json-властивість, і об'єкт визначає властивості в json-документі в полі бази даних, які будуть використані для агрегації.

Нижче наведено приклад корисного навантаження, яке запитує дані зчитування і повертає об'єднанні значенння властивостей JSON зі стовпця зчитування. Показання стовпця - це JSON-блок у базі даних.

{
"where" : {
"column" : "asset_code",
"condition" : "=",
"value" : "MyAsset"
},
"aggregate" : [
{
"operation" : "min",
"json" : {
"column" : "reading",
"properties" : "rate"
},
"alias" : "Minimum"
},
{
"operation" : "max",
"json" : {
"column" : "reading",
"properties" : "rate"
},
"alias" : "Maximum"
},
{
"operation" : "avg",
"json" : {
"column" : "reading",
"properties" : "rate"
},
"alias" : "Average"
}
],
"group" : "asset_code"
}

Групування

Групування записів можна здійснити, додавши властивість групи до документа JSON, значенням властивості групи є ім’я стовпця для групування.

"group" : "<column name>"

Сортування

Якщо вихідні дані потрібно відсортувати, до документа JSON можна додати об'єкт сортування. Він містить стовпець для сортування та напрямок сортування "за зростанням" або "за спаданням".

"sort"   : {
"column" : "c1",
"direction" : "asc"
}

Також можна застосувати декілька операцій сортування, у цьому випадку властивість сортування стає впорядкованим масивом об'єктів, а не одним об'єктом

"sort"   : [
{
"column" : "c1",
"direction" : "asc"
},
{
"column" : "c3",
"direction" : "asc"
}
]

Примітка

Властивість direction є необов'язковою, і якщо її пропустити, то за замовчуванням буде використано порядок за зростанням.

Обмеження

Можна включити властивість limit, яка обмежить кількість повернених рядків не більше, ніж значенням властивості limit.

"limit" : <number>

Створення часових рядів даних

Механізм часових міток на рівні зберігання дозволяє витягувати дані, які містять значення мітки часу, у порядку мітки часу, згруповані за фіксований період часу.

Директива time bucket дозволяє визначити стовпець мітки часу, розмір кожного часового відра у секундах, необов'язковий формат дати для мітки часу, записаної у результатах, і необов'язковий псевдонім для властивості мітки часу, яка записується.

"timebucket" :  {
"timestamp" : "user_ts",
"size" : "5",
"format" : "DD-MM-YYYY HH24:MI:SS",
"alias" : "bucket"
}

Якщо елемент розміру відсутній, то за замовчуванням розмір часового інтервалу становить 1 секунду.

Це призводить до групування результатів даних, тому очікується, що він буде використовуватися разом з агрегатами для вилучення результатів даних. У наступному прикладі наведено повне корисне навантаження, яке буде використано для вилучення активів з інтерфейсу зчитування

{
"where" : {
"column" : "asset_code",
"condition" : "=",
"value" : "MyAsset"
},
"aggregate" : [
{
"operation" : "min",
"json" : {
"column" : "reading",
"properties" : "rate"
},
"alias" : "Minimum"
},
{
"operation" : "max",
"json" : {
"column" : "reading",
"properties" : "rate"
},
"alias" : "Maximum"
},
{
"operation" : "avg",
"json" : {
"column" : "reading",
"properties" : "rate"
},
"alias" : "Average"
}
],
"timebucket" : {
"timestamp" : "user_ts",
"size" : "30",
"format" : "DD-MM-YYYY HH24:MI:SS",
"alias" : "Time"
}
}

У цьому випадку корисне навантаження буде надіслано в PUT-запиті на URL-адресу /storage/reading/query, а повернуті значення міститимуть дані зчитування для активу під назвою MyAsset, який має швидкість зчитування датчика в JSON-даних, що повертаються. Дані будуть об'єднані в 30-секундні часові інтервали, а значення, що повертаються, будуть у форматі JSON, як показано нижче.

{
"count":2,
"Rows":[
{
"Minimum" : 2,
"Maximum" : 96,
"Average" : 47.9523809523809,
"asset_code" : "MyAsset",
"Time" : "11-10-20177 15:10:50"
},
{
"Minimum" : 1,
"Maximum" : 98,
"Average" : 53.7721518987342,
"asset_code" : "MyAsset",
"Time" : "11-10-20177 15:11:20"
}
]
}

Об'єднання таблиць

Об'єднання між таблицями можна створювати за допомогою об'єкта join. Об'єкт JSON містить ім'я таблиці, стовпець для об'єднання в таблиці самого запиту і необов'язковий стовпець в об'єднаній таблиці. Він також дозволяє додати запит, який може визначати умову where для вибору стовпців у приєднаній таблиці, і об'єкт повернення, щоб визначити, які рядки слід використовувати з цієї таблиці і як їх назвати.

У наступному прикладі приєднується таблиця з назвою attributes до таблиці, вказаної в URL-адресі запиту. Він використовує стовпець з назвою parent_id у таблиці атрибутів для приєднання до стовпця id у таблиці, вказаній у запиті. Якщо ім'я стовпця в обох таблицях однакове, то немає необхідності вказувати поле стовпця в об'єкті таблиці, замість цього ім'я стовпця можна вказати у полі on.

{
"join" : {
"table" : {
"name" : "attributes",
"column" : "parent_id"
},
"on" : "id",
"query" : {
"where" : {
"column" : "name",
"condition" : "=",
"value" : "MyName"

},
"return" : [
"parent_id",
{
"column" : "name",
"alias" : "attribute_name"
},
{
"column" : "value",
"alias" : "attribute_value"
}
]
}
}
}

Якщо припустити, що в запиті до головної таблиці немає додаткових умов where або обмежень на повернення, це дасть SQL-код вигляду

select t1.*, t2.parent_id, t2.name as "attribute_name", t2.value as "attribute_value"  from parent t1, attributes t2 where t1.id = t2.parent_id and t2.name = "MyName";

Об'єднання можуть бути вкладеними, що дозволяє з'єднати більше двох таблиць. Припустимо, що у нас є батьківська таблиця, яка містить елементи, і таблиця атрибутів, яка містить атрибути цих елементів. Ми хочемо повернути елементи, які мають атрибут MyName і колір. Нам потрібно двічі приєднатися до таблиці атрибутів, щоб отримати потрібні нам запити. Корисне навантаження JSON буде наступним

{
"join" : {
"table" : {
"name" : "attributes",
"column" : "parent_id"
},
"on" : "id",
"query" : {
"where" : {
"column" : "name",
"condition" : "=",
"value" : "MyName"

},
"return" : [
"parent_id",
{
"column" : "value",
"alias" : "my_name"
}
]
"join" : {
"table" : {
"name" : "attributes",
"column" : "parent_id"
},
"on" : "id",
"query" : {
"where" : {
"column" : "name",
"condition" : "=",
"value" : "colour"

},
"return" : [
"parent_id",
{
"column" : "value",
"alias" : "colour"
}
]
}
}
}
}
}

І результуючий SQL-запит буде таким

select t1.*, t2.parent_id, t2.value as "my_name", t3.value as "colour"  from parent t1, attributes t2, attributes t3 where t1.id = t2.parent_id and t2.name = "MyName" and t1.id = t3.parent_id and t3.name = "colour";

JSON Predicate Schema

Нижче наведено визначення схеми JSON для кодування предикатів.

{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {},
"id": "http://example.com/example.json",
"properties": {
"group": {
"id": "/properties/group",
"type": "string"
},
"sort": {
"id": "/properties/sort",
"properties": {
"column": {
"id": "/properties/sort/properties/column",
"type": "string"
},
"direction": {
"id": "/properties/sort/properties/direction",
"type": "string"
}
},
"type": "object"
},
"aggregate": {
"id": "/properties/aggregate",
"properties": {
"column": {
"id": "/properties/aggregate/properties/column",
"type": "string"
},
"operation": {
"id": "/properties/sort/properties/operation",
"type": "string"
}
},
"type": "object"
},
"properties": {
"limit": {
"id": "/properties/limit",
"type": "number"
}
"where": {
"id": "/properties/where",
"properties": {
"and": {
"id": "/properties/where/properties/and",
"properties": {
"column": {
"id": "/properties/where/properties/and/properties/column",
"type": "string"
},
"condition": {
"id": "/properties/where/properties/and/properties/condition",
"type": "string"
},
"value": {
"id": "/properties/where/properties/and/properties/value",
"type": "string"
}
},
"type": "object"
},
"column": {
"id": "/properties/where/properties/column",
"type": "string"
},
"condition": {
"id": "/properties/where/properties/condition",
"type": "string"
},
"or": {
"id": "/properties/where/properties/or",
"properties": {
"column": {
"id": "/properties/where/properties/or/properties/column",
"type": "string"
},
"condition": {
"id": "/properties/where/properties/or/properties/condition",
"type": "string"
},
"value": {
"id": "/properties/where/properties/or/properties/value",
"type": "string"
}
},
"type": "object"
},
"value": {
"id": "/properties/where/properties/value",
"type": "string"
}
},
"type": "object"
}
},
"type": "object"
}

Контроль повернених значень

Загальним API пошуку та API зчитування можна керувати, щоб повертати підмножини даних, визначаючи "стовпці", які повертаються в необов'язковому об'єкті "return" в JSON-даних цих точок входу.

Повернення обмеженого набору стовпців

За необов'язковим об'єктом "returns" може слідувати масив JSON, який містить назви стовпців, що повертаються.

"return" : [ "column1", "column2", "column3" ]

Масив може бути простими рядками, які повертають стовпці, або це можуть бути JSON-об'єкти, які передають стовпець і псевдонім для цього стовпця

"return : [ "column1", {
"column" : "column2",
"alias" : "SecondColumn"
}
]

Окремі елементи масиву також можуть бути змішані, як у наведеному вище прикладі.

Форматування стовпців

Якщо вказано об'єкт, що повертається, можна також форматувати дані, що повертаються, особливо це стосується дат. Форматування здійснюється шляхом додавання властивості format до об'єкта стовпця, що повертається.

"return" : [ "key", "description", 
{
"column" : "ts",
"format" : "DD Mon YYYY",
"alias" : "date"
}
]

Рядок формату може бути для дат або числових значень. Вміст рядка для дат - це шаблон, що складається з комбінації наступних елементів.

ПаттернОпис
HHГодини в 12-часовому форматі
HH24Години в 24-часовому форматі
MIзначення хвилин
SSзначення секунд
MSзначення мілісекунд
USзначення мікросекунд
SSSSСекунди з півночі
YYYYРік у 4 знаках
YYРік у 2 знаках
Monthповна назва місяця
Monназва місяця в 3 знаках
MMномер місяця
Dayдень тижня
Dyабревіатура дня тижня
DDDдень року
DDдень місяцю
Dдень тижня
Wтуждень року
amпокажчик до/після полудня

Повернути вміст документа JSON

Механізм повернення також може бути використаний для повернення властивостей з JSON-документа, що зберігається в базі даних.

{
"return" : [
"code",
{
"column" : "ts",
"alias" : "timestamp"
},
{
"json" : {
"column" : "log",
"properties" : "reason"
},
"alias" : "myJson"
}
]
}

У наведеному вище прикладі стовпець бази даних з назвою json містить JSON-документ з властивістю reason на базовому рівні JSON-документа. Вищенаведений оператор витягує значення властивостей JSON і повертає його в наборі результатів, використовуючи ім'я властивості myJSON.

Для доступу до властивостей, вкладених глибше в JSON-документі, властивість properties у наведеному вище прикладі також може бути масивом імен JSON-властивостей для кожного рівня в ієрархії. Якщо стовпчик містить JSON-документ, як показано нижче,

{
"building" : {
"floor" : {
"room" : {
"number" : 432,
...
},
},
}
}

Для доступу до номера кімнати буде використано фрагмент повернення, як показано нижче.

{       
"return" : [
{
"json" : {
"column" : "street",
"properties" : [
"building",
"floor",
"room",
"number"
]
},
"alias" : "RoomNumber"
}
]
}