Модель данных | Tdg

Версия:

2.x

Модель данных

В этой главе подробно описана структура модели данных TDG, а также перечислены доступные способы работы с ней – в файле и в веб-интерфейсе. В главе рассмотрен пример создания модели данных и её загрузки в TDG. Для выполнения примера требуется настроенный кластер TDG.

Язык модели данных

Модель данных включает в себя:

  • структуру объектов для заданной предметной области;

  • описание связей между объектами;

  • ограничения, которые накладываются на объекты.

В TDG для описания модели данных используются язык Avro Schema, а также расширения, разработанные специально для системы TDG.

Схема в стандарте Avro Schema – это JSON-файл с расширением .avsc, содержащий описание типов данных для объектов и для их полей. Приложение использует схему в качестве формата и понимает ее, как массив типов объектов:

[
    {"name": "TypeA", "type": "record", ...},
    {"name": "TypeB", "type": "record", ...}
]

Все типы объектов соответствуют стандарту Avro Schema, за исключением расширений для системы TDG. Расширения обратно совместимы, а модель, описанная с их помощью, успешно преобразуется стандартными парсерами (синтаксическими анализаторами).

Определение модели данных

Для начала зададим модель данных с двумя типами объектов – Country (страна) и City (город). В качестве примера возьмем следующую диаграмму объектов:

../../_images/data-model.svg

Теперь представим модель с диаграммы в виде схемы данных 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 – тип данных для поля.

    Примечание

    Если в модели данных есть опциональное поле, его тип данных описывают с помощью объединяющего массива union. Такой массив содержит основной тип данных для этого поля и тип null. Пример:

    {"name": "phone_code", "type": ["null", "string"]}
    
  • indexes – индексы, которые используются при выполнении операций. Первый по счету индекс в списке является первичным. Поле indexes – расширение TDG для задания ключей;

  • relations – вид связи между объектами. В этой модели данных типы объектов Country и City имеют связь один ко многим, так как в одной стране обычно расположено много городов. Поле relations – расширение TDG для задания отношений между объектами.

Описания всех доступных расширений TDG приведены в разделе Расширения модели данных.

Загрузка модели данных

Чтобы применить модель данных и делать запросы к данным, нужно загрузить модель данных в TDG. Основные способы загрузки данных:

  • через веб-интерфейс на вкладке Model;

  • в файле .avsc, который будет запакован в архив вместе с файлом конфигурации.

Загрузка через веб-интерфейс

Чтобы загрузить модель данных через веб-интерфейс TDG, выполните следующие шаги:

  1. Откройте веб-интерфейс на экземпляре, входящем в набор реплик с кластерной ролью runner. Если вы используете уже развернутый TDG-кластер, URL экземпляра будет следующий: http://172.19.0.2:8082.

  2. В веб-интерфейсе выберите вкладку Model.

  3. Вставьте созданную модель данных в поле Request.

  4. Нажмите Submit. Если модель данных загружена успешно, в окне Response появится ответ OK.

Загрузка в составе zip-архива

Загрузить модель данных можно также в составе zip-архива вместе с файлом конфигурации. Для этого:

  1. Упакуйте в zip-архив:

    • модель данных в файле с расширением .avsc, например, model.avsc;

    • файл конфигурации config.yml.

  2. Загрузите архив в 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 создает спейсы под этот тип.

Примечание

Если поле indexes содержит список индексов, первичным индексом считается первый индекс в списке.

Синтаксис поля 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' = 'á' = 'Á').

Примечание

Существуют ограничения при использовании в поле indexes мультиключевого индекса – индекса по полю, содержащему массив. Подробная информация об этих ограничениях приведена в разделе Запросы по мультиключевому индексу.

Пример составного индекса

{
    "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"
    ]
}

Пример индексации по полю вложенного объекта

Иногда требуется построить индекс по полю не из самого объекта, а из его вложенного объекта. Вложенный объект при этом создается, чтобы сгруппировать логически набор стандартных полей.

Примечание

Тип объекта можно использовать как вложенный только в случае, если у этого типа не задано поле indexes.

Например, представьте, что каждая страна (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 может содержать только те поля, которые входят в первичный ключ (в том числе составной).

Пример

Перед загрузкой модели с новым полем 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.

Пример

Расширьте тип объекта 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.

    Примечание

    Начиная с версии TDG 2.7, тип DateTime объявлен устаревшим, используйте вместо него тип Timestamp.

  • 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"]
    }
]
Нашли ответ на свой вопрос?
Обратная связь