Skip to content

Commit ac90048

Browse files
drewdzzzalyapunov
authored andcommitted
Misc: add an example for SQL in tntcxx
The commit introduces new example `Sql`, which demonstrates usage of prepare and execute requests - in other words, it shows how to use SQL with tntcxx. The example is designed to cover all possible sql metadata fields.
1 parent fad59b5 commit ac90048

File tree

3 files changed

+257
-0
lines changed

3 files changed

+257
-0
lines changed

CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,8 @@ TNTCXX_TEST(NAME SimpleExample TYPE other
228228
SOURCES examples/Simple.cpp
229229
LIBRARIES ${COMMON_LIB}
230230
)
231+
232+
TNTCXX_TEST(NAME SqlExample TYPE other
233+
SOURCES examples/Sql.cpp
234+
LIBRARIES ${COMMON_LIB}
235+
)

examples/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ SET(CMAKE_C_STANDARD 17)
99
ADD_COMPILE_OPTIONS(-Wall -Wextra -Werror)
1010

1111
ADD_EXECUTABLE(Simple Simple.cpp)
12+
ADD_EXECUTABLE(Sql Sql.cpp)

examples/Sql.cpp

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Copyright 2010-2023, Tarantool AUTHORS, please see AUTHORS file.
3+
*
4+
* Redistribution and use in source and binary forms, with or
5+
* without modification, are permitted provided that the following
6+
* conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above
9+
* copyright notice, this list of conditions and the
10+
* following disclaimer.
11+
*
12+
* 2. Redistributions in binary form must reproduce the above
13+
* copyright notice, this list of conditions and the following
14+
* disclaimer in the documentation and/or other materials
15+
* provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
18+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
19+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21+
* <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
22+
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
25+
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
28+
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29+
* SUCH DAMAGE.
30+
*/
31+
/**
32+
* To build this example see CMakeLists.txt or Makefile in current directory.
33+
* Prerequisites to run this test:
34+
* 1. Run Tarantool instance on localhost and set listening port 3301;
35+
* 2. Grant privileges for guest (box.schema.user.grant('guest', 'super'))
36+
* 3. Compile and run ./Sql
37+
*/
38+
39+
#include "../src/Client/Connector.hpp"
40+
#include "../src/Buffer/Buffer.hpp"
41+
42+
#include "Reader.hpp"
43+
44+
const char *address = "127.0.0.1";
45+
int port = 3301;
46+
int WAIT_TIMEOUT = 1000; //milliseconds
47+
48+
using Buf_t = tnt::Buffer<16 * 1024>;
49+
#include "../src/Client/LibevNetProvider.hpp"
50+
using Net_t = LibevNetProvider<Buf_t, DefaultStream>;
51+
52+
template <class BUFFER>
53+
std::vector<UserTuple>
54+
decodeUserTuple(Data<BUFFER> &data)
55+
{
56+
std::vector<UserTuple> results;
57+
bool ok = data.decode(results);
58+
(void)ok;
59+
assert(ok);
60+
return results;
61+
}
62+
63+
template<class BUFFER>
64+
void
65+
printResponse(Response<BUFFER> &response)
66+
{
67+
std::cout << ">>> RESPONSE {" << std::endl;
68+
if (response.body.error_stack != std::nullopt) {
69+
Error err = (*response.body.error_stack)[0];
70+
std::cout << "RESPONSE ERROR: msg=" << err.msg <<
71+
" line=" << err.file << " file=" << err.file <<
72+
" errno=" << err.saved_errno <<
73+
" type=" << err.type_name <<
74+
" code=" << err.errcode << std::endl;
75+
}
76+
if (response.body.stmt_id != std::nullopt)
77+
std::cout << "stmt_id: " << *response.body.stmt_id << std::endl;
78+
if (response.body.bind_count != std::nullopt)
79+
std::cout << "bind_count: " << *response.body.bind_count << std::endl;
80+
if (response.body.sql_info != std::nullopt) {
81+
std::cout << "row_count: " << response.body.sql_info->row_count << std::endl;
82+
if (!response.body.sql_info->autoincrement_ids.empty()) {
83+
std::cout << "autoincrements ids: ";
84+
for (const auto &id : response.body.sql_info->autoincrement_ids)
85+
std::cout << id << " ";
86+
std::cout << std::endl;
87+
}
88+
}
89+
if (response.body.metadata != std::nullopt) {
90+
for (const auto &cm : response.body.metadata->column_maps) {
91+
std::cout << "SQL column " << cm.field_name <<
92+
" of type " << cm.field_type;
93+
if (cm.is_nullable)
94+
std::cout << " nullable";
95+
if (cm.is_autoincrement)
96+
std::cout << " autoincrement";
97+
if (!cm.collation.empty())
98+
std::cout << " with collation " << cm.collation;
99+
if (cm.span != std::nullopt)
100+
std::cout << " with span " << *cm.span;
101+
std::cout << std::endl; }
102+
}
103+
if (response.body.data != std::nullopt) {
104+
Data<BUFFER>& data = *response.body.data;
105+
std::vector<UserTuple> tuples = decodeUserTuple(data);
106+
if (tuples.empty()) {
107+
std::cout << "Empty result" << std::endl;
108+
} else {
109+
for (auto const& t : tuples)
110+
std::cout << t << std::endl;
111+
}
112+
}
113+
std::cout << "}" << std::endl;
114+
}
115+
116+
int
117+
main()
118+
{
119+
/*
120+
* Create default connector.
121+
*/
122+
Connector<Buf_t, Net_t> client;
123+
/*
124+
* Create single connection. Constructor takes only client reference.
125+
*/
126+
Connection<Buf_t, Net_t> conn(client);
127+
/*
128+
* Try to connect to given address:port. Current implementation is
129+
* exception free, so we rely only on return codes.
130+
*/
131+
int rc = client.connect(conn, {.address = address,
132+
.service = std::to_string(port),
133+
/*.user = ...,*/
134+
/*.passwd = ...,*/
135+
/* .transport = STREAM_SSL, */});
136+
if (rc != 0) {
137+
std::cerr << conn.getError().msg << std::endl;
138+
return -1;
139+
}
140+
/*
141+
* Now let's execute several sql requests.
142+
* Note that any of :request() methods can't fail; they always
143+
* return request id - the future (number) which is used to get
144+
* response once it is received. Also note that at this step,
145+
* requests are encoded (into msgpack format) and saved into
146+
* output connection's buffer - they are ready to be sent.
147+
* But network communication itself will be done later.
148+
*/
149+
/*
150+
* Let's create a space. An empty tuple at the end means that we pass
151+
* no arguments to the SQL statement.
152+
*/
153+
rid_t create_space = conn.execute(
154+
"CREATE TABLE IF NOT EXISTS tntcxx_sql_example "
155+
"(id UNSIGNED PRIMARY KEY AUTOINCREMENT, "
156+
"str_id STRING, value DOUBLE);",
157+
std::make_tuple());
158+
/*
159+
* Fill the newly created table.
160+
* Let's try to use different approaches in one statement to learn more.
161+
* Firstly, generate tuple with arguments for the insert statement. Then,
162+
* execute the insert statement and pass generated arguments.
163+
*/
164+
std::tuple tntcxx_sql_data = std::make_tuple(
165+
/*
166+
* Pass NULL as the first field - it will be generated
167+
* in tarantool since it has autoincrement attribute.
168+
*/
169+
nullptr, "One", 12.8,
170+
nullptr, "Two", -8.0,
171+
/* NULL for the 1st field is written right in statement. */
172+
"Three", 345.298,
173+
/* Pass the first field expilictly. */
174+
10, "Ten", -308.098
175+
);
176+
rid_t fill_space = conn.execute(
177+
"INSERT INTO tntcxx_sql_example VALUES "
178+
"(?, ?, ?), (?, ?, ?), (NULL, ?, ?), (?, ?, ?);",
179+
tntcxx_sql_data
180+
);
181+
/*
182+
* Let's read from tarantool.
183+
* Add a parameter to select statement to reuse it later.
184+
*/
185+
std::string select_stmt = "SELECT * FROM tntcxx_sql_example WHERE id = ?";
186+
rid_t select_from_space = conn.execute(select_stmt, std::make_tuple(1));
187+
/*
188+
* Let's enable sql metadata and then read from tarantool again.
189+
* Note that each session has its own _session_settings space, so
190+
* this statement enables metadata only for the connection in
191+
* which it was executed.
192+
*/
193+
rid_t enable_metadata = conn.execute(
194+
"UPDATE \"_session_settings\" SET \"value\" = true "
195+
"WHERE \"name\" = 'sql_full_metadata';", std::make_tuple());
196+
rid_t select_with_meta = conn.execute(select_stmt, std::make_tuple(2));
197+
198+
/* Let's wait for all futures at once. */
199+
std::vector<rid_t> futures = {
200+
create_space, fill_space, select_from_space,
201+
enable_metadata, select_with_meta
202+
};
203+
204+
/* No specified timeout means that we poll futures until they are ready. */
205+
client.waitAll(conn, futures);
206+
for (size_t i = 0; i < futures.size(); ++i) {
207+
assert(conn.futureIsReady(futures[i]));
208+
Response<Buf_t> response = conn.getResponse(futures[i]);
209+
printResponse<Buf_t>(response);
210+
}
211+
212+
/* Let's prepare select_stmt to use it later. */
213+
rid_t prepare_stmt = conn.prepare(select_stmt);
214+
client.wait(conn, prepare_stmt);
215+
assert(conn.futureIsReady(prepare_stmt));
216+
Response<Buf_t> response = conn.getResponse(prepare_stmt);
217+
printResponse<Buf_t>(response);
218+
assert(response.body.stmt_id.has_value());
219+
uint32_t stmt_id = *response.body.stmt_id;
220+
221+
/* Let's read some data using prepared statement. */
222+
std::vector<rid_t> prepared_select_futures;
223+
prepared_select_futures.push_back(conn.execute(stmt_id, std::make_tuple(3)));
224+
prepared_select_futures.push_back(conn.execute(stmt_id, std::make_tuple(10)));
225+
/* Again, wait for all futures at once. */
226+
client.waitAll(conn, prepared_select_futures);
227+
for (size_t i = 0; i < prepared_select_futures.size(); ++i) {
228+
assert(conn.futureIsReady(prepared_select_futures[i]));
229+
Response<Buf_t> response =
230+
conn.getResponse(prepared_select_futures[i]);
231+
printResponse<Buf_t>(response);
232+
}
233+
234+
/*
235+
* Let's calculate a very important statistic.
236+
* Add integer and string at the beginning to suit UserTuple format.
237+
*/
238+
rid_t calculate_stmt = conn.execute(
239+
"SELECT 0 as id, 'zero' as zero_id, sum(value) as important_statistic "
240+
"FROM SEQSCAN tntcxx_sql_example;",
241+
std::make_tuple()
242+
);
243+
client.wait(conn, calculate_stmt);
244+
assert(conn.futureIsReady(calculate_stmt));
245+
response = conn.getResponse(calculate_stmt);
246+
printResponse<Buf_t>(response);
247+
248+
/* Finally, user is responsible for closing connections. */
249+
client.close(conn);
250+
return 0;
251+
}

0 commit comments

Comments
 (0)