Расширения MessagePack
Tarantool использует предопределенные типы расширений MessagePack для
представления некоторых специальных значений. Типы расширений включают
MP_DECIMAL, MP_UUID, MP_ERROR, MP_DATETIME и MP_INTERVAL. Эти
типы требуют особого внимания со стороны разработчиков коннекторов,
поскольку их необходимо обрабатывать отдельно от стандартных типов
MessagePack и корректно сопоставлять с типами языка программирования.
Тип расширения MessagePack MP_EXT вместе с типом расширения
MP_DECIMAL является заголовком для значений типа DECIMAL.
Тип MP_DECIMAL равен 1.
Спецификация MessagePack определяет два вида типов:
- Типы
fixext 1/2/4/8/16имеют фиксированную длину, поэтому длина не кодируется явно. - Типы
ext 8/16/32требуют явного кодирования длины данных.
MP_EXP + необязательный length подразумевают использование одного из
этих типов.
Представление decimal в MessagePack выглядит следующим образом:
<table class="tableblock frame-all grid-all stretch"><colgroup><col style="width: 20%;"><col style="width: 40%;"><col style="width: 20%;"><col style="width: 20%;"></colgroup><thead><tr><th class="tableblock halign-left valign-top">{decodeBase64(IE1QX0VYVA==)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IGxlbmd0aCAob3B0aW9uYWwp)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IE1QX0RFQ0lNQUw=)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IFBhY2tlZERlY2ltYWw=)}</th></tr></thead></table>
Здесь length – длина поля PackedDecimal, имеет тип MP_UINT, если
кодируется явно (т.е. когда используется тип ext 8/16/32).
PackedDecimal имеет следующую структуру:
<--- length bytes --><table class="tableblock frame-all grid-all stretch"><colgroup><col style="width: 50%;"><col style="width: 50%;"></colgroup><thead><tr><th class="tableblock halign-left valign-top">{decodeBase64(IHNjYWxl)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IEJDRA==)}</th></tr></thead></table>
Здесь scale – значение типа MP_INT или MP_UINT.
scale =
количество цифр после десятичной точки
BCD – последовательность байтов, представляющих десятичные цифры
закодированного числа (каждый байт содержит две десятичные цифры, каждая
из которых кодируется 4-битным nibble), поэтому byte >> 4 – первая
цифра, а byte & 0x0f – вторая цифра. Крайняя левая цифра в массиве
является старшей. Крайняя правая цифра в массиве является младшей.
Первый байт массива BCD содержит первую цифру числа, представленную
следующим образом:
| 4 bits | 4 bits |= 0x = the 1st digit
(Первый nibble содержит 0, если десятичное число имеет четное
количество цифр.) Последний байт массива BCD содержит последнюю цифру
числа и завершающий nibble, представленные следующим образом:
| 4 bits | 4 bits |= the last digit = nibble
Завершающий nibble представляет знак числа:
0x0a,0x0c,0x0e,0x0f– плюс,0x0bи0x0d– минус.
Примеры
Десятичное число -12.34 будет закодировано как
0xd6,0x01,0x02,0x01,0x23,0x4d:
|MP_EXT (fixext 4) | MP_DECIMAL | scale | 1 | 2,3 | 4 (minus) || 0xd6 | 0x01 | 0x02 | 0x01 | 0x23 | 0x4d |
Десятичное число 0.000000000000000000000000000000000010 будет
закодировано как 0xc7,0x03,0x01,0x24,0x01,0x0c:
| MP_EXT (ext 8) | length | MP_DECIMAL | scale | 1 | 0 (plus) || 0xc7 | 0x03 | 0x01 | 0x24 | 0x01 | 0x0c |
Тип расширения MessagePack MP_EXT вместе с типом расширения MP_UUID
для значений типа UUID. Начиная с версии
2.4.1.
Тип MP_UUID равен 2.
Спецификация
MessagePack
определяет d8 как fixext с размером 16, а размер UUID всегда равен
16. Поэтому представление UUID в MessagePack выглядит следующим образом:
<table class="tableblock frame-all grid-all stretch"><colgroup><col style="width: 25%;"><col style="width: 25%;"><col style="width: 50%;"></colgroup><tbody><tr><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IE1QX0VYVCAgPSBkOA==)}</p></td><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IE1QX1VVSUQgID0gMg==)}</p></td><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IFV1aWRWYWx1ZSAgPSAxNi1ieXRlIHZhbHVl)}</p></td></tr></tbody></table>
16-байтовое значение содержит 2 цифры на байт. Как правило, оно состоит из 11 полей, которые кодируются как целые числа без знака в порядке big-endian следующим образом:
time_low(4 байта)time_mid(2 байта)time_hi_and_version(2 байта)clock_seq_hi_and_reserved(1 байт)clock_seq_low(1 байт)node[0], ...,node[5](по 1 байту)
Некоторые функции из модуля uuid могут возвращать значения, совместимые с типом данных UUID. Например, после
uuid = require('uuid')box.schema.space.create('t')box.space.t:create_index('i', {parts={1,'uuid'}})box.space.t:insert{uuid.fromstr('f6423bdf-b49e-4913-b361-0740c9702e4b')}box.space.t:select()
анализ пакета ответа сервера покажет, что он содержит
d8 02 f6 42 3b df b4 9e 49 13 b3 61 07 40 c9 70 2e 4b
Начиная с версии 2.4.1,
ответы об ошибках содержат дополнительную информацию после данных,
описанных в
Протокол Box – ответы об ошибках. Это
«совместимое» расширение, поскольку клиенты, ожидающие ответов сервера в
старом формате, должны игнорировать неизвестные им компоненты map.
Однако обратите внимание, что было переименовано имя константы: раньше
IPROTO_ERROR в ./box/iproto_constants.h было 0x31, теперь IPROTO_ERROR – 0x52, а
IPROTO_ERROR_24 – 0x31.
Тип MP_ERROR равен 3.
<table class="tableblock frame-all grid-all stretch"></table>MP_MAP
Дополнительная информация, большая часть которой также присутствует в полях объекта error:
MP_ERROR_TYPE (0x00) (MP_STR) Тип, указывающий на источник, как в
{error_object}.base_type, например
"ClientError".
MP_ERROR_FILE (0x01) (MP_STR) Файл исходного кода, в котором была
перехвачена ошибка, как в {error_object}.trace.
MP_ERROR_LINE (0x02) (MP_UINT) Номер строки в файле исходного кода,
как в {error_object}.trace.
MP_ERROR_MESSAGE (0x03) (MP_STR) Текст причины ошибки, как в
{error_object}.message. Значение здесь
будет тем же, что и в значении IPROTO_ERROR_24.
MP_ERROR_ERRNO (0x04) (MP_UINT) Порядковый номер ошибки, как в
{error_object}.errno. Не путать с
MP_ERROR_ERRCODE.
MP_ERROR_ERRCODE (0x05) (MP_UINT) Номер ошибки, определенный в
errcode.h, как в {error_object}.code,
который также можно получить с помощью C-функции
box_error_code(). Значение здесь будет тем
же, что и в младшей части значения Response-Code-Indicator.
MP_ERROR_FIELDS (0x06) (MP_MAPs) Дополнительные поля, зависящие от
типа ошибки. Например, если MP_ERROR_TYPE – "AccessDeniedError",
то MP_ERROR_FIELDS будет включать "object_type", "object_name",
"access_type". Это поле будет опущено в теле ответа, если
дополнительные поля отсутствуют.
Разработчикам клиентов и коннекторов следует убедиться, что неизвестные ключи map игнорируются, а также проверять добавление новых ключей в исходном файле Tarantool, где определено создание объекта ошибки. В версии 2.4.1 этот файл исходного кода называется mp_error.cc.
Например, в версии 2.4.1 или более поздней, при попытке создать дубликат
спейса с помощью
conn:eval([[box.schema.space.create('_space');]])
ответ сервера будет выглядеть следующим образом:
ce 00 00 00 88 MP_UINT = HEADER + BODY SIZE83 MP_MAP, size 3 (i.e. 3 items in header)00 Response-Code-Indicatorce 00 00 80 0a MP_UINT = hexadecimal 800a01 IPROTO_SYNCcf 00 00 00 00 00 00 00 05 MP_UINT = sync value05 IPROTO_SCHEMA_VERSIONce 00 00 00 4e MP_UINT = schema version value82 MP_MAP, size 231 IPROTO_ERROR_24bd 53 70 61 63 etc. MP_STR = "Space '_space' already exists"52 IPROTO_ERROR81 MP_MAP, size 100 MP_ERROR_STACK91 MP_ARRAY, size 186 MP_MAP, size 600 MP_ERROR_TYPEab 43 6c 69 65 6e 74 etc. MP_STR = "ClientError"02 MP_ERROR_LINEcd MP_UINT = line number01 MP_ERROR_FILEaa 01 b6 62 75 69 6c etc. MP_STR "builtin/box/schema.lua"03 MP_ERROR_MESSAGEbd 53 70 61 63 65 20 etc. MP_STR = Space.'_space'.already.exists"04 MP_ERROR_ERRNO00 MP_UINT = error number05 MP_ERROR_ERRCODE0a MP_UINT = error code ER_SPACE_EXISTS
Начиная с версии 2.10.0. Тип расширения MessagePack MP_EXT вместе с типом
расширения MP_DATETIME является заголовком для значений типа DATETIME.
Он создает контейнер с полезной нагрузкой размером 8 или 16 байт.
Тип MP_DATETIME равен 4.
Спецификация
MessagePack
определяет d7 как fixext с размером 8, а d8 – как fixext с
размером 16.
Поэтому представление datetime в MessagePack выглядит следующим образом:
<table class="tableblock frame-all grid-all stretch"><colgroup><col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;"><col style="width: 25%;"></colgroup><thead><tr><th class="tableblock halign-left valign-top">{decodeBase64(IE1QX0VYVCAgPSBkNy9kOA==)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IE1QX0RBVEVUSU1FICA9IDQ=)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IHNlY29uZHM=)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IG5zZWM7IHR6b2Zmc2V0OyAgdHppbmRleDs=)}</th></tr></thead></table>
Данные MessagePack содержат:
- Секунды (8 байт) как незакодированное 64-битное целое число со знаком, сохраненное в порядке little-endian.
- Необязательные поля (8 байт), если любое из них имеет ненулевое значение.
Поля включают
nsec,tzoffsetиtzindex, упакованные в порядке little-endian.
Подробнее о типе datetime см. подробности о типе поля datetime и описание модуля datetime.
Начиная с версии 2.10.0. Тип расширения MessagePack MP_EXT вместе с типом
расширения MP_INTERVAL является заголовком для значений типа INTERVAL.
Тип MP_INTERVAL равен 6.
Интервал сохраняется как вариант map с предопределенным количеством известных имен атрибутов. Если некоторые атрибуты не определены, они опускаются в формируемой полезной нагрузке.
Представление interval в MessagePack выглядит следующим образом:
<table class="tableblock frame-all grid-all stretch"><colgroup><col style="width: 14.2857%;"><col style="width: 42.8571%;"><col style="width: 14.2857%;"><col style="width: 28.5715%;"></colgroup><tbody><tr><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IE1QX0VYVA==)}</p></td><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IFNpemUgb2YgcGFja2VkIGludGVydmFs)}</p></td><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IE1QX0lOVEVSVkFM)}</p></td><td class="tableblock halign-left valign-top"><p class="tableblock">{decodeBase64(IFBhY2tlZEludGVydmFs)}</p></td></tr></tbody></table>
Упакованный интервал состоит из:
- Упакованного количества ненулевых полей.
- Упакованных ненулевых полей.
Каждое упакованное поле имеет следующую структуру:
<table class="tableblock frame-all grid-all stretch"><colgroup><col style="width: 33.3333%;"><col style="width: 66.6667%;"></colgroup><thead><tr><th class="tableblock halign-left valign-top">{decodeBase64(IGZpZWxkIElE)}</th><th class="tableblock halign-left valign-top">{decodeBase64(IGZpZWxkIHZhbHVl)}</th></tr></thead></table>
Количество определенных (ненулевых) полей может быть равно нулю. В этом случае упакованный интервал будет закодирован как целое число 0.
Список идентификаторов полей:
- 0 – год
- 1 – месяц
- 2 – неделя
- 3 – день
- 4 – час
- 5 – минута
- 6 – секунда
- 7 – наносекунда
- 8 – корректировка
Пример
Значение интервала 1 years, 200 months, -77 days кодируется следующим
образом:
tarantool> I = datetime.interval.new{year = 1, month = 200, day = -77}---...tarantool> I---- +1 years, 200 months, -77 days...tarantool> M = msgpack.encode(I)---...tarantool> M---- !!binary xwsGBAABAczIA9CzCAE=...tarantool> tohex = function(s) return (s:gsub('.', function(c) return string.format('%02X ', string.byte(c)) end)) end---...tarantool> tohex(M)---- 'C7 0B 06 04 00 01 01 CC C8 03 D0 B3 08 01 '...
Где:
- C7 – MP_EXT
- 0B – размер упакованного значения интервала (11 байт)
- 06 – тип MP_INTERVAL
- 04 – количество определенных полей
- 00 – идентификатор поля (год)
- 01 – упакованное значение
1 - 01 – идентификатор поля (месяц)
- CCC8 – упакованное значение
200 - 03 – идентификатор поля (день)
- D0B3 – упакованное значение
-77 - 08 – идентификатор поля (корректировка)
- 01 – упакованное значение
1(DT_LIMIT)
Подробнее о типе interval см. подробности о типе поля interval и описание модуля datetime.