diff options
author | haslersn <sebastian.hasler@gmx.net> | 2019-07-08 01:19:01 +0200 |
---|---|---|
committer | haslersn <sebastian.hasler@gmx.net> | 2019-07-08 02:00:34 +0200 |
commit | 835c3f94a3301392d56396b25e3f5a3012b38b90 (patch) | |
tree | a8c42fe08450590eddad127df65c1de20d0c16dc | |
parent | f0983603306cb66c7e5bbd1f2da81953bdc636ef (diff) |
transaction_guard: RAII class for database transactions
A default-constructed `transaction_guard` is empty. That is, it
represents no transaction. If a `transaction_guard` isn't empty, then
you can
* perform a COMMIT via `transaction_guard::commit()` or
* perform a ROLLBACK via `transaction_guard::rollback()` or
* perform a ROLLBACK implicitly by destructing the `transaction_guard`.
In any of those cases, the `transaction_guard` becomes empty (even if
the operation fails by throwing an exception). Of those, only
`transaction_guard::commit()` can throw an exception. Performing a
rollback does always succeed.
-rw-r--r-- | include/djinterop/database.hpp | 3 | ||||
-rw-r--r-- | include/djinterop/transaction_guard.hpp | 58 | ||||
-rw-r--r-- | include/meson.build | 3 | ||||
-rw-r--r-- | src/djinterop/database.cpp | 6 | ||||
-rw-r--r-- | src/djinterop/enginelibrary/el_database_impl.cpp | 8 | ||||
-rw-r--r-- | src/djinterop/enginelibrary/el_database_impl.hpp | 1 | ||||
-rw-r--r-- | src/djinterop/enginelibrary/el_storage.hpp | 3 | ||||
-rw-r--r-- | src/djinterop/enginelibrary/el_transaction_guard_impl.cpp | 62 | ||||
-rw-r--r-- | src/djinterop/enginelibrary/el_transaction_guard_impl.hpp | 44 | ||||
-rw-r--r-- | src/djinterop/impl/database_impl.hpp | 2 | ||||
-rw-r--r-- | src/djinterop/impl/transaction_guard_impl.cpp | 26 | ||||
-rw-r--r-- | src/djinterop/impl/transaction_guard_impl.hpp | 33 | ||||
-rw-r--r-- | src/djinterop/transaction_guard.cpp | 67 | ||||
-rw-r--r-- | src/meson.build | 3 |
14 files changed, 317 insertions, 2 deletions
diff --git a/include/djinterop/database.hpp b/include/djinterop/database.hpp index 9988c0b..ed98237 100644 --- a/include/djinterop/database.hpp +++ b/include/djinterop/database.hpp @@ -40,6 +40,7 @@ class crate; class database_impl; class semantic_version; class track; +class transaction_guard; class database_not_found : public std::runtime_error { @@ -62,6 +63,8 @@ public: /// Copy assignment operator database& operator=(const database& db); + transaction_guard begin_transaction() const; + /// Returns the crate with the given ID /// /// If no such crate exists in the database, then boost::none is returned. diff --git a/include/djinterop/transaction_guard.hpp b/include/djinterop/transaction_guard.hpp new file mode 100644 index 0000000..0bd62f1 --- /dev/null +++ b/include/djinterop/transaction_guard.hpp @@ -0,0 +1,58 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once +#ifndef DJINTEROP_TRANSACTION_GUARD_HPP +#define DJINTEROP_TRANSACTION_GUARD_HPP + +#if __cplusplus < 201103L && _MSVC_LANG < 201103L +#error This library needs at least a C++11 compliant compiler +#endif + +#include <memory> + +namespace djinterop +{ +class transaction_guard_impl; + +class transaction_guard +{ +public: + transaction_guard() noexcept; + + transaction_guard(transaction_guard&& other) noexcept; + + transaction_guard& operator=(transaction_guard&& other) noexcept; + + ~transaction_guard(); + + explicit operator bool() noexcept; + + void commit(); + + void rollback(); + + // TODO (haslersn): non public? + transaction_guard(std::unique_ptr<transaction_guard_impl> pimpl) noexcept; + +private: + std::unique_ptr<transaction_guard_impl> pimpl_; +}; + +} // namespace djinterop + +#endif diff --git a/include/meson.build b/include/meson.build index f3c6928..a91dbd3 100644 --- a/include/meson.build +++ b/include/meson.build @@ -9,7 +9,8 @@ public_header_files = [ 'djinterop/pad_color.hpp', 'djinterop/performance_data.hpp', 'djinterop/semantic_version.hpp', - 'djinterop/track.hpp' + 'djinterop/track.hpp', + 'djinterop/transaction_guard.hpp' ] install_headers(public_header_files, subdir: 'djinterop') diff --git a/src/djinterop/database.cpp b/src/djinterop/database.cpp index 9c072f1..cd259e1 100644 --- a/src/djinterop/database.cpp +++ b/src/djinterop/database.cpp @@ -22,6 +22,7 @@ #include <djinterop/enginelibrary/schema.hpp> #include <djinterop/impl/database_impl.hpp> #include <djinterop/impl/util.hpp> +#include <djinterop/transaction_guard.hpp> namespace djinterop { @@ -31,6 +32,11 @@ database::~database() = default; database& database::operator=(const database& db) = default; +transaction_guard database::begin_transaction() const +{ + return pimpl_->begin_transaction(); +} + boost::optional<crate> database::crate_by_id(int64_t id) const { return pimpl_->crate_by_id(id); diff --git a/src/djinterop/enginelibrary/el_database_impl.cpp b/src/djinterop/enginelibrary/el_database_impl.cpp index e8283e0..f6efc87 100644 --- a/src/djinterop/enginelibrary/el_database_impl.cpp +++ b/src/djinterop/enginelibrary/el_database_impl.cpp @@ -20,8 +20,10 @@ #include <djinterop/enginelibrary/el_database_impl.hpp> #include <djinterop/enginelibrary/el_storage.hpp> #include <djinterop/enginelibrary/el_track_impl.hpp> +#include <djinterop/enginelibrary/el_transaction_guard_impl.hpp> #include <djinterop/enginelibrary/schema.hpp> #include <djinterop/impl/util.hpp> +#include <djinterop/transaction_guard.hpp> namespace djinterop { @@ -43,6 +45,12 @@ el_database_impl::el_database_impl(std::shared_ptr<el_storage> storage) { } +transaction_guard el_database_impl::begin_transaction() +{ + return transaction_guard{ + std::make_unique<el_transaction_guard_impl>(storage_)}; +} + boost::optional<crate> el_database_impl::crate_by_id(int64_t id) { boost::optional<crate> cr; diff --git a/src/djinterop/enginelibrary/el_database_impl.hpp b/src/djinterop/enginelibrary/el_database_impl.hpp index e1e8280..dd9facc 100644 --- a/src/djinterop/enginelibrary/el_database_impl.hpp +++ b/src/djinterop/enginelibrary/el_database_impl.hpp @@ -32,6 +32,7 @@ public: el_database_impl(std::string directory); el_database_impl(std::shared_ptr<el_storage> storage); + transaction_guard begin_transaction() override; boost::optional<djinterop::crate> crate_by_id(int64_t id) override; std::vector<djinterop::crate> crates() override; std::vector<djinterop::crate> crates_by_name( diff --git a/src/djinterop/enginelibrary/el_storage.hpp b/src/djinterop/enginelibrary/el_storage.hpp index cb52879..d5f0b2d 100644 --- a/src/djinterop/enginelibrary/el_storage.hpp +++ b/src/djinterop/enginelibrary/el_storage.hpp @@ -30,8 +30,9 @@ class el_storage public: el_storage(std::string directory); - std::string directory; sqlite::database db; + int64_t last_savepoint = 0; + std::string directory; }; } // namespace enginelibrary diff --git a/src/djinterop/enginelibrary/el_transaction_guard_impl.cpp b/src/djinterop/enginelibrary/el_transaction_guard_impl.cpp new file mode 100644 index 0000000..6632d51 --- /dev/null +++ b/src/djinterop/enginelibrary/el_transaction_guard_impl.cpp @@ -0,0 +1,62 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <djinterop/enginelibrary/el_storage.hpp> +#include <djinterop/enginelibrary/el_transaction_guard_impl.hpp> + +namespace djinterop +{ +namespace enginelibrary +{ +el_transaction_guard_impl::el_transaction_guard_impl( + std::shared_ptr<el_storage> storage) + : storage_{std::move(storage)}, savepoint_{++storage_->last_savepoint} +{ + // TODO (haslersn): Should el_storage::last_savepoint be atomic such that + // this is thread-safe? + storage_->db << "SAVEPOINT ?" << savepoint_; +} + +el_transaction_guard_impl::~el_transaction_guard_impl() +{ + if (savepoint_ != 0) + { + try + { + storage_->db << "ROLLBACK TO ?" << savepoint_; + } + catch (...) + { + // The exception is intentionally swallowed. An exception could for + // example arise if SQLite performed an automatic rollback, causing + // the explicit rollback to return an error. Such an error does no + // harm, so we swallow it. + // + // TODO (haslersn): We could still issue a warning + } + } +} + +void el_transaction_guard_impl::commit() +{ + auto savepoint = savepoint_; + savepoint_ = 0; + storage_->db << "RELEASE ?" << savepoint; +} + +} // namespace enginelibrary +} // namespace djinterop diff --git a/src/djinterop/enginelibrary/el_transaction_guard_impl.hpp b/src/djinterop/enginelibrary/el_transaction_guard_impl.hpp new file mode 100644 index 0000000..4a1ecad --- /dev/null +++ b/src/djinterop/enginelibrary/el_transaction_guard_impl.hpp @@ -0,0 +1,44 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <array> +#include <memory> + +#include <djinterop/impl/transaction_guard_impl.hpp> + +namespace djinterop +{ +namespace enginelibrary +{ +class el_storage; + +class el_transaction_guard_impl : public transaction_guard_impl +{ +public: + el_transaction_guard_impl(std::shared_ptr<el_storage> storage_); + ~el_transaction_guard_impl(); + void commit() override; + +private: + std::shared_ptr<el_storage> storage_; + int64_t savepoint_; +}; + +} // namespace enginelibrary +} // namespace djinterop
\ No newline at end of file diff --git a/src/djinterop/impl/database_impl.hpp b/src/djinterop/impl/database_impl.hpp index b5d21ca..891704b 100644 --- a/src/djinterop/impl/database_impl.hpp +++ b/src/djinterop/impl/database_impl.hpp @@ -28,12 +28,14 @@ namespace djinterop class crate; class semantic_version; class track; +class transaction_guard; class database_impl { public: virtual ~database_impl(); + virtual transaction_guard begin_transaction() = 0; virtual boost::optional<crate> crate_by_id(int64_t id) = 0; virtual std::vector<crate> crates() = 0; virtual std::vector<crate> crates_by_name(boost::string_view name) = 0; diff --git a/src/djinterop/impl/transaction_guard_impl.cpp b/src/djinterop/impl/transaction_guard_impl.cpp new file mode 100644 index 0000000..474dcac --- /dev/null +++ b/src/djinterop/impl/transaction_guard_impl.cpp @@ -0,0 +1,26 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <djinterop/impl/transaction_guard_impl.hpp> + +namespace djinterop +{ +transaction_guard_impl::transaction_guard_impl() noexcept = default; + +transaction_guard_impl::~transaction_guard_impl() noexcept = default; + +} // namespace djinterop diff --git a/src/djinterop/impl/transaction_guard_impl.hpp b/src/djinterop/impl/transaction_guard_impl.hpp new file mode 100644 index 0000000..f0c316f --- /dev/null +++ b/src/djinterop/impl/transaction_guard_impl.hpp @@ -0,0 +1,33 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +namespace djinterop +{ +class transaction_guard_impl +{ +public: + transaction_guard_impl() noexcept; + virtual ~transaction_guard_impl() noexcept; + + /// For the implementation, we guarantee that `commit()` is called at most + /// once per object. (See `djinterop/transaction_guard.cpp` to see why.) + virtual void commit() = 0; +}; + +} // namespace djinterop diff --git a/src/djinterop/transaction_guard.cpp b/src/djinterop/transaction_guard.cpp new file mode 100644 index 0000000..52c9eeb --- /dev/null +++ b/src/djinterop/transaction_guard.cpp @@ -0,0 +1,67 @@ +/* + This file is part of libdjinterop. + + libdjinterop is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + libdjinterop is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with libdjinterop. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <djinterop/impl/transaction_guard_impl.hpp> +#include <djinterop/transaction_guard.hpp> + +namespace djinterop +{ +transaction_guard::transaction_guard() noexcept = default; + +transaction_guard::transaction_guard(transaction_guard&& other) noexcept = + default; + +transaction_guard& transaction_guard::operator=( + transaction_guard&& other) noexcept = default; + +transaction_guard::~transaction_guard() = default; + +transaction_guard::operator bool() noexcept +{ + return static_cast<bool>(pimpl_); +} + +void transaction_guard::commit() +{ + if (pimpl_ == nullptr) + { + // TODO (haslersn): Consider a custom exception type + throw std::logic_error{"Called commit on an empty transaction_guard"}; + } + // Move to a temporary such that there can only be one attempt to commit + auto temp = std::move(pimpl_); + temp->commit(); +} + +void transaction_guard::rollback() +{ + if (pimpl_ == nullptr) + { + // TODO (haslersn): Consider a custom exception type + throw std::logic_error{"Called rollback on an empty transaction_guard"}; + } + // The destructor of `transaction_guard_impl` performs the rollback + pimpl_ = nullptr; +} + +transaction_guard::transaction_guard( + std::unique_ptr<transaction_guard_impl> pimpl) noexcept + : pimpl_{std::move(pimpl)} +{ +} + +} // namespace djinterop diff --git a/src/meson.build b/src/meson.build index c8e64a2..8c5edae 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,6 +3,7 @@ sources = [ 'djinterop/enginelibrary/el_database_impl.cpp', 'djinterop/enginelibrary/el_storage.cpp', 'djinterop/enginelibrary/el_track_impl.cpp', + 'djinterop/enginelibrary/el_transaction_guard_impl.cpp', 'djinterop/enginelibrary/encode_decode_utils.cpp', 'djinterop/enginelibrary/performance_data_format.cpp', 'djinterop/enginelibrary/schema_1_6_0.cpp', @@ -12,9 +13,11 @@ sources = [ 'djinterop/database.cpp', 'djinterop/enginelibrary.cpp', 'djinterop/track.cpp', + 'djinterop/transaction_guard.cpp', 'djinterop/impl/crate_impl.cpp', 'djinterop/impl/database_impl.cpp', 'djinterop/impl/track_impl.cpp', + 'djinterop/impl/transaction_guard_impl.cpp', 'djinterop/impl/util.cpp', 'sqlite3.c' ] |