@@ -112,7 +112,7 @@ Connector can be embedded in any C++ application with including main header:
112112To create client one should specify buffer's and network provider's implementations
113113as template parameters. Connector's main class has the following signature:
114114
115- ```
115+ ``` c++
116116template <class BUFFER , class NetProvider = EpollNetProvider<BUFFER >>
117117class Connector;
118118```
@@ -122,7 +122,7 @@ one can use default one: `tnt::Buffer<16 * 1024>` and
122122`EpollNetProvider<tnt::Buffer<16 * 1024>>`.
123123So the default instantiation would look
124124like:
125- ```
125+ ```c++
126126using Buf_t = tnt::Buffer<16 * 1024>;
127127using Net_t = EpollNetProvider<Buf_t >;
128128Connector<Buf_t, Net_t> client;
@@ -131,7 +131,7 @@ Connector<Buf_t, Net_t> client;
131131Client itself is not enough to work with Tarantool instances, so let's also create
132132connection objects. Connection takes buffer and network provider as template
133133parameters as well (note that they must be the same as ones of client):
134- ```
134+ ``` c++
135135Connection<Buf_t, Net_t> conn (client);
136136```
137137
@@ -140,17 +140,18 @@ Connection<Buf_t, Net_t> conn(client);
140140Now assume Tarantool instance is listening `3301` port on localhost. To connect
141141to the server we should invoke `Connector::connect()` method of client object and
142142pass three arguments: connection instance, address and port.
143- ` int rc = client.connect(conn, address, port) ` .
143+ ```c++
144+ int rc = client.connect(conn, address, port);
145+ ```
144146
145147### Error handling
146148
147149Implementation of connector is exception
148150free, so we rely on return codes: in case of fail, ` connect() ` will return ` rc < 0 ` .
149151To get error message corresponding to the last error happened during communication
150152with server, we can invoke ` Connection::getError() ` method:
151- ```
153+ ``` c++
152154if (rc != 0 ) {
153- assert(conn.status.is_failed);
154155 std::cerr << conn.getError() << std::endl;
155156}
156157```
@@ -161,16 +162,18 @@ one can use `Connection::reset()`.
161162### Preparing requests
162163
163164To execute simplest request (i.e. ping), one can invoke corresponding method of
164- connection object:
165- ` rid_t ping = conn.ping(); `
165+ connection object:
166+ ``` c++
167+ rid_t ping = conn.ping();
168+ ```
166169Each request method returns request id, which is sort of future. It can be used
167170to get the result of request execution once it is ready (i.e. response). Requests
168171are queued in the input buffer of connection until ` Connector::wait() ` is called.
169172
170173### Sending requests
171174
172175That said, to send requests to the server side, we should invoke ` client.wait() ` :
173- ```
176+ ``` c++
174177client.wait(conn, ping, WAIT_TIMEOUT);
175178```
176179Basically, ` wait() ` takes connection to poll (both IN and OUT), request id and
@@ -187,7 +190,7 @@ in case response is not ready yet). Note that on each future it can be called
187190only once: ` getResponse() ` erases request id from internal map once it is
188191returned to user.
189192
190- ```
193+ ``` c++
191194std::optional<Response<Buf_t>> response = conn.getResponse(ping);
192195```
193196Response consists of header and body (` response.header ` and ` response.body ` ).
@@ -204,103 +207,59 @@ Assume we have space with `id = 512` and following format on the server:
204207` CREATE TABLE t(id INT PRIMARY KEY, a TEXT, b DOUBLE); `
205208Preparing analogue of ` t:replace(1, "111", 1.01); ` request can be done this way:
206209
207- ```
210+ ``` c++
208211std::tuple data = std::make_tuple(1 /* field 1*/ , " 111" /* field 2*/ , 1.01 /* field 3*/ );
209212rid_t replace = conn.space[512 ].replace(data);
210213```
211214To execute select query ` t.index[1]:select({1}, {limit = 1}) ` :
212215
213- ```
216+ ``` c++
214217auto i = conn.space[512 ].index[1 ];
215218rid_t select = i.select(std::make_tuple(1 ), 1 , 0 /* offset*/ , IteratorType::EQ);
216219```
217220
218221### Data readers
219222
220- Responses from server contain raw data (i.e. encoded into msgpuck tuples). To
221- decode client's data, users have to write their own decoders (based on featured
222- schema). Let's define structure describing data stored in space ` t ` :
223+ Responses from server contain raw data (i.e. encoded into MsgPack tuples).
224+ Let's define structure describing data stored in space ` t ` :
223225
224- ```
226+ ``` c++
225227struct UserTuple {
226228 uint64_t field1;
227229 std::string field2;
228230 double field3;
229- };
230- ```
231231
232- Prototype of the base reader is given in ` src/mpp/Dec.hpp ` :
233- ```
234- template <class BUFFER, Type TYPE>
235- struct SimpleReaderBase : DefaultErrorHandler {
236- using BufferIterator_t = typename BUFFER::iterator;
237- /* Allowed type of values to be parsed. */
238- static constexpr Type VALID_TYPES = TYPE;
239- BufferIterator_t* StoreEndIterator() { return nullptr; }
240- };
241- ```
242- So every new reader should inherit from it or directly from ` DefaultErrorHandler ` .
243- To parse particular value, we should define ` Value() ` method. First two arguments
244- are common and unused as a rule, but the third - defines parsed value. So in
245- case of POD stuctures it's enough to provide byte-to-byte copy. Since in our
246- schema there are fields of three different types, let's descripe three ` Value() `
247- functions:
248- ```
249- struct UserTupleValueReader : mpp::DefaultErrorHandler {
250- /* Store instance of tuple to be parsed. */
251- UserTuple& tuple;
252- /* Enumerate all types which can be parsed. Otherwise */
253- static constexpr mpp::Type VALID_TYPES = mpp::MP_UINT | mpp::MP_STR | mpp::MP_DBL;
254- UserTupleValueReader(UserTuple& t) : tuple(t) {}
255-
256- /* Value's extractors. */
257- void Value(const BufIter_t&, mpp::compact::Type, uint64_t u)
258- {
259- tuple.field1 = u;
260- }
261- void Value(const BufIter_t&, mpp::compact::Type, double d)
262- {
263- tuple.field3 = d;
264- }
265- void Value(const BufIter_t& itr, mpp::compact::Type, mpp::StrValue v)
266- {
267- BufIter_t tmp = itr;
268- tmp += v.offset;
269- std::string &dst = tuple.field2;
270- while (v.size) {
271- dst.push_back(*tmp);
272- ++tmp;
273- --v.size;
274- }
275- }
232+ static constexpr auto mpp = std::make_tuple(
233+ &UserTuple::field1, &UserTuple::field2, &UserTuple::field3);
276234};
277235```
278- It is worth mentioning that tuple itself is wrapped into array, so in fact
279- firstly we should parse array. Let's define another one reader:
280- ```
281- template <class BUFFER>
282- struct UserTupleReader : mpp::SimpleReaderBase<BUFFER, mpp::MP_ARR> {
283- mpp::Dec<BUFFER>& dec;
284- UserTuple& tuple;
285-
286- UserTupleReader(mpp::Dec<BUFFER>& d, UserTuple& t) : dec(d), tuple(t) {}
287- void Value(const iterator_t<BUFFER>&, mpp::compact::Type, mpp::ArrValue)
288- {
289- dec.SetReader(false, UserTupleValueReader{tuple});
290- }
291- };
292- ```
293- ` SetReader(); ` sets the reader which is invoked while every entry of the array is
294- parsed. Now, to make these two readers work, we should create decoder, set
295- its iterator to the position of encoded tuple and invoke ` Read() ` method:
296- ```
297- UserTuple tuple;
298- mpp::Dec dec(conn.getInBuf());
299- dec.SetPosition(*t.begin);
300- dec.SetReader(false, UserTupleReader<BUFFER>{dec, tuple});
301- dec.Read();
302- ```
303236
304- ### Writing custom buffer and network provider
237+ Member `mpp` is neccessary - it sets the relationship between the structure
238+ members and associated tuple's fields. It is used by encoder and decoder
239+ for Object <-> MsgPack serialization. For instance, such structure will be
240+ serialied as a MsgPack array `[<field1>, <field2>, <field3>]`. If you need
241+ to serialize non-static members of objects,
242+ [pointers to data members](https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_data_members)
243+ can be used, just as in this example.
244+
245+ Let's get back to the example with `select`. Consider the request successful.
246+ We can decode data in this way:
247+
248+ ```c++
249+ if (response.body.data != std::nullopt) {
250+ std::vector<UserTuple> results;
251+ bool ok = response.body.data->decode(results);
252+ if (ok)
253+ print_results(results);
254+ }
255+ ```
305256
306- TODO
257+ Firstly, we check if the response actually contains any data (Tarantool has
258+ sent ` IPROTO_DATA ` in response). According to
259+ [`IPROTO` protocol](https:// www.tarantool.io/ru/doc/latest/reference/internals/box_protocol/),
260+ key `IPROTO_DATA`
261+ [has](https:// www.tarantool.io/ru/doc/latest/reference/internals/iproto/format/#body)
262+ an array of tuples as value in response to `select`. So, in order to
263+ successfully decode them, we should pass an array of tuples to decoder - that' s
264+ why `std::vector<UserTuple>` is needed. If decoding was successful, `results`
265+ will contain all decoded `UserTuples`.
0 commit comments