// // Copyright (c) 2019-2020 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "boost/mysql/row.hpp" #include "integration_test_common.hpp" #include "test_common.hpp" using namespace boost::mysql::test; using boost::mysql::error_code; using boost::mysql::ssl_mode; using boost::mysql::connection; using boost::mysql::resultset; using boost::mysql::row; BOOST_AUTO_TEST_SUITE(test_resultset) // Helpers template std::vector makerows(std::size_t row_size, Types&&... args) { auto values = make_value_vector(std::forward(args)...); assert(values.size() % row_size == 0); std::vector res; for (std::size_t i = 0; i < values.size(); i += row_size) { std::vector row_values ( values.begin() + i, values.begin() + i + row_size); res.push_back(row(std::move(row_values), {})); } return res; } bool operator==(const std::vector& lhs, const std::vector& rhs) { return boost::mysql::detail::container_equals(lhs, rhs); } template void validate_eof( const resultset& result, int affected_rows=0, int warnings=0, int last_insert=0, boost::string_view info="" ) { BOOST_TEST_REQUIRE(result.valid()); BOOST_TEST_REQUIRE(result.complete()); BOOST_TEST(result.affected_rows() == affected_rows); BOOST_TEST(result.warning_count() == warnings); BOOST_TEST(result.last_insert_id() == last_insert); BOOST_TEST(result.info() == info); } // Interface to generate a resultset template class resultset_generator { public: virtual ~resultset_generator() {} virtual const char* name() const = 0; virtual resultset generate(connection&, boost::string_view) = 0; }; template class text_resultset_generator : public resultset_generator { public: const char* name() const override { return "text"; } resultset generate(connection& conn, boost::string_view query) override { return conn.query(query); } }; template class binary_resultset_generator : public resultset_generator { public: const char* name() const override { return "binary"; } resultset generate(connection& conn, boost::string_view query) override { return conn.prepare_statement(query).execute(boost::mysql::no_statement_params); } }; // Sample type template struct resultset_sample { network_functions* net; ssl_mode ssl; resultset_generator* gen; resultset_sample(network_functions* funs, ssl_mode ssl, resultset_generator* gen): net(funs), ssl(ssl), gen(gen) { } void set_test_attributes(boost::unit_test::test_case& test) const { test.add_label(net->name()); test.add_label(to_string(ssl)); } }; template std::ostream& operator<<(std::ostream& os, const resultset_sample& input) { return os << input.net->name() << '_' << to_string(input.ssl) << '_' << input.gen->name(); } struct sample_gen { template static std::vector> make_all() { static text_resultset_generator text_obj; static binary_resultset_generator binary_obj; resultset_generator* all_resultset_generators [] = { &text_obj, &binary_obj }; ssl_mode all_ssl_modes [] = { ssl_mode::disable, ssl_mode::require }; std::vector> res; for (auto* net: all_network_functions()) { for (auto ssl: all_ssl_modes) { for (auto* gen: all_resultset_generators) { res.emplace_back(net, ssl, gen); } } } return res; } template static const std::vector>& generate() { static auto res = make_all(); return res; } }; BOOST_AUTO_TEST_SUITE(read_one) // Verify read_one clears its paremeter correctly static row make_initial_row() { return row(make_value_vector(10), {}); } BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM empty_table"); BOOST_TEST(result.valid()); BOOST_TEST(!result.complete()); BOOST_TEST(result.fields().size() == 2); // Already in the end of the resultset, we receive the EOF row r = make_initial_row(); auto row_result = sample.net->read_one(result, r); row_result.validate_no_error(); BOOST_TEST(!row_result.value); BOOST_TEST(r == row()); validate_eof(result); // Fetching again just returns null r = make_initial_row(); row_result = sample.net->read_one(result, r); row_result.validate_no_error(); BOOST_TEST(!row_result.value); BOOST_TEST(r == row()); validate_eof(result); } BOOST_MYSQL_NETWORK_TEST(one_row, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM one_row_table"); BOOST_TEST(result.valid()); BOOST_TEST(!result.complete()); BOOST_TEST(result.fields().size() == 2); // Fetch only row row r = make_initial_row(); auto row_result = sample.net->read_one(result, r); row_result.validate_no_error(); this->validate_2fields_meta(result, "one_row_table"); BOOST_TEST(row_result.value); BOOST_TEST((r == makerow(1, "f0"))); BOOST_TEST(!result.complete()); // Fetch next: end of resultset r = make_initial_row(); row_result = sample.net->read_one(result, r); row_result.validate_no_error(); BOOST_TEST(!row_result.value); BOOST_TEST(r == row()); validate_eof(result); } BOOST_MYSQL_NETWORK_TEST(two_rows, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM two_rows_table"); BOOST_TEST(result.valid()); BOOST_TEST(!result.complete()); BOOST_TEST(result.fields().size() == 2); // Fetch first row row r = make_initial_row(); auto row_result = sample.net->read_one(result, r); row_result.validate_no_error(); BOOST_TEST(row_result.value); this->validate_2fields_meta(result, "two_rows_table"); BOOST_TEST((r == makerow(1, "f0"))); BOOST_TEST(!result.complete()); // Fetch next row r = make_initial_row(); row_result = sample.net->read_one(result, r); row_result.validate_no_error(); BOOST_TEST(row_result.value); this->validate_2fields_meta(result, "two_rows_table"); BOOST_TEST((r == makerow(2, "f1"))); BOOST_TEST(!result.complete()); // Fetch next: end of resultset r = make_initial_row(); row_result = sample.net->read_one(result, r); row_result.validate_no_error(); BOOST_TEST(!row_result.value); BOOST_TEST(r == row()); validate_eof(result); } // There seems to be no real case where fetch can fail (other than net fails) BOOST_AUTO_TEST_SUITE_END() // read_one BOOST_AUTO_TEST_SUITE(read_many) BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM empty_table"); // Fetch many, but there are no results auto rows_result = sample.net->read_many(result, 10); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); // Fetch again, should return OK and empty rows_result = sample.net->read_many(result, 10); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); } BOOST_MYSQL_NETWORK_TEST(more_rows_than_count, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM three_rows_table"); // Fetch 2, one remaining auto rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); BOOST_TEST(!result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); // Fetch another two (completes the resultset) rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); validate_eof(result); BOOST_TEST((rows_result.value == makerows(2, 3, "f2"))); } BOOST_MYSQL_NETWORK_TEST(less_rows_than_count, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM two_rows_table"); // Fetch 3, resultset exhausted auto rows_result = sample.net->read_many(result, 3); rows_result.validate_no_error(); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); validate_eof(result); } BOOST_MYSQL_NETWORK_TEST(same_rows_as_count, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM two_rows_table"); // Fetch 2, 0 remaining but resultset not exhausted auto rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); BOOST_TEST(!result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); // Fetch again, exhausts the resultset rows_result = sample.net->read_many(result, 2); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); } BOOST_MYSQL_NETWORK_TEST(count_equals_one, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM one_row_table"); // Fetch 1, 1 remaining auto rows_result = sample.net->read_many(result, 1); rows_result.validate_no_error(); BOOST_TEST(!result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0"))); } BOOST_AUTO_TEST_SUITE_END() // read_many BOOST_AUTO_TEST_SUITE(read_all) BOOST_MYSQL_NETWORK_TEST(no_results, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM empty_table"); // Fetch many, but there are no results auto rows_result = sample.net->read_all(result); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); BOOST_TEST(result.complete()); // Fetch again, should return OK and empty rows_result = sample.net->read_all(result); rows_result.validate_no_error(); BOOST_TEST(rows_result.value.empty()); validate_eof(result); } BOOST_MYSQL_NETWORK_TEST(one_row, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM one_row_table"); auto rows_result = sample.net->read_all(result); rows_result.validate_no_error(); BOOST_TEST(result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0"))); } BOOST_MYSQL_NETWORK_TEST(several_rows, network_fixture, sample_gen) { this->connect(sample.ssl); auto result = sample.gen->generate(this->conn, "SELECT * FROM two_rows_table"); auto rows_result = sample.net->read_all(result); rows_result.validate_no_error(); validate_eof(result); BOOST_TEST(result.complete()); BOOST_TEST((rows_result.value == makerows(2, 1, "f0", 2, "f1"))); } BOOST_AUTO_TEST_SUITE_END() // read_all BOOST_AUTO_TEST_SUITE_END() // test_resultset