Boost.MySQL 1.87 and the new Boost citizens

Jan 6, 2025

Boost.MySQL 1.87

I already anticipated in my previous post that Boost 1.87 was going to be an exciting release for the users of Boost.MySQL. Many new features have been promoted to stable, making using the library much more enjoyable. After putting the final touches during this last month, Boost 1.87 was released on December the 12th with all these new features.

Many of these changes make frequent tasks much easier. In this post, we will review some of the recommendations that changed in this release. We suggest sticking to these for new code. Old code will keep working as expected.

Type-erased connections

any_connection is the new recommended way to open connections to MySQL. It features simpler connection establishment semantics, more functionality and lower compile times with no loss of performance. We recommend using it over tcp_ssl_connection and friends in new code. Since an example is worth a thousand words, here’s one:

// Boost 1.86
int main()
{
    // The execution context, required for all I/O operations
    asio::io_context ctx;

    // The SSL context, required for connections that use TLS.
    asio::ssl::context ssl_ctx(asio::ssl::context::tlsv12_client);

    // Construct the connection
    mysql::tcp_ssl_connection conn(ctx, ssl_ctx);

    // Resolve the hostname to get a collection of endpoints
    auto endpoints = resolver.resolve("localhost", mysql::default_port_string);

    // Parameters specifying how to perform the MySQL handshake operation.
    mysql::handshake_params params(
        "some_username",
        "some_password",
        "some_database"
    );

    // Connect to the server using the first endpoint returned by the resolver
    conn.connect(*endpoints.begin(), params);
}

// Boost 1.87
int main()
{
    // The execution context, required to run I/O operations.
    asio::io_context ctx;

    // Represents a connection to the MySQL server.
    mysql::any_connection conn(ctx);

    // The hostname, username and password to use
    mysql::connect_params params {
        .server_address = mysql::host_and_port("some_host"),
        .username = "some_username",
        .password = "some_password",
        .database = "some_database",
    };

    // Connect to the server
    conn.connect(params);
}

Client-side SQL formatting and with_params

with_params can be used instead of prepared statements for one-off queries:

// Boost 1.86
void lookup(mysql::tcp_ssl_connection& conn, int id)
{
    // Prepare a statement
    mysql::statement stmt = conn.prepare_statement("SELECT * FROM user WHERE id = ?");

    // Execute it
    mysql::static_results<user> res;
    conn.execute(stmt.bind(id), res);

    // Close it
    conn.close_statement(stmt);

    // Do something with the results
}

// Boost 1.87
void lookup(mysql::any_connection& conn, int id)
{
    // Execute your query
    mysql::static_results<user> res;
    conn.execute(mysql::with_params("SELECT * FROM user WHERE id = {}", id), res);

    // Do something with the results
}

Since I already talked about this feature in my last post, I won’t delve into more detail here.

Connection pools

Might be the most requested feature in the library. Establishing sessions is costly, especially if TLS is enabled. Maintaining connections alive and reconnecting them on failure is also non-trivial. Connection pools do both for you, so you can focus on your queries, rather than on the infrastructure:

// Boost 1.87. There's no equivalent in previous versions!
int main()
{
    asio::io_context ctx;

    // pool_params contains configuration for the pool.
    mysql::pool_params params {
        .server_address = mysql::host_and_port("my_server_hostname.com");
        .username = "my_username",
        .password = "my_password",
        .database = "my_database",
    };

    // Create the pool and run it. async_run maintains the connections healthy
    mysql::connection_pool pool(ctx, std::move(params));
    pool.async_run(asio::detached);

    // ...
}

asio::awaitable<void> lookup(mysql::connection_pool& pool, int id)
{
    // Get a connection from the pool. We don't need to connect or close the connection
    mysql::pooled_connection conn = co_await pool.async_get_connection();

    // Execute your query
    mysql::static_results<user> res;
    co_await conn->async_execute(mysql::with_params("SELECT * FROM user WHERE id = {}", id), res);

    // Do something with the results
}

Built-in diagnostics in exceptions when using async functions

MySQL may produce diagnostic text when queries fail. You can get this passing a diagnostics object that will be populated on error. This, combined with Asio’s built-in error checking, made using throwing async functions cumbersome. As I already explained in my previous post, with_diagnostics and default completion tokens solve this problem:

// Boost 1.86
asio::awaitable<void> handle_request(mysql::tcp_ssl_connection& conn)
{
    mysql::results r;
    mysql::diagnostics diag;
    auto [ec] = co_await conn.async_execute("SELECT 1", r, diag, asio::as_tuple(asio::deferred));
    mysql::throw_on_error(ec, diag);
}

// Boost 1.87
asio::awaitable<void> handle_request(mysql::any_connection& conn)
{
    mysql::results r;
    co_await conn.async_execute("SELECT 1", r);
}

During these last months, I’ve polished these features. I’ve fix a myriad of small issues in connection_pool, made mysql::sequence owning (and thus easier to use as argument to with_params), and made mysql::with_diagnostics more interoperable with other tokens, like asio::as_tuple.

A new exposition for Boost.MySQL

With great power comes great responsibility. I strongly believe that these new, exciting features are almost worthless if they are not properly explained. I wrote Boost.MySQL docs some time ago, and user experience has changed my mind on how an exposition should be. I’ve re-written many of the pages for this release, making them more practical, with more examples and use cases. I’ve introduced not one, but seven tutorials, slowly walking the user through the most common MySQL (and Asio) concepts to get them up to speed. And I’ve added new examples out of questions I received on GitHub.

The old discussion used sync functions to expose library concepts, because they were by far the easiest to use. This lack of async examples led many users down to using sync functions when they shouldn’t. Now that C++20 coroutines yield clean code, I’ve re-written part of the exposition to use them, providing more guidance on async patterns. As not everyone can (or want) to use them, I’ve also added some pages on how to port C++20 coroutine code to other completion styles.

Writing all this has proven to be really time-consuming. Some might think of it as unexciting, but I hope my users will appreciate it. I’d like to thank the C++ Alliance sponsorship here, because these new docs wouldn’t be possible without it.

My next step here will be migrating the docs to Asciidoc, so they get a “younger” look and feel. This implies moving from the old Docca toolchain to an Asciidoc generator like MrDocs. I’ve had the pleasure to be an early user of the tool, and been able to provide some (hopefully useful) feedback to its authors. My thank you to Allan, Krystian and Fernando here.

New citizens in Boost: MQTT5 and Hash2

It’s been some intense months in Boost. I’ve had the pleasure to participate in three Boost reviews: a MQTT5 Asio-based library, an SQLite wrapper and a hashing library.

I’ve been specially involved in the first one, since Asio is one of my main areas of expertise. With this library, our Asio ecosystem grows, and with it, the overall usefulness of Boost.

Other contributions

One of my features that I implemented in Boost.MySQL required me to write a std::to_array backport. I’ve contributed it to Boost.Compat, with the help of its maintainer, Peter Dimov. As always, a great learning opportunity.

I’ve also helped with some maintenance tasks in Boost.Redis.

All Posts by This Author