Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved discussion and examples #381

Merged
merged 50 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
97aa48a
Made transactions/updates a tutorial
anarthal Nov 6, 2024
58695d9
Example update and tutorial outline
anarthal Nov 6, 2024
57f1a62
Full tutorial development
anarthal Nov 6, 2024
ed8043c
Tutorial fixes
anarthal Nov 6, 2024
45bc482
Fix broken links & cleanup
anarthal Nov 6, 2024
379e65d
Templated connection doc (1)
anarthal Nov 11, 2024
fcf20f9
Finished templated connection section
anarthal Nov 11, 2024
d2dd032
Templated connection fixes
anarthal Nov 11, 2024
cfee73e
Snippets to separate file
anarthal Nov 11, 2024
e3dd369
Reference fixes
anarthal Nov 11, 2024
9da24cb
Example on INSERTs
anarthal Nov 12, 2024
087332e
C++20 comment
anarthal Nov 12, 2024
c110491
Example on deletes
anarthal Nov 12, 2024
d3a2981
New charsets
anarthal Nov 14, 2024
aac17bb
Charset fixes
anarthal Nov 14, 2024
807effd
connection_pool tutorial
anarthal Nov 19, 2024
6609e29
connection_pool tutorial fixes
anarthal Nov 19, 2024
5d3c542
Python runner
anarthal Nov 19, 2024
9a304aa
cmake
anarthal Nov 19, 2024
7b1304f
jamfile
anarthal Nov 19, 2024
e6ffcd4
Error handling first approach
anarthal Nov 19, 2024
4c17339
Removed obsolete timeouts example
anarthal Nov 19, 2024
3ae3da6
Jamfile refactor
anarthal Nov 19, 2024
4cbc886
Section on completion tokens
anarthal Nov 20, 2024
9bbf393
Tutorial discussion
anarthal Nov 20, 2024
a684791
Error handling fixes
anarthal Nov 20, 2024
edba297
Rework tutorial 6 to skip section on cancellations
anarthal Nov 23, 2024
aaf69cc
Simplify timeouts
anarthal Nov 26, 2024
32713c8
Wording 1
anarthal Nov 26, 2024
e9afdc2
Final wording
anarthal Nov 26, 2024
97b5fd2
Refactor tutorial snippets and add guards
anarthal Nov 27, 2024
0d28d27
Tutorial snippets to code
anarthal Nov 27, 2024
b88c9c1
Minor fixes
anarthal Nov 27, 2024
1fae8a0
Old Python fixes
anarthal Nov 27, 2024
6491d38
Fix templated connection snippets on Windows
anarthal Nov 27, 2024
2b65713
Missing include in inserts
anarthal Nov 27, 2024
0eefe6d
Fix valgrind in examples
anarthal Nov 27, 2024
765b1f1
Workaround gcc bugs
anarthal Nov 27, 2024
447143a
Improve CI logs
anarthal Nov 27, 2024
05c7ab0
Update coverage to gcc-14
anarthal Nov 27, 2024
a2f5719
32 bit fixes
anarthal Nov 27, 2024
3b01d0d
section name mismatch
anarthal Nov 27, 2024
f41c70c
Proper gcc bug workaround
anarthal Nov 27, 2024
363b1b5
Recover reference
anarthal Nov 27, 2024
a5ad5d1
Updated tutorial listing titles
anarthal Nov 27, 2024
31a3d11
Fixed async link
anarthal Nov 27, 2024
c115f1b
Fix race condition in tutorials snippets
anarthal Nov 27, 2024
8e00492
Permissive lcov flags
anarthal Nov 27, 2024
673686c
Merge branch 'develop' into feature/366-new-disussion-2
anarthal Nov 29, 2024
cb39a2c
Comment fix in jamfile
anarthal Nov 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions doc/qbk/00_main.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
[template reflink[id][reflink2 [id] [id]]]
[template refmem[class mem][reflink2 [class].[mem] [class]::[mem]]]
[template refmemunq[class mem][reflink2 [class].[mem] [mem]]]
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^boost::asio::[term]]]]
[template asioreflink[id term][@boost:/doc/html/boost_asio/reference/[id].html [^asio::[term]]]]
[template mysqllink[id text][@https://dev.mysql.com/doc/refman/8.0/en/[id] [text]]]

[def __CompletionToken__ [@boost:/doc/html/boost_asio/reference/asynchronous_operations.html#boost_asio.reference.asynchronous_operations.completion_tokens_and_handlers ['CompletionToken]]]
Expand Down Expand Up @@ -129,9 +129,12 @@ END
[import ../../example/1_tutorial/2_async.cpp]
[import ../../example/1_tutorial/3_with_params.cpp]
[import ../../example/1_tutorial/4_static_interface.cpp]
[import ../../example/1_tutorial/5_updates_transactions.cpp]
[import ../../example/1_tutorial/6_connection_pool.cpp]
[import ../../example/1_tutorial/7_error_handling.cpp]
[import ../../example/2_simple/inserts.cpp]
[import ../../example/2_simple/deletes.cpp]
[import ../../example/2_simple/prepared_statements.cpp]
[import ../../example/2_simple/timeouts.cpp]
[import ../../example/2_simple/multi_queries_transactions.cpp]
[import ../../example/2_simple/disable_tls.cpp]
[import ../../example/2_simple/tls_certificate_verification.cpp]
[import ../../example/2_simple/metadata.cpp]
Expand Down Expand Up @@ -160,6 +163,7 @@ END
[import ../../test/integration/test/snippets/sql_formatting_custom.cpp]
[import ../../test/integration/test/snippets/multi_function.cpp]
[import ../../test/integration/test/snippets/tutorials.cpp]
[import ../../test/integration/test/snippets/templated_connection.cpp]
[import ../../test/integration/test/snippets/metadata.cpp]
[import ../../test/integration/test/snippets/connection_pool.cpp]
[import ../../test/integration/test/snippets/time_types.cpp]
Expand All @@ -178,6 +182,9 @@ END
[include 03_2_tutorial_async.qbk]
[include 03_3_tutorial_with_params.qbk]
[include 03_4_tutorial_static_interface.qbk]
[include 03_5_tutorial_updates_transactions.qbk]
[include 03_6_tutorial_connection_pool.qbk]
[include 03_7_tutorial_error_handling.qbk]
[include 04_overview.qbk]
[include 05_connection_establishment.qbk]
[include 06_sql_formatting.qbk]
Expand All @@ -193,8 +200,7 @@ END
[include 16_metadata.qbk]
[include 17_charsets.qbk]
[include 18_time_types.qbk]
[/ TODO: re-enable this
[include 19_templated_connection.qbk] ]
[include 19_templated_connection.qbk]
[include 20_pipeline.qbk]
[include 21_examples.qbk]

Expand Down
4 changes: 1 addition & 3 deletions doc/qbk/03_4_tutorial_static_interface.qbk
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ The mechanics are quite similar to what's been explained here.

Full program listing for this tutorial is [link mysql.examples.tutorial_static_interface here].

This concludes our tutorial series. You can now look at the [link mysql.overview overview section]
to learn more about the library features, or to the [link mysql.examples example section]
if you prefer to learn by doing.
You can now proceed to [link mysql.tutorial_updates_transactions the next tutorial].

[endsect]
149 changes: 149 additions & 0 deletions doc/qbk/03_5_tutorial_updates_transactions.qbk
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
[/
Copyright (c) 2019-2024 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)
]

[section:tutorial_updates_transactions Tutorial 5: UPDATEs, transactions and semicolon-separated queries]

All the previous tutorials have only used `SELECT` statements, but
Boost.MySQL is not limited to them. Using [refmemunq any_connection async_execute]
you can run any SQL statement supported by MySQL.

In this tutorial, we will write a program that changes the first name of an
employee, given their ID, and prints the updated employee details.
We will use an `UPDATE` and transaction management statements.
`INSERT` and `DELETE` statements have similar mechanics.



[heading A simple UPDATE]

We can use the same tools and functions as in previous tutorials:

[tutorial_updates_transactions_update]

By default, auto-commit is enabled, meaning that when `async_execute`
returns, the `UPDATE` is visible to other client connections.




[heading Checking that the UPDATE took effect]

The above query will succeed even if there was no employee with the given ID.
We would like to retrieve the updated employee details on success, and emit
a useful error message if no employee was matched.

We may be tempted to use [refmem results affected_rows] at first, but
this doesn't convey the information we're looking for:
a row may be matched but not affected. For example, if you try to
set `first_name` to the same value it already has,
MySQL will count the row as a matched, but not affected.


MySQL does not support the `UPDATE ... RETURNING` syntax, so we will
have to retrieve the employee manually after updating it.
We can add the following after our `UPDATE`:

[tutorial_updates_transactions_select]

However, the code above contains a race condition. Imagine the following situation:

* The `UPDATE` is issued. No employee is matched.
* Before our program sends the `SELECT` query, a different program inserts
an employee with the ID that we're trying to update.
* Our program runs the `SELECT` statement and retrieves the newly inserted row.

To our program, it looks like we succeeded performing the update, when
we really didn't. Depending on the nature of our program, this may
or may not have serious consequences, but it's something we should avoid.


[heading Avoiding the race condition with a transaction block]

We can fix the race condition using transactions.
In MySQL, a transaction block is opened with `START TRANSACTION`.
Subsequent statements will belong to the transaction block,
until the transaction either commits or is rolled back.
A `COMMIT` statement commits the transaction.
A rollback happens if the connection that initiated the transaction
closes or an explicit `ROLLBACK` statement is used.

We will enclose our `UPDATE` and `SELECT` statements in
a transaction block. This will ensure that the `SELECT`
will get the updated row, if any:

[tutorial_updates_transactions_txn]




[heading Using multi-queries]

While the code we've written is correct, it's not very performant.
We're incurring in 4 round-trips to the server, when our queries don't depend
on the result of previous ones. The round-trips occur within a transaction
block, which causes certain database rows to be locked, increasing contention.
We can improve the situation by running our four statements in a single batch.

Multi-queries are a protocol feature that lets you execute several queries
at once. Individual queries must be separated by semicolons.

Multi-queries are disabled by default. To enable them, set
[refmem connect_params multi_queries] to `true` before connecting:

[tutorial_updates_transactions_connect]

Multi-queries can be composed an executed using the same
functions we've been using:

[tutorial_updates_transactions_multi_queries]

Accessing the results is slightly different. MySQL returns 4 resultsets,
one for each query. In Boost.MySQL, this operation is said to be
[link mysql.multi_resultset multi-resultset].
[reflink results] can actually store more than one resultset.
[refmem results rows] actually accesses the rows in the first resultset,
because it's the most common use case.

We want to get the rows retrieved by the `SELECT` statement,
which corresponds to the third resultset.
[refmem results at] returns a [reflink resultset_view] containing data
for the requested resultset:

[tutorial_updates_transactions_dynamic_results]


[heading Using manual indices in with_params]

Repeating `employee_id` in the parameter list passed to `with_params`
violates the DRY principle.
As with `std::format`, we can refer to a format argument more than once
by using manual indices:

[tutorial_updates_transactions_manual_indices]



[heading Using the static interface with multi-resultset]

Finally, we can rewrite our code to use the static interface so it's safer.
In multi-resultset scenarios, we can pass as many row types
to [reflink static_results] as resultsets we expect.
We can use the empty tuple (`std::tuple<>`) as a row type
for operations that don't return rows, like the `UPDATE`.
Our code becomes:

[tutorial_updates_transactions_static]


[heading Wrapping up]

Full program listing for this tutorial is [link mysql.examples.tutorial_updates_transactions here].

You can now proceed to [link mysql.tutorial_connection_pool the next tutorial].


[endsect]
150 changes: 150 additions & 0 deletions doc/qbk/03_6_tutorial_connection_pool.qbk
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
[/
Copyright (c) 2019-2024 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)
]

[section:tutorial_connection_pool Tutorial 6: Connection pools]

All our programs until now have used one-shot connections.
They also didn't feature any fault tolerance:
if the server is unavailable, our program throws an exception
and terminates. Most real world scenarios require
long-lived, reliable connections, instead.

In this tutorial, we will implement a server for a simple request-reply
protocol. The protocol allows clients to retrieve the full name of
an employee given their ID.
We will use [reflink connection_pool] to maintain a set of healthy connections
that we can use when a client connects to our server.




[heading The protocol]

The protocol is TCP based, and can be described as follows:

* After connecting, the client sends a message containing the employee ID,
encoded as an 8-byte, big-endian integer.
* The server replies with a string containing the employee full name,
or "NOT_FOUND", if the ID doesn't match any employee.
* The connection is closed after that.

This protocol is intentionally overly simplistic, and
shouldn't be used in production. See our
[link mysql.examples.connection_pool HTTP examples]
for more advanced use cases.




[heading Creating a connection pool]

[reflink connection_pool] is an I/O object that contains
[reflink any_connection] objects, and can be
constructed from an execution context and a [reflink pool_params]
config struct:

[tutorial_connection_pool_create]

A single connection pool is usually created per application.

[refmem connection_pool async_run] should be called once per pool:

[tutorial_connection_pool_run]






[heading Using pooled connections]

Let's first write a coroutine that encapsulates database access.
Given an employee ID, it should return the string to be sent as response to the client.
Don't worry about error handling for now - we will take care of it in the next tutorial.

When using a pool, we don't need to explicitly create, connect or close connections.
Instead, we use [refmem connection_pool async_get_connection] to obtain them from the pool:

[tutorial_connection_pool_get_connection]

[reflink pooled_connection] is a wrapper around [reflink any_connection],
with some pool-specific additions. We can use it like a regular connection:

[tutorial_connection_pool_use]

When a [reflink pooled_connection] is destroyed, the connection is returned
to the pool. The underlying connection will be cleaned up using a lightweight
session reset mechanism and recycled.
Subsequent [refmemunq connection_pool async_get_connection]
calls may retrieve the same connection. This improves efficiency,
since session establishment is costly.

[refmemunq connection_pool async_get_connection] waits
for a client connection to become available before completing.
If the server is unavailable or credentials are invalid,
it may wait indefinitely. This is a problem for both development and production.
We can solve this by using [asioreflink cancel_after cancel_after],
which allows setting timeouts to async operations:

[tutorial_connection_pool_get_connection_timeout]

Don't worry if you don't fully understand how this works.
We will go into more detail on [asioreflink cancel_after cancel_after],
cancellations and completion tokens in the next tutorial.

Putting all pieces together, our coroutine becomes:

[tutorial_connection_pool_db]




[heading Handling a client session]

Let's now build a function that handles a client sessions,
invoking the database access logic in the process:

[tutorial_connection_pool_session]




[heading Listening for connections]

We now need logic to accept incoming TCP connections.
We will use an `asio::ip::tcp::acceptor` object
to accomplish it, listening for connections in a loop
until the server is stopped:

[tutorial_connection_pool_listener]




[heading Waiting for signals]

Finally, we need a way to stop our program. We will use an `asio::signal_set` object
to catch signals, and call `io_context::stop` when Ctrl-C is pressed:

[tutorial_connection_pool_signals]

Putting all these pieces together, our main program becomes:

[tutorial_connection_pool_main]




[heading Wrapping up]

Full program listing for this tutorial is [link mysql.examples.tutorial_connection_pool here].

For simplicity, we've left error handling out of this tutorial.
This is usually very important in a server like the one we've written,
and is the topic of our [link mysql.tutorial_error_handling next tutorial].

[endsect]
Loading