* [tarantool-patches] [PATCH 1/1] rfc: describe a Tarantool wire protocol @ 2018-04-04 21:22 Vladislav Shpilevoy 2018-04-05 8:25 ` [tarantool-patches] " Konstantin Osipov [not found] ` <CAFoyxqh0QqNBVr7tuFF_uoUw-CvBKOVA3FCyWvixikberOxP9w@mail.gmail.com> 0 siblings, 2 replies; 7+ messages in thread From: Vladislav Shpilevoy @ 2018-04-04 21:22 UTC (permalink / raw) To: tarantool-patches; +Cc: kostja, alg1973, Vladislav Shpilevoy --- Original: https://github.com/tarantool/tarantool/blob/sql-proto-rfc/doc/RFC/wire_protocol.md doc/RFC/wire_protocol.md | 214 +++++++++++++++++++++++++++++++++++++++++ doc/RFC/wire_protocol_img1.png | Bin 0 -> 48970 bytes 2 files changed, 214 insertions(+) create mode 100644 doc/RFC/wire_protocol.md create mode 100644 doc/RFC/wire_protocol_img1.png diff --git a/doc/RFC/wire_protocol.md b/doc/RFC/wire_protocol.md new file mode 100644 index 000000000..d1fc1329c --- /dev/null +++ b/doc/RFC/wire_protocol.md @@ -0,0 +1,214 @@ +# Tarantool Wire protocol + +* **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**: [#2677](https://github.com/tarantool/tarantool/issues/2677), [#2620](https://github.com/tarantool/tarantool/issues/2620), [#2618](https://github.com/tarantool/tarantool/issues/2618) + +## Summary + +Tarantool wire protocol is a convention how to encode and send results of execution of SQL, Lua and C stored functions, DML (Data Manipulation Language), DDL (Data Definition Language), DQL (Data Query Language) requests to remote clients via network. The protocol is unified for all request types. For a single request multiple responses of different types can be sent. + +## Background and motivation + +Tarantool wire protocol is called **IProto**, and is used by database connectors written on different languages and working on remote clients. The protocol describes how to distinguish different message types and what data can be stored in each message. Tarantool has the following response types: +* A response, that finalizes a request, and has just data - tuples array, or scalar values, or mixed. It has no any metadata. This response type incorporates results of any pure Lua and C calls including stored procedures, space and index methods. Such response is single per request; +* A response with just data, but with no request finalization - it is so called push-message. During single request execution multiple pushes can be sent, and they do not finalize the request - a client must be ready to receive more responses; +* A formatted response, that is sent on SQL DQL and does not finalize a request. Such response contains metadata with result set column names, types, flags etc; +* A response with metadata only, that is sent on SQL DDL/DML requests, and contains affected row count, last autoincrement column value, flags; +* A response with error message and code, that finalizes a request. + +In supporting this responses set 2 main challenges appear: +1. How to unify responses; +2. How to support multiple messages inside a single request. + +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 my_sql_func(a1, a2, a3, a4) BEGIN + SELECT my_lua_func(a1); + SELECT * FROM table1; + SELECT my_c_func(a2); + INSERT INTO table1 VALUES (1, 2, 3); + RETURN a4; +END +``` +, where `my_lua_func()` is the function, written in Lua and sending its own push-messages: +```Lua +function my_lua_func(arg) + box.session.push(arg) + return arg +end +``` +and `my_c_func()` is the function, written in C and returning some raw data: +```C +int +my_c_func(box_function_ctx_t *ctx) { + box_tuple_t *tuple; + /* Fill a tuple with any data. */ + return box_return_tuple(ctx, tuple); +} +``` +Consider each statement: +* `SELECT FROM` can split a big result set in multiple messages; +* `SELECT my_lua_func()` produces 2 messages: one is the push-message generated in `my_lua_func` 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 send as a single TCP packet, but it does not matter for the wire protocol. + +In the next section it is described, how the Tarantool wire protocol deals with this mess. + +For the protocol details - code values, all header and body keys - see Tarantool [website](tarantool.io). + +## Detailed design + +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. + +### Header + +There are 3 response codes in header: +* `IPROTO_OK` - the last response in a request, that is finished successfully; +* `IPROTO_CHUNK` - non-final response. One request can generate multuple chunk messages; +* `IPROTO_ERROR | error code` - the last response in a request, that is finished with an error. + +`IPROTO_ERROR` response is trivial, and consists just of code and message. It is no considered further. +`IPROTO_OK` and `IPROTO_CHUNK` have the same body format. The only exception between them is that `IPROTO_OK` finalizes the request. In the next subsection the body format is presented. + +### Body + +The common body structure: +``` ++----------------------------------------------+ +| IPROTO_BODY: { | +| IPROTO_METADATA: [ | +| { | +| IPROTO_FIELD_NAME: string, | +| IPROTO_FIELD_TYPE: number, | +| IPROTO_FIELD_FLAGS: number, | +| }, | +| ... | +| ], | +| | +| IPROTO_SQL_INFO: { | +| SQL_INFO_ROW_COUNT: number, | +| SQL_INFO_LAST_ID: number, | +| SQL_INFO_FLAGS: number, | +| ... | +| }, | +| | +| IPROTO_DATA: [ | +| tuple/scalar, | +| ... | +| ] | +| } | ++----------------------------------------------+ +``` +For a while the single `SQL_INFO_FLAGS` value is available: `SQL_INFO_HAS_NEXT_CHUNK` - a response having this flag means, that the current result set is not fully read - more responses are available. For example, it could be big `SELECT FROM` sent in multiple chunks. + +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 next or previous messages of the same request. + +_A non formatted response with metadata_ has only `IPROTO_SQL_INFO` and it is always result of DDL/DML executed via SQL. As well as the previous type, this response is all-independent. + +_A formatted response_ always has `IPROTO_DATA`, and can have both `IPROTO_SQL_INFO` and `IPROTO_METADATA`. It is always result of SQL DQL (`SELECT`). The response can be part of a continuous sequence of responses. A first message of the sequence always contains `IPROTO_METADATA`, while all non-last ones always contain `IPROTO_SQL_INFO` with `SQL_INFO_HAS_NEXT_CHUNK` flag. The last message has only `IPROTO_DATA` or nothing. + +On the picture the state machine of the protocol is showed: +![alt text](https://raw.githubusercontent.com/tarantool/tarantool/sql-proto-rfc/doc/RFC/wire_protocol_img1.png) + +For the `FUNCTION my_sql_func` call the following responses are sent: +``` +/* Push from my_lua_func(a1). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ a1 ] | +| } | ++----------------------------------------------+ + +/* Result of SELECT my_lua_func(a1). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ [ a1 ] ], | +| IPROTO_METADATA: [ | +| { /* field name, type ... */ } | +| ] | +| } | ++----------------------------------------------+ + +/* First chunk of SELECT * FROM table1. */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ tuple1, tuple2, ... ] | +| IPROTO_METADATA: [ | +| { /* field1 name, type ... */ }, | +| { /* field2 name, type ... */ }, | +| ... | +| ], | +| IPROTO_SQL_INFO: { | +| SQL_INFO_FLAGS: | +| SQL_INFO_HAS_NEXT_CHUNK | +| } | +| } | ++----------------------------------------------+ + + /* From second to next to last chunk. */ + +----------------------------------------------+ + | HEADER: IPROTO_CHUNK | + +- - - - - - - - - - - - - - - - - - - - - - - + + | BODY: { | + | IPROTO_DATA: [ tuple1, tuple2, ... ], | + | IPROTO_SQL_INFO: { | + | SQL_INFO_FLAGS: | + | SQL_INFO_HAS_NEXT_CHUNK | + | } | + | } | + +----------------------------------------------+ + + /* Last chunk. */ + +----------------------------------------------+ + | HEADER: IPROTO_CHUNK | + +- - - - - - - - - - - - - - - - - - - - - - - + + | BODY: { | + | IPROTO_DATA: [ tuple1, tuple2, ... ] | + | } | + +----------------------------------------------+ + +/* Result of SELECT my_c_func(a2). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ [ tuple ] ], | +| IPROTO_METADATA: [ | +| { /* field name, type ... */ } | +| ] | +| } | ++----------------------------------------------+ + +/* Result of INSERT INTO table1 VALUES (1, 2, 3). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_SQL_INFO: { | +| SQL_INFO_ROW_COUNT: number, | +| SQL_INFO_LAST_ID: number, | +| } | +| } | ++----------------------------------------------+ + +/* Result of RETURN a4 */ ++----------------------------------------------+ +| HEADER: IPROTO_OK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ a4 ] | +| } | ++----------------------------------------------+ +``` -- 2.14.3 (Apple Git-98) ^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: [PATCH 1/1] rfc: describe a Tarantool wire protocol 2018-04-04 21:22 [tarantool-patches] [PATCH 1/1] rfc: describe a Tarantool wire protocol Vladislav Shpilevoy @ 2018-04-05 8:25 ` Konstantin Osipov 2018-04-09 15:31 ` [tarantool-patches] " Vladislav Shpilevoy [not found] ` <CAFoyxqh0QqNBVr7tuFF_uoUw-CvBKOVA3FCyWvixikberOxP9w@mail.gmail.com> 1 sibling, 1 reply; 7+ messages in thread From: Konstantin Osipov @ 2018-04-05 8:25 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches, alg1973 * Vladislav Shpilevoy <v.shpilevoy@tarantool.org> [18/04/05 10:25]: I think HAS_NEXT_CHUNK should be part of header, not body. Otherwise the idea looks good to me, apart from the minor details (e.g. field names and exact format of each map). Let's get together one more time to finalize the spec after soliciting input from the community. > --- > Original: https://github.com/tarantool/tarantool/blob/sql-proto-rfc/doc/RFC/wire_protocol.md > > doc/RFC/wire_protocol.md | 214 +++++++++++++++++++++++++++++++++++++++++ > doc/RFC/wire_protocol_img1.png | Bin 0 -> 48970 bytes > 2 files changed, 214 insertions(+) > create mode 100644 doc/RFC/wire_protocol.md > create mode 100644 doc/RFC/wire_protocol_img1.png > > diff --git a/doc/RFC/wire_protocol.md b/doc/RFC/wire_protocol.md > new file mode 100644 > index 000000000..d1fc1329c > --- /dev/null > +++ b/doc/RFC/wire_protocol.md > @@ -0,0 +1,214 @@ > +# Tarantool Wire protocol > + > +* **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**: [#2677](https://github.com/tarantool/tarantool/issues/2677), [#2620](https://github.com/tarantool/tarantool/issues/2620), [#2618](https://github.com/tarantool/tarantool/issues/2618) > + > +## Summary > + > +Tarantool wire protocol is a convention how to encode and send results of execution of SQL, Lua and C stored functions, DML (Data Manipulation Language), DDL (Data Definition Language), DQL (Data Query Language) requests to remote clients via network. The protocol is unified for all request types. For a single request multiple responses of different types can be sent. > + > +## Background and motivation > + > +Tarantool wire protocol is called **IProto**, and is used by database connectors written on different languages and working on remote clients. The protocol describes how to distinguish different message types and what data can be stored in each message. Tarantool has the following response types: > +* A response, that finalizes a request, and has just data - tuples array, or scalar values, or mixed. It has no any metadata. This response type incorporates results of any pure Lua and C calls including stored procedures, space and index methods. Such response is single per request; > +* A response with just data, but with no request finalization - it is so called push-message. During single request execution multiple pushes can be sent, and they do not finalize the request - a client must be ready to receive more responses; > +* A formatted response, that is sent on SQL DQL and does not finalize a request. Such response contains metadata with result set column names, types, flags etc; > +* A response with metadata only, that is sent on SQL DDL/DML requests, and contains affected row count, last autoincrement column value, flags; > +* A response with error message and code, that finalizes a request. > + > +In supporting this responses set 2 main challenges appear: > +1. How to unify responses; > +2. How to support multiple messages inside a single request. > + > +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 my_sql_func(a1, a2, a3, a4) BEGIN > + SELECT my_lua_func(a1); > + SELECT * FROM table1; > + SELECT my_c_func(a2); > + INSERT INTO table1 VALUES (1, 2, 3); > + RETURN a4; > +END > +``` > +, where `my_lua_func()` is the function, written in Lua and sending its own push-messages: > +```Lua > +function my_lua_func(arg) > + box.session.push(arg) > + return arg > +end > +``` > +and `my_c_func()` is the function, written in C and returning some raw data: > +```C > +int > +my_c_func(box_function_ctx_t *ctx) { > + box_tuple_t *tuple; > + /* Fill a tuple with any data. */ > + return box_return_tuple(ctx, tuple); > +} > +``` > +Consider each statement: > +* `SELECT FROM` can split a big result set in multiple messages; > +* `SELECT my_lua_func()` produces 2 messages: one is the push-message generated in `my_lua_func` 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 send as a single TCP packet, but it does not matter for the wire protocol. > + > +In the next section it is described, how the Tarantool wire protocol deals with this mess. > + > +For the protocol details - code values, all header and body keys - see Tarantool [website](tarantool.io). > + > +## Detailed design > + > +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. > + > +### Header > + > +There are 3 response codes in header: > +* `IPROTO_OK` - the last response in a request, that is finished successfully; > +* `IPROTO_CHUNK` - non-final response. One request can generate multuple chunk messages; > +* `IPROTO_ERROR | error code` - the last response in a request, that is finished with an error. > + > +`IPROTO_ERROR` response is trivial, and consists just of code and message. It is no considered further. > +`IPROTO_OK` and `IPROTO_CHUNK` have the same body format. The only exception between them is that `IPROTO_OK` finalizes the request. In the next subsection the body format is presented. > + > +### Body > + > +The common body structure: > +``` > ++----------------------------------------------+ > +| IPROTO_BODY: { | > +| IPROTO_METADATA: [ | > +| { | > +| IPROTO_FIELD_NAME: string, | > +| IPROTO_FIELD_TYPE: number, | > +| IPROTO_FIELD_FLAGS: number, | > +| }, | > +| ... | > +| ], | > +| | > +| IPROTO_SQL_INFO: { | > +| SQL_INFO_ROW_COUNT: number, | > +| SQL_INFO_LAST_ID: number, | > +| SQL_INFO_FLAGS: number, | > +| ... | > +| }, | > +| | > +| IPROTO_DATA: [ | > +| tuple/scalar, | > +| ... | > +| ] | > +| } | > ++----------------------------------------------+ > +``` > +For a while the single `SQL_INFO_FLAGS` value is available: `SQL_INFO_HAS_NEXT_CHUNK` - a response having this flag means, that the current result set is not fully read - more responses are available. For example, it could be big `SELECT FROM` sent in multiple chunks. > + > +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 next or previous messages of the same request. > + > +_A non formatted response with metadata_ has only `IPROTO_SQL_INFO` and it is always result of DDL/DML executed via SQL. As well as the previous type, this response is all-independent. > + > +_A formatted response_ always has `IPROTO_DATA`, and can have both `IPROTO_SQL_INFO` and `IPROTO_METADATA`. It is always result of SQL DQL (`SELECT`). The response can be part of a continuous sequence of responses. A first message of the sequence always contains `IPROTO_METADATA`, while all non-last ones always contain `IPROTO_SQL_INFO` with `SQL_INFO_HAS_NEXT_CHUNK` flag. The last message has only `IPROTO_DATA` or nothing. > + > +On the picture the state machine of the protocol is showed: > +![alt text](https://raw.githubusercontent.com/tarantool/tarantool/sql-proto-rfc/doc/RFC/wire_protocol_img1.png) > + > +For the `FUNCTION my_sql_func` call the following responses are sent: > +``` > +/* Push from my_lua_func(a1). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ a1 ] | > +| } | > ++----------------------------------------------+ > + > +/* Result of SELECT my_lua_func(a1). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ [ a1 ] ], | > +| IPROTO_METADATA: [ | > +| { /* field name, type ... */ } | > +| ] | > +| } | > ++----------------------------------------------+ > + > +/* First chunk of SELECT * FROM table1. */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ tuple1, tuple2, ... ] | > +| IPROTO_METADATA: [ | > +| { /* field1 name, type ... */ }, | > +| { /* field2 name, type ... */ }, | > +| ... | > +| ], | > +| IPROTO_SQL_INFO: { | > +| SQL_INFO_FLAGS: | > +| SQL_INFO_HAS_NEXT_CHUNK | > +| } | > +| } | > ++----------------------------------------------+ > + > + /* From second to next to last chunk. */ > + +----------------------------------------------+ > + | HEADER: IPROTO_CHUNK | > + +- - - - - - - - - - - - - - - - - - - - - - - + > + | BODY: { | > + | IPROTO_DATA: [ tuple1, tuple2, ... ], | > + | IPROTO_SQL_INFO: { | > + | SQL_INFO_FLAGS: | > + | SQL_INFO_HAS_NEXT_CHUNK | > + | } | > + | } | > + +----------------------------------------------+ > + > + /* Last chunk. */ > + +----------------------------------------------+ > + | HEADER: IPROTO_CHUNK | > + +- - - - - - - - - - - - - - - - - - - - - - - + > + | BODY: { | > + | IPROTO_DATA: [ tuple1, tuple2, ... ] | > + | } | > + +----------------------------------------------+ > + > +/* Result of SELECT my_c_func(a2). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ [ tuple ] ], | > +| IPROTO_METADATA: [ | > +| { /* field name, type ... */ } | > +| ] | > +| } | > ++----------------------------------------------+ > + > +/* Result of INSERT INTO table1 VALUES (1, 2, 3). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_SQL_INFO: { | > +| SQL_INFO_ROW_COUNT: number, | > +| SQL_INFO_LAST_ID: number, | > +| } | > +| } | > ++----------------------------------------------+ > + > +/* Result of RETURN a4 */ > ++----------------------------------------------+ > +| HEADER: IPROTO_OK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ a4 ] | > +| } | > ++----------------------------------------------+ > +``` > -- > 2.14.3 (Apple Git-98) > -- Konstantin Osipov, Moscow, Russia, +7 903 626 22 32 http://tarantool.io - www.twitter.com/kostja_osipov ^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] [PATCH 1/1] rfc: describe a Tarantool wire protocol 2018-04-05 8:25 ` [tarantool-patches] " Konstantin Osipov @ 2018-04-09 15:31 ` Vladislav Shpilevoy 2018-06-28 11:45 ` [tarantool-patches] " Konstantin Osipov 0 siblings, 1 reply; 7+ messages in thread From: Vladislav Shpilevoy @ 2018-04-09 15:31 UTC (permalink / raw) To: tarantool-patches; +Cc: kostja Part of #3328 --- Original: https://github.com/tarantool/tarantool/blob/gh-3328-new-iproto/doc/rfc/3328-wire_protocol.md doc/rfc/3328-wire_protocol.md | 232 ++++++++++++++++++++++++++++++++++++ doc/rfc/3328-wire_protocol_img1.png | Bin 0 -> 50820 bytes 2 files changed, 232 insertions(+) create mode 100644 doc/rfc/3328-wire_protocol.md create mode 100644 doc/rfc/3328-wire_protocol_img1.png diff --git a/doc/rfc/3328-wire_protocol.md b/doc/rfc/3328-wire_protocol.md new file mode 100644 index 000000000..f6aab0b16 --- /dev/null +++ b/doc/rfc/3328-wire_protocol.md @@ -0,0 +1,232 @@ +# Tarantool Wire protocol + +* **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**: [#2677](https://github.com/tarantool/tarantool/issues/2677), [#2620](https://github.com/tarantool/tarantool/issues/2620), [#2618](https://github.com/tarantool/tarantool/issues/2618) + +## Summary + +Tarantool wire protocol is a convention how to encode and send results of execution of SQL, Lua and C stored functions, DML (Data Manipulation Language), DDL (Data Definition Language), DQL (Data Query Language) requests to remote clients via network. The protocol is unified for all request types. For a single request multiple responses of different types can be sent. + +## Background and motivation + +Tarantool wire protocol is called **IProto**, and is used by database connectors written on different languages and working on remote clients. The protocol describes how to distinguish different message types and what data can be stored in each message. Tarantool has the following response types: +* A response, that finalizes a request, and has just data - tuples array, or scalar values, or mixed. It has no any metadata. This response type incorporates results of any pure Lua and C calls including stored procedures, space and index methods. Such response is single per request; +* A response with just data, but with no request finalization - it is so called push-message. During single request execution multiple pushes can be sent, and they do not finalize the request - a client must be ready to receive more responses; +* A formatted response, that is sent on SQL DQL and does not finalize a request. Such response contains metadata with result set column names, types, flags etc; +* A response with metadata only, that is sent on SQL DDL/DML requests, and contains affected row count, last autoincrement column value, flags; +* A response with error message and code, that finalizes a request. + +In supporting this responses set 2 main challenges appear: +1. How to unify responses; +2. How to support multiple messages inside a single request. + +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 my_sql_func(a1, a2, a3, a4) BEGIN + SELECT my_lua_func(a1); + SELECT * FROM table1; + SELECT my_c_func(a2); + INSERT INTO table1 VALUES (1, 2, 3); + RETURN a4; +END +``` +, where `my_lua_func()` is the function, written in Lua and sending its own push-messages: +```Lua +function my_lua_func(arg) + box.session.push(arg) + return arg +end +``` +and `my_c_func()` is the function, written in C and returning some raw data: +```C +int +my_c_func(box_function_ctx_t *ctx) { + box_tuple_t *tuple; + /* Fill a tuple with any data. */ + return box_return_tuple(ctx, tuple); +} +``` +Consider each statement: +* `SELECT FROM` can split a big result set in multiple messages; +* `SELECT my_lua_func()` produces 2 messages: one is the push-message generated in `my_lua_func` 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 send as a single TCP packet, but it does not matter for the wire protocol. + +In the next section it is described, how the Tarantool wire protocol deals with this mess. + +For the protocol details - code values, all header and body keys - see Tarantool [website](tarantool.io). + +## Detailed design + +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. + +### Header + +There are 3 response codes in header: +* `IPROTO_OK` - the last response in a request, that is finished successfully; +* `IPROTO_CHUNK` - non-final response. One request can generate multuple chunk messages; +* `IPROTO_ERROR | error code` - the last response in a request, that is finished with an error. + +`IPROTO_ERROR` response is trivial, and consists just of code and message. It is no considered further. +`IPROTO_OK` and `IPROTO_CHUNK` have the same body format. But +1. `IPROTO_OK` finalizes a request; +2. `IPROTO_CHUNK` can have `IPROTO_CHUNK_ID` field in the header, that allows to build a chain of chunks with the same `ID`. Absense of this field means, that the chunk is not a part of a chain. + +### Body + +The common body structure: +``` ++----------------------------------------------+ +| IPROTO_BODY: { | +| IPROTO_METADATA: [ | +| { | +| IPROTO_FIELD_NAME: string, | +| IPROTO_FIELD_TYPE: number, | +| IPROTO_FIELD_FLAGS: number, | +| }, | +| ... | +| ], | +| | +| IPROTO_SQL_INFO: { | +| SQL_INFO_ROW_COUNT: number, | +| SQL_INFO_LAST_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 next or previous messages of the same request. + +_A non formatted response with metadata_ has only `IPROTO_SQL_INFO` and it is always result of DDL/DML executed via SQL. As well as the previous type, this response is all-independent. + +_A formatted response_ always has `IPROTO_DATA`, and can have both `IPROTO_SQL_INFO` and `IPROTO_METADATA`. It can be result of SQL DQL (`SELECT`) or SQL DML (`INSERT` with human readable response like `NN rows inserted/updated/deleted`). The response can be part of a continuous sequence of responses. A first message of the sequence always contains `IPROTO_METADATA` in the body and `IPROTO_CHUNK_ID` in the header, if there are multiple responses. All sequence chunks always contain `IPROTO_CHUNK_ID` with the same value. + +On the picture the state machine of the protocol is showed: +![alt text](https://raw.githubusercontent.com/tarantool/tarantool/gh-3328-new-iproto/doc/rfc/3328-wire_protocol_img1.png) + +For the `FUNCTION my_sql_func` call the following responses are sent: +``` +/* Push from my_lua_func(a1). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ a1 ] | +| } | ++----------------------------------------------+ + +/* Result of SELECT my_lua_func(a1). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ [ a1 ] ], | +| IPROTO_METADATA: [ | +| { /* field name, type ... */ } | +| ] | +| } | ++----------------------------------------------+ + +/* First chunk of SELECT * FROM table1. */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK, IPROTO_CHUNK_ID = <id1>| ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ tuple1, tuple2, ... ] | +| IPROTO_METADATA: [ | +| { /* field1 name, type ... */ }, | +| { /* field2 name, type ... */ }, | +| ... | +| ] | +| } | ++----------------------------------------------+ + + /* From second to next to last chunk. */ + +----------------------------------------------+ + | HEADER: IPROTO_CHUNK, IPROTO_CHUNK_ID = <id1>| + +- - - - - - - - - - - - - - - - - - - - - - - + + | BODY: { | + | IPROTO_DATA: [ tuple1, tuple2, ... ] | + | } | + +----------------------------------------------+ + + /* Last chunk. */ + +----------------------------------------------+ + | HEADER: IPROTO_CHUNK, IPROTO_CHUNK_ID = <id1>| + +- - - - - - - - - - - - - - - - - - - - - - - + + | BODY: { | + | IPROTO_DATA: [ tuple1, tuple2, ... ] | + | } | + +----------------------------------------------+ + +/* Result of SELECT my_c_func(a2). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ [ tuple ] ], | +| IPROTO_METADATA: [ | +| { /* field name, type ... */ } | +| ] | +| } | ++----------------------------------------------+ + +/* Result of INSERT INTO table1 VALUES (1, 2, 3). */ ++----------------------------------------------+ +| HEADER: IPROTO_CHUNK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_SQL_INFO: { | +| SQL_INFO_ROW_COUNT: number, | +| SQL_INFO_LAST_ID: number, | +| } | +| } | ++----------------------------------------------+ + +/* Result of RETURN a4 */ ++----------------------------------------------+ +| HEADER: IPROTO_OK | ++- - - - - - - - - - - - - - - - - - - - - - - + +| BODY: { | +| IPROTO_DATA: [ a4 ] | +| } | ++----------------------------------------------+ +``` + +## Rationale and alternatives + +Another way to link chunks together exists, replacing `IPROTO_CHUNK_ID`. +Chunks can be linked via flag in a header: `IPROTO_FLAG_IS_CHAIN`, that would be stored in `IPROTO_FLAGS` header value. When a multiple messages form a chain, all of them except last one contain this flag. For example: +``` +IPROTO_CHUNK + | +IPROTO_CHUNK, IS_CHAIN + | + +--IPROTO_CHUNK, IS_CHAIN + | + +--IPROTO_CHUNK, IS_CHAIN + | + +--IPROTO_CHUNK + | +IPROTO_CHUNK + | +... + | +IPROTO_OK/ERROR +``` + +It is slightly simpler than `CHAIN_ID`, but +1. Does not enable to mix parts of different chains, if it will be needed sometimes; +2. The last response does not contain `IS_CHAIN`, but it is actually a part of chain. `IS_CHAIN` 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 `IS_CHAIN` to `HAS_NEXT_CHAIN` or something, but `CHAIN_ID` seems better - it has no these problems, and is more scalable. diff --git a/doc/rfc/3328-wire_protocol_img1.png b/doc/rfc/3328-wire_protocol_img1.png new file mode 100644 index 0000000000000000000000000000000000000000..714a06ba10574285f43d9569fb21bd2815006644 GIT binary patch literal 50820 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 literal 0 HcmV?d00001 -- 2.14.3 (Apple Git-98) ^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: [PATCH 1/1] rfc: describe a Tarantool wire protocol 2018-04-09 15:31 ` [tarantool-patches] " Vladislav Shpilevoy @ 2018-06-28 11:45 ` Konstantin Osipov 0 siblings, 0 replies; 7+ messages in thread From: Konstantin Osipov @ 2018-06-28 11:45 UTC (permalink / raw) To: Vladislav Shpilevoy; +Cc: tarantool-patches * Vladislav Shpilevoy <v.shpilevoy@tarantool.org> [18/04/09 18:34]: > Part of #3328 Hi, All the design decisions are OK. I made some edits and renames, rebased the branch to the latest 1.10 and pushed. If you're ok with my edits, I will squash and merge. I haven't checked the wire diagram, it may need some updates as well to follow my renames -please check and do. -- Konstantin Osipov, Moscow, Russia, +7 903 626 22 32 http://tarantool.io - www.twitter.com/kostja_osipov ^ permalink raw reply [flat|nested] 7+ messages in thread
[parent not found: <CAFoyxqh0QqNBVr7tuFF_uoUw-CvBKOVA3FCyWvixikberOxP9w@mail.gmail.com>]
* [tarantool-patches] Re: Fwd: [PATCH 1/1] rfc: describe a Tarantool wire protocol [not found] ` <CAFoyxqh0QqNBVr7tuFF_uoUw-CvBKOVA3FCyWvixikberOxP9w@mail.gmail.com> @ 2018-04-05 9:01 ` Алексей Гаджиев 2018-04-05 9:37 ` Vladislav Shpilevoy 2018-04-05 10:03 ` Konstantin Osipov 0 siblings, 2 replies; 7+ messages in thread From: Алексей Гаджиев @ 2018-04-05 9:01 UTC (permalink / raw) To: tarantool-patches; +Cc: Konstantin Osipov, Vladislav Shpilevoy [-- Attachment #1: Type: text/plain, Size: 14565 bytes --] Looks cool! Now we can send chunked data! I have one question: Is IPROTO_OK mandatory packet in the response messages sequence? How can I detect if next push/result_set available if previous consists only of one chunk without SQL_INFO_HAS_NEXT_CHUNK? With best regards, Alex >Четверг, 5 апреля 2018, 9:32 +03:00 от Alexey Gadzhiev <alg1973@gmail.com>: > > >---------- Forwarded message ---------- >From: Vladislav Shpilevoy < v.shpilevoy@tarantool.org > >Date: Thu, Apr 5, 2018 at 12:22 AM >Subject: [PATCH 1/1] rfc: describe a Tarantool wire protocol >To: tarantool-patches@freelists.org >Cc: kostja@tarantool.org , alg1973@gmail.com , Vladislav Shpilevoy < v.shpilevoy@tarantool.org > > > >--- >Original: https://github.com/tarantool/tarantool/blob/sql-proto-rfc/doc/RFC/wire_protocol.md > > doc/RFC/ wire_protocol.md | 214 +++++++++++++++++++++++++++++++++++++++++ > doc/RFC/wire_protocol_img1.png | Bin 0 -> 48970 bytes > 2 files changed, 214 insertions(+) > create mode 100644 doc/RFC/ wire_protocol.md > create mode 100644 doc/RFC/wire_protocol_img1.png > >diff --git a/doc/RFC/ wire_protocol.md b/doc/RFC/ wire_protocol.md >new file mode 100644 >index 000000000..d1fc1329c >--- /dev/null >+++ b/doc/RFC/ wire_protocol.md >@@ -0,0 +1,214 @@ >+# Tarantool Wire protocol >+ >+* **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**: [#2677]( https://github.com/tarantool/tarantool/issues/2677 ), [#2620]( https://github.com/tarantool/tarantool/issues/2620 ), [#2618]( https://github.com/tarantool/tarantool/issues/2618 ) >+ >+## Summary >+ >+Tarantool wire protocol is a convention how to encode and send results of execution of SQL, Lua and C stored functions, DML (Data Manipulation Language), DDL (Data Definition Language), DQL (Data Query Language) requests to remote clients via network. The protocol is unified for all request types. For a single request multiple responses of different types can be sent. >+ >+## Background and motivation >+ >+Tarantool wire protocol is called **IProto**, and is used by database connectors written on different languages and working on remote clients. The protocol describes how to distinguish different message types and what data can be stored in each message. Tarantool has the following response types: >+* A response, that finalizes a request, and has just data - tuples array, or scalar values, or mixed. It has no any metadata. This response type incorporates results of any pure Lua and C calls including stored procedures, space and index methods. Such response is single per request; >+* A response with just data, but with no request finalization - it is so called push-message. During single request execution multiple pushes can be sent, and they do not finalize the request - a client must be ready to receive more responses; >+* A formatted response, that is sent on SQL DQL and does not finalize a request. Such response contains metadata with result set column names, types, flags etc; >+* A response with metadata only, that is sent on SQL DDL/DML requests, and contains affected row count, last autoincrement column value, flags; >+* A response with error message and code, that finalizes a request. >+ >+In supporting this responses set 2 main challenges appear: >+1. How to unify responses; >+2. How to support multiple messages inside a single request. >+ >+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 my_sql_func(a1, a2, a3, a4) BEGIN >+ SELECT my_lua_func(a1); >+ SELECT * FROM table1; >+ SELECT my_c_func(a2); >+ INSERT INTO table1 VALUES (1, 2, 3); >+ RETURN a4; >+END >+``` >+, where `my_lua_func()` is the function, written in Lua and sending its own push-messages: >+```Lua >+function my_lua_func(arg) >+ box.session.push(arg) >+ return arg >+end >+``` >+and `my_c_func()` is the function, written in C and returning some raw data: >+```C >+int >+my_c_func(box_function_ctx_t *ctx) { >+ box_tuple_t *tuple; >+ /* Fill a tuple with any data. */ >+ return box_return_tuple(ctx, tuple); >+} >+``` >+Consider each statement: >+* `SELECT FROM` can split a big result set in multiple messages; >+* `SELECT my_lua_func()` produces 2 messages: one is the push-message generated in `my_lua_func` 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 send as a single TCP packet, but it does not matter for the wire protocol. >+ >+In the next section it is described, how the Tarantool wire protocol deals with this mess. >+ >+For the protocol details - code values, all header and body keys - see Tarantool [website]( tarantool.io ). >+ >+## Detailed design >+ >+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. >+ >+### Header >+ >+There are 3 response codes in header: >+* `IPROTO_OK` - the last response in a request, that is finished successfully; >+* `IPROTO_CHUNK` - non-final response. One request can generate multuple chunk messages; >+* `IPROTO_ERROR | error code` - the last response in a request, that is finished with an error. >+ >+`IPROTO_ERROR` response is trivial, and consists just of code and message. It is no considered further. >+`IPROTO_OK` and `IPROTO_CHUNK` have the same body format. The only exception between them is that `IPROTO_OK` finalizes the request. In the next subsection the body format is presented. >+ >+### Body >+ >+The common body structure: >+``` >++----------------------------------------------+ >+| IPROTO_BODY: { | >+| IPROTO_METADATA: [ | >+| { | >+| IPROTO_FIELD_NAME: string, | >+| IPROTO_FIELD_TYPE: number, | >+| IPROTO_FIELD_FLAGS: number, | >+| }, | >+| ... | >+| ], | >+| | >+| IPROTO_SQL_INFO: { | >+| SQL_INFO_ROW_COUNT: number, | >+| SQL_INFO_LAST_ID: number, | >+| SQL_INFO_FLAGS: number, | >+| ... | >+| }, | >+| | >+| IPROTO_DATA: [ | >+| tuple/scalar, | >+| ... | >+| ] | >+| } | >++----------------------------------------------+ >+``` >+For a while the single `SQL_INFO_FLAGS` value is available: `SQL_INFO_HAS_NEXT_CHUNK` - a response having this flag means, that the current result set is not fully read - more responses are available. For example, it could be big `SELECT FROM` sent in multiple chunks. >+ >+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 next or previous messages of the same request. >+ >+_A non formatted response with metadata_ has only `IPROTO_SQL_INFO` and it is always result of DDL/DML executed via SQL. As well as the previous type, this response is all-independent. >+ >+_A formatted response_ always has `IPROTO_DATA`, and can have both `IPROTO_SQL_INFO` and `IPROTO_METADATA`. It is always result of SQL DQL (`SELECT`). The response can be part of a continuous sequence of responses. A first message of the sequence always contains `IPROTO_METADATA`, while all non-last ones always contain `IPROTO_SQL_INFO` with `SQL_INFO_HAS_NEXT_CHUNK` flag. The last message has only `IPROTO_DATA` or nothing. >+ >+On the picture the state machine of the protocol is showed: >+![alt text]( https://raw.githubusercontent.com/tarantool/tarantool/sql-proto-rfc/doc/RFC/wire_protocol_img1.png ) >+ >+For the `FUNCTION my_sql_func` call the following responses are sent: >+``` >+/* Push from my_lua_func(a1). */ >++----------------------------------------------+ >+| HEADER: IPROTO_CHUNK | >++- - - - - - - - - - - - - - - - - - - - - - - + >+| BODY: { | >+| IPROTO_DATA: [ a1 ] | >+| } | >++----------------------------------------------+ >+ >+/* Result of SELECT my_lua_func(a1). */ >++----------------------------------------------+ >+| HEADER: IPROTO_CHUNK | >++- - - - - - - - - - - - - - - - - - - - - - - + >+| BODY: { | >+| IPROTO_DATA: [ [ a1 ] ], | >+| IPROTO_METADATA: [ | >+| { /* field name, type ... */ } | >+| ] | >+| } | >++----------------------------------------------+ >+ >+/* First chunk of SELECT * FROM table1. */ >++----------------------------------------------+ >+| HEADER: IPROTO_CHUNK | >++- - - - - - - - - - - - - - - - - - - - - - - + >+| BODY: { | >+| IPROTO_DATA: [ tuple1, tuple2, ... ] | >+| IPROTO_METADATA: [ | >+| { /* field1 name, type ... */ }, | >+| { /* field2 name, type ... */ }, | >+| ... | >+| ], | >+| IPROTO_SQL_INFO: { | >+| SQL_INFO_FLAGS: | >+| SQL_INFO_HAS_NEXT_CHUNK | >+| } | >+| } | >++----------------------------------------------+ >+ >+ /* From second to next to last chunk. */ >+ +----------------------------------------------+ >+ | HEADER: IPROTO_CHUNK | >+ +- - - - - - - - - - - - - - - - - - - - - - - + >+ | BODY: { | >+ | IPROTO_DATA: [ tuple1, tuple2, ... ], | >+ | IPROTO_SQL_INFO: { | >+ | SQL_INFO_FLAGS: | >+ | SQL_INFO_HAS_NEXT_CHUNK | >+ | } | >+ | } | >+ +----------------------------------------------+ >+ >+ /* Last chunk. */ >+ +----------------------------------------------+ >+ | HEADER: IPROTO_CHUNK | >+ +- - - - - - - - - - - - - - - - - - - - - - - + >+ | BODY: { | >+ | IPROTO_DATA: [ tuple1, tuple2, ... ] | >+ | } | >+ +----------------------------------------------+ >+ >+/* Result of SELECT my_c_func(a2). */ >++----------------------------------------------+ >+| HEADER: IPROTO_CHUNK | >++- - - - - - - - - - - - - - - - - - - - - - - + >+| BODY: { | >+| IPROTO_DATA: [ [ tuple ] ], | >+| IPROTO_METADATA: [ | >+| { /* field name, type ... */ } | >+| ] | >+| } | >++----------------------------------------------+ >+ >+/* Result of INSERT INTO table1 VALUES (1, 2, 3). */ >++----------------------------------------------+ >+| HEADER: IPROTO_CHUNK | >++- - - - - - - - - - - - - - - - - - - - - - - + >+| BODY: { | >+| IPROTO_SQL_INFO: { | >+| SQL_INFO_ROW_COUNT: number, | >+| SQL_INFO_LAST_ID: number, | >+| } | >+| } | >++----------------------------------------------+ >+ >+/* Result of RETURN a4 */ >++----------------------------------------------+ >+| HEADER: IPROTO_OK | >++- - - - - - - - - - - - - - - - - - - - - - - + >+| BODY: { | >+| IPROTO_DATA: [ a4 ] | >+| } | >++----------------------------------------------+ >+``` >-- >2.14.3 (Apple Git-98) > > -- [-- Attachment #2: Type: text/html, Size: 23520 bytes --] ^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: Fwd: [PATCH 1/1] rfc: describe a Tarantool wire protocol 2018-04-05 9:01 ` [tarantool-patches] Re: Fwd: " Алексей Гаджиев @ 2018-04-05 9:37 ` Vladislav Shpilevoy 2018-04-05 10:03 ` Konstantin Osipov 1 sibling, 0 replies; 7+ messages in thread From: Vladislav Shpilevoy @ 2018-04-05 9:37 UTC (permalink / raw) To: Алексей Гаджиев, tarantool-patches Cc: Konstantin Osipov 05.04.2018 12:01, Алексей Гаджиев (Redacted sender alexey.gadzhiev for DMARC) пишет: > Looks cool! Now we can send chunked data! > > I have one question: > Is IPROTO_OK mandatory packet in the response messages sequence? Yes, IPROTO_OK is mandatory. > How can I detect if next push/result_set available if previous > consists only of one chunk without SQL_INFO_HAS_NEXT_CHUNK? See the state machine picture - next chunk is available, if in the header the response type is IPROTO_CHUNK. SQL_INFO_HAS_NEXT_CHUNK just means, that the next chunk is logically linked with the current. And I have 2 better ideas instead of the 'has_next_chunk' flag: 1) Lets instead of SQL_INFO_HAS_NEXT_CHUNK, that is placed in body, introduce a header flag: IPROTO_CHUNK_IS_CHAIN. When a several chunks are logically linked (for example, they are parts of the same result set), all of them, except last, contains this flag. I think, that 'Chain' term is more easy to understand, than 'HAS_NEXT_CHUNK'. Moreover, HAS_NEXT_CHUNK can be false, but a client must read more responses, if the message does not contain IPROTO_OK - it confuses. Actually IPROTO_CHUNK in the header is the single sign, that more responses are available. The sample of responses: IPROTO_CHUNK | IPROTO_CHUNK, IS_CHAIN | +--IPROTO_CHUNK, IS_CHAIN | +--IPROTO_CHUNK, IS_CHAIN | +--IPROTO_CHUNK | IPROTO_CHUNK | ... | IPROTO_OK/ERROR 2) The similar idea - instead of IPROTO_CHUNK_IS_CHAIN lest introduce IPROTO_CHAIN_ID - it is an identifier, that is unique for each chunks sequence. And I like this variant most, because in a future it can be extended to allow mixing different chains. The sample of responses: IPROTO_CHUNK | IPROTO_CHUNK, CHAIN_ID = 1 | +--IPROTO_CHUNK, CHAIN_ID = 1 | +--IPROTO_CHUNK, CHAIN_ID = 1 | IPROTO_CHUNK | IPROTO_CHUNK, CHAIN_ID = 2 | +--IPROTO_CHUNK, CHAIN_ID = 2 | +--IPROTO_CHUNK, CHAIN_ID = 2 | IPROTO_CHUNK | ... | IPROTO_OK/ERROR > > With best regards, > Alex > > > Четверг, 5 апреля 2018, 9:32 +03:00 от Alexey Gadzhiev > <alg1973@gmail.com>: > > > ---------- Forwarded message ---------- > From: *Vladislav Shpilevoy* <v.shpilevoy@tarantool.org > <//e.mail.ru/compose/?mailto=mailto%3av.shpilevoy@tarantool.org>> > Date: Thu, Apr 5, 2018 at 12:22 AM > Subject: [PATCH 1/1] rfc: describe a Tarantool wire protocol > To: tarantool-patches@freelists.org > <//e.mail.ru/compose/?mailto=mailto%3atarantool%2dpatches@freelists.org> > Cc: kostja@tarantool.org > <//e.mail.ru/compose/?mailto=mailto%3akostja@tarantool.org>, > alg1973@gmail.com > <//e.mail.ru/compose/?mailto=mailto%3aalg1973@gmail.com>, > Vladislav Shpilevoy <v.shpilevoy@tarantool.org > <//e.mail.ru/compose/?mailto=mailto%3av.shpilevoy@tarantool.org>> > > > --- > Original: > https://github.com/tarantool/tarantool/blob/sql-proto-rfc/doc/RFC/wire_protocol.md > > doc/RFC/wire_protocol.md <http://wire_protocol.md> | 214 > +++++++++++++++++++++++++++++++++++++++++ > doc/RFC/wire_protocol_img1.png | Bin 0 -> 48970 bytes > 2 files changed, 214 insertions(+) > create mode 100644 doc/RFC/wire_protocol.md <http://wire_protocol.md> > create mode 100644 doc/RFC/wire_protocol_img1.png > > diff --git a/doc/RFC/wire_protocol.md <http://wire_protocol.md> > b/doc/RFC/wire_protocol.md <http://wire_protocol.md> > new file mode 100644 > index 000000000..d1fc1329c > --- /dev/null > +++ b/doc/RFC/wire_protocol.md <http://wire_protocol.md> > @@ -0,0 +1,214 @@ > +# Tarantool Wire protocol > + > +* **Status**: In progress > +* **Start date**: 04-04-2018 > +* **Authors**: Vladislav Shpilevoy @Gerold103 > v.shpilevoy@tarantool.org > <//e.mail.ru/compose/?mailto=mailto%3av.shpilevoy@tarantool.org>, > Konstantin Osipov @kostja kostja@tarantool.org > <//e.mail.ru/compose/?mailto=mailto%3akostja@tarantool.org>, > Alexey Gadzhiev @alg1973 alg1973@gmail.com > <//e.mail.ru/compose/?mailto=mailto%3aalg1973@gmail.com> > +* **Issues**: > [#2677](https://github.com/tarantool/tarantool/issues/2677), > [#2620](https://github.com/tarantool/tarantool/issues/2620), > [#2618](https://github.com/tarantool/tarantool/issues/2618) > + > +## Summary > + > +Tarantool wire protocol is a convention how to encode and send > results of execution of SQL, Lua and C stored functions, DML (Data > Manipulation Language), DDL (Data Definition Language), DQL (Data > Query Language) requests to remote clients via network. The > protocol is unified for all request types. For a single request > multiple responses of different types can be sent. > + > +## Background and motivation > + > +Tarantool wire protocol is called **IProto**, and is used by > database connectors written on different languages and working on > remote clients. The protocol describes how to distinguish > different message types and what data can be stored in each > message. Tarantool has the following response types: > +* A response, that finalizes a request, and has just data - > tuples array, or scalar values, or mixed. It has no any metadata. > This response type incorporates results of any pure Lua and C > calls including stored procedures, space and index methods. Such > response is single per request; > +* A response with just data, but with no request finalization - > it is so called push-message. During single request execution > multiple pushes can be sent, and they do not finalize the request > - a client must be ready to receive more responses; > +* A formatted response, that is sent on SQL DQL and does not > finalize a request. Such response contains metadata with result > set column names, types, flags etc; > +* A response with metadata only, that is sent on SQL DDL/DML > requests, and contains affected row count, last autoincrement > column value, flags; > +* A response with error message and code, that finalizes a request. > + > +In supporting this responses set 2 main challenges appear: > +1. How to unify responses; > +2. How to support multiple messages inside a single request. > + > +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 my_sql_func(a1, a2, a3, a4) BEGIN > + SELECT my_lua_func(a1); > + SELECT * FROM table1; > + SELECT my_c_func(a2); > + INSERT INTO table1 VALUES (1, 2, 3); > + RETURN a4; > +END > +``` > +, where `my_lua_func()` is the function, written in Lua and > sending its own push-messages: > +```Lua > +function my_lua_func(arg) > + box.session.push(arg) > + return arg > +end > +``` > +and `my_c_func()` is the function, written in C and returning > some raw data: > +```C > +int > +my_c_func(box_function_ctx_t *ctx) { > + box_tuple_t *tuple; > + /* Fill a tuple with any data. */ > + return box_return_tuple(ctx, tuple); > +} > +``` > +Consider each statement: > +* `SELECT FROM` can split a big result set in multiple messages; > +* `SELECT my_lua_func()` produces 2 messages: one is the > push-message generated in `my_lua_func` 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 send as a single TCP packet, but it does not matter for the > wire protocol. > + > +In the next section it is described, how the Tarantool wire > protocol deals with this mess. > + > +For the protocol details - code values, all header and body keys > - see Tarantool [website](tarantool.io <http://tarantool.io>). > + > +## Detailed design > + > +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. > + > +### Header > + > +There are 3 response codes in header: > +* `IPROTO_OK` - the last response in a request, that is finished > successfully; > +* `IPROTO_CHUNK` - non-final response. One request can generate > multuple chunk messages; > +* `IPROTO_ERROR | error code` - the last response in a request, > that is finished with an error. > + > +`IPROTO_ERROR` response is trivial, and consists just of code and > message. It is no considered further. > +`IPROTO_OK` and `IPROTO_CHUNK` have the same body format. The > only exception between them is that `IPROTO_OK` finalizes the > request. In the next subsection the body format is presented. > + > +### Body > + > +The common body structure: > +``` > ++----------------------------------------------+ > +| IPROTO_BODY: { | > +| IPROTO_METADATA: [ | > +| { | > +| IPROTO_FIELD_NAME: string, | > +| IPROTO_FIELD_TYPE: number, | > +| IPROTO_FIELD_FLAGS: number, | > +| }, | > +| ... | > +| ], | > +| | > +| IPROTO_SQL_INFO: { | > +| SQL_INFO_ROW_COUNT: number, | > +| SQL_INFO_LAST_ID: number, | > +| SQL_INFO_FLAGS: number, | > +| ... | > +| }, | > +| | > +| IPROTO_DATA: [ | > +| tuple/scalar, | > +| ... | > +| ] | > +| } | > ++----------------------------------------------+ > +``` > +For a while the single `SQL_INFO_FLAGS` value is available: > `SQL_INFO_HAS_NEXT_CHUNK` - a response having this flag means, > that the current result set is not fully read - more responses are > available. For example, it could be big `SELECT FROM` sent in > multiple chunks. > + > +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 next or > previous messages of the same request. > + > +_A non formatted response with metadata_ has only > `IPROTO_SQL_INFO` and it is always result of DDL/DML executed via > SQL. As well as the previous type, this response is all-independent. > + > +_A formatted response_ always has `IPROTO_DATA`, and can have > both `IPROTO_SQL_INFO` and `IPROTO_METADATA`. It is always result > of SQL DQL (`SELECT`). The response can be part of a continuous > sequence of responses. A first message of the sequence always > contains `IPROTO_METADATA`, while all non-last ones always contain > `IPROTO_SQL_INFO` with `SQL_INFO_HAS_NEXT_CHUNK` flag. The last > message has only `IPROTO_DATA` or nothing. > + > +On the picture the state machine of the protocol is showed: > +![alt > text](https://raw.githubusercontent.com/tarantool/tarantool/sql-proto-rfc/doc/RFC/wire_protocol_img1.png) > + > +For the `FUNCTION my_sql_func` call the following responses are sent: > +``` > +/* Push from my_lua_func(a1). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ a1 ] | > +| } | > ++----------------------------------------------+ > + > +/* Result of SELECT my_lua_func(a1). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ [ a1 ] ], | > +| IPROTO_METADATA: [ | > +| { /* field name, type ... */ } | > +| ] | > +| } | > ++----------------------------------------------+ > + > +/* First chunk of SELECT * FROM table1. */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ tuple1, tuple2, ... ] | > +| IPROTO_METADATA: [ | > +| { /* field1 name, type ... */ }, | > +| { /* field2 name, type ... */ }, | > +| ... | > +| ], | > +| IPROTO_SQL_INFO: { | > +| SQL_INFO_FLAGS: | > +| SQL_INFO_HAS_NEXT_CHUNK | > +| } | > +| } | > ++----------------------------------------------+ > + > + /* From second to next to last chunk. */ > + +----------------------------------------------+ > + | HEADER: IPROTO_CHUNK | > + +- - - - - - - - - - - - - - - - - - - - - - - + > + | BODY: { | > + | IPROTO_DATA: [ tuple1, tuple2, ... ], | > + | IPROTO_SQL_INFO: { | > + | SQL_INFO_FLAGS: | > + | SQL_INFO_HAS_NEXT_CHUNK | > + | } | > + | } | > + +----------------------------------------------+ > + > + /* Last chunk. */ > + +----------------------------------------------+ > + | HEADER: IPROTO_CHUNK | > + +- - - - - - - - - - - - - - - - - - - - - - - + > + | BODY: { | > + | IPROTO_DATA: [ tuple1, tuple2, ... ] | > + | } | > + +----------------------------------------------+ > + > +/* Result of SELECT my_c_func(a2). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ [ tuple ] ], | > +| IPROTO_METADATA: [ | > +| { /* field name, type ... */ } | > +| ] | > +| } | > ++----------------------------------------------+ > + > +/* Result of INSERT INTO table1 VALUES (1, 2, 3). */ > ++----------------------------------------------+ > +| HEADER: IPROTO_CHUNK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_SQL_INFO: { | > +| SQL_INFO_ROW_COUNT: number, | > +| SQL_INFO_LAST_ID: number, | > +| } | > +| } | > ++----------------------------------------------+ > + > +/* Result of RETURN a4 */ > ++----------------------------------------------+ > +| HEADER: IPROTO_OK | > ++- - - - - - - - - - - - - - - - - - - - - - - + > +| BODY: { | > +| IPROTO_DATA: [ a4 ] | > +| } | > ++----------------------------------------------+ > +``` > -- > 2.14.3 (Apple Git-98) > > > > > -- > ^ permalink raw reply [flat|nested] 7+ messages in thread
* [tarantool-patches] Re: Fwd: [PATCH 1/1] rfc: describe a Tarantool wire protocol 2018-04-05 9:01 ` [tarantool-patches] Re: Fwd: " Алексей Гаджиев 2018-04-05 9:37 ` Vladislav Shpilevoy @ 2018-04-05 10:03 ` Konstantin Osipov 1 sibling, 0 replies; 7+ messages in thread From: Konstantin Osipov @ 2018-04-05 10:03 UTC (permalink / raw) To: Алексей Гаджиев Cc: tarantool-patches, Vladislav Shpilevoy * Алексей Гаджиев <alexey.gadzhiev@corp.mail.ru> [18/04/05 12:05]: > Looks cool! Now we can send chunked data! > > I have one question: > Is IPROTO_OK mandatory packet in the response messages sequence? > How can I detect if next push/result_set available if previous consists only of one chunk without SQL_INFO_HAS_NEXT_CHUNK? > Generally, you can't. The server doesn't know it yet itself. For example, imagine this: while (condition) box.session.push() end return; In this case there can be variable number of pushes. But you know for sure there will be next packet before the sequence of packets for this request ends - either CHUNK or OK. -- Konstantin Osipov, Moscow, Russia, +7 903 626 22 32 http://tarantool.io - www.twitter.com/kostja_osipov ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2018-06-28 11:45 UTC | newest] Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2018-04-04 21:22 [tarantool-patches] [PATCH 1/1] rfc: describe a Tarantool wire protocol Vladislav Shpilevoy 2018-04-05 8:25 ` [tarantool-patches] " Konstantin Osipov 2018-04-09 15:31 ` [tarantool-patches] " Vladislav Shpilevoy 2018-06-28 11:45 ` [tarantool-patches] " Konstantin Osipov [not found] ` <CAFoyxqh0QqNBVr7tuFF_uoUw-CvBKOVA3FCyWvixikberOxP9w@mail.gmail.com> 2018-04-05 9:01 ` [tarantool-patches] Re: Fwd: " Алексей Гаджиев 2018-04-05 9:37 ` Vladislav Shpilevoy 2018-04-05 10:03 ` Konstantin Osipov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox