From 331df43725e1c851e4d4286be22a265dda49fb54 Mon Sep 17 00:00:00 2001 From: ruben Date: Fri, 10 Jan 2020 15:32:01 +0000 Subject: [PATCH] Added sync examples --- CMakeLists.txt | 5 +- TODO.txt | 2 + examples/CMakeLists.txt | 15 +++++ examples/db_setup.sql | 35 ++++++++++ examples/query_async.cpp | 10 +++ examples/query_sync.cpp | 122 +++++++++++++++++++++++++++++++++++ examples/run_examples.sh | 6 ++ include/mysql/connection.hpp | 6 ++ include/mysql/resultset.hpp | 5 +- 9 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 examples/CMakeLists.txt create mode 100644 examples/db_setup.sql create mode 100644 examples/query_async.cpp create mode 100644 examples/query_sync.cpp create mode 100755 examples/run_examples.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c4a84ab..9083c1d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ find_package(Threads REQUIRED) find_package(OpenSSL REQUIRED) set(CMAKE_CXX_STANDARD 17) +enable_testing() # Date FetchContent_Declare( @@ -42,6 +43,9 @@ target_include_directories( ${date_SOURCE_DIR}/include ) +# Examples +add_subdirectory(examples) + # Unit testing # TODO: build tests just when required # TODO: fetch gtest and gmock @@ -49,7 +53,6 @@ target_include_directories( # without runtime going nuts? find_package(GTest REQUIRED) find_library(GMOCK_LIBRARY gmock PATHS ${GTEST_ROOT}/lib) -enable_testing() add_executable( unittests test/serialization.cpp diff --git a/TODO.txt b/TODO.txt index 8c6367c0..438d85d1 100644 --- a/TODO.txt +++ b/TODO.txt @@ -16,6 +16,7 @@ Handshake SSL compression Usability + Incomplete query reads: how does this affect further queries? Metadata in rows: being able to index by name Errors Error code descriptions @@ -27,6 +28,7 @@ Usability Decimal Bit Geometry + UNIX socket connection Consider if header-only is a good idea Technical debt Concept checking diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 00000000..fe2eed00 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,15 @@ + +add_executable( + example_query_sync + query_sync.cpp +) +target_link_libraries( + example_query_sync + PRIVATE + mysql_asio +) + +add_test( + NAME example_query_sync + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run_examples.sh +) \ No newline at end of file diff --git a/examples/db_setup.sql b/examples/db_setup.sql new file mode 100644 index 00000000..9e919225 --- /dev/null +++ b/examples/db_setup.sql @@ -0,0 +1,35 @@ +-- Connection system variables +SET NAMES utf8; + +-- Database +DROP DATABASE IF EXISTS mysql_asio_examples; +CREATE DATABASE mysql_asio_examples; +USE mysql_asio_examples; + +-- Tables +CREATE TABLE company( + id CHAR(10) NOT NULL PRIMARY KEY, + name VARCHAR(100) NOT NULL +); +CREATE TABLE employee( + id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + salary DOUBLE, + company_id CHAR(10) NOT NULL, + FOREIGN KEY (company_id) REFERENCES company(id) +); + +INSERT INTO company (name, id) VALUES + ("Award Winning Company, Inc.", "AWC"), + ("Sector Global Leader Plc", "SGL"), + ("High Growth Startup, Ltd", "HGS") +; +INSERT INTO employee (first_name, last_name, salary, company_id) VALUES + ("Efficient", "Developer", 30000, "AWC"), + ("Lazy", "Manager", 80000, "AWC"), + ("Good", "Team Player", 35000, "HGS"), + ("Enormous", "Slacker", 45000, "SGL"), + ("Coffee", "Drinker", 30000, "HGS"), + ("Underpaid", "Intern", 15000, "AWC") +; \ No newline at end of file diff --git a/examples/query_async.cpp b/examples/query_async.cpp new file mode 100644 index 00000000..9836dcb7 --- /dev/null +++ b/examples/query_async.cpp @@ -0,0 +1,10 @@ +/* + * query_async.cpp + * + * Created on: Jan 9, 2020 + * Author: ruben + */ + + + + diff --git a/examples/query_sync.cpp b/examples/query_sync.cpp new file mode 100644 index 00000000..ffab2398 --- /dev/null +++ b/examples/query_sync.cpp @@ -0,0 +1,122 @@ + +#include "mysql/connection.hpp" +#include +#include +#include + +/** + * For this example, we will be using the 'mysql_asio_examples' database. + * You can get this database by running db_setup.sql. + * This example assumes you are connecting to a localhost MySQL server. + * + * This example uses synchronous functions and handles errors using exceptions. + */ + +/** + * Prints an employee to std::cout. An employee here is a mysql::row, + * which represents a row returned by a SQL query. You can access the values in + * the row using row::values(), which returns a vector of mysql::value. + * + * mysql::value represents a single value returned by MySQL, and is defined to be + * a std::variant of all the types MySQL supports. + * + * row::values() has the same number of elements as fields are in the SQL query, + * and in the same order. + */ +void print_employee(const mysql::row& employee) +{ + using mysql::operator<<; // Required for mysql::value objects to be streamable, due to ADL rules + std::cout << "Employee '" + << employee.values()[0] << " " // first_name (type std::string_view) + << employee.values()[1] << "' earns " // last_name (type std::string_view) + << employee.values()[2] << " dollars yearly\n"; // salary (type double) +} + +void main_impl(int argc, char** argv) +{ + if (argc != 3) + { + std::cerr << "Usage: " << argv[0] << " \n"; + exit(1); + } + + /** + * Connection parameters that tell us where and how to connect to the MySQL server. + * There are two types of parameters: + * - TCP-level connection parameters, identifying the host and port to connect to. + * - MySQL level parameters: database credentials and schema to use. + */ + boost::asio::ip::tcp::endpoint ep ( + boost::asio::ip::address_v4::loopback(), // host + mysql::default_port // port + ); + mysql::connection_params params ( + argv[1], // username + argv[2], // password + "mysql_asio_examples" // database to use; leave empty or omit the parameter for no database + ); + + boost::asio::io_context ctx; + + /** + * Represents a single connection over TCP to a MySQL server. + * Before being able to use it, you have to connect to the server by: + * - Establishing the TCP-level session. + * - Authenticating to the MySQL server. + */ + mysql::tcp_connection conn (ctx); + conn.next_level().connect(ep); // next_level() returns a boost::asio::ip::tcp::socket + conn.handshake(params); // Authenticates to the MySQL server + + /** + * To issue a SQL query to the database server, use tcp_connection::query, which takes + * the SQL to be executed as parameter and returns a resultset object. + * + * Resultset objects represent the result of a query, in tabular format. + * They hold metadata describing the fields the resultset holds (in this case, first_name, + * last_name and salary). To get the actual data, use fetch_one, fetch_many or fetch_all. + * We will use fetch_all, which returns all the received rows as a std::vector. + * + * We will get all employees working for 'High Growth Startup'. + */ + const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'"; + mysql::tcp_resultset result = conn.query(sql); + + // Get all the rows in the resultset + std::vector employees = result.fetch_all(); + for (const auto& employee: employees) + { + print_employee(employee); + } + + // We can issue any SQL statement, not only SELECTs. In this case, the returned + // resultset will have no fields and no rows + sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'"; + result = conn.query(sql); + assert(result.fields().size() == 0); // fields() returns a vector containing metadata about the query fields + + // Check we have updated our poor intern salary + result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'"); + auto rows = result.fetch_all(); + assert(rows.size() == 1); + auto salary = std::get(rows[0].values()[0]); + assert(salary == 10000); +} + +int main(int argc, char** argv) +{ + try + { + main_impl(argc, argv); + } + catch (const boost::system::system_error& err) + { + std::cerr << "Error: " << err.what() << ", error code: " << err.code() << std::endl; + return 1; + } + catch (const std::exception& err) + { + std::cerr << "Error: " << err.what() << std::endl; + return 1; + } +} diff --git a/examples/run_examples.sh b/examples/run_examples.sh new file mode 100755 index 00000000..91b33f91 --- /dev/null +++ b/examples/run_examples.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" + +mysql -u root -proot < $SCRIPTPATH/db_setup.sql +./example_query_sync root root \ No newline at end of file diff --git a/include/mysql/connection.hpp b/include/mysql/connection.hpp index 8e4ea703..3f9095fe 100644 --- a/include/mysql/connection.hpp +++ b/include/mysql/connection.hpp @@ -6,6 +6,7 @@ #include "mysql/impl/basic_types.hpp" #include "mysql/error.hpp" #include "mysql/resultset.hpp" +#include namespace mysql { @@ -61,6 +62,11 @@ public: async_query(std::string_view query_string, CompletionToken&& token); }; +using tcp_connection = connection; +// TODO: UNIX socket connection + +constexpr unsigned short default_port = 3306; + } #include "mysql/impl/connection.ipp" diff --git a/include/mysql/resultset.hpp b/include/mysql/resultset.hpp index 87fd6932..04c42d72 100644 --- a/include/mysql/resultset.hpp +++ b/include/mysql/resultset.hpp @@ -5,6 +5,7 @@ #include "mysql/metadata.hpp" #include "mysql/impl/messages.hpp" #include "mysql/impl/channel.hpp" +#include #include namespace mysql @@ -59,8 +60,10 @@ public: // TODO: status flags accessors }; +using tcp_resultset = resultset; + } #include "mysql/impl/resultset.hpp" -#endif \ No newline at end of file +#endif