Skip to content

Commit

Permalink
Merge pull request #20 from jgaa/transactions
Browse files Browse the repository at this point in the history
Transactions, persistent handle and Beta 2
  • Loading branch information
jgaa authored Apr 22, 2024
2 parents 5bc7f92 + 74a949e commit 2003f02
Show file tree
Hide file tree
Showing 8 changed files with 729 additions and 147 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
submodules: true

- name: Cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/vcpkg
Expand Down
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.24)

if (NOT DEFINED MYSQLPOOL_VERSION)
set(MYSQLPOOL_VERSION 0.3.0)
set(MYSQLPOOL_VERSION 0.4.0)
endif()

project(mysqlpool-cpp
Expand Down Expand Up @@ -90,6 +90,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

set(MYSQLPOOL_ROOT ${CMAKE_CURRENT_SOURCE_DIR})

if(WIN32)
add_compile_options(-D_WIN32_WINNT=0x0601 -DWINVER=0x0601 -DWIN32_LEAN_AND_MEAN=1)
endif()

message(STATUS "Using ${CMAKE_CXX_COMPILER}")

if (MYSQLPOOL_WITH_CONAN)
Expand Down
82 changes: 80 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The library require C++20 and use C++20 coroutines.

## Status

First BETA release.
Second BETA release.

Please create an issue if you find any problems or if you have suggestions
on how to make the library more useful.
Expand Down Expand Up @@ -77,6 +77,8 @@ need. However, you will have to deal with the error handling yourself.
- Requests are queued if all the available connections are in use. The pool will open more connections in the background up to the limit.
- The high level `exec` method handles connection allocation, per request options, error handling and reporting and data binding.
- Prepared Statements are handled automatically. Each connection has a cache of prepared statements, based on a cryptographic hash from the SQL query. If a new query is seen, a prepared statement is created and added to the cache before the query is executed.
- Sequential execution of queries by using the same connection.
- Transactions with automatic rollback if the transaction is not committed.
- All SQL queries can be logged, include the arguments to prepared statements.
- Time Zone can be specified for a query. The pool will then ensure that the connection
used for that request use the specified time zone. Useful for servers that handle
Expand Down Expand Up @@ -365,6 +367,83 @@ boost::asio::awaitable<void> add_and_query_data(mp::Mysqlpool& pool) {

```
You can execute a series of queries using the same database connection.
```C++
boost::asio::awaitable<void> use_the_same_connection(mp::Mysqlpool& pool) {
// Some times you may want to use the same connection for several queries
// This can be done by using the same handle for several queries.
// Create the handle
auto handle = co_await pool.getConnection();
// Make some queries
// Note that we use `handle` to execute the queries.
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 7, "Ivan");
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 8, "Jane");
// Using the same connection lets you set specific options on that connection,
// and it guarantees that the queries are executed in sequential order.
}
```

Often you'll need transactions to ensure consistency. Either all queries in the
transaction are successful, or all of them fail. A transaction also allows you
to roll back the changes if something else in your application fails.

A transaction is handled by a transaction object. Normally you will create the
transaction object, do some queries and then commit the transaction. If you
don't commit a transaction by the time the transaction object goes out of scope,
the transaction is rolled back.

```C++

boost::asio::awaitable<void> use_transaction(mp::Mysqlpool& pool) {

// Very often, you will need transactions in order to commit
// several related queries to the database.
// This is one area where the traditional SQL database servers
// shine compared to most NoSQL databases.

// In order to use transactions, we need to use a Handle to a Connection
// and keep that handle instance alive until the transaction is done.
// In most cases that simply means to first create the handle, and
// then the transaction.

// Create the handle
auto handle = co_await pool.getConnection();

// Create the transaction
auto trx = co_await handle.transaction();

// Make some queries - typically INSERT, UPDATE or DELETE
// Note that you must use the `handle` instance to execute the queries,
// since the transaction is bound to that handle.
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 5, "George");
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 6, "Hannah");

// Now you can commit the transaction or roll it back.
// If the trx instance goes out of scope with an active transaction,
// the transaction is rolled back.
co_await trx.commit();

// You cannot use the trx instance after commit or rollback, but the handle is
// still valid. You can use it directly with `exec(...)` or you can create
// a new transaction.

co_return;
}

```
## TLS
If you enable TLS using the `DbConfig.tls_mode` option, it should work without
any further tweaks in most situations. If you use a DB-server on an insecure
network, you may harden the TLS settings using the [DbConfig.tls](include/mysqlpool/conf.h) settings.
## Building
You can build the library using CMake.
Expand Down Expand Up @@ -395,4 +474,3 @@ add_subdirectory(dependencies/mysqlpool-cpp)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/dependencies/mysqlpool-cpp/include)
```

57 changes: 56 additions & 1 deletion examples/simple/fun_with_sql.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,62 @@ boost::asio::awaitable<void> add_and_query_data(mp::Mysqlpool& pool) {
res = co_await pool.exec("SELECT id, name, DATE_FORMAT(created_date, '%Y-%m-%d %H:%m') FROM test_table", opts);
print(res.rows());

co_await pool.exec("DROP TABLE test_table");
co_return;
}

boost::asio::awaitable<void> use_transaction(mp::Mysqlpool& pool) {

// Very often, you will need transactions in order to commit
// several related queries to the database.
// This is one area where the traditional SQL database servers
// shine compared to most NoSQL databases.

// In order to use transactions, we need to use a Handle to a Connection
// and keep that handle instance alive until the transaction is done.
// In most cases that simply means to first create the handle, and
// then the transaction.

// Create the handle
auto handle = co_await pool.getConnection();

// Create the transaction
auto trx = co_await handle.transaction();

// Make some queries - typically INSERT, UPDATE or DELETE
// Note that you must use the `handle` instance to execute the queries,
// since the transaction is bound to that handle.
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 5, "George");
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 6, "Hannah");

// Now you can commit the transaction or roll it back.
// If the trx instance goes out of scope with an active transaction,
// the transaction is rolled back.
co_await trx.commit();

// You cannot use the trx instance after commit or rollback, but the handle is
// still valid. You can use it directly with `exec(...)` or you can create
// a new transaction.

co_return;
}

boost::asio::awaitable<void> use_the_same_connection(mp::Mysqlpool& pool) {
// Some times you may want to use the same connection for several queries
// This can be done by using the same handle for several queries.

// Create the handle
auto handle = co_await pool.getConnection();

// Make some queries
// Note that we use `handle` to execute the queries.
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 7, "Ivan");
co_await handle.exec("INSERT INTO test_table (id, name) VALUES (?, ?)", 8, "Jane");

// Using the same connection lets you set specific options on that connection,
// and it guarantees that the queries are executed in sequential order.
}


// Entry point from main()
void run_examples(const mp::DbConfig& config){

Expand All @@ -190,7 +242,10 @@ void run_examples(const mp::DbConfig& config){
co_await get_db_version_using_boost_mysql(pool);
co_await get_db_version(pool);
co_await add_and_query_data(pool);
co_await use_transaction(pool);
co_await use_the_same_connection(pool);

co_await pool.exec("DROP TABLE test_table");
// Gracefully shut down the connection-pool.
co_await pool.close();
} catch (const exception& ex) {
Expand Down
60 changes: 59 additions & 1 deletion include/mysqlpool/conf.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

#include <string>
#include <cstdint>
#include <vector>
#include <string_view>
#include <functional>
#include <boost/asio/ssl.hpp>

#include "mysqlpool/config.h"

Expand All @@ -11,6 +15,57 @@ std::string getEnv(const std::string& name, std::string defaultValue = {});

/** Configuration for an instance of mysqlpool. */

struct TlsConfig {
using password_cb_t = std::function<std::string (
std::size_t, // The maximum size for a password.
boost::asio::ssl::context_base::password_purpose // Whether password is for reading or writing.
)>;

/*! TLS version to use.
*
* If unset, it use the default client settings for boost.asio (boost::asio::ssl::context_base::tls_client)
*
* One of:
* - "" (empty string): Use the default settings for the client
* - "": Use TLS 1.2
* - "tls_1.3": Use TLS 1.3
*/
std::string version;

/*! Allow SSL versions. They are all depricated and should not be allowed */
bool allow_ssl = false;

/*! Allow TLS 1.1 */
bool allow_tls_1_1 = false;

/*! Allow TLS 1.2 */
bool allow_tls_1_2 = true;

/*! Allow TLS 1.3 */
bool allow_tls_1_3 = true;

/*! Very often the peer will use a self-signed certificate.
*
* Must be enabled if you use a public database server on the internet.
*/
bool verify_peer = false;

/*! CA files for verification */
std::vector<std::string> ca_files;

/*! CA paths for verification */
std::vector<std::string> ca_paths;

/*! Cert to use for the client */
std::string cert_file;

/*! Key to use for the client */
std::string key_file;

/*! Password callback if the private key use a password */
password_cb_t password_callback;
};

struct DbConfig {
/// Host where the DB server is running.
std::string host = getEnv(MYSQLPOOL_DBHOST, DEFAULT_MYSQLPOOL_HOST);
Expand Down Expand Up @@ -44,7 +99,10 @@ struct DbConfig {
* - enable: Use TLS if the server supports it, fall back to non-encrypted connection if it does not.
* - require: Always use TLS; abort the connection if the server does not support it.
*/
std::string ssl_mode = getEnv(MYSQLPOOL_DB_TLS_MODE, DEFAULT_MYSQLPOOL_TLS_MODE);
std::string tls_mode = getEnv(MYSQLPOOL_DB_TLS_MODE, DEFAULT_MYSQLPOOL_TLS_MODE);

/*! TLS configuration */
TlsConfig tls;

/*! Number of times to retry connecting to the database
*
Expand Down
Loading

0 comments on commit 2003f02

Please sign in to comment.