diff --git a/README.md b/README.md index b01756e9e..14ce5bc22 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Connector can be embedded in any C++ application with including main header: To create client one should specify buffer's and network provider's implementations as template parameters. Connector's main class has the following signature: -``` +```c++ template> class Connector; ``` @@ -122,7 +122,7 @@ one can use default one: `tnt::Buffer<16 * 1024>` and `EpollNetProvider>`. So the default instantiation would look like: -``` +```c++ using Buf_t = tnt::Buffer<16 * 1024>; using Net_t = EpollNetProvider; Connector client; @@ -131,7 +131,7 @@ Connector client; Client itself is not enough to work with Tarantool instances, so let's also create connection objects. Connection takes buffer and network provider as template parameters as well (note that they must be the same as ones of client): -``` +```c++ Connection conn(client); ``` @@ -140,7 +140,9 @@ Connection conn(client); Now assume Tarantool instance is listening `3301` port on localhost. To connect to the server we should invoke `Connector::connect()` method of client object and pass three arguments: connection instance, address and port. -`int rc = client.connect(conn, address, port)`. +```c++ +int rc = client.connect(conn, address, port); +``` ### Error handling @@ -148,9 +150,8 @@ Implementation of connector is exception free, so we rely on return codes: in case of fail, `connect()` will return `rc < 0`. To get error message corresponding to the last error happened during communication with server, we can invoke `Connection::getError()` method: -``` +```c++ if (rc != 0) { - assert(conn.status.is_failed); std::cerr << conn.getError() << std::endl; } ``` @@ -161,8 +162,10 @@ one can use `Connection::reset()`. ### Preparing requests To execute simplest request (i.e. ping), one can invoke corresponding method of -connection object: -`rid_t ping = conn.ping();` +connection object: +```c++ +rid_t ping = conn.ping(); +``` Each request method returns request id, which is sort of future. It can be used to get the result of request execution once it is ready (i.e. response). Requests are queued in the input buffer of connection until `Connector::wait()` is called. @@ -170,7 +173,7 @@ are queued in the input buffer of connection until `Connector::wait()` is called ### Sending requests That said, to send requests to the server side, we should invoke `client.wait()`: -``` +```c++ client.wait(conn, ping, WAIT_TIMEOUT); ``` Basically, `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 only once: `getResponse()` erases request id from internal map once it is returned to user. -``` +```c++ std::optional> response = conn.getResponse(ping); ``` Response 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: `CREATE TABLE t(id INT PRIMARY KEY, a TEXT, b DOUBLE);` Preparing analogue of `t:replace(1, "111", 1.01);` request can be done this way: -``` +```c++ std::tuple data = std::make_tuple(1 /* field 1*/, "111" /* field 2*/, 1.01 /* field 3*/); rid_t replace = conn.space[512].replace(data); ``` To execute select query `t.index[1]:select({1}, {limit = 1})`: -``` +```c++ auto i = conn.space[512].index[1]; rid_t select = i.select(std::make_tuple(1), 1, 0 /*offset*/, IteratorType::EQ); ``` ### Data readers -Responses from server contain raw data (i.e. encoded into msgpuck tuples). To -decode client's data, users have to write their own decoders (based on featured -schema). Let's define structure describing data stored in space `t`: +Responses from server contain raw data (i.e. encoded into MsgPack tuples). +Let's define structure describing data stored in space `t`: -``` +```c++ struct UserTuple { uint64_t field1; std::string field2; double field3; -}; -``` -Prototype of the base reader is given in `src/mpp/Dec.hpp`: -``` -template -struct SimpleReaderBase : DefaultErrorHandler { - using BufferIterator_t = typename BUFFER::iterator; - /* Allowed type of values to be parsed. */ - static constexpr Type VALID_TYPES = TYPE; - BufferIterator_t* StoreEndIterator() { return nullptr; } -}; -``` -So every new reader should inherit from it or directly from `DefaultErrorHandler`. -To parse particular value, we should define `Value()` method. First two arguments -are common and unused as a rule, but the third - defines parsed value. So in -case of POD stuctures it's enough to provide byte-to-byte copy. Since in our -schema there are fields of three different types, let's descripe three `Value()` -functions: -``` -struct UserTupleValueReader : mpp::DefaultErrorHandler { - /* Store instance of tuple to be parsed. */ - UserTuple& tuple; - /* Enumerate all types which can be parsed. Otherwise */ - static constexpr mpp::Type VALID_TYPES = mpp::MP_UINT | mpp::MP_STR | mpp::MP_DBL; - UserTupleValueReader(UserTuple& t) : tuple(t) {} - - /* Value's extractors. */ - void Value(const BufIter_t&, mpp::compact::Type, uint64_t u) - { - tuple.field1 = u; - } - void Value(const BufIter_t&, mpp::compact::Type, double d) - { - tuple.field3 = d; - } - void Value(const BufIter_t& itr, mpp::compact::Type, mpp::StrValue v) - { - BufIter_t tmp = itr; - tmp += v.offset; - std::string &dst = tuple.field2; - while (v.size) { - dst.push_back(*tmp); - ++tmp; - --v.size; - } - } + static constexpr auto mpp = std::make_tuple( + &UserTuple::field1, &UserTuple::field2, &UserTuple::field3); }; ``` -It is worth mentioning that tuple itself is wrapped into array, so in fact -firstly we should parse array. Let's define another one reader: -``` -template -struct UserTupleReader : mpp::SimpleReaderBase { - mpp::Dec& dec; - UserTuple& tuple; - - UserTupleReader(mpp::Dec& d, UserTuple& t) : dec(d), tuple(t) {} - void Value(const iterator_t&, mpp::compact::Type, mpp::ArrValue) - { - dec.SetReader(false, UserTupleValueReader{tuple}); - } -}; -``` -`SetReader();` sets the reader which is invoked while every entry of the array is -parsed. Now, to make these two readers work, we should create decoder, set -its iterator to the position of encoded tuple and invoke `Read()` method: -``` - UserTuple tuple; - mpp::Dec dec(conn.getInBuf()); - dec.SetPosition(*t.begin); - dec.SetReader(false, UserTupleReader{dec, tuple}); - dec.Read(); -``` -### Writing custom buffer and network provider +Member `mpp` is neccessary - it sets the relationship between the structure +members and associated tuple's fields. It is used by encoder and decoder +for Object <-> MsgPack serialization. For instance, such structure will be +serialied as a MsgPack array `[, , ]`. If you need +to serialize non-static members of objects, +[pointers to data members](https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_data_members) +can be used, just as in this example. + +Let's get back to the example with `select`. Consider the request successful. +We can decode data in this way: + +```c++ +if (response.body.data != std::nullopt) { + std::vector results; + bool ok = response.body.data->decode(results); + if (ok) + print_results(results); +} +``` -TODO \ No newline at end of file +Firstly, we check if the response actually contains any data (Tarantool has +sent `IPROTO_DATA` in response). According to +[`IPROTO` protocol](https://www.tarantool.io/ru/doc/latest/reference/internals/box_protocol/), +key `IPROTO_DATA` +[has](https://www.tarantool.io/ru/doc/latest/reference/internals/iproto/format/#body) +an array of tuples as value in response to `select`. So, in order to +successfully decode them, we should pass an array of tuples to decoder - that's +why `std::vector` is needed. If decoding was successful, `results` +will contain all decoded `UserTuples`. diff --git a/doc/tntcxx_api.rst b/doc/tntcxx_api.rst index 19a2790d2..6d1d2ee5e 100644 --- a/doc/tntcxx_api.rst +++ b/doc/tntcxx_api.rst @@ -420,7 +420,6 @@ Public methods int rc = client.connect(conn, address, port); if (rc != 0) { - assert(conn.status.is_failed); std::cerr << conn.getError() << std::endl; return -1; }