summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhaslersn <sebastian.hasler@gmx.net>2019-07-08 01:19:01 +0200
committerhaslersn <sebastian.hasler@gmx.net>2019-07-08 02:00:34 +0200
commit835c3f94a3301392d56396b25e3f5a3012b38b90 (patch)
treea8c42fe08450590eddad127df65c1de20d0c16dc
parentf0983603306cb66c7e5bbd1f2da81953bdc636ef (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.hpp3
-rw-r--r--include/djinterop/transaction_guard.hpp58
-rw-r--r--include/meson.build3
-rw-r--r--src/djinterop/database.cpp6
-rw-r--r--src/djinterop/enginelibrary/el_database_impl.cpp8
-rw-r--r--src/djinterop/enginelibrary/el_database_impl.hpp1
-rw-r--r--src/djinterop/enginelibrary/el_storage.hpp3
-rw-r--r--src/djinterop/enginelibrary/el_transaction_guard_impl.cpp62
-rw-r--r--src/djinterop/enginelibrary/el_transaction_guard_impl.hpp44
-rw-r--r--src/djinterop/impl/database_impl.hpp2
-rw-r--r--src/djinterop/impl/transaction_guard_impl.cpp26
-rw-r--r--src/djinterop/impl/transaction_guard_impl.hpp33
-rw-r--r--src/djinterop/transaction_guard.cpp67
-rw-r--r--src/meson.build3
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'
]