Рейтинг@Mail.ru
Справочники / Детали реализации / Дополнительные типы MessagePack

Дополнительные типы MessagePack

Замечание

Документация находится в процессе перевода и может отставать от английской версии.

Дополнительные типы MessagePack

Tarantool использует предопределенные дополнительные типы MessagePack для представления некоторых специальных значений. Дополнительные типы включают MP_DECIMAL, MP_UUID,``MP_ERROR``, MP_DATETIME, and MP_INTERVAL. Эти типы требуют особого внимания со стороны разработчиков коннекторов, так как должны рассматриваться отдельно от типов MessagePack по умолчанию и корректно приводиться к типам языков программирования.

Тип MessagePack EXT MP_EXT вместе с типом расширения MP_DECIMAL является заголовком для значений типа DECIMAL.

MP_DECIMAL – это 1.

Спецификация MessagePack определяет два вида типов:

  • типы fixext 1/2/4/8/16 имеют фиксированную длину, поэтому длина не кодируется явно;
  • типы ext 8/16/32 требуют кодирования длины данных.

MP_EXP + (не обязательно) length подразумевает использование одного из этих типов.

Десятичное представление MessagePack выглядит следующим образом:

+--------+-------------------+------------+===============+
| MP_EXT | length (optional) | MP_DECIMAL | PackedDecimal |
+--------+-------------------+------------+===============+

Здесь length – это длина поля PackedDecimal, и оно имеет тип MP_UINT, когда кодируется явно (т.е. когда тип – ext 8/16/32).

PackedDecimal имеет следующую структуру:

 <--- длина в байтах -->
+-------+=============+
| scale |     BCD     |
+-------+=============+

Здесь scale – это либо MP_INT, либо MP_UINT.
scale = количество цифр после запятой.

BCD - это последовательность байтов, обозначающая десятичные цифры кодируемого числа (каждый байт имеет две десятичные цифры, каждая из которых кодируется с использованием 4-битных nibbles), поэтому 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 and 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 EXT MP_EXT вместе с типом расширения MP_UUID является заголовком для значений типа UUID. Доступно с версии 2.4.1.

MP_UUID – это 2.

Спецификация MessagePack определяет d8 как fixext с размером 16, а размер UUID всегда равен 16. Таким образом, представление UUID в MessagePack выглядит следующим образом:

+--------+------------+-----------------+
| MP_EXT | MP_UUID    | UuidValue       |
| = d8   | = 2        | = 16-byte value |
+--------+------------+-----------------+

The 16-byte value has 2 digits per byte. Typically, it consists of 11 fields, which are encoded as big-endian unsigned integers in the following order:

  • time_low (4 bytes)
  • time_mid (2 bytes)
  • time_hi_and_version (2 bytes)
  • clock_seq_hi_and_reserved (1 byte)
  • clock_seq_low (1 byte)
  • node[0], …, node[5] (1 byte each)

Некоторые функции в модуле 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, в ответах на ошибки содержится дополнительная информация, соответствующая описанию в разделе Бинарный протокол — ответы на ошибки. Это «совместимое» улучшение, потому что клиенты, которые ожидают ответы сервера старого образца, должны игнорировать компоненты ассоциативного массива, которые они не распознают. Обратите внимание, что константа IPROTO_ERROR в ./box/iproto_constants.h была 0x31, а теперь IPROTO_ERROR0x52, а IPROTO_ERROR_240x31.

MP_ERROR – это 3.

++=========================+============================+
||                         |                            |
||   0x31: IPROTO_ERROR_24 |   0x52: IPROTO_ERROR                                 |
|| MP_INT: MP_STRING       | MP_MAP: дополнительная информация  |
||                         |                            |
++=========================+============================+
                        MP_MAP

Дополнительная информация, большая часть которой также хранится в полях объекта ошибки:

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». При отсутствии дополнительных полей это поле будет пропущено в теле ответа.

Разработчики клиента и коннекторов должны убедиться, что неизвестные ключи ассоциативных массивов игнорируются, а также проверить наличие новых ключей в файле исходного кода 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 SIZE
83                              MP_MAP, size 3 (i.e. 3 items in header)
  00                              Response-Code-Indicator
  ce 00 00 80 0a                  MP_UINT = hexadecimal 800a
  01                              IPROTO_SYNC
  cf 00 00 00 00 00 00 00 05      MP_UINT = sync value
  05                              IPROTO_SCHEMA_VERSION
  ce 00 00 00 4e                  MP_UINT = schema version value
82                              MP_MAP, size 2
  31                              IPROTO_ERROR_24
  bd 53 70 61 63 etc.             MP_STR = "Space '_space' already exists"
  52                              IPROTO_ERROR
  81                              MP_MAP, size 1
    00                              MP_ERROR_STACK
    91                              MP_ARRAY, size 1
      86                              MP_MAP, size 6
        00                              MP_ERROR_TYPE
        ab 43 6c 69 65 6e 74 etc.       MP_STR = "ClientError"
        02                              MP_ERROR_LINE
        cd                              MP_UINT = line number
        01                              MP_ERROR_FILE
        aa 01 b6 62 75 69 6c etc.       MP_STR "builtin/box/schema.lua"
        03                              MP_ERROR_MESSAGE
        bd 53 70 61 63 65 20 etc.       MP_STR = Space.'_space'.already.exists"
        04                              MP_ERROR_ERRNO
        00                              MP_UINT = error number
        05                              MP_ERROR_ERRCODE
        0a                              MP_UINT = eror code ER_SPACE_EXISTS

Since version 2.10.0. The MessagePack EXT type MP_EXT together with the extension type MP_DATETIME is a header for values of the DATETIME type. It creates a container with a payload of 8 or 16 bytes.

MP_DATETIME type is 4.

The MessagePack specification defines d7 to mean fixext with size 8 or d8 to mean fixext with size 16.

So the datetime MessagePack representation looks like this:

+---------+----------------+==========+-----------------+
| MP_EXT  | MP_DATETIME    | seconds  | nsec; tzoffset; |
| = d7/d8 | = 4            |          | tzindex;        |
+---------+----------------+==========+-----------------+

MessagePack data contains:

  • Seconds (8 bytes) as an unencoded 64-bit signed integer stored in the little-endian order.
  • The optional fields (8 bytes), if any of them have a non-zero value. The fields include nsec, tzoffset, and tzindex packed in the little-endian order.

For more information about the datetime type, see datetime field type details and reference for the datetime module.

Since version 2.10.0. The MessagePack EXT type MP_EXT together with the extension type MP_INTERVAL is a header for values of the INTERVAL type.

MP_INTERVAL type is 6.

The interval is saved as a variant of a map with a predefined number of known attribute names. If some attributes are undefined, they are omitted from the generated payload.

The interval MessagePack representation looks like this:

+--------+-------------------------+-------------+----------------+
| MP_EXT | Size of packed interval | MP_INTERVAL | PackedInterval |
+--------+-------------------------+-------------+----------------+

Packed interval consists of:

  • Packed number of non-zero fields.
  • Packed non-null fields.

Each packed field has the following structure:

+----------+=====================+
| field ID |     field value     |
+----------+=====================+

The number of defined (non-null) fields can be zero. In this case, the packed interval will be encoded as integer 0.

List of the field IDs:

  • 0 – year
  • 1 – month
  • 2 – week
  • 3 – day
  • 4 – hour
  • 5 – minute
  • 6 – second
  • 7 – nanosecond
  • 8 – adjust

Example

Interval value 1 years, 200 months, -77 days is encoded in the following way:

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 '
...

Where:

  • C7 – MP_EXT
  • 0B – size of a packed interval value (11 bytes)
  • 06 – MP_INTERVAL type
  • 04 – number of defined fields
  • 00 – field ID (year)
  • 01 – packed value 1
  • 01 – field ID (month)
  • CCC8 – packed value 200
  • 03 – field ID (day)
  • D0B3 – packed value -77
  • 08 – field ID (adjust)
  • 01 – packed value 1 (DT_LIMIT)

For more information about the interval type, see interval field type details and description of the datetime module.