Модель данных
В этой главе подробно описана структура модели данных TDG, а также перечислены доступные способы работы с ней - в файле и в веб-интерфейсе. В главе рассмотрен пример создания модели данных и её загрузки в TDG. Для выполнения примера требуется настроенный кластер TDG.
Модель данных включает в себя:
- структуру объектов для заданной предметной области;
- описание связей между объектами;
- ограничения, которые накладываются на объекты.
В TDG для описания модели данных используются язык Avro Schema, а также расширения, разработанные специально для системы TDG.
Схема в стандарте Avro Schema - это JSON-файл с расширением .avsc,
содержащий описание типов данных для объектов и для их полей. Приложение
использует схему в качестве формата и понимает ее, как массив типов
объектов:
[{"name": "TypeA", "type": "record", ...},{"name": "TypeB", "type": "record", ...}]
Все типы объектов соответствуют стандарту Avro Schema, за исключением расширений для системы TDG. Расширения обратно совместимы, а модель, описанная с их помощью, успешно преобразуется стандартными парсерами (синтаксическими анализаторами).
Для начала зададим модель данных с двумя типами объектов - Country
(страна) и City (город). В качестве примера возьмем следующую
диаграмму объектов:
Теперь представим модель с диаграммы в виде схемы данных Avro:
[{"name": "Country","type": "record","doc": "Страна","fields": [{"name": "title", "type": "string"},{"name": "phone_code", "type": ["null", "string"]}],"indexes": ["title"],"relations": [{ "name": "city_relation", "to": "City", "count": "many", "from_fields": "title", "to_fields": "country" }]},{"name": "City","type": "record","doc": "Город","fields": [{"name": "title", "type": "string"},{"name": "country", "type": "string"},{"name": "population", "type": "int"},{"name": "capital", "type": "boolean"},{"name": "postcodes", "type": {"type":"array", "items":"int"}}],"indexes": [{"name":"primary", "parts":["title", "country"]},"title","country","population","postcodes"]}]
где
name- название типа объекта;type- вид схемы;doc- текстовое описание для типа объекта;fields- поля для типа объекта вместе с соответствующими им типами данных.-
name- название поля; -
type- тип данных для поля. Поддерживаются типы из спецификации Avro Schema, а также логические типы данных.Если в модели данных есть опциональное поле, его тип данных описывают с помощью объединяющего массива
union. Такой массив содержит основной тип данных для этого поля и типnull. Пример:{"name": "phone_code", "type": ["null", "string"]}
-
indexes- индексы, которые используются при выполнении операций. Первый по счету индекс в списке является первичным. Полеindexes- расширение TDG для задания ключей;relations- вид связи между объектами. В этой модели данных типы объектовCountryиCityимеют связь один ко многим, так как в одной стране обычно расположено много городов. Полеrelations- расширение TDG для задания отношений между объектами.
Описания всех доступных расширений TDG приведены в разделе Расширения модели данных.
Чтобы применить модель данных и делать ссылка, нужно загрузить модель данных в TDG. Основные способы загрузки данных:
- через веб-интерфейс на вкладке
Model; - в файле
.avsc, который будет запакован в архив вместе с файлом конфигурации.
Чтобы загрузить модель данных через веб-интерфейс TDG, выполните следующие шаги:
- Откройте веб-интерфейс на экземпляре, входящем в набор реплик с
кластерной ролью
runner. Если вы используете уже развернутый TDG-кластер, URL экземпляра будет следующий: http://172.19.0.2:8082. - В веб-интерфейсе выберите вкладку
Model. - Вставьте созданную модель данных в поле
Request. - Нажмите
Submit. Если модель данных загружена успешно, в окнеResponseпоявится ответOK.
Загрузить модель данных можно также в составе zip-архива вместе с файлом конфигурации. Для этого:
- Упакуйте в zip-архив:
- модель данных в файле с расширением
.avsc, например,model.avsc; - файл конфигурации
config.yml.
- модель данных в файле с расширением
- Загрузите архив в TDG согласно инструкции.
Расширения для системы TDG дополняют спецификацию Avro Schema. Эти расширения позволяют:
- задавать связи между объектами;
- индексировать поля объектов и задавать ограничения на запросы и хранение;
- распределять объекты по хранилищам;
- задавать дополнительные свойства полей.
Явно задавать связи между объектами нужно:
- для валидации внешних ключей при вставке объектов;
- для запросов связанных объектов через GraphQL.
Чтобы указать такую связь, используется поле relations в теле описания
типа объекта. Это поле игнорируется существующими парсерами Avro Schema.
Связываемые поля (внешние ключи в терминологии SQL) должны быть
объявлены на уровне типов объектов.
Например, чтобы задать связь между страной и ее городами, требуются следующие поля:
title(Country) - первичный ключ доступен через обращение к типу объектаCountry.title;country(City) - внешний ключ доступен через обращение к типу объектаCity.country.
Пример поля relations из заданной ранее модели данных:
{"relations": [{"name": "city_relation","to": "City","count": "many","from_fields": "title","to_fields": "country"},...]}
где:
name- название поля, через которое можно получить связанные объекты в GraphQL-запросах;to- тип объекта, с которым устанавливается связь;count- вид связи. Возможные значения:one- связь один к одному;many- связь один ко многим;
from_fields- название поля или индекса, содержащего первичный ключ. Возможные значения:- название индекса (
"from_fields": "title"); - название поля (
"from_fields": "field_name"); - список названий полей
(
"from_fields": ["field_name1", "field_name2"]);
- название индекса (
to_fields- название поля или индекса, содержащего внешний ключ. Возможные значения:- название индекса (
"to_fields": "country"); - название поля (
"to_fields": "field_name"); - список названий полей
(
"to_fields": ["field_name1", "field_name2"]).
- название индекса (
Все параметры поля обязательные.
Поле relations можно задать как с одной стороны отношения, так и с
обеих. Поле указывается только в одном типе объекта, когда запрашивать
данные в обратном направлении не требуется.
Дополним модель данных еще одним полем relations (City), чтобы можно
было запрашивать данные в обоих направлениях. Полный пример может
выглядеть так:
[{"name": "Country","type": "record","doc": "Страна","fields": [{"name": "title", "type": "string"},{"name": "phone_code", "type": ["null", "string"]}],"indexes": ["title"],"relations": [{"name": "city_relation", "to": "City", "count": "many", "from_fields": "title", "to_fields": "country"}]},{"name": "City","type": "record","doc": "Город","fields": [{"name": "title", "type": "string"},{"name": "country", "type": "string"},{"name": "population", "type": "int"},{"name": "capital", "type": "boolean"},{"name": "postcodes", "type": {"type":"array", "items":"int"}}],"indexes": [{"name":"primary", "parts":["title", "country"]},"title","country","population","postcodes"],"relations": [{"name": "country_relation", "to": "Country", "count": "one", "from_fields": "country", "to_fields": "title"}]}]
Для задания индексов используется поле indexes в описании типа
объекта. Поле не меняет поведение, регулируемое стандартом Avro Schema,
а добавляет дополнительные ограничения на хранение и запросы. Если для
типа объекта указаны индексы, TDG создает спейсы под этот тип.
Индексы по полю с типом bytes строятся так же, как и по полю со
строковым типом.
Синтаксис поля indexes:
{"indexes": ["<index1>", <"index2">, <"index3">, ...]}
Каждый индекс в поле indexes может быть задан в виде:
-
строки с названием поля, по которому будет построен индекс (
"indexes": ["title"]); -
словаря, на основе которого строятся составной индекс или индекс по полю вложенного объекта. Такой индекс указывается в следующем формате:
{"name": "<index_name>","parts": [{"path": "<field_name>", "collation": "<collation_type>"},{"path": "<field_name>", "collation": "<collation_type>"},...]}где:
name- название индекса, не совпадающее с именами существующих полей;parts- части индекса. Включает в себя:path- названия полей, по которым строится индекс;collation(опционально) - способ сравнения строк. Возможные значения:binary(по умолчанию) - бинарное сравнение ('A' < 'B' < 'a');case_sensitivity- сравнение, зависящее от регистра ('a' < 'A' < 'B');case_insensitivity- сравнение, не зависящее от регистра (('a' = 'A') < 'B'и'a' = 'A' = 'á' = 'Á').
Пример составного индекса
{"name": "City","type": "record","fields": [{"name": "title", "type": "string"},{"name": "country", "type": "string"},{"name": "population", "type": "int"},{"name": "capital", "type": "boolean"},{"name": "postcodes", "type": {"type":"array", "items":"int"}}],"indexes": [{"name":"primary", "parts":["title", "country"]},"title","country","population","postcodes"]}
Пример индексации по полю вложенного объекта
Иногда требуется построить индекс по полю не из самого объекта, а из его вложенного объекта. Вложенный объект при этом создается, чтобы сгруппировать логически набор стандартных полей.
Например, представьте, что каждая страна (Country) в примере содержит
дополнительный блок информации для туристов. Создадим новый тип объекта
Info и сложный индекс в типе объекта Country. Тип объекта Info
включается здесь непосредственно в тип объекта Country, поэтому индекс
может сослаться на поле из Info:
[{"name": "Info","type": "record","doc": "Информация для туристов","fields": [{"name": "id", "type": "long"},{"name": "info_text", "type": "string"}]},{"name": "Country","type": "record","doc": "Страна","fields": [{"name": "title", "type": "string"},{"name": "info", "type": "Info"},{"name": "phone_code", "type": ["null", "string"]}],"indexes": ["title",{"name": "info_id", "parts": ["info.id"]}]}]
В случае распределенного хранилища данных объекты распределяются с
использованием хеш-функции от первичного ключа
объекта. Указать такой индекс можно в поле affinity.
Пример
Перед загрузкой модели с новым полем affinity удалите старую модель
данных. Кроме того, проверьте список спейсов во вкладке Settings >
Unlinked spaces в веб-интерфейсе TDG. Если в списке есть
спейсы с типом объекта City, удалите их, чтобы избежать ошибки при
загрузке новой модели.
Теперь дополните модель данных новым полем и загрузите модель в TDG:
{"name": "City","type": "record","doc": "Город","fields": [{"name": "title", "type": "string"},{"name": "country", "type": "string"},{"name": "population", "type": "int"},{"name": "capital", "type": "boolean"},{"name": "postcodes", "type": {"type":"array", "items":"int"}}],"indexes": [{"name":"primary", "parts":["title", "country"]},"title","country","population","postcodes"],"affinity": ["country"]}
В примере города одной и той же страны размещены физически на одном
хранилище. Поле affinity при этом содержит индекс из составного
первичного ключа.
Расширение для TDG дополняет список стандартных атрибутов полей Avro Schema:
-
default_function(function) - задает для поля динамическое значение по умолчанию. Значение атрибута - это функция, файл с которой хранится в директорииsrcв корне проекта. При вставке в спейс новой записи вызывается указанная функция, и результат функции становится значением для данного поля. Атрибут не стоит путать со стандартным атрибутомdefault, который задает для поля статическое значение; -
auto_increment(boolean) - значениеtrueделает поле автоинкрементным. По умолчанию:false. Может использоваться в качестве числового идентификатора, уникального для объектов данного типа, даже при шардировании базы данных. Автоинкрементное поле обязательно должно иметь типlong. Атрибут несовместим с атрибутамиdefaultиdefault_function; -
api-encoding(string) - задает способ кодирования и декодирования бинарных данных в API HTTP и GraphQL. Доступно с версии 2.19.0. Опция поддерживается только для полей типаstringилиbytes, по умолчанию опция отключена. Поддерживаемые кодировки:base64,hex. Принимает на вход один из вариантов ниже:- одну строку кодировки, которая применяется сразу к двум интерфейсам
- HTTP и GraphQL;
- таблицу с отдельным значением для каждого интерфейса (
http,graphql).
{"name": "Payload","type": "record","fields": [{"name": "id", "type": "string"},{"name": "data", "type": "bytes", "api-encoding": "base64"},{"name": "hash", "type": "string", "api-encoding": {"http": "hex", "graphql": "base64"}}]} - одну строку кодировки, которая применяется сразу к двум интерфейсам
Пример
Расширьте тип объекта City этими атрибутами:
- задайте по умолчанию для поля
populationдинамическое значение; - добавьте поле с числовым идентификатором города.
Обновленные поля объекта могут выглядеть так:
{"name": "City","type": "record","fields": [{"name": "city_id", "type": "long", "auto_increment": true},{"name": "title", "type": "string"},{"name": "country", "type": "string"},{"name": "population", "type": "int", "default" : "count_population.call"}},...],...}
Логический тип - это простой или составной тип данных Avro,
содержащий дополнительные атрибуты для представления нового типа данных
для поля. Чтобы объявить в модели поле с логическим типом, используется
атрибут logicalType. Поля этих типов можно использовать при построении
любых индексов, кроме
мультиключевых. По умолчанию для логических
типов задается строковый тип данных.
TDG поддерживает следующие логические типы:
-
Date- дата. Хранится в видеint. Формат записи:YYYY-MM-DDZ; -
Time- время. Хранится в видеint. Формат записи:HH:MM:SSZ; -
Datetime- дата и время. Хранится в видеint. Форматы записи:- дата и время с миллисекундами:
YYYY-MM-DDTHH:MM:SS.sssZ; - дата и время с микросекундами:
YYYY-MM-DDTHH:MM:SS.ssssssZ; - дата и время с наносекундами:
YYYY-MM-DDTHH:MM:SS.sssssssssZ.
- дата и время с миллисекундами:
-
Timestamp- тип для работы с датой и временем на основе Tarantool-модуля datetime; -
Decimal- вычисления с точными числами. Пример записи:10.001. Тип использует Tarantool-модуль decimal; -
UUID- уникальный идентификатор. Формат записи:00000000-0000-0000-0000-000000000000. Тип использует Tarantool-модуль uuid.
Примеры объявления полей с логическими типами:
[{"name": "LogicalTypes","type": "record","fields": [{"name": "id", "type": {"type": "string", "logicalType": "Timestamp"}},{"name": "datetime", "type": ["null", {"type": "string", "logicalType": "DateTime"}]},{"name": "time", "type": ["null", {"type": "string", "logicalType": "Time"}]},{"name": "date", "type": ["null", {"type": "string", "logicalType": "Date"}]},{"name": "decimal", "type": ["null", {"type": "string", "logicalType": "Decimal"}]},{"name": "uuid", "type": ["null", {"type": "string", "logicalType": "UUID"}]}],"indexes": ["id", "datetime", "time", "date", "decimal", "uuid"]}]