Project: Tarantool (App Definition and Development / Database)
File: 3328-wire_protocol.md

Status*: In progress Start date*: 04-04-2018 Authors*: Vladislav Shpilevoy @Gerold103 v.shpilevoy@tarantool.org, Konstantin Osipov @kostja kostja@tarantool.org, Alexey Gadzhiev @alg1973 alg1973@gmail.com Issues*: , , Tarantool wire protocol is a convention for encoding and sending execution results of SQL, Lua and C stored functions, DML (Data Manipulation Language), DDL (Data Definition Language), DQL (Data Query Language). The protocol is unified for all request types. For a single request multiple responses of different types can be sent. Tarantool wire protocol is called IProto, and is used by database connectors written in different languages and accessing the database via network. The protocol describes how to distinguish different message types and what data can be stored in each message. Tarantool response messages can be of the following kind: A response, which represents a single reply to a request or completes a chain of replies. There are two response types of this kind: OK and ERROR. Error response is trivial, and contains an error code and message. OK response may contain useful payload, such as result set rows or metadata. A response which is a part of a chain of replies - a so called PUSH-message. Multiple push messages can be sent in response to a single request, but they never indicate an end of reply stream: the end is always flagged by a response of the previous kind. In support of this response set 2 main challenges appear: How to unify responses; How to support multiple messages inside a single request. A response which contains a payload can contain either data or metadata, or both. If it is necessary to share the same response metadata among multiple PUSH messages, the metadata can be assigned a numeric identifier (PUSH ID) and referenced in the stream by this identifier. The metadata itself can contain: affected row count, last autoincrement column value, flags (such metadata is sent in response to DML statements such as INSERT/UPDATE/DELETE); column count, names and types (used to describe result set rows that follow). To understand how a single request can produce multiple responses, consider the stored procedure (do not pay attention to the syntax - it does not matter here): ```SQL FUNCTION mysqlfunc(a1, a2, a3, a4) BEGIN SELECT myluafunc(a1); SELECT * FROM table1; SELECT mycfunc(a2); INSERT INTO table1 VALUES (1, 2, 3); RETURN a4; END; ``` , where `myluafunc()` is the function, written in Lua and sending its own push-messages: ```Lua function myluafunc(arg) box.session.push(arg) return arg end ``` and `mycfunc()` is the function, written in C and returning some raw data: ```C int mycfunc(boxfunctionctx_t *ctx) { boxtuplet *tuple; / Fill the tuple with any data. / return boxreturntuple(ctx, tuple); } ``` Consider each statement: `SELECT FROM` can split a big result set into multiple messages; `SELECT myluafunc()` produces 2 messages: one is the push-message generated in `myluafunc` and another is the result of `SELECT` itself; `INSERT` creates 1 message with metadata; `RETURN` creates a final response message. Of course, some of messages, or even all of them can be batched and sent as a single TCP

In the next section we specify code names and messages used by the protocol. For the protocol details - code values, all header and body keys - see Tarantool . Tarantool response consists of a body and a header. Header is used to store response code and some internal metainfo such as schema version, request id (called sync in Tarantool). Body is used to store result data and request-dependent metainfo. There are 3 response codes in header: `IPROTO_OK` - the terminal response to a successful request; `IPROTO_ERROR | error code` - the terminal response to a request, which ended with an error. `IPROTO_PUSH` - non-final response. One request can generate multuple PUSH messages; `IPROTO_ERROR` response is trivial, and consists just of code and message. `IPROTOOK` and `IPROTOPUSH` have the same body format. But `IPROTO_OK` finalizes a request; `IPROTOPUSH` can have `IPROTOPUSH_ID` field in the header, that allows to build a chain of pushes with the same `ID`. Absense of this field means, that the push is not a part of a chain. All pushes which are part of the same chain (are identified by the same id) should share chain metadata. The common body structure: ``` +-+ | IPROTO_BODY: { | | IPROTO_METADATA: [ | | { | | IPROTOFIELDNAME: string, | | IPROTOFIELDTYPE: number, | | IPROTOFIELDFLAGS: number, | | }, | | ... | | ], | | | | IPROTOSQLINFO: { | | SQLINFOROW_COUNT: number, | | SQLINFOLAST_ID: number, | | ... | | }, | | | | IPROTO_DATA: [ | | tuple/scalar, | | ... | | ] | | } | +-+ ``` Consider, how different responses use the body, and how they can be distinguished. A non formatted response has only `IPROTO_DATA` key in a body. It is the result of Lua and C DML, DDL, DQL, stored procedures calls, push messages. Such response is never linked with the next or previous messages of the same request. A non formatted response with metadata has only `IPROTOSQLINFO` and it is always a result of DDL/DML executed via SQL. As well as the previous type, this response is independent from other messages in the stream. A formatted response always has `IPROTODATA`, and can have both `IPROTOSQLINFO` and `IPROTOMETADATA`. It is a result of SQL DQL (`SELECT`) or SQL DML (`INSERT`). The response can be part of a stream. The first message of the stream always contains `IPROTOMETADATA` in the body and sets `IPROTOPUSHID` in the header, should there be multiple messages sharing the same metadata. All other messasges in the stream contain `IPROTOPUSH_ID` with the same value. On the picture the state machine of the protocol is showed: For a call to `FUNCTION mysqlfunc` the following responses are sent: ``` / Push from my_lua_func(a1). / +-+ | HEADER: IPROTO_PUSH | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTO_DATA: [ a1 ] | | } | +-+ / Result of SELECT

/ +-+ | HEADER: IPROTO_PUSH | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTO_DATA: [ [ a1 ] ], | | IPROTO_METADATA: [ | | { / field name, type ... / } | | ] | | } | +-+ / First push of SELECT FROM table1. */ +-+ | HEADER: IPROTOPUSH, IPROTOPUSH_ID = <id1> | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTO_DATA: [ tuple1, tuple2, ... ] | | IPROTO_METADATA: [ | | { / field1 name, type ... / }, | | { / field2 name, type ... / }, | | ... | | ] | | } | +-+ / From second to last push. / +-+ | HEADER: IPROTOPUSH, IPROTOPUSH_ID = <id1> | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTO_DATA: [ tuple1, tuple2, ... ] | | } | +-+ / Result of SELECT my_c_func(a2). / +-+ | HEADER: IPROTO_PUSH | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTO_DATA: [ [ tuple ] ], | | IPROTO_METADATA: [ | | { / field name, type ... / } | | ] | | } | +-+ / Result of INSERT INTO table1 VALUES (1, 2, 3). / +-+ | HEADER: IPROTO_PUSH | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTOSQLINFO: { | | SQLINFOROW_COUNT: number, | | SQLINFOLAST_ID: number, | | } | | } | +-+ / Result of RETURN a4 / +-+ | HEADER: IPROTO_OK | +- - - - - - - - - - - - - - - - - - - - - - - + | BODY: { | | IPROTO_DATA: [ a4 ] | | } | +-+ ``` Another way to link pushes together exists, replacing `IPROTOPUSHID`. Pushes can be linked via flag in a header: `IPROTOFLAGISCHAIN`, that would be stored in `IPROTOFLAGS` header value. When a multiple messages form a chain, all of them except last one contain this flag. For example: ``` IPROTO_PUSH | IPROTOPUSH, ISCHAIN | +--IPROTOPUSH, ISCHAIN | +--IPROTOPUSH, ISCHAIN | +--IPROTO_PUSH | IPROTO_PUSH | ... | IPROTO_OK/ERROR ``` It is slightly simpler than `PUSH_ID`, but Does not enable to mix parts of different chains, if it will be needed sometimes; The last response does not contain `ISCHAIN`, but it is actually a part of chain. `ISCHAIN` can not be stored in the last response, because else it will not be distinguishable from the next chain. This can be solved by renaming `ISCHAIN` to `HASNEXTCHAIN` or something, but `PUSHID` seems better - it has no these problems, and is more scalable.