-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from yowidin/feature/transactions
Transactions support
- Loading branch information
Showing
11 changed files
with
314 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
#!/usr/bin/env python3 | ||
|
||
import os | ||
import sys | ||
from subprocess import run | ||
from argparse import ArgumentParser | ||
|
||
|
||
def do_run(*args): | ||
print(f"--- running: {' '.join(*args)} ---") | ||
sys.stdout.flush() | ||
run(*args, check=True) | ||
|
||
|
||
def main(): | ||
parser = ArgumentParser('Project builder') | ||
parser.add_argument('preset', help='Build preset') | ||
parser.add_argument('shared', help='Controls whether a shared library should be built') | ||
|
||
args = parser.parse_args() | ||
|
||
preset = args.preset | ||
preset_name = f'conan-{preset.lower()}' | ||
shared = args.shared | ||
|
||
source_dir = os.getcwd() | ||
build_dir = os.path.join(source_dir, 'build', args.preset) | ||
toolchain_path = os.path.join(build_dir, 'generators', 'conan_toolchain.cmake') | ||
|
||
if not os.path.exists(build_dir): | ||
os.makedirs(build_dir) | ||
|
||
do_run(['conan', 'install', '-b', 'missing', '-s', 'compiler.cppstd=17', '-s', | ||
f'build_type={preset}', '-c:h', 'tools.cmake.cmake_layout:build_folder_vars=["settings.build_type"]', | ||
source_dir]) | ||
|
||
do_run(['cmake', '--preset', preset_name, '-DBUILD_TESTING=ON', f'-DBUILD_SHARED_LIBS={shared}', | ||
f'-DCMAKE_RUNTIME_OUTPUT_DIRECTORY={build_dir}']) | ||
|
||
do_run(['cmake', '--build', '--preset', preset_name, '--config', preset]) | ||
|
||
do_run(['ctest', '--test-dir', build_dir, '-C', preset, '--output-on-failure']) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
/** | ||
* @file transaction.h | ||
* @author Dennis Sitelew | ||
* @date May 19, 2023 | ||
*/ | ||
|
||
#ifndef INCLUDE_SQLITE_BURRITO_TRANSACTION_H | ||
#define INCLUDE_SQLITE_BURRITO_TRANSACTION_H | ||
|
||
#include <sqlite-burrito/export.h> | ||
|
||
#include <system_error> | ||
|
||
namespace sqlite_burrito { | ||
|
||
class connection; | ||
|
||
class SQLITE_BURRITO_EXPORT transaction { | ||
public: | ||
//! Transaction behavior, see https://www.sqlite.org/lang_transaction.html for more details | ||
enum class behavior { | ||
deferred, | ||
immediate, | ||
exclusive, | ||
default_behavior = deferred, | ||
}; | ||
|
||
public: | ||
transaction(connection &con, behavior behavior = behavior::default_behavior); | ||
|
||
transaction(transaction &) = delete; | ||
transaction(transaction &&) = default; | ||
|
||
~transaction(); | ||
|
||
public: | ||
transaction &operator=(transaction &) = delete; | ||
transaction &operator=(transaction &&) = default; | ||
|
||
public: | ||
void commit(); | ||
void commit(std::error_code &ec) noexcept; | ||
|
||
void rollback(); | ||
void rollback(std::error_code &ec) noexcept; | ||
|
||
private: | ||
connection *con_; | ||
|
||
bool committed_{false}; | ||
bool rolled_back_{false}; | ||
}; | ||
|
||
} // namespace sqlite_burrito | ||
|
||
#endif // INCLUDE_SQLITE_BURRITO_TRANSACTION_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
/** | ||
* @file transaction.cpp | ||
* @author Dennis Sitelew | ||
* @date May 19, 2023 | ||
*/ | ||
|
||
#include <sqlite-burrito/transaction.h> | ||
#include <sqlite-burrito/statement.h> | ||
|
||
using namespace sqlite_burrito; | ||
|
||
transaction::transaction(connection &con, behavior behavior) | ||
: con_{&con} { | ||
const char *stmt; | ||
switch (behavior) { | ||
case behavior::deferred: | ||
stmt = "BEGIN DEFERRED"; | ||
break; | ||
|
||
case behavior::immediate: | ||
stmt = "BEGIN IMMEDIATE"; | ||
break; | ||
|
||
case behavior::exclusive: | ||
stmt = "BEGIN EXCLUSIVE"; | ||
break; | ||
|
||
default: | ||
throw std::system_error(std::make_error_code(std::errc::invalid_argument)); | ||
} | ||
|
||
statement::execute(*con_, stmt); | ||
} | ||
|
||
transaction::~transaction() { | ||
if (rolled_back_ || committed_) { | ||
return; | ||
} | ||
|
||
// TODO: Handle rollback errors to avoid throwing from the destructor? | ||
statement::execute(*con_, "ROLLBACK TRANSACTION"); | ||
} | ||
|
||
void transaction::commit() { | ||
std::error_code ec; | ||
commit(ec); | ||
if (ec) { | ||
throw std::system_error(ec); | ||
} | ||
} | ||
|
||
void transaction::commit(std::error_code &ec) noexcept { | ||
if (committed_ || rolled_back_) { | ||
ec = std::make_error_code(std::errc::invalid_argument); | ||
return; | ||
} | ||
|
||
statement::execute(*con_, "COMMIT TRANSACTION", ec); | ||
committed_ = true; | ||
} | ||
|
||
void transaction::rollback() { | ||
std::error_code ec; | ||
rollback(ec); | ||
if (ec) { | ||
throw std::system_error(ec); | ||
} | ||
} | ||
|
||
void transaction::rollback(std::error_code &ec) noexcept { | ||
if (committed_ || rolled_back_) { | ||
ec = std::make_error_code(std::errc::invalid_argument); | ||
return; | ||
} | ||
|
||
statement::execute(*con_, "ROLLBACK TRANSACTION", ec); | ||
rolled_back_ = true; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* @file transaction.cpp | ||
* @author Dennis Sitelew | ||
* @date May 19, 2023 | ||
*/ | ||
|
||
#include <catch2/catch.hpp> | ||
|
||
#include <sqlite-burrito/connection.h> | ||
#include <sqlite-burrito/statement.h> | ||
#include <sqlite-burrito/transaction.h> | ||
|
||
#include <system_error> | ||
|
||
using namespace sqlite_burrito; | ||
|
||
namespace { | ||
|
||
class transaction_test { | ||
public: | ||
transaction_test() { | ||
con_.open(":memory:"); | ||
statement::execute(con_, "CREATE TABLE test(value TEXT);"); | ||
select_.prepare("SELECT COUNT(*) FROM test;"); | ||
insert_.prepare("INSERT INTO test(value) VALUES (:pvalue);"); | ||
} | ||
|
||
public: | ||
int count_entries() { | ||
int result; | ||
select_.reset(); | ||
select_.step(); | ||
select_.get(0, result); | ||
return result; | ||
} | ||
|
||
void add_entry(const char *entry) { | ||
insert_.reset(); | ||
insert_.bind(":pvalue", entry); | ||
insert_.execute(); | ||
} | ||
|
||
protected: | ||
connection con_{}; | ||
statement select_{con_}; | ||
statement insert_{con_}; | ||
}; | ||
|
||
} // namespace | ||
|
||
TEST_CASE_METHOD(transaction_test, "Commit should propagate changes", "[transaction]") { | ||
REQUIRE(count_entries() == 0); | ||
|
||
auto trans = con_.begin_transaction(); | ||
REQUIRE_NOTHROW(add_entry("test")); | ||
REQUIRE_NOTHROW(trans.commit()); | ||
REQUIRE(count_entries() == 1); | ||
|
||
// Already committed | ||
REQUIRE_THROWS_AS(trans.commit(), std::system_error); | ||
REQUIRE_THROWS_AS(trans.rollback(), std::system_error); | ||
} | ||
|
||
TEST_CASE_METHOD(transaction_test, "Rollback should discard changes", "[transaction]") { | ||
REQUIRE(count_entries() == 0); | ||
|
||
transaction trans{con_}; | ||
REQUIRE_NOTHROW(add_entry("test")); | ||
REQUIRE_NOTHROW(trans.rollback()); | ||
REQUIRE(count_entries() == 0); | ||
|
||
// Already committed | ||
REQUIRE_THROWS_AS(trans.commit(), std::system_error); | ||
REQUIRE_THROWS_AS(trans.rollback(), std::system_error); | ||
} | ||
|
||
TEST_CASE_METHOD(transaction_test, "Auto-rollback on scope exit", "[transaction]") { | ||
REQUIRE(count_entries() == 0); | ||
{ | ||
transaction trans{con_}; | ||
REQUIRE_NOTHROW(add_entry("test")); | ||
} | ||
REQUIRE(count_entries() == 0); | ||
} | ||
|
||
TEST_CASE_METHOD(transaction_test, "Manual-rollback should not result in errors on scope exit", "[transaction]") { | ||
REQUIRE(count_entries() == 0); | ||
{ | ||
transaction trans{con_}; | ||
REQUIRE_NOTHROW(add_entry("test")); | ||
trans.rollback(); | ||
} | ||
REQUIRE(count_entries() == 0); | ||
} | ||
|
||
TEST_CASE_METHOD(transaction_test, "Manual-commit should not result in errors on scope exit", "[transaction]") { | ||
REQUIRE(count_entries() == 0); | ||
{ | ||
transaction trans{con_}; | ||
REQUIRE_NOTHROW(add_entry("test")); | ||
trans.commit(); | ||
} | ||
REQUIRE(count_entries() == 1); | ||
} | ||
|
||
TEST_CASE_METHOD(transaction_test, "Changes after rolling back should be propagated", "[transaction]") { | ||
REQUIRE(count_entries() == 0); | ||
|
||
{ | ||
transaction trans{con_}; | ||
REQUIRE_NOTHROW(add_entry("1")); | ||
trans.rollback(); | ||
|
||
REQUIRE_NOTHROW(add_entry("2")); | ||
REQUIRE_NOTHROW(add_entry("3")); | ||
} | ||
|
||
REQUIRE(count_entries() == 2); | ||
} |