mirror of
https://github.com/rbock/sqlpp11.git
synced 2024-11-15 20:31:16 +08:00
dff8c23b22
Add support for connection pooling * Add support for connection pooling to the core code. * Add support for PostgreSQL connection pooling with tests. * Add support for SQLite3 connection pooling with tests. * Add support for MySQL connection pooling with tests.
261 lines
8.7 KiB
C++
261 lines
8.7 KiB
C++
#pragma once
|
|
|
|
/*
|
|
Copyright (c) 2017 - 2018, Roland Bock
|
|
Copyright (c) 2023, Vesselin Atanasov
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without modification,
|
|
are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright notice, this
|
|
list of conditions and the following disclaimer in the documentation and/or
|
|
other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <random>
|
|
#include <set>
|
|
#include <thread>
|
|
#include <unordered_set>
|
|
|
|
#include "TabDepartment.h"
|
|
|
|
namespace sqlpp
|
|
{
|
|
namespace test
|
|
{
|
|
namespace
|
|
{
|
|
template<typename Pool>
|
|
using native_type = std::decay_t<decltype(std::declval<Pool>().get().native_handle())>;
|
|
|
|
template<typename Pool>
|
|
using native_set = std::unordered_set<native_type<Pool>>;
|
|
|
|
template<typename Pool>
|
|
using pool_conn_type = std::decay_t<decltype(std::declval<Pool>().get())>;
|
|
|
|
template<typename Pool>
|
|
native_set<Pool> get_native_handles(Pool& pool)
|
|
{
|
|
native_set<Pool> ns;
|
|
if (pool.available() == 0) {
|
|
return ns;
|
|
}
|
|
for (;;) {
|
|
auto handle = pool.get().native_handle();
|
|
auto insert_res = ns.insert(handle);
|
|
if (insert_res.second == false) {
|
|
return ns;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_conn_move(Pool& pool)
|
|
{
|
|
auto nh_all = get_native_handles(pool);
|
|
{
|
|
// Get one connection from the pool
|
|
auto conn_1 = pool.get();
|
|
// If the native handles list was empty before getting a connection, then a new connection
|
|
// was created, so we need to update the set of all native handles
|
|
if (nh_all.empty()) {
|
|
nh_all.insert(conn_1.native_handle());
|
|
}
|
|
auto nh_removed = nh_all;
|
|
if (nh_removed.erase(conn_1.native_handle()) != 1) {
|
|
throw std::logic_error{"Got an unknown native connection handle"};
|
|
}
|
|
if (get_native_handles(pool) != nh_removed) {
|
|
throw std::logic_error{"Could not get correctly a connection from the pool"};
|
|
}
|
|
{
|
|
// Move the pooled connection once
|
|
auto conn_2 = std::move(conn_1);
|
|
if (get_native_handles(pool) != nh_removed) {
|
|
throw std::logic_error{"Moving a connection changes the pool"};
|
|
}
|
|
|
|
// Move the pooled connection again
|
|
conn_1 = std::move(conn_2);
|
|
if (get_native_handles(pool) != nh_removed) {
|
|
throw std::logic_error{"Moving a connection changes the pool"};
|
|
}
|
|
|
|
// The empty connection conn_2 goes out of scope and gets destroyed
|
|
}
|
|
|
|
// Check if destroying an empty connection changed the pool
|
|
if (get_native_handles(pool) != nh_removed) {
|
|
throw std::logic_error{"Destroying an empty connection changes the pool"};
|
|
}
|
|
|
|
// The valid connection conn_1 goes out of scope and gets destroyed
|
|
}
|
|
|
|
// Check if destroying a valid connection from the pool returned the handle to the pool
|
|
if (get_native_handles(pool) != nh_all) {
|
|
throw std::logic_error{"Destroying a valid connection does not return its handle to the pool"};
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_basic(Pool& pool, const std::string& create_table)
|
|
{
|
|
try
|
|
{
|
|
auto db = pool.get();
|
|
db.execute("DROP TABLE IF EXISTS tab_department");
|
|
db.execute(create_table);
|
|
model::TabDepartment tabDept = {};
|
|
db(insert_into(tabDept).default_values());
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << "Exception in " << __func__ << "\n";
|
|
throw;
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_single_connection(Pool& pool)
|
|
{
|
|
try
|
|
{
|
|
auto* handle = [&pool]() {
|
|
auto db = pool.get();
|
|
return db.native_handle();
|
|
}();
|
|
|
|
for (auto i = 0; i < 100; ++i)
|
|
{
|
|
auto db = pool.get();
|
|
if (handle != db.native_handle())
|
|
{
|
|
std::cerr << "original connection: " << handle << std::endl;
|
|
std::cerr << "received connection: " << db.native_handle() << std::endl;
|
|
throw std::logic_error{"Pool acquired more than one connection"};
|
|
}
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << "Exception in " << __func__ << "\n";
|
|
throw;
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_multiple_connections(Pool& pool)
|
|
{
|
|
try
|
|
{
|
|
model::TabDepartment tabDept = {};
|
|
auto connections = std::vector<std::decay_t<decltype(pool.get())>>{};
|
|
auto pointers = std::set<void*>{};
|
|
for (auto i = 0; i < 50; ++i)
|
|
{
|
|
connections.push_back(pool.get());
|
|
if (pointers.count(connections.back().native_handle()))
|
|
{
|
|
throw std::logic_error{"Pool yielded connection twice (without getting it back in between)"};
|
|
}
|
|
pointers.insert(connections.back().native_handle());
|
|
connections.back()(insert_into(tabDept).default_values());
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << "Exception in " << __func__ << "\n";
|
|
throw;
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_multithreaded(Pool& pool)
|
|
{
|
|
std::random_device r;
|
|
std::default_random_engine random_engine(r());
|
|
std::uniform_int_distribution<int> uniform_dist(1, 20);
|
|
|
|
std::clog << "Run a random number [1,20] of threads\n";
|
|
std::clog << "Each with a random number [1,20] of {pool.get() & insert}\n";
|
|
|
|
try
|
|
{
|
|
model::TabDepartment tabDept = {};
|
|
auto threads = std::vector<std::thread>{};
|
|
const auto thread_count = uniform_dist(random_engine);
|
|
|
|
for (auto i = 0; i < thread_count; ++i)
|
|
{
|
|
threads.push_back(std::thread([func = __func__, call_count = uniform_dist(random_engine), &pool, &tabDept]() {
|
|
try
|
|
{
|
|
for (auto k = 0; k < call_count; ++k)
|
|
{
|
|
auto connection = pool.get();
|
|
connection(insert_into(tabDept).default_values());
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << std::string(func) + ": In-thread exception: " + e.what() + "\n";
|
|
std::abort();
|
|
}
|
|
}));
|
|
}
|
|
for (auto&& t : threads)
|
|
{
|
|
t.join();
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
std::cerr << "Exception in " << __func__ << "\n";
|
|
throw;
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_destruction_order(typename Pool::_config_ptr_t config)
|
|
{
|
|
// Create a pool, get a connection from it and then destroy the pool before the connection
|
|
auto pool = std::make_unique<Pool>(config, 5);
|
|
auto conn = pool->get();
|
|
pool = nullptr;
|
|
}
|
|
}
|
|
|
|
template <typename Pool>
|
|
void test_connection_pool (typename Pool::_config_ptr_t config, const std::string& create_table, bool test_mt)
|
|
{
|
|
auto pool = Pool {config, 5};
|
|
sqlpp::test::test_conn_move(pool);
|
|
sqlpp::test::test_basic(pool, create_table);
|
|
sqlpp::test::test_single_connection(pool);
|
|
sqlpp::test::test_multiple_connections(pool);
|
|
if (test_mt)
|
|
{
|
|
sqlpp::test::test_multithreaded(pool);
|
|
}
|
|
sqlpp::test::test_destruction_order<Pool>(config);
|
|
}
|
|
} // namespace test
|
|
} // namespace sqlpp
|