diff --git a/connector_api/connection.h b/connector_api/connection.h index 49dc75c7..18fc8d03 100644 --- a/connector_api/connection.h +++ b/connector_api/connection.h @@ -67,8 +67,11 @@ namespace sqlpp connection_handle& operator=(connection_handle&&); // Used by the connection pool to check if the connection handle is still - // connected to the database server - bool check_connection(); + // connected to the database server. + bool is_connected(); + + // Send a dummy request to the server to check if the connection is still alive + bool ping_server(); // Optional method that returns a native (low-level) database handle. // Used by the test code to test the connection pool diff --git a/include/sqlpp11/connection.h b/include/sqlpp11/connection.h index 8e83e3a2..13bbce36 100644 --- a/include/sqlpp11/connection.h +++ b/include/sqlpp11/connection.h @@ -29,8 +29,8 @@ #include -#include #include +#include namespace sqlpp { @@ -38,9 +38,27 @@ namespace sqlpp { }; + template + class common_connection : public ConnectionBase + { + public: + bool is_connected() const + { + return ConnectionBase::_handle ? ConnectionBase::_handle->is_connected() : false; + } + + bool ping_server() const + { + return ConnectionBase::_handle ? ConnectionBase::_handle->ping_server() : false; + } + + protected: + using ConnectionBase::ConnectionBase; + }; + // Normal (non-pooled) connection template - class normal_connection : public ConnectionBase + class normal_connection : public common_connection { public: using _config_t = typename ConnectionBase::_config_t; @@ -53,7 +71,8 @@ namespace sqlpp { } - normal_connection(const _config_ptr_t& config) : ConnectionBase{compat::make_unique<_handle_t>(config)} + normal_connection(const _config_ptr_t& config) + : common_connection{compat::make_unique<_handle_t>(config)} { } @@ -80,7 +99,7 @@ namespace sqlpp // Pooled connection template - class pooled_connection : public ConnectionBase + class pooled_connection : public common_connection { friend class connection_pool::pool_core; @@ -118,12 +137,12 @@ namespace sqlpp // Constructors used by the connection pool pooled_connection(_handle_ptr_t&& handle, _pool_core_ptr_t pool_core) - : ConnectionBase{std::move(handle)}, _pool_core{pool_core} + : common_connection{std::move(handle)}, _pool_core{pool_core} { } pooled_connection(const _config_ptr_t& config, _pool_core_ptr_t pool_core) - : ConnectionBase{compat::make_unique<_handle_t>(config)}, _pool_core{pool_core} + : common_connection{compat::make_unique<_handle_t>(config)}, _pool_core{pool_core} { } diff --git a/include/sqlpp11/connection_pool.h b/include/sqlpp11/connection_pool.h index bccf395e..68928ef6 100644 --- a/include/sqlpp11/connection_pool.h +++ b/include/sqlpp11/connection_pool.h @@ -27,12 +27,20 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include - #include +#include +#include + namespace sqlpp { + enum class connection_check + { + none, + passive, + ping + }; + template class connection_pool { @@ -56,7 +64,7 @@ namespace sqlpp pool_core& operator=(const pool_core&) = delete; pool_core& operator=(pool_core&&) = delete; - _pooled_connection_t get() + _pooled_connection_t get(connection_check check) { std::unique_lock lock{_mutex}; if (_handles.empty()) @@ -68,8 +76,10 @@ namespace sqlpp _handles.pop_front(); lock.unlock(); // If the fetched connection is dead, drop it and create a new one on the fly - return handle->check_connection() ? _pooled_connection_t{std::move(handle), this->shared_from_this()} - : _pooled_connection_t{_connection_config, this->shared_from_this()}; + return + check_connection(handle, check) ? + _pooled_connection_t{std::move(handle), this->shared_from_this()} : + _pooled_connection_t{_connection_config, this->shared_from_this()}; } void put(_handle_ptr_t& handle) @@ -90,6 +100,21 @@ namespace sqlpp } private: + inline bool check_connection(_handle_ptr_t& handle, connection_check check) + { + switch (check) + { + case connection_check::none: + return true; + case connection_check::passive: + return handle->is_connected(); + case connection_check::ping: + return handle->ping_server(); + default: + throw std::invalid_argument{"Invalid connection check value"}; + } + } + _config_ptr_t _connection_config; sqlpp::detail::circular_buffer<_handle_ptr_t> _handles; std::mutex _mutex; @@ -117,9 +142,9 @@ namespace sqlpp _core = std::make_shared(connection_config, capacity); } - _pooled_connection_t get() + _pooled_connection_t get(connection_check check = connection_check::passive) { - return _core->get(); + return _core->get(check); } // Returns number of connections available in the pool. Only used in tests. diff --git a/include/sqlpp11/mysql/connection.h b/include/sqlpp11/mysql/connection.h index d5c8fec1..d597041a 100644 --- a/include/sqlpp11/mysql/connection.h +++ b/include/sqlpp11/mysql/connection.h @@ -281,9 +281,9 @@ namespace sqlpp return serialize(t, context); } - bool is_valid() const + [[deprecated("Use ping_server() instead")]] bool is_valid() const { - return _handle->check_connection(); + return _handle->ping_server(); } void reconnect() diff --git a/include/sqlpp11/mysql/detail/connection_handle.h b/include/sqlpp11/mysql/detail/connection_handle.h index 89cd6ad7..45b4b868 100644 --- a/include/sqlpp11/mysql/detail/connection_handle.h +++ b/include/sqlpp11/mysql/detail/connection_handle.h @@ -101,10 +101,17 @@ namespace sqlpp return mysql.get(); } - bool check_connection() const + bool is_connected() const { - auto nh = native_handle(); - return nh && (mysql_ping(nh) == 0); + // The connection is established in the constructor and the MySQL client + // library doesn't seem to have a way to check passively if the connection + // is still valid + return true; + } + + bool ping_server() const + { + return mysql_ping(native_handle()) == 0; } void reconnect() diff --git a/include/sqlpp11/postgresql/detail/connection_handle.h b/include/sqlpp11/postgresql/detail/connection_handle.h index ebd77a91..2e57fd4d 100644 --- a/include/sqlpp11/postgresql/detail/connection_handle.h +++ b/include/sqlpp11/postgresql/detail/connection_handle.h @@ -185,7 +185,7 @@ namespace sqlpp if (!postgres) throw std::bad_alloc{}; - if (check_connection() == false) + if (is_connected() == false) { std::string msg{PQerrorMessage(native_handle())}; throw broken_connection{std::move(msg)}; @@ -220,11 +220,24 @@ namespace sqlpp return postgres.get(); } - bool check_connection() const + bool is_connected() const { auto nh = native_handle(); return nh && (PQstatus(nh) == CONNECTION_OK); } + + bool ping_server() const + { + // Loosely based on the implementation of PHP's pg_ping() + if (is_connected() == false) + { + return false; + } + auto exec_res = PQexec(native_handle(), "SELECT 1"); + auto exec_ok = PQresultStatus(exec_res) == PGRES_TUPLES_OK; + PQclear(exec_res); + return exec_ok; + } }; } } diff --git a/include/sqlpp11/sqlite3/detail/connection_handle.h b/include/sqlpp11/sqlite3/detail/connection_handle.h index 10aa02f5..a33161ce 100644 --- a/include/sqlpp11/sqlite3/detail/connection_handle.h +++ b/include/sqlpp11/sqlite3/detail/connection_handle.h @@ -96,9 +96,22 @@ namespace sqlpp return sqlite.get(); } - bool check_connection() const + bool is_connected() const { - return native_handle() != nullptr; + // The connection is established in the constructor and the SQLite3 client + // library doesn't seem to have a way to check passively if the connection + // is still valid + return true; + } + + bool ping_server() const + { + // Loosely based on the implementation of PHP's pg_ping() + if (sqlite3_exec(native_handle(), "SELECT 1", nullptr, nullptr, nullptr) != SQLITE_OK) + { + return false; + } + return true; } }; } // namespace detail diff --git a/tests/include/ConnectionPoolTests.h b/tests/include/ConnectionPoolTests.h index f54a3408..0d000e8d 100644 --- a/tests/include/ConnectionPoolTests.h +++ b/tests/include/ConnectionPoolTests.h @@ -116,6 +116,24 @@ namespace sqlpp } } + template + void test_conn_check(Pool& pool) + { + auto check_db = [] (typename Pool::_pooled_connection_t db) { + if (db.is_connected() == false) + { + throw std::runtime_error{"is_connected() returned false"}; + } + if (db.ping_server() == false) + { + throw std::runtime_error{"ping_server() returned false"}; + } + }; + check_db(pool.get(connection_check::none)); + check_db(pool.get(connection_check::passive)); + check_db(pool.get(connection_check::ping)); + } + template void test_basic(Pool& pool, const std::string& create_table) { @@ -250,15 +268,16 @@ namespace sqlpp 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); + test_conn_move(pool); + test_basic(pool, create_table); + test_conn_check(pool); + test_single_connection(pool); + test_multiple_connections(pool); if (test_mt) { - sqlpp::test::test_multithreaded(pool); + test_multithreaded(pool); } - sqlpp::test::test_destruction_order(config); + test_destruction_order(config); } } // namespace test } // namespace sqlpp diff --git a/tests/include/ConnectionTests.h b/tests/include/ConnectionTests.h new file mode 100644 index 00000000..720d5fdb --- /dev/null +++ b/tests/include/ConnectionTests.h @@ -0,0 +1,69 @@ +#pragma once + +/* +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 + +namespace sqlpp +{ + namespace test + { + namespace + { + template + void test_conn_empty() + { + Connection db; + if (db.is_connected()) { + throw std::runtime_error{"Unexpected is_connected() == true"}; + } + if (db.ping_server()) { + throw std::runtime_error{"Unexpected ping_server() == true"}; + } + } + + template + void test_conn_connected(const ConfigPtr& connection_config) + { + Connection db{connection_config}; + if (db.is_connected() == false) { + throw std::runtime_error{"Unexpected is_connected() == false"}; + } + if (db.ping_server() == false) { + throw std::runtime_error{"Unexpected ping_server() == false"}; + } + } + } + + template + void test_normal_connection(const ConfigPtr& connection_config) + { + test_conn_empty(); + test_conn_connected(connection_config); + } + } // namespace test +} // namespace sqlpp diff --git a/tests/mysql/usage/CMakeLists.txt b/tests/mysql/usage/CMakeLists.txt index 3735f433..a154f43d 100644 --- a/tests/mysql/usage/CMakeLists.txt +++ b/tests/mysql/usage/CMakeLists.txt @@ -40,6 +40,7 @@ set(test_files Truncated.cpp Update.cpp Remove.cpp + Connection.cpp ConnectionPool.cpp ) diff --git a/tests/mysql/usage/Connection.cpp b/tests/mysql/usage/Connection.cpp new file mode 100644 index 00000000..ee010591 --- /dev/null +++ b/tests/mysql/usage/Connection.cpp @@ -0,0 +1,39 @@ +/* + * 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: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * 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 "../../include/ConnectionTests.h" +#include "make_test_connection.h" + +#include +#include + +int Connection(int, char*[]) +{ + namespace sql = sqlpp::mysql; + namespace test = sqlpp::test; + + test::test_normal_connection(sql::make_test_config()); + return 0; +} diff --git a/tests/postgresql/usage/CMakeLists.txt b/tests/postgresql/usage/CMakeLists.txt index d2b6826b..d4eafc88 100644 --- a/tests/postgresql/usage/CMakeLists.txt +++ b/tests/postgresql/usage/CMakeLists.txt @@ -30,8 +30,8 @@ set(test_files Basic.cpp BasicConstConfig.cpp Blob.cpp + Connection.cpp ConnectionPool.cpp - Constructor.cpp Date.cpp DateTime.cpp Exceptions.cpp diff --git a/tests/postgresql/usage/Constructor.cpp b/tests/postgresql/usage/Connection.cpp similarity index 83% rename from tests/postgresql/usage/Constructor.cpp rename to tests/postgresql/usage/Connection.cpp index bdf52332..29cf20e4 100644 --- a/tests/postgresql/usage/Constructor.cpp +++ b/tests/postgresql/usage/Connection.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2017, Serge Robyns + * Copyright (c) 2023, Vesselin Atanasov * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -23,13 +24,17 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include "../../include/ConnectionTests.h" +#include "make_test_connection.h" + #include #include -namespace sql = sqlpp::postgresql; - -int Constructor(int, char*[]) +int Connection(int, char*[]) { - sql::connection db; + namespace sql = sqlpp::postgresql; + namespace test = sqlpp::test; + + test::test_normal_connection(sql::make_test_config()); return 0; } diff --git a/tests/sqlite3/usage/CMakeLists.txt b/tests/sqlite3/usage/CMakeLists.txt index 09ccca9d..fc8f3b66 100644 --- a/tests/sqlite3/usage/CMakeLists.txt +++ b/tests/sqlite3/usage/CMakeLists.txt @@ -38,6 +38,7 @@ set(test_files FloatingPoint.cpp Integral.cpp Blob.cpp + Connection.cpp ConnectionPool.cpp ) diff --git a/tests/sqlite3/usage/Connection.cpp b/tests/sqlite3/usage/Connection.cpp new file mode 100644 index 00000000..4ec827ad --- /dev/null +++ b/tests/sqlite3/usage/Connection.cpp @@ -0,0 +1,43 @@ +/* + * 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: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * 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 "../../include/ConnectionTests.h" + +#include +#include + +int Connection(int, char*[]) +{ + namespace sql = sqlpp::sqlite3; + namespace test = sqlpp::test; + + auto config = std::make_shared(); + config->path_to_database = ":memory:"; + config->flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + config->debug = true; + + test::test_normal_connection(config); + return 0; +}