diff --git a/_config.yml b/_config.yml new file mode 100644 index 00000000..2f7efbea --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/connector_api/connection.h b/connector_api/connection.h index dc2f81c2..d3455fde 100644 --- a/connector_api/connection.h +++ b/connector_api/connection.h @@ -29,6 +29,7 @@ #include #include +#include #include // You may use char result or bind result or both #include // to represent results of select and prepared select @@ -129,9 +130,20 @@ namespace sqlpp return t._prepare(*this); } + + //! set the transaction isolation level for the current connection + /// time of effect is connector-specific, for most is will only affect new transactions + void set_default_isolation_level(sqlpp::isolation_level); + + //! read the default transaction isolation level for the current connection + sqlpp::isolation_level get_default_isolation_level(); + //! start transaction void start_transaction(); + //! start transaction with defined isolation level (optional only for connectors that support it) + void start_transaction(isolation_level isolation /* = isolation_level::undefined */); + //! commit transaction (or throw transaction if the transaction has been finished already) void commit_transaction(); diff --git a/include/sqlpp11/connection_pool.h b/include/sqlpp11/connection_pool.h new file mode 100644 index 00000000..67ce9ea9 --- /dev/null +++ b/include/sqlpp11/connection_pool.h @@ -0,0 +1,192 @@ +/* +* Copyright (c) 2013 - 2017, Roland Bock, Frank Park +* 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. +*/ + +#ifndef SQLPP_CONNECTION_POOL_H +#define SQLPP_CONNECTION_POOL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sqlpp +{ + namespace reconnect_policy + { + struct auto_reconnect { + template + void operator()(Connection* connection) + { + if(!connection->is_valid()) + connection->reconnect() + } + template + void clean(Connection* connection) {} + }; + + using namespace std::chrono_literals; + class periodic_reconnect + { + private: + std::chrono::seconds revalidate_after; + std::unordered_map > last_checked; + + public: + periodic_reconnect(const std::chrono::seconds r = 28800s) //default wait_timeout in MySQL + : revalidate_after(r), last_checked() {} + + template + void operator()(Connection* con) + { + auto last = last_checked.find(con); + auto now = std::chrono::system_clock::now(); + if(last == last_checked.end()) + { + if (!con->is_valid()) + { + con->reconnect(); + } + last_checked.emplace_hint(last, con, now); + } + else if(now - last->second > revalidate_after) + { + if (!con->is_valid()) + { + con->reconnect(); + } + last = now; + } + } + template + void clean(Connection* con) { + auto itr = last_checked.find(con); + if(itr != last_checked.end()) + { + last_checked.erase(itr); + } + } + }; + + struct never_reconnect { + template + void operator()(Connection*) {} + template + void clean(Connection*) {} + }; + } + + template ::value, Connection_config::connection>::type> + class connection_pool + { + friend pool_connection; + + private: + std::mutex connection_pool_mutex; + const std::shared_ptr config; + size_t maximum_pool_size = 0; + std::stack> free_connections; + Reconnect_policy reconnect_policy; + + void free_connection(std::unique_ptr& connection) + { + std::lock_guard lock(connection_pool_mutex); + if (free_connections.size() >= maximum_pool_size) + { + // Exceeds default size, do nothign and let connection self destroy. + } + else + { + if (connection.get()) + { + if (connection->is_valid()) + { + free_connections.push(std::move(connection)); + } + else + { + throw sqlpp::exception("Trying to free a connection with incompatible config."); + } + } + else + { + throw sqlpp::exception("Trying to free an empty connection."); + } + } + } + + public: + connection_pool(const std::shared_ptr& config, size_t pool_size) + : config(config), maximum_pool_size(pool_size), reconnect_policy(Reconnect_policy()) {} + ~connection_pool() = default; + connection_pool(const connection_pool&) = delete; + connection_pool(connection_pool&& other) + : config(std::move(other.config)), maximum_pool_size(std::move(other.maximum_pool_size)), + reconnect_policy(std::move(other.reconnect_policy)) {} + connection_pool& operator=(const connection_pool&) = delete; + connection_pool& operator=(connection_pool&&) = delete; + + pool_connection get_connection() + { + std::lock_guard lock(connection_pool_mutex); + if (!free_connections.empty()) + { + auto connection = std::move(free_connections.top()); + free_connections.pop(); + return pool_connection(connection, this); + } + + try + { + return pool_connection(std::move(std::make_unique(config)), this); + } + catch (const sqlpp::exception& e) + { + std::cerr << "Failed to spawn a new connection." << std::endl; + std::cerr << e.what() << std::endl; + throw; + } + } + }; + + template::value,Connection_config::connection>::type> + connection_pool make_connection_pool( + const std::shared_ptr& config, + size_t max_pool_size) + { + return connection_pool(config, max_pool_size); + } +} + +#endif diff --git a/include/sqlpp11/for_update.h b/include/sqlpp11/for_update.h new file mode 100644 index 00000000..4d84f9b6 --- /dev/null +++ b/include/sqlpp11/for_update.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017, Serge Robyns + * 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. + */ + +#ifndef SQLPP_FOR_UPDATE_H +#define SQLPP_FOR_UPDATE_H + +#include +#include +#include + +namespace sqlpp +{ + // FOR_UPDATE DATA + struct for_update_data_t + { + }; + + // FOR_UPDATE + struct for_update_t + { + using _traits = make_traits; + using _nodes = detail::type_vector<>; + + // Data + using _data_t = for_update_data_t; + + // Member implementation with data and methods + template + struct _impl_t + { + // workaround for msvc bug https://connect.microsoft.com/VisualStudio/Feedback/Details/2091069 + _impl_t() = default; + _impl_t(const _data_t& data) : _data(data) + { + } + + _data_t _data; + }; + + // Base template to be inherited by the statement + template + struct _base_t + { + using _data_t = for_update_data_t; + + // workaround for msvc bug https://connect.microsoft.com/VisualStudio/Feedback/Details/2091069 + template + _base_t(Args&&... args) : for_update{std::forward(args)...} + { + } + + _impl_t for_update; + _impl_t& operator()() + { + return for_update; + } + const _impl_t& operator()() const + { + return for_update; + } + + template + static auto _get_member(T t) -> decltype(t.for_update) + { + return t.for_update; + } + + using _consistency_check = consistent_t; + }; + }; + + struct no_for_update_t + { + using _traits = make_traits; + using _nodes = detail::type_vector<>; + + // Data + using _data_t = no_data_t; + + // Member implementation with data and methods + template + struct _impl_t + { + // workaround for msvc bug https://connect.microsoft.com/VisualStudio/Feedback/Details/2091069 + _impl_t() = default; + _impl_t(const _data_t& data) : _data(data) + { + } + + _data_t _data; + }; + + // Base template to be inherited by the statement + template + struct _base_t + { + using _data_t = no_data_t; + + // workaround for msvc bug https://connect.microsoft.com/VisualStudio/Feedback/Details/2091069 + template + _base_t(Args&&... args) : no_for_update{std::forward(args)...} + { + } + + _impl_t no_for_update; + _impl_t& operator()() + { + return no_for_update; + } + const _impl_t& operator()() const + { + return no_for_update; + } + + template + static auto _get_member(T t) -> decltype(t.no_for_update) + { + return t.no_for_update; + } + + using _database_t = typename Policies::_database_t; + + template + using _new_statement_t = new_statement_t; + + using _consistency_check = consistent_t; + + auto for_update() const -> _new_statement_t + { + return {static_cast&>(*this), for_update_data_t{}}; + } + }; + }; + + // Interpreters + template + struct serializer_t + { + using _serialize_check = serialize_check_of; + using T = for_update_data_t; + + static Context& _(const T& t, Context& context) + { + context << " FOR UPDATE "; + return context; + } + }; + + template + auto for_update(T&& t) -> decltype(statement_t().for_update(std::forward(t))) + { + return statement_t().for_update(std::forward(t)); + } +} + +#endif diff --git a/include/sqlpp11/pool_connection.h b/include/sqlpp11/pool_connection.h new file mode 100644 index 00000000..12ba5bce --- /dev/null +++ b/include/sqlpp11/pool_connection.h @@ -0,0 +1,76 @@ +/* +* Copyright (c) 2013 - 2017, Roland Bock, Frank Park +* 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. +*/ + +#ifndef SQLPP_POOL_CONNECTION_H +#define SQLPP_POOL_CONNECTION_H + +#include + +namespace sqlpp +{ + template > + struct pool_connection + { + private: + std::unique_ptr _impl; + Connection_pool* origin; + + public: + pool_connection(std::unique_ptr& connection, Connection_pool* origin) + : _impl(std::move(connection)), origin(origin) {} + + ~pool_connection() + { + origin->free_connection(_impl); + } + + template + auto operator()(Args&&... args) -> decltype(_impl->args(std::forward(args)...)) + { + return _impl->args(std::forward(args)...); + } + + template + auto operator()(const T& t) -> decltype(_impl->run(t)) + { + return _impl->run(t); + } + + Connection* operator->() + { + return &_impl; + } + + pool_connection(const pool_connection&) = delete; + pool_connection(pool_connection&& other) + : _impl(std::move(other._impl)), origin(other.origin) {} + pool_connection& operator=(const pool_connection&) = delete; + pool_connection& operator=(pool_connection&&) = delete; + }; +} + +#endif diff --git a/include/sqlpp11/select.h b/include/sqlpp11/select.h index 824645c4..0a1b301f 100644 --- a/include/sqlpp11/select.h +++ b/include/sqlpp11/select.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include #include @@ -82,7 +83,8 @@ namespace sqlpp no_order_by_t, no_limit_t, no_offset_t, - no_union_t>; + no_union_t, + no_for_update_t>; inline blank_select_t select() // FIXME: These should be constexpr { diff --git a/include/sqlpp11/transaction.h b/include/sqlpp11/transaction.h index 130a664b..0d2a16be 100644 --- a/include/sqlpp11/transaction.h +++ b/include/sqlpp11/transaction.h @@ -28,12 +28,21 @@ #define SQLPP_TRANSACTION_H #include +#include namespace sqlpp { static constexpr bool quiet_auto_rollback = false; static constexpr bool report_auto_rollback = true; + enum class isolation_level { + undefined, // use the current database default + serializable, // highest level, stronguest guarantee + repeatable_read, // DBMS holds read and write locks + read_committed, // DMBS holds read locks, non-repeatable reads can occur + read_uncommitted // lowest isolation level, dirty reads may occur + }; + template class transaction_t { @@ -48,6 +57,12 @@ namespace sqlpp _db.start_transaction(); } + transaction_t(Db& db, bool report_unfinished_transaction, isolation_level isolation) + : _db(db), _report_unfinished_transaction(report_unfinished_transaction) + { + _db.start_transaction(isolation); + } + transaction_t(const transaction_t&) = delete; transaction_t(transaction_t&&) = default; transaction_t& operator=(const transaction_t&) = delete; @@ -88,7 +103,13 @@ namespace sqlpp template transaction_t start_transaction(Db& db, bool report_unfinished_transaction = report_auto_rollback) { - return {db, report_unfinished_transaction}; + return {db, report_unfinished_transaction}; + } + + template + transaction_t start_transaction(Db& db, isolation_level isolation, bool report_unfinished_transaction = report_auto_rollback) + { + return {db, report_unfinished_transaction, isolation}; } } diff --git a/include/sqlpp11/type_traits.h b/include/sqlpp11/type_traits.h index cf377f07..427308c6 100644 --- a/include/sqlpp11/type_traits.h +++ b/include/sqlpp11/type_traits.h @@ -200,6 +200,7 @@ namespace sqlpp SQLPP_VALUE_TRAIT_GENERATOR(is_having) SQLPP_VALUE_TRAIT_GENERATOR(is_order_by) SQLPP_VALUE_TRAIT_GENERATOR(is_limit) + SQLPP_VALUE_TRAIT_GENERATOR(is_for_update) SQLPP_VALUE_TRAIT_GENERATOR(is_offset) SQLPP_VALUE_TRAIT_GENERATOR(is_using_) SQLPP_VALUE_TRAIT_GENERATOR(is_column_list) diff --git a/scripts/ddl2cpp b/scripts/ddl2cpp index d0406c15..158b4b0f 100755 --- a/scripts/ddl2cpp +++ b/scripts/ddl2cpp @@ -204,6 +204,7 @@ types = { 'integer': 'integer', 'int': 'integer', 'serial': 'integer', # PostgreSQL + 'mediumint' : 'integer', 'bigint': 'bigint', 'bigserial': 'bigint', # PostgreSQL 'char': 'char_', diff --git a/test_serializer/CMakeLists.txt b/test_serializer/CMakeLists.txt index 07add5a0..2246b936 100644 --- a/test_serializer/CMakeLists.txt +++ b/test_serializer/CMakeLists.txt @@ -30,6 +30,7 @@ set(test_serializer_names Insert TableAlias Where + ForUpdate ) create_test_sourcelist(test_serializer_sources test_serializer_main.cpp ${test_serializer_names}) diff --git a/test_serializer/ForUpdate.cpp b/test_serializer/ForUpdate.cpp new file mode 100644 index 00000000..3d57fe43 --- /dev/null +++ b/test_serializer/ForUpdate.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017, Serge Robyns + * 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 "compare.h" +#include "Sample.h" +#include + +#include + +namespace +{ + auto getTrue() -> std::string + { + MockDb::_serializer_context_t printer = {}; + return serialize(sqlpp::value(true), printer).str(); + } + + auto getFalse() -> std::string + { + MockDb::_serializer_context_t printer = {}; + return serialize(sqlpp::value(false), printer).str(); + } +} + +int ForUpdate(int, char* []) +{ + const auto foo = test::TabFoo{}; + const auto bar = test::TabBar{}; + + // Unconditionally + compare(__LINE__, select(foo.omega).from(foo).unconditionally().for_update(), "SELECT tab_foo.omega FROM tab_foo FOR UPDATE "); + + // Never + compare(__LINE__, where(sqlpp::value(false)), " WHERE " + getFalse()); + + + return 0; +} diff --git a/tests/MockDb.h b/tests/MockDb.h index 6e9dece4..8e5d5308 100644 --- a/tests/MockDb.h +++ b/tests/MockDb.h @@ -28,12 +28,19 @@ #include #include +#include #include #include #include #include #include +// an object to store internal Mock flags and values to validate in tests +struct InternalMockData { + sqlpp::isolation_level _last_isolation_level; + sqlpp::isolation_level _default_isolation_level; +}; + template struct MockDbT : public sqlpp::connection { @@ -244,6 +251,38 @@ struct MockDbT : public sqlpp::connection { return {name}; } + + void start_transaction() + { + _mock_data._last_isolation_level = _mock_data._default_isolation_level; + } + + void start_transaction(sqlpp::isolation_level level) + { + _mock_data._last_isolation_level = level; + } + + void set_default_isolation_level(sqlpp::isolation_level level) + { + _mock_data._default_isolation_level = level; + } + + sqlpp::isolation_level get_default_isolation_level() + { + return _mock_data._default_isolation_level; + } + + void rollback_transaction(bool) + {} + + void commit_transaction() + {} + + void report_rollback_failure(std::string) + {} + + // temporary data store to verify the expected results were produced + InternalMockData _mock_data; }; using MockDb = MockDbT; diff --git a/tests/Select.cpp b/tests/Select.cpp index 74edacfc..90f25f70 100644 --- a/tests/Select.cpp +++ b/tests/Select.cpp @@ -107,6 +107,14 @@ int Select(int, char* []) std::cout << a << ", " << b << ", " << g << std::endl; } + for (const auto& row : db(select(all_of(t).as(t), t.gamma).from(t).where(t.alpha > 7).for_update())) + { + int64_t a = row.tabBar.alpha; + const std::string b = row.tabBar.beta; + const bool g = row.gamma; + std::cout << a << ", " << b << ", " << g << std::endl; + } + for (const auto& row : db(select(all_of(t), all_of(f)).from(t.join(f).on(t.alpha > f.omega and not t.gamma)).unconditionally())) { @@ -199,5 +207,22 @@ int Select(int, char* []) for_each_field(row, to_cerr{}); } + { + auto transaction = start_transaction(db, sqlpp::isolation_level::read_committed); + if (db._mock_data._last_isolation_level != sqlpp::isolation_level::read_committed) + { + std::cout << "Error: transaction isolation level does not match expected level" << std::endl; + } + + } + db.set_default_isolation_level(sqlpp::isolation_level::read_uncommitted); + { + auto transaction = start_transaction(db); + if (db._mock_data._last_isolation_level != sqlpp::isolation_level::read_uncommitted) + { + std::cout << "Error: transaction isolation level does not match default level" << std::endl; + } + } + return 0; }