diff --git a/src/Client/Connection.hpp b/src/Client/Connection.hpp index 623031785..ae6f7910b 100644 --- a/src/Client/Connection.hpp +++ b/src/Client/Connection.hpp @@ -158,6 +158,33 @@ class Connection template rid_t call(const std::string &func, const T &args); rid_t ping(); + + /** + * Execute the SQL statement contained in the 'statement' parameter. + * @param statement statement, which should conform to the rules for SQL grammar + * @param parameters tuple for placeholders in the statement + * @retval request id + */ + + template + rid_t execute(const std::string& statement, const T& parameters); + + /** + * Execute the SQL statement contained in the 'statement' parameter. + * @param stmt_id the statement id obtained with prepare() + * @param parameters tuple for placeholders in the statement + * @retval request id + */ + template + rid_t execute(unsigned int stmt_id, const T& parameters); + + /** + * Prepare the SQL statement contained in the 'statement' parameter. + * The syntax and requirements for Connection::prepare() are the same as for Connection::execute(). + * @param statement statement, which should conform to the rules for SQL grammar + * @retval request id + */ + rid_t prepare(const std::string& statement); void setError(const std::string &msg, int errno_ = 0); ConnectionError& getError(); @@ -581,6 +608,34 @@ decodeGreeting(Connection &conn) } ////////////////////////////BOX-like interface functions//////////////////////// +template +template +rid_t +Connection::execute(const std::string& statement, const T& parameters) +{ + impl->enc.encodeExecute(statement, parameters); + impl->connector.readyToSend(*this); + return RequestEncoder::getSync(); +} + +template +template +rid_t +Connection::execute(unsigned int stmt_id, const T& parameters) +{ + impl->enc.encodeExecute(stmt_id, parameters); + impl->connector.readyToSend(*this); + return RequestEncoder::getSync(); +} + +template +rid_t +Connection::prepare(const std::string& statement) +{ + impl->enc.encodePrepare(statement); + impl->connector.readyToSend(*this); + return RequestEncoder::getSync(); +} template template diff --git a/src/Client/IprotoConstants.hpp b/src/Client/IprotoConstants.hpp index fdac67ba8..df665ebbe 100644 --- a/src/Client/IprotoConstants.hpp +++ b/src/Client/IprotoConstants.hpp @@ -102,6 +102,13 @@ namespace Iproto { FIELD_SPAN = 5, }; + enum ColumnMap { + FIELD_NAME_MAX = 256, + FIELD_TYPE_NAME_MAX = 32, + COLLATION_MAX = 32, + SPAN_MAX = 256 + }; + enum Type { OK = 0, SELECT = 1, @@ -135,6 +142,12 @@ namespace Iproto { TYPE_ERROR = 1 << 15 }; + /** Keys of IPROTO_SQL_INFO map. */ + enum SqlInfoKey { + SQL_INFO_ROW_COUNT = 0x00, + SQL_INFO_AUTOINCREMENT_IDS = 0x01 + }; + enum ErrorStack { ERROR_STACK = 0x00 }; diff --git a/src/Client/RequestEncoder.hpp b/src/Client/RequestEncoder.hpp index 48839bda2..3f9916f25 100644 --- a/src/Client/RequestEncoder.hpp +++ b/src/Client/RequestEncoder.hpp @@ -83,6 +83,11 @@ class RequestEncoder { uint32_t limit = UINT32_MAX, uint32_t offset = 0, IteratorType iterator = EQ); template + size_t encodeExecute(const std::string& statement, const T& parameters); + template + size_t encodeExecute(unsigned int stmt_id, const T& parameters); + size_t encodePrepare(const std::string& statement); + template size_t encodeCall(const std::string &func, const T &args); /** Sync value is used as request id. */ @@ -236,6 +241,57 @@ RequestEncoder::encodeSelect(const T &key, return request_size + PREHEADER_SIZE; } +template +template +size_t +RequestEncoder::encodeExecute(const std::string& statement, const T& parameters) +{ + iterator_t request_start = m_Buf.end(); + m_Buf.addBack('\xce'); + m_Buf.addBack(uint32_t{0}); + encodeHeader(Iproto::EXECUTE); + m_Enc.add(mpp::as_map(std::forward_as_tuple( + MPP_AS_CONST(Iproto::SQL_TEXT), statement, + MPP_AS_CONST(Iproto::SQL_BIND), parameters, + MPP_AS_CONST(Iproto::OPTIONS), std::make_tuple()))); + uint32_t request_size = (m_Buf.end() - request_start) - PREHEADER_SIZE; + m_Buf.set(request_start + 1, __builtin_bswap32(request_size)); + return request_size + PREHEADER_SIZE; +} + +template +template +size_t +RequestEncoder::encodeExecute(unsigned int stmt_id, const T& parameters) +{ + iterator_t request_start = m_Buf.end(); + m_Buf.addBack('\xce'); + m_Buf.addBack(uint32_t{0}); + encodeHeader(Iproto::EXECUTE); + m_Enc.add(mpp::as_map(std::forward_as_tuple( + MPP_AS_CONST(Iproto::STMT_ID), stmt_id, + MPP_AS_CONST(Iproto::SQL_BIND), parameters, + MPP_AS_CONST(Iproto::OPTIONS), std::make_tuple()))); + uint32_t request_size = (m_Buf.end() - request_start) - PREHEADER_SIZE; + m_Buf.set(request_start + 1, __builtin_bswap32(request_size)); + return request_size + PREHEADER_SIZE; +} + +template +size_t +RequestEncoder::encodePrepare(const std::string& statement) +{ + iterator_t request_start = m_Buf.end(); + m_Buf.addBack('\xce'); + m_Buf.addBack(uint32_t{0}); + encodeHeader(Iproto::PREPARE); + m_Enc.add(mpp::as_map(std::forward_as_tuple( + MPP_AS_CONST(Iproto::SQL_TEXT), statement))); + uint32_t request_size = (m_Buf.end() - request_start) - PREHEADER_SIZE; + m_Buf.set(request_start + 1, __builtin_bswap32(request_size)); + return request_size + PREHEADER_SIZE; +} + template template size_t diff --git a/src/Client/ResponseReader.hpp b/src/Client/ResponseReader.hpp index ad392590d..3e91aafb5 100644 --- a/src/Client/ResponseReader.hpp +++ b/src/Client/ResponseReader.hpp @@ -72,6 +72,40 @@ struct Tuple { size_t field_count; }; +struct SqlInfo +{ + int row_count; +}; + +struct ColumnMap +{ + char field_name[Iproto::FIELD_NAME_MAX] = {}; + size_t field_name_len = 0; + char field_type[Iproto::FIELD_TYPE_NAME_MAX] = {}; + size_t field_type_len = 0; + char collation[Iproto::COLLATION_MAX] = {}; + size_t collation_len = 0; + bool is_nullable = 0; + bool is_autoincrement = 0; + char span[Iproto::SPAN_MAX] = {}; + size_t span_len = 0; +}; + +struct Metadata +{ + size_t dimension = 0; + std::vector column_maps; +}; + +struct SqlData +{ + std::optional metadata = std::nullopt; + std::optional stmt_id = std::nullopt; + std::optional bind_count = std::nullopt; + std::optional sql_info = std::nullopt; +}; + + template struct Data { Data(iterator_t &itr) : end(itr) {} @@ -82,6 +116,8 @@ struct Data { size_t dimension = 0; std::vector> tuples; iterator_t end; + + std::optional sql_data = std::nullopt; }; template @@ -354,6 +390,125 @@ struct ErrorReader : mpp::SimpleReaderBase { ErrorStack& error; }; +template +struct ColumnMapKeyReader : mpp::SimpleReaderBase { + + ColumnMapKeyReader(mpp::Dec& d, ColumnMap& cm) : dec(d), column_map(cm) {} + + void Value(const iterator_t&, mpp::compact::Type, uint64_t key) + { + using FieldNameReader_t = mpp::SimpleStrReader; + using FieldTypeReader_t = mpp::SimpleStrReader; + using CollationReader_t = mpp::SimpleStrReader; + using SpanReader_t = mpp::SimpleStrReader; + using Bool_t = mpp::SimpleReader; + LOG_DEBUG("Column map switch....................."); + switch (key) { + case Iproto::FIELD_NAME: { + LOG_DEBUG("field name....................."); + dec.SetReader(true, FieldNameReader_t{column_map.field_name, column_map.field_name_len}); + break; + } + case Iproto::FIELD_TYPE: { + LOG_DEBUG("Field type....................."); + dec.SetReader(true, FieldTypeReader_t{column_map.field_type, column_map.field_type_len}); + break; + } + case Iproto::FIELD_COLL: { + LOG_DEBUG("Collation....................."); + dec.SetReader(true, CollationReader_t{column_map.collation, column_map.collation_len}); + break; + } + case Iproto::FIELD_IS_NULLABLE: { + LOG_DEBUG("is nullable....................."); + dec.SetReader(true, Bool_t{column_map.is_nullable}); + break; + } + case Iproto::FIELD_IS_AUTOINCREMENT: { + LOG_DEBUG("auto increment....................."); + dec.SetReader(true, Bool_t{column_map.is_autoincrement}); + break; + } + case Iproto::FIELD_SPAN: { + LOG_DEBUG("span....................."); + dec.SetReader(true, SpanReader_t{column_map.span, column_map.span_len}); + break; + } + default: + LOG_ERROR("Invalid column map key: ", key); + dec.AbortAndSkipRead(); + } + } + mpp::Dec& dec; + ColumnMap& column_map; +}; + +template +struct MetadataArrayValueReader : mpp::SimpleReaderBase { + + MetadataArrayValueReader(mpp::Dec& d, Metadata& md) : dec(d), metadata(md) {} + + void Value(const iterator_t&, mpp::compact::Type, mpp::MapValue) + { + metadata.column_maps.push_back(ColumnMap()); + metadata.dimension += 1; + dec.SetReader(false, ColumnMapKeyReader{dec, metadata.column_maps.back()}); + LOG_DEBUG("MetadataArrayValueReader.........."); + + } + mpp::Dec& dec; + Metadata& metadata; +}; + +template +struct MetadataArrayReader : mpp::SimpleReaderBase { + + MetadataArrayReader(mpp::Dec& d, Metadata& md) : dec(d), metadata(md) {} + + void Value(const iterator_t&, mpp::compact::Type, mpp::ArrValue) + { + dec.SetReader(false, MetadataArrayValueReader{dec, metadata}); + LOG_DEBUG("MetadataArrayReader.........."); + } + mpp::Dec& dec; + Metadata& metadata; +}; + +template +struct SqlInfoKeyReader : mpp::SimpleReaderBase { + + SqlInfoKeyReader(mpp::Dec& d, SqlInfo& sql_i) : dec(d), sql_info(sql_i) {} + + void Value(const iterator_t&, mpp::compact::Type, uint64_t key) + { + using Int_t = mpp::SimpleReader; + switch (key) { + case Iproto::SQL_INFO_ROW_COUNT: { + dec.SetReader(true, Int_t{sql_info.row_count}); + break; + } + default: + LOG_ERROR("Invalid sql info key: ", key); + dec.AbortAndSkipRead(); + } + } + mpp::Dec& dec; + SqlInfo& sql_info; +}; + +template +struct SqlInfoReader : mpp::SimpleReaderBase { + + SqlInfoReader(mpp::Dec& d, SqlInfo& sql_i) : dec(d), sql_info(sql_i) {} + + void Value(const iterator_t&, mpp::compact::Type, mpp::MapValue) + { + dec.SetReader(false, SqlInfoKeyReader{dec, sql_info}); + } + + mpp::Dec& dec; + SqlInfo& sql_info; +}; template struct BodyKeyReader : mpp::SimpleReaderBase { @@ -365,9 +520,14 @@ struct BodyKeyReader : mpp::SimpleReaderBase { using Str_t = mpp::SimpleStrReader; using Err_t = ErrorReader; using Data_t = DataReader; + using Unsigned_t = mpp::SimpleReader; + using Int_t = mpp::SimpleReader; + switch (key) { case Iproto::DATA: { - body.data = Data(itr); + if (body.data == std::nullopt) { + body.data = Data(itr); + } dec.SetReader(true, Data_t{dec, *body.data}); break; } @@ -384,6 +544,57 @@ struct BodyKeyReader : mpp::SimpleReaderBase { dec.SetReader(true, Err_t{dec, error_stack}); break; } + case Iproto::SQL_INFO: { + if (body.data == std::nullopt) { + body.data = Data(itr); + } + if (body.data->sql_data == std::nullopt) { + body.data->sql_data = SqlData(); + } + body.data->sql_data->sql_info = SqlInfo(); + dec.SetReader(true, SqlInfoReader{dec, *body.data->sql_data->sql_info}); + break; + } + case Iproto::METADATA: { + if (body.data == std::nullopt) { + body.data = Data(itr); + } + if (body.data->sql_data == std::nullopt) { + body.data->sql_data = SqlData(); + } + body.data->sql_data->metadata = Metadata(); + dec.SetReader(true, MetadataArrayReader{dec, *body.data->sql_data->metadata}); + break; + } + case Iproto::STMT_ID: { + if (body.data == std::nullopt) { + body.data = Data(itr); + } + if (body.data->sql_data == std::nullopt) { + body.data->sql_data = SqlData(); + } + body.data->sql_data->stmt_id = 0; + dec.SetReader(true, Unsigned_t{*body.data->sql_data->stmt_id}); + break; + } + case Iproto::BIND_COUNT: { + if (body.data == std::nullopt) { + body.data = Data(itr); + } + if (body.data->sql_data == std::nullopt) { + body.data->sql_data = SqlData(); + } + body.data->sql_data->bind_count = 0; + dec.SetReader(true, Int_t{*body.data->sql_data->bind_count}); + break; + } + case Iproto::BIND_METADATA: { + if (body.data == std::nullopt) { + body.data = Data(itr); + } + dec.Skip(); + break; + } default: LOG_ERROR("Invalid body key: ", key); dec.AbortAndSkipRead(); diff --git a/test/ClientTest.cpp b/test/ClientTest.cpp index 2df10bcf6..36056d871 100644 --- a/test/ClientTest.cpp +++ b/test/ClientTest.cpp @@ -59,28 +59,58 @@ printResponse(Connection &conn, Response &response, " code=" << err.errcode << std::endl; return; } - assert(response.body.data != std::nullopt); - Data& data = *response.body.data; - if (data.tuples.empty()) { - std::cout << "Empty result" << std::endl; - return; - } - std::vector tuples; - switch (format) { - case TUPLES: - tuples = decodeUserTuple(conn.getInBuf(), data); - break; - case MULTI_RETURN: - tuples = decodeMultiReturn(conn.getInBuf(), data); - break; - case SELECT_RETURN: - tuples = decodeSelectReturn(conn.getInBuf(), data); - break; - default: - assert(0); - } - for (auto const& t : tuples) { - std::cout << t << std::endl; + if (response.body.data != std::nullopt) { + Data& data = *response.body.data; + if (response.body.data->sql_data != std::nullopt) { + if (response.body.data->sql_data->sql_info != std::nullopt) { + std::cout << "Row count = " << response.body.data->sql_data->sql_info->row_count << std::endl; + } + if (response.body.data->sql_data->metadata != std::nullopt) { + std::vector& maps = response.body.data->sql_data->metadata->column_maps; + std::cout << "Metadata:\n"; + for (auto& map : maps) { + std::cout << "Field name: " << map.field_name << std::endl; + std::cout << "Field name len: " << map.field_name_len << std::endl; + std::cout << "Field type: " << map.field_type << std::endl; + std::cout << "Field type len: " << map.field_type_len << std::endl; + if (map.span_len != 0) { + std::cout << "Collation: " << map.collation << std::endl; + std::cout << "Collation len: " << map.collation_len << std::endl; + std::cout << "Is nullable: " << map.is_nullable << std::endl; + std::cout << "Is autoincrement: " << map.is_autoincrement << std::endl; + std::cout << "Span: " << map.span << std::endl; + std::cout << "Span len: " << map.span_len << std::endl; + } + } + } + if (response.body.data->sql_data->stmt_id != std::nullopt) { + std::cout << "statement id = " << *response.body.data->sql_data->stmt_id << std::endl; + } + if (response.body.data->sql_data->bind_count != std::nullopt) { + std::cout << "bind count = " << *response.body.data->sql_data->bind_count << std::endl; + } + } + if (data.tuples.empty()) { + std::cout << "No tuples" << std::endl; + return; + } + std::vector tuples; + switch (format) { + case TUPLES: + tuples = decodeUserTuple(conn.getInBuf(), data); + break; + case MULTI_RETURN: + tuples = decodeMultiReturn(conn.getInBuf(), data); + break; + case SELECT_RETURN: + tuples = decodeSelectReturn(conn.getInBuf(), data); + break; + default: + assert(0); + } + for (auto const& t : tuples) { + std::cout << t << std::endl; + } } } @@ -565,6 +595,192 @@ single_conn_call(Connector &client) client.close(conn); } +/** Single connection, separate executes */ +template +void +single_conn_sql_statements(Connector &client) +{ + TEST_INIT(0); + Connection conn(client); + int rc = client.connect(conn, localhost, port); + fail_unless(rc == 0); + + TEST_CASE("CREATE TABLE"); + rid_t create_table = conn.execute("CREATE TABLE IF NOT EXISTS testing_sql " + "(column1 UNSIGNED PRIMARY KEY AUTOINCREMENT, " + "column2 VARCHAR(50), " + "column3 DOUBLE);", std::make_tuple()); + + client.wait(conn, create_table, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(create_table)); + std::optional> response = conn.getResponse(create_table); + fail_unless(response != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info != std::nullopt); + + TEST_CASE("Simple INSERT"); + rid_t insert = conn.execute("INSERT INTO testing_sql VALUES (20, 'first', 3.2)," + "(21, 'second', 5.4)", std::make_tuple()); + + client.wait(conn, insert, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(insert)); + response = conn.getResponse(insert); + fail_unless(response != std::nullopt); + if (response->body.error_stack != std::nullopt) { + std::cout << response->body.error_stack->error.msg << std::endl; + } + fail_unless(response->body.data->sql_data->sql_info != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info->row_count == 2); + + TEST_CASE("INSERT with binding arguments"); + std::tuple args = std::make_tuple(1, "Timur", 12.8, + 2, "Nikita", -8.0, + 3, "Anastas", 345.298); + rid_t insert_args = conn.execute("INSERT INTO testing_sql VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?);", args); + + client.wait(conn, insert_args, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(insert_args)); + response = conn.getResponse(insert_args); + fail_unless(response != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info->row_count == 3); + printResponse(conn, *response); + + + TEST_CASE("SELECT"); + rid_t select = conn.execute("SELECT * FROM testing_sql;", std::make_tuple()); + + client.wait(conn, select, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(select)); + response = conn.getResponse(select); + fail_unless(response != std::nullopt); + fail_unless(response->body.data != std::nullopt); + fail_unless(response->body.data->dimension == 5); + + printResponse(conn, *response); + + TEST_CASE("metadata"); + { + std::vector& column_maps = response->body.data->sql_data->metadata->column_maps; + fail_unless(response->body.data->sql_data->metadata != std::nullopt); + fail_unless(response->body.data->sql_data->metadata->dimension == 3); + fail_unless(strcmp(column_maps[0].field_name, "COLUMN1") == 0); + fail_unless(strcmp(column_maps[1].field_name, "COLUMN2") == 0); + fail_unless(strcmp(column_maps[2].field_name, "COLUMN3") == 0); + fail_unless(column_maps[0].field_name_len == 7); + fail_unless(column_maps[1].field_name_len == 7); + fail_unless(column_maps[2].field_name_len == 7); + fail_unless(strcmp(column_maps[0].field_type, "unsigned") == 0); + fail_unless(strcmp(column_maps[1].field_type, "string") == 0); + fail_unless(strcmp(column_maps[2].field_type, "double") == 0); + fail_unless(column_maps[0].field_type_len == 8); + fail_unless(column_maps[1].field_type_len == 6); + fail_unless(column_maps[2].field_type_len == 6); + fail_unless(column_maps[0].span_len == 0); + fail_unless(column_maps[1].span_len == 0); + fail_unless(column_maps[2].span_len == 0); + } + + + TEST_CASE("full metadata"); + /* Setting full metadata */ + uint32_t space_id = 380; + std::tuple key = std::make_tuple("sql_full_metadata"); + std::tuple op1 = std::make_tuple("=", "value", true); + rid_t set_full_metadata = conn.space[space_id].update(key, std::make_tuple(op1)); + client.wait(conn, set_full_metadata, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(set_full_metadata)); + + rid_t new_select = conn.execute("SELECT * FROM testing_sql;", std::make_tuple()); + + client.wait(conn, new_select, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(new_select)); + response = conn.getResponse(new_select); + fail_unless(response != std::nullopt); + fail_unless(response->body.data != std::nullopt); + fail_unless(response->body.data->dimension == 5); + + printResponse(conn, *response); + + std::vector& column_maps = response->body.data->sql_data->metadata->column_maps; + fail_unless(response->body.data->sql_data->metadata != std::nullopt); + fail_unless(response->body.data->sql_data->metadata->dimension == 3); + fail_unless(strcmp(column_maps[0].field_name, "COLUMN1") == 0); + fail_unless(strcmp(column_maps[1].field_name, "COLUMN2") == 0); + fail_unless(strcmp(column_maps[2].field_name, "COLUMN3") == 0); + fail_unless(column_maps[0].field_name_len == 7); + fail_unless(column_maps[1].field_name_len == 7); + fail_unless(column_maps[2].field_name_len == 7); + fail_unless(strcmp(column_maps[0].field_type, "unsigned") == 0); + fail_unless(strcmp(column_maps[1].field_type, "string") == 0); + fail_unless(strcmp(column_maps[2].field_type, "double") == 0); + fail_unless(column_maps[0].field_type_len == 8); + fail_unless(column_maps[1].field_type_len == 6); + fail_unless(column_maps[2].field_type_len == 6); + // fail_unless(column_maps[0].span_len == 0); + // fail_unless(column_maps[1].span_len == 0); + // fail_unless(column_maps[2].span_len == 0); + + TEST_CASE("prepare"); + rid_t pr_select = conn.prepare("SELECT * FROM testing_sql;"); + + client.wait(conn, pr_select, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(pr_select)); + response = conn.getResponse(pr_select); + fail_unless(response != std::nullopt); + fail_unless(response->body.data->sql_data->stmt_id != std::nullopt); + fail_unless(response->body.data->sql_data->bind_count != std::nullopt); + if (response->body.error_stack != std::nullopt) { + std::cout << response->body.error_stack->error.msg << std::endl; + } + printResponse(conn, *response); + + TEST_CASE("execute SELECT by stmt_id"); + rid_t exec_by_id = conn.execute(*response->body.data->sql_data->stmt_id, std::make_tuple()); + + client.wait(conn, exec_by_id, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(exec_by_id)); + response = conn.getResponse(exec_by_id); + fail_unless(response != std::nullopt); + fail_unless(response->body.data != std::nullopt); + fail_unless(response->body.data->sql_data->metadata != std::nullopt); + + printResponse(conn, *response); + + TEST_CASE("prepare with binding"); + rid_t pr_insert = conn.prepare("INSERT INTO testing_sql VALUES (?, ?, ?);"); + + client.wait(conn, pr_insert, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(pr_insert)); + response = conn.getResponse(pr_insert); + fail_unless(response != std::nullopt); + fail_unless(response->body.data->sql_data->stmt_id != std::nullopt); + fail_unless(response->body.data->sql_data->bind_count != std::nullopt); + + + TEST_CASE("execute INSERT by stmt_id with binding"); + rid_t exec_by_id_ins = conn.execute(*response->body.data->sql_data->stmt_id, std::make_tuple(5, "Stranger", 7.9)); + + client.wait(conn, exec_by_id_ins, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(exec_by_id_ins)); + response = conn.getResponse(exec_by_id_ins); + fail_unless(response != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info->row_count == 1); + + + TEST_CASE("DROP TABLE"); + rid_t drop_table = conn.execute("DROP TABLE IF EXISTS testing_sql;", std::make_tuple()); + + client.wait(conn, drop_table, WAIT_TIMEOUT); + fail_unless(conn.futureIsReady(drop_table)); + response = conn.getResponse(drop_table); + fail_unless(response != std::nullopt); + fail_unless(response->body.data->sql_data->sql_info != std::nullopt); + + client.close(conn); +} + + int main() { if (cleanDir() != 0) @@ -586,6 +802,7 @@ int main() single_conn_upsert(client); single_conn_select(client); single_conn_call(client); + single_conn_sql_statements(client); #endif /* LibEv network provide */ using NetLibEv_t = LibevNetProvider; @@ -601,5 +818,6 @@ int main() single_conn_upsert(another_client); single_conn_select(another_client); single_conn_call(another_client); + single_conn_sql_statements(another_client); return 0; } diff --git a/test/cfg.lua b/test/cfg.lua index bf85cff06..6606c62af 100644 --- a/test/cfg.lua +++ b/test/cfg.lua @@ -1,6 +1,9 @@ box.cfg{listen = 3301, net_msg_max=10000, readahead=163200, log='tarantool.txt'} box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists=true}) +sset = box.space._session_settings +sset:update('sql_full_metadata', {{'=', 'value', true}}) + if box.space.t then box.space.t:drop() end s = box.schema.space.create('T') s:format{{name='id',type='integer'},{name='a',type='string'},{name='b',type='number'}}