| Home | Libraries | People | FAQ | More

## The static interface

We have already covered the static interface basics in the tutorials. This section expands on this topic.

To use the static interface, we must first define a data structure that describes the shape of our rows. We have several options:

- Use Boost.Describe to annotate a plain `struct` with `BOOST_DESCRIBE_STRUCT` to enable reflection on it.
- Use Boost.Pfr and `pfr_by_name` or `pfr_by_position` to use PFR automatic reflection capabilities.
- Use `std::tuple`.

We will work with the following table:

    CREATE TABLE employee(
        id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
        first_name VARCHAR(100) NOT NULL,
        last_name VARCHAR(100) NOT NULL,
        salary INT UNSIGNED,
        company_id CHAR(10) NOT NULL,
        FOREIGN KEY (company_id) REFERENCES company(id)
    );

| Note
For simplicity, code snippets use C++20 coroutines. You can use the techniques described here with lower C++ standards by using sync functions, callbacks or `asio::yield_context`. The comparison table shows the C++ standard required by each technique shown here.

Let's start with Boost.Describe. If we want to query the first three fields, we can define our row type like this:

    // We can use a plain struct with ints and strings to describe our rows.
    struct employee_v1
    {
        int id;
        std::string first_name;
        std::string last_name;
    };

    // This must be placed at the namespace level. It adds reflection capabilities to our struct
    BOOST_DESCRIBE_STRUCT(employee_v1, (), (id, first_name, last_name))

And write the following to query our table:

    mysql::static_results<employee_v1> result;
    co_await conn.async_execute("SELECT id, first_name, last_name FROM employee LIMIT 10", result);

    for (const employee_v1& emp : result.rows())
    {
        // Process the employee as required
        std::cout << "ID: " << emp.id << ": " << emp.first_name << ' ' << emp.last_name << "\n";
    }

Note that `static_results::rows` returns a `boost::span` object, which is a C++11 backport of `std::span`. The span points into memory owned by the `static_results` object. Care must be taken not to use this view object after the `static_results` goes out of scope.

#### Field matching

Columns in the query are matched to fields in the struct by name. If a struct field cannot be matched to any query column, an error is issued. Extra columns in the query are ignored.

If your query contains columns with names that don't qualify as C++ identifiers, you can use SQL aliases. For example, given this struct:

    struct statistics
    {
        std::string company;
        double average;
        double max_value;
    };
    BOOST_DESCRIBE_STRUCT(statistics, (), (company, average, max_value))

You can write your query as:

    // Summing 0e0 is MySQL way to cast a DECIMAL field to DOUBLE
    constexpr const char* sql = R"%(
        SELECT
            IFNULL(AVG(salary), 0.0) + 0e0 AS average,
            IFNULL(MAX(salary), 0.0) + 0e0 AS max_value,
            company_id AS company
        FROM employee
        GROUP BY company_id
    )%";

    mysql::static_results<statistics> result;
    co_await conn.async_execute(sql, result);

#### Metadata checking

The static interface will try to validate as soon as possible that the provided row type is compatible with the schema returned by the server. This process is known as metadata checking, and is performed before reading any data. The following checks are performed:

- Type compatibility: the C++ type must be able to represent any value that the MySQL type can represent. For example, `std::int32_t` is compatible with `TINYINT` (1 byte integer), but not with `BIGINT` (8 byte integer). For a full list of allowable field types, refer to this table.
- Nullability: if MySQL reports that a column can be `NULL`, your type must account for it. You can use `std::optional<T>` or `boost::optional<T>` for these columns.

Let's add the `salary` field to our employee query. We might try the following:

    // If we try to query the employee table with this struct definition,
    // an error will be issued because salary can be NULL in the database,
    // but not in the C++ type
    struct employee_v2
    {
        int id;
        std::string first_name;
        std::string last_name;
        unsigned salary;
    };
    BOOST_DESCRIBE_STRUCT(employee_v2, (), (id, first_name, last_name, salary))

However, this won't work because of the nullability check. In this case, the correct definition would be:

    struct employee_v3
    {
        int id;
        std::string first_name;
        std::string last_name;
        std::optional<unsigned> salary;  // salary might be NULL in the database
    };
    BOOST_DESCRIBE_STRUCT(employee_v3, (), (id, first_name, last_name, salary))

#### Using Boost.PFR

If you're using C++20 or above, you can use Boost.PFR to reflect types without the `BOOST_DESCRIBE_STRUCT` macro:

    // employee_v4 doesn't contain any metadata - we're not using BOOST_DESCRIBE_STRUCT here
    struct employee_v4
    {
        int id;
        std::string first_name;
        std::string last_name;
        std::optional<unsigned> salary;
    };

PFR reflection can be enabled in Boost.MySQL by using `pfr_by_name`:

    // pfr_by_name is a marker type. It tells static_results to use
    // Boost.PFR for reflection, instead of Boost.Describe.
    mysql::static_results<mysql::pfr_by_name<employee_v4>> result;

    // As with Boost.Describe, query fields are matched to struct
    // members by name. This means that the fields in the query
    // may appear in any order.
    co_await conn.async_execute("SELECT * FROM employee LIMIT 10", result);

    // Note that result.rows() is a span of employee_v4 objects,
    // rather than pfr_by_name<employee_v4> objects. employee_v4
    // is the underlying row type for pfr_by_name<employee_v4>
    for (const employee_v4& emp : result.rows())
    {
        // Process the employee as required
        std::cout << "ID: " << emp.id << ": " << emp.first_name << ' ' << emp.last_name << "\n";
    }

Note that `pfr_by_name` is what we call a marker type - an empty type that tells classes like `static_results` how to reflect a type. If no marker type is used, Boost.Describe is used to retrieve reflection data for struct types.

`pfr_by_position` is similar to `pfr_by_name`, but will match columns in the query to struct fields by position, rather than name. It only requires C++17 to work. For instance:

    // pfr_by_position is another marker type.
    // Fields in employee_v4 must appear in the same order as in the query,
    // as matching will be done by position.
    mysql::static_results<mysql::pfr_by_position<employee_v4>> result;
    co_await conn.async_execute("SELECT id, first_name, last_name, salary FROM employee", result);

    // The underlying row type is employee_v4
    for (const employee_v4& emp : result.rows())
    {
        // Process the employee as required
        std::cout << "ID: " << emp.id << ": " << emp.first_name << ' ' << emp.last_name << "\n";
    }

Please refer to this table for a comparison with Boost.Describe.

#### Using tuples

You can also use `std::tuple`s as row types. This can be handy for simple queries:

    mysql::static_results<std::tuple<std::int64_t>> result;
    co_await conn.async_execute("SELECT COUNT(*) FROM employee", result);
    std::cout << "Number of employees: " << std::get<0>(result.rows()[0]) << "\n";

Fields in tuples are matched to query columns by order. The query must return as many columns as fields the tuple has, at least. Any extra trailing columns in the query are ignored.

#### Multi-resultset and multi-function operations

You can use both with the dynamic interface. Please refer to the sections on multi-resultset operations and multi-function operations for more information.

#### Reflection techniques comparison

Should I use Boost.Describe, Boost.PFR or tuples? Each one has its advantages and drawbacks. This table may help you decide:

Technique | Sample code | Minimum C++ standard | Comments | Feature test macro
--- | --- | --- | --- | ---
Boost.Describe |
    // Definition should be at namespace scope
    struct employee
    {
        int id;
        std::string first_name;
        std::string last_name;
    };
    BOOST_DESCRIBE_STRUCT(employee, (), (id, first_name, last_name))
    // Usage
    mysql::static_results<employee> result;
    conn.execute("SELECT first_name, last_name, id FROM employee", result);
| C++14 | Requires adding metadata with `BOOST_DESCRIBE_STRUCT`. Matches fields by name. No limitations placed on the row type (e.g. works for structs using inheritance). | `BOOST_MYSQL_CXX14` is defined
Boost.PFR using names |
    // Definition should be at namespace scope
    struct employee
    {
        int id;
        std::string first_name;
        std::string last_name;
    };
    // Usage
    mysql::static_results<mysql::pfr_by_name<employee>> result;
    conn.execute("SELECT first_name, last_name, id FROM employee", result);
| C++20 | Doesn't require adding metadata to structs. Matches fields by name. Works for row types satisfying `SimpleAggregate`. | `BOOST_PFR_CORE_NAME_ENABLED` is defined and set to `1`
Boost.PFR using field position |
    // Definition should be at namespace scope
    struct employee
    {
        int id;
        std::string first_name;
        std::string last_name;
    };
    // Usage
    mysql::static_results<mysql::pfr_by_position<employee>> result;
    conn.execute("SELECT id, first_name, last_name FROM employee", result);
| C++17 C++14 with limitations | Doesn't require adding metadata to structs. Matches fields by position. In C++17 mode, it works for row types satisfying `SimpleAggregate`. In C++14 mode, it may not work for rows containing certain field types, like strings. See Boost.PFR documentation on C++14 limitations. | `BOOST_PFR_ENABLED` is defined and set to `1`. `BOOST_PFR_USE_CPP17` is defined and set to `1` for C++17 mode.
Standard tuples |
    using tuple_t = std::tuple<int, std::string, std::string>;
    mysql::static_results<tuple_t> result;
    conn.execute("SELECT id, first_name, last_name FROM employee", result);
| C++14 | Should only be used for very simple queries. Matches fields by position. | `BOOST_MYSQL_CXX14` is defined

Note that using the static interface always requires C++14, at least. The `BOOST_MYSQL_CXX14` test macro is defined only if the static interface is supported. Including the static interface headers on an unsupported compiler doesn't cause any error, but classes like `static_results` and `static_execution_state` are not defined. The test macro is brought on scope by any of the static interface headers.

#### Allowed field types

All the types used within your Describe structs or tuples must be within the following table. A Describe struct or tuple composed of valid field types models the `StaticRow` concept.

The following table is a reference of the C++ types that can be used in a `StaticRow` and their compatibility with MySQL database types:

C++ type | Compatible with...
--- | ---
`std::int8_t` | `TINYINT`
`std::uint8_t` | `TINYINT` `UNSIGNED`
`std::int16_t` | `TINYINT` `TINYINT` `UNSIGNED` `SMALLINT` `YEAR`
`std::uint16_t` | `TINYINT` `UNSIGNED` `SMALLINT` `UNSIGNED` `YEAR`
`std::int32_t` | `TINYINT`, `TINYINT` `UNSIGNED` `SMALLINT`, `SMALLINT` `UNSIGNED` `MEDIUMINT`, `MEDIUMINT` `UNSIGNED` `INT` `YEAR`
`std::uint32_t` | `TINYINT` `UNSIGNED` `SMALLINT` `UNSIGNED` `MEDIUMINT` `UNSIGNED` `INT` `UNSIGNED` `YEAR`
`std::int64_t` | `TINYINT`, `TINYINT` `UNSIGNED` `SMALLINT`, `SMALLINT` `UNSIGNED` `MEDIUMINT`, `MEDIUMINT` `UNSIGNED` `INT`, `INT` `UNSIGNED` `BIGINT` `YEAR`
`std::uint64_t` | `TINYINT` `UNSIGNED` `SMALLINT` `UNSIGNED` `MEDIUMINT` `UNSIGNED` `INT` `UNSIGNED` `BIGINT` `UNSIGNED` `YEAR` `BIT`
`bool` | `BOOL` or `BOOLEAN` (alias for `TINYINT`).
`float` | `FLOAT`
`double` | `FLOAT`, `DOUBLE`
`date` | `DATE`
`datetime` | `DATETIME`, `TIMESTAMP`
`time` | `TIME`
`std::basic_string<char, std::char_traits<char>, Allocator>` The object must be default-constructible. | `CHAR`, `VARCHAR`, `TEXT` `ENUM`, `SET` `JSON` `DECIMAL`/`NUMERIC`
`std::basic_vector<unsigned char, Allocator>` The object must be default-constructible. | `BINARY`, `VARBINARY`, `BLOB` `GEOMETRY`
`std::optional<T>` `T` must be any of the types listed in this table. | Any type compatible with `T`
`boost::optional<T>` `T` must be any of the types listed in this table. | Any type compatible with `T`