Boost.MySQL is a C++11 client for the MySQL database server, based on Boost.Asio.
Why another MySQL C++ client?
- It is fully compatible with Boost.Asio and integrates well with any other library in the Boost.Asio ecosystem (like Boost.Beast).
- It supports Boost.Asio's universal asynchronous model, which means you can go asyncrhonous using callbacks, futures or coroutines (including C++20 coroutines).
- It is written in modern C++ (C++11) and takes advantage of the latest language features and standard library additions.
- It is header only.
Building
As this is a header-only library, you do not need to build it. However, as it has a bunch of dependencies, we suggest you use CMake to pull them in as you build your application.
Download Boost.MySQL and make it available to your CMake script (we suggest you use CMake's FetchContent module to do this), and then call add_subdirectory() on the Boost.MySQL root directory. This will look for all the required dependencies.
Finally, link your target against the Boost::mysql interface library, and you will be done!
Requirements
- C++11 capable compiler (tested with gcc 7.4, clang 7.0, Apple clang 11.0, MSVC 19.25).
- Boost 1.72 or higher.
- OpenSSL.
- CMake 3.13.0 or higher, if using CMake to build against the library (this is the preferred way).
- Tested with MySQL v5.7.29, MySQL v8.0.19, MariaDB v10.3 and MariaDB v10.5.
Features
- Text queries (execution of text SQL queries and data retrieval). MySQL refers to this as the "text protocol", as all information is passed using text (as opposed to prepared statements, see below).
- Prepared statements. MySQL refers to this as the "binary protocol", as the result of executing a prepared statement is sent in binary format rather than in text.
- Authentication methods (authentication plugins): mysql_native_password and caching_sha2_password. These are the default methods in MySQL 5 and MySQL 8, respectively.
- Encrypted connections (TLS).
Tutorial
This tutorial shows an example of how to use the Boost.MySQL library. It employs synchronous functions with exceptions as error handling strategy, which is the simplest.
Tutorial code
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <iostream>
#define ASSERT(expr) \
if (!(expr)) \
{ \
std::cerr << "Assertion failed: " #expr << std::endl; \
exit(1); \
}
{
std::cout << "Employee '"
<< employee.
values()[0] <<
" " << employee.
values()[1] <<
"' earns " << employee.
values()[2] <<
" dollars yearly\n";
}
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(),
);
argv[1],
argv[2],
"boost_mysql_examples"
);
boost::asio::io_context ctx;
conn.connect(ep, params);
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
std::vector<boost::mysql::owning_row> employees = result.
fetch_all();
for (const auto& employee: employees)
{
print_employee(employee);
}
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
result = conn.query(sql);
ASSERT(result.
fields().size() == 0);
result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'");
ASSERT(rows.size() == 1);
double salary = rows[0].values()[0].get<double>();
ASSERT(salary == 10000);
conn.close();
}
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;
}
}
Database setup
You can run the db_setup.sql
file included with the example to set it up, or copy and paste the following commands:
SET NAMES utf8;
DROP DATABASE IF EXISTS boost_mysql_examples;
CREATE DATABASE boost_mysql_examples;
USE boost_mysql_examples;
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")
;
DROP USER IF EXISTS 'example_user'@'%';
CREATE USER 'example_user'@'%' IDENTIFIED WITH 'mysql_native_password';
ALTER USER 'example_user'@'%' IDENTIFIED BY 'example_password';
GRANT ALL PRIVILEGES ON boost_mysql_examples.* TO 'example_user'@'%';
FLUSH PRIVILEGES;
Examples
Query, asynchronous with callbacks
This example demonstrates issuing text queries to the server and reading results, using callbacks.
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/coroutine.hpp>
#include <boost/asio/yield.hpp>
#include <iostream>
#define ASSERT(expr) \
if (!(expr)) \
{ \
std::cerr << "Assertion failed: " #expr << std::endl; \
exit(1); \
}
{
std::cout << "Employee '"
<< employee.
values()[0] <<
" " << employee.
values()[1] <<
"' earns " << employee.
values()[2] <<
" dollars yearly\n";
}
void die_on_error(
)
{
if (err)
{
std::cerr << "Error: " << err << ": " << info.message() << std::endl;
exit(1);
}
}
class application
{
boost::asio::ip::tcp::endpoint ep;
boost::asio::io_context ctx;
public:
application(const char* username, const char* password) :
conn_params(username, password, "boost_mysql_examples"),
connection(ctx)
{
}
void start() { connect(); }
void connect()
{
die_on_error(err, additional_info);
query_employees();
});
}
void query_employees()
{
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
die_on_error(err, additional_info);
resultset = std::move(result);
die_on_error(err, additional_info);
for (const auto& employee: rows)
{
print_employee(employee);
}
update_slacker();
});
});
}
void update_slacker()
{
const char* sql = "UPDATE employee SET salary = 15000 WHERE last_name = 'Slacker'";
die_on_error(err, additional_info);
ASSERT(result.fields().size() == 0);
query_intern();
});
}
void query_intern()
{
const char* sql = "SELECT salary FROM employee WHERE last_name = 'Slacker'";
die_on_error(err, additional_info);
resultset = std::move(result);
die_on_error(err, additional_info);
ASSERT(rows.size() == 1);
auto salary = rows[0].values()[0].get<double>();
ASSERT(salary == 15000);
close();
});
});
}
void close()
{
die_on_error(err, additional_info);
});
}
void run() { ctx.run(); }
};
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
application app (argv[1], argv[2]);
app.start();
app.run();
}
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;
}
}
Query, asynchronous with futures
This example demonstrates issuing text queries to the server and reading results, using futures.
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/use_future.hpp>
#include <iostream>
#include <thread>
using boost::asio::use_future;
{
std::cout << "Employee '"
<< employee.
values()[0] <<
" " << employee.
values()[1] <<
"' earns " << employee.
values()[2] <<
" dollars yearly\n";
}
class application
{
boost::asio::io_context ctx_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard_;
std::thread runner_;
public:
application(): guard_(ctx_.get_executor()), runner_([this] { ctx_.run(); }) {}
application(const application&) = delete;
application(application&&) = delete;
application& operator=(const application&) = delete;
application& operator=(application&&) = delete;
~application()
{
guard_.reset();
runner_.join();
}
boost::asio::io_context& context() { return ctx_; }
};
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
application app;
boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(),
);
argv[1],
argv[2],
"boost_mysql_examples"
);
std::future<void> fut = conn.async_connect(ep, params, use_future);
fut.get();
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
std::future<boost::mysql::tcp_resultset> resultset_fut = conn.async_query(sql, use_future);
{
print_employee(*current_row);
}
conn.async_close(use_future).get();
}
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;
}
}
Query, asynchronous with coroutines
This example demonstrates issuing text queries to the server and reading results, using coroutines.
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>
{
std::cout << "Employee '"
<< employee.
values()[0] <<
" " << employee.
values()[1] <<
"' earns " << employee.
values()[2] <<
" dollars yearly\n";
}
void check_error(
const error_info& info = {}
)
{
if (err)
{
throw boost::system::system_error(err, info.message());
}
}
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
boost::asio::io_context ctx;
boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(),
);
argv[1],
argv[2],
"boost_mysql_examples"
);
boost::asio::spawn(ctx.get_executor(), [&conn, ep, params](boost::asio::yield_context yield) {
conn.async_connect(ep, params, additional_info, yield[ec]);
check_error(ec);
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
check_error(ec, additional_info);
while (true)
{
check_error(ec, additional_info);
if (!row) break;
print_employee(*row);
}
conn.async_close(additional_info, yield[ec]);
check_error(ec, additional_info);
});
ctx.run();
}
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;
}
}
Query, asynchronous with C++20 coroutines
This example demonstrates issuing text queries to the server and reading results, using C++20 coroutines (boost::asio::use_awaitable).
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/use_future.hpp>
#include <iostream>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
{
std::cout << "Employee '"
<< employee.
values()[0] <<
" " << employee.
values()[1] <<
"' earns " << employee.
values()[2] <<
" dollars yearly\n";
}
class application
{
boost::asio::io_context ctx_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard_;
std::thread runner_;
public:
application(): guard_(ctx_.get_executor()), runner_([this] { ctx_.run(); }) {}
application(const application&) = delete;
application(application&&) = delete;
application& operator=(const application&) = delete;
application& operator=(application&&) = delete;
~application()
{
guard_.reset();
runner_.join();
}
boost::asio::io_context& context() { return ctx_; }
};
using base_executor_type = boost::asio::io_context::executor_type;
using coro_executor_type = boost::asio::use_awaitable_t<
base_executor_type>::executor_with_default<base_executor_type>;
boost::asio::awaitable<void, base_executor_type> start_query(
const boost::asio::io_context::executor_type& ex,
const boost::asio::ip::tcp::endpoint& ep,
)
{
connection_type conn (ex);
co_await conn.async_connect(ep, params);
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
auto result = co_await conn.async_query(sql);
{
print_employee(*row);
}
co_await conn.async_close();
}
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
application app;
boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(),
);
argv[1],
argv[2],
"boost_mysql_examples"
);
auto executor = app.context().get_executor();
std::promise<void> prom;
boost::asio::co_spawn(executor, [executor, ep, params] {
return start_query(executor, ep, params);
}, [&prom](std::exception_ptr err) {
prom.set_exception(std::move(err));
});
prom.get_future().get();
}
#else
void main_impl(int, char**)
{
std::cout << "Sorry, your compiler does not support C++20 coroutines" << std::endl;
}
#endif
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;
}
}
Prepared statements
This example demonstrates preparing statements, executing them and reading back the results. It employs synchronous functions.
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <iostream>
#define ASSERT(expr) \
if (!(expr)) \
{ \
std::cerr << "Assertion failed: " #expr << std::endl; \
exit(1); \
}
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
boost::asio::ip::tcp::endpoint ep (
boost::asio::ip::address_v4::loopback(),
);
argv[1],
argv[2],
"boost_mysql_examples"
);
boost::asio::io_context ctx;
conn.connect(ep, params);
const char* salary_getter_sql = "SELECT salary FROM employee WHERE first_name = ?";
const char* salary_updater_sql = "UPDATE employee SET salary = ? WHERE first_name = ?";
std::vector<boost::mysql::owning_row> salaries = result.fetch_all();
ASSERT(salaries.size() == 1);
double salary = salaries[0].values().at(0).get<double>();
std::cout << "The salary before the payrise was: " << salary << std::endl;
salary_updater.
execute(boost::mysql::make_values(35000,
"Efficient"));
result = salary_getter.
execute(boost::mysql::make_values(
"Efficient"));
salaries = result.fetch_all();
ASSERT(salaries.size() == 1);
salary = salaries[0].values().at(0).get<double>();
ASSERT(salary == 35000);
std::cout << "The salary after the payrise was: " << salary << std::endl;
conn.close();
}
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;
}
}
Metadata
This example demonstrates inspecting the metadata of a resultset.
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <iostream>
#define ASSERT(expr) \
if (!(expr)) \
{ \
std::cerr << "Assertion failed: " #expr << std::endl; \
exit(1); \
}
void main_impl(int argc, char** argv)
{
if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <username> <password>\n";
exit(1);
}
boost::asio::io_context ctx;
conn.connect(ep, params);
const char* sql = R"(
SELECT comp.name AS company_name, emp.id AS employee_id
FROM employee emp
JOIN company comp ON (comp.id = emp.company_id)
)";
ASSERT(result.
fields().size() == 2);
ASSERT(company_name.database() == "boost_mysql_examples");
ASSERT(company_name.table() == "comp");
ASSERT(company_name.original_table() == "company");
ASSERT(company_name.field_name() == "company_name");
ASSERT(company_name.original_field_name() == "name");
ASSERT(!company_name.is_primary_key());
ASSERT(!company_name.is_auto_increment());
ASSERT(company_name.is_not_null());
ASSERT(employee_id.database() == "boost_mysql_examples");
ASSERT(employee_id.table() == "emp");
ASSERT(employee_id.original_table() == "employee");
ASSERT(employee_id.field_name() == "employee_id");
ASSERT(employee_id.original_field_name() == "id");
ASSERT(employee_id.is_primary_key());
ASSERT(employee_id.is_auto_increment());
ASSERT(employee_id.is_not_null());
conn.close();
}
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;
}
}
UNIX domain sockets
This example demonstrates connecting to a MySQL server over a UNIX domain socket. It employs synchronous functions with exceptions.
#include "boost/mysql/mysql.hpp"
#include <boost/asio/io_context.hpp>
#include <boost/system/system_error.hpp>
#include <iostream>
{
std::cout << "Employee '"
<< employee.
values()[0] <<
" " << employee.
values()[1] <<
"' earns " << employee.
values()[2] <<
" dollars yearly\n";
}
#define ASSERT(expr) \
if (!(expr)) \
{ \
std::cerr << "Assertion failed: " #expr << std::endl; \
exit(1); \
}
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
void main_impl(int argc, char** argv)
{
if (argc != 3 && argc != 4)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> [<socket-path>]\n";
exit(1);
}
const char* socket_path = "/var/run/mysqld/mysqld.sock";
if (argc == 4)
{
socket_path = argv[3];
}
boost::asio::local::stream_protocol::endpoint ep (socket_path);
argv[1],
argv[2],
"boost_mysql_examples"
);
boost::asio::io_context ctx;
conn.connect(ep, params);
const char* sql = "SELECT first_name, last_name, salary FROM employee WHERE company_id = 'HGS'";
std::vector<boost::mysql::owning_row> employees = result.
fetch_all();
for (const auto& employee: employees)
{
print_employee(employee);
}
sql = "UPDATE employee SET salary = 10000 WHERE first_name = 'Underpaid'";
result = conn.query(sql);
ASSERT(result.
fields().size() == 0);
result = conn.query("SELECT salary FROM employee WHERE first_name = 'Underpaid'");
ASSERT(rows.size() == 1);
double salary = rows[0].values()[0].get<double>();
ASSERT(salary == 10000);
conn.close();
}
#else
void main_impl(int, char**)
{
std::cout << "Sorry, your system does not support UNIX sockets" << std::endl;
}
#endif
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;
}
}