This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <noto/config.hpp>
|
||||
#include <noto/connection.hpp>
|
||||
#include <noto/errors.hpp>
|
||||
#include <noto/migration.hpp>
|
||||
#include <noto/schema.hpp>
|
||||
#include <noto/transaction.hpp>
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/json/value.hpp>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
namespace noto {
|
||||
|
||||
enum class open_mode {
|
||||
READ_ONLY,
|
||||
READ_WRITE,
|
||||
READ_WRITE_CREATE,
|
||||
MEMORY,
|
||||
};
|
||||
|
||||
struct pragma_setting {
|
||||
std::string name{};
|
||||
std::variant<std::bool_t, std::int64_t, std::string> value{};
|
||||
};
|
||||
|
||||
struct connection_config {
|
||||
std::string name{};
|
||||
std::string filename{":memory:"};
|
||||
noto::open_mode mode{noto::open_mode::READ_WRITE_CREATE};
|
||||
std::bool_t uri{false};
|
||||
std::chrono::milliseconds busy_timeout{std::chrono::seconds{5}};
|
||||
std::bool_t foreign_keys{true};
|
||||
std::vector<noto::pragma_setting> pragmas{};
|
||||
};
|
||||
|
||||
auto validate(noto::connection_config const& config) -> std::expected<void, modulus::error>;
|
||||
auto memory_connection_config(std::string name = {}) -> noto::connection_config;
|
||||
auto file_connection_config(std::string filename, std::string name = {}) -> noto::connection_config;
|
||||
auto parse_database_config(boost::json::value const& value) -> std::expected<noto::connection_config, modulus::error>;
|
||||
auto parse_database_configs(boost::json::value const& value) -> std::expected<std::vector<noto::connection_config>, modulus::error>;
|
||||
auto load_database_configs(std::string const& path) -> std::expected<std::vector<noto::connection_config>, modulus::error>;
|
||||
auto to_json(std::variant<std::bool_t, std::int64_t, std::string> const& value) -> boost::json::value;
|
||||
auto to_json(noto::connection_config const& config) -> boost::json::value;
|
||||
auto to_json(std::vector<noto::connection_config> const& configs) -> boost::json::value;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
#include <noto/config.hpp>
|
||||
#include <noto/detail/errors.hpp>
|
||||
#include <noto/schema.hpp>
|
||||
#include <noto/transaction.hpp>
|
||||
|
||||
namespace noto {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct statement_core;
|
||||
struct connection_core;
|
||||
|
||||
}
|
||||
|
||||
enum class connection_state {
|
||||
IDLE,
|
||||
CONNECTED,
|
||||
CLOSING,
|
||||
CLOSED,
|
||||
FAILED,
|
||||
};
|
||||
|
||||
struct sqlite_value {
|
||||
sqlite_value() = default;
|
||||
sqlite_value(std::nullptr_t);
|
||||
sqlite_value(std::int32_t value);
|
||||
sqlite_value(std::int64_t value);
|
||||
sqlite_value(std::float64_t value);
|
||||
sqlite_value(std::char_t const* value);
|
||||
sqlite_value(std::string value);
|
||||
sqlite_value(std::string_view value);
|
||||
sqlite_value(std::vector<std::byte> value);
|
||||
auto is_null() const -> std::bool_t;
|
||||
std::variant<std::monostate, std::int64_t, std::float64_t, std::string, std::vector<std::byte>> value{};
|
||||
};
|
||||
|
||||
struct query_result {
|
||||
std::vector<std::string> columns{};
|
||||
std::vector<std::vector<noto::sqlite_value>> rows{};
|
||||
std::int64_t changes{};
|
||||
std::int64_t last_insert_rowid{};
|
||||
};
|
||||
|
||||
struct backup_options {
|
||||
std::string source_name{"main"};
|
||||
std::string destination_name{"main"};
|
||||
std::int32_t pages_per_step{128};
|
||||
std::chrono::milliseconds retry_delay{std::chrono::milliseconds{10}};
|
||||
std::optional<std::chrono::milliseconds> retry_timeout{};
|
||||
};
|
||||
|
||||
struct execute_options {
|
||||
std::optional<std::size_t> max_rows{100000};
|
||||
std::optional<std::size_t> max_result_bytes{64ULL * 1024ULL * 1024ULL};
|
||||
};
|
||||
|
||||
struct export_options {
|
||||
std::optional<std::size_t> max_output_bytes{64ULL * 1024ULL * 1024ULL};
|
||||
std::size_t rows_per_yield{128};
|
||||
};
|
||||
|
||||
class connection;
|
||||
|
||||
class statement {
|
||||
public:
|
||||
statement() = default;
|
||||
statement(noto::statement const&) = delete;
|
||||
statement(noto::statement&& other) noexcept = default;
|
||||
~statement();
|
||||
auto operator=(noto::statement const&) -> noto::statement& = delete;
|
||||
auto operator=(noto::statement&& other) noexcept -> noto::statement& = default;
|
||||
auto valid() const -> std::bool_t;
|
||||
auto async_reset(std::vector<noto::sqlite_value> params = {}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_step() -> boost::asio::awaitable<std::expected<std::optional<std::vector<noto::sqlite_value>>, modulus::error>>;
|
||||
auto async_execute(std::vector<noto::sqlite_value> params = {}) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_execute(std::vector<noto::sqlite_value> params, noto::execute_options options) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_finalize() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
private:
|
||||
friend class noto::connection;
|
||||
friend struct noto::detail::connection_core;
|
||||
explicit statement(std::shared_ptr<noto::detail::statement_core> core);
|
||||
std::shared_ptr<noto::detail::statement_core> core_{};
|
||||
};
|
||||
|
||||
class connection {
|
||||
public:
|
||||
connection(boost::asio::any_io_executor executor, noto::connection_config config);
|
||||
connection(noto::connection const&) = delete;
|
||||
connection(noto::connection&&) noexcept = default;
|
||||
~connection();
|
||||
auto operator=(noto::connection const&) -> noto::connection& = delete;
|
||||
auto operator=(noto::connection&&) noexcept -> noto::connection& = default;
|
||||
auto config() const -> noto::connection_config;
|
||||
auto state() const -> noto::connection_state;
|
||||
auto async_connect() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_close() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_execute(std::string sql, std::vector<noto::sqlite_value> params = {}) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_execute(std::string sql, std::vector<noto::sqlite_value> params, noto::execute_options options) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_prepare(std::string sql) -> boost::asio::awaitable<std::expected<noto::statement, modulus::error>>;
|
||||
auto async_begin_transaction(noto::transaction_mode mode = noto::transaction_mode::IMMEDIATE) -> boost::asio::awaitable<std::expected<noto::transaction, modulus::error>>;
|
||||
template <typename CALLBACK>
|
||||
auto async_with_transaction(noto::transaction_mode mode, CALLBACK callback) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_create_table(noto::table_definition definition) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_drop_table(noto::table_name table, std::bool_t if_exists = true) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_create_index(noto::index_definition definition) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_drop_index(noto::index_definition definition) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_backup_to_file(std::string path) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_backup_to_file(std::string path, noto::backup_options options) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_restore_from_file(std::string path) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_restore_from_file(std::string path, noto::backup_options options) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_export_sql() -> boost::asio::awaitable<std::expected<std::string, modulus::error>>;
|
||||
auto async_export_sql(noto::export_options options) -> boost::asio::awaitable<std::expected<std::string, modulus::error>>;
|
||||
auto async_export_sql_to_file(std::string path) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_export_sql_to_file(std::string path, noto::export_options options) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_integrity_check() -> boost::asio::awaitable<std::expected<std::vector<std::string>, modulus::error>>;
|
||||
auto async_quick_check() -> boost::asio::awaitable<std::expected<std::vector<std::string>, modulus::error>>;
|
||||
auto async_foreign_key_check() -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_get_pragma(std::string name) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_set_pragma(std::string name, std::variant<std::bool_t, std::int64_t, std::string> value) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_foreign_keys() -> boost::asio::awaitable<std::expected<std::bool_t, modulus::error>>;
|
||||
auto async_set_foreign_keys(std::bool_t enabled) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_busy_timeout() -> boost::asio::awaitable<std::expected<std::chrono::milliseconds, modulus::error>>;
|
||||
auto async_set_busy_timeout(std::chrono::milliseconds timeout) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_user_version() -> boost::asio::awaitable<std::expected<std::int64_t, modulus::error>>;
|
||||
auto async_set_user_version(std::int64_t version) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_application_id() -> boost::asio::awaitable<std::expected<std::int64_t, modulus::error>>;
|
||||
auto async_set_application_id(std::int64_t id) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_journal_mode(std::string mode) -> boost::asio::awaitable<std::expected<std::string, modulus::error>>;
|
||||
auto async_wal_checkpoint(std::string mode = "PASSIVE") -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
private:
|
||||
friend class noto::transaction;
|
||||
explicit connection(std::shared_ptr<noto::detail::connection_core> core);
|
||||
auto core() const -> std::shared_ptr<noto::detail::connection_core>;
|
||||
std::shared_ptr<noto::detail::connection_core> core_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template <typename CALLBACK>
|
||||
auto noto::connection::async_with_transaction(noto::transaction_mode mode, CALLBACK callback) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
return [](noto::connection connection, noto::transaction_mode mode, CALLBACK callback) -> boost::asio::awaitable<std::expected<void, modulus::error>> {
|
||||
auto transaction_result = co_await connection.async_begin_transaction(mode);
|
||||
if (!transaction_result) {
|
||||
co_return std::unexpected(transaction_result.error());
|
||||
}
|
||||
auto transaction = std::move(*transaction_result);
|
||||
auto callback_status = std::expected<void, modulus::error>{};
|
||||
auto exception_status = std::optional<modulus::error>{};
|
||||
try {
|
||||
callback_status = co_await callback(connection);
|
||||
} catch (std::exception const& error) {
|
||||
exception_status = noto::detail::make_error(noto::detail::error_internal, std::string{"transaction callback threw: "} + error.what());
|
||||
} catch (...) {
|
||||
exception_status = noto::detail::make_error(noto::detail::error_internal, "transaction callback threw an unknown exception");
|
||||
}
|
||||
if (exception_status) {
|
||||
auto rollback = co_await transaction.async_rollback();
|
||||
if (!rollback) {
|
||||
co_return rollback;
|
||||
}
|
||||
co_return std::unexpected(*exception_status);
|
||||
}
|
||||
if (!callback_status) {
|
||||
auto rollback = co_await transaction.async_rollback();
|
||||
if (!rollback) {
|
||||
co_return rollback;
|
||||
}
|
||||
co_return callback_status;
|
||||
}
|
||||
auto commit = co_await transaction.async_commit();
|
||||
if (!commit && transaction.active()) {
|
||||
auto rollback = co_await transaction.async_rollback();
|
||||
if (!rollback) {
|
||||
co_return rollback;
|
||||
}
|
||||
}
|
||||
co_return commit;
|
||||
}(noto::connection{core_}, mode, std::move(callback));
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
#include <noto/connection.hpp>
|
||||
#include <noto/config.hpp>
|
||||
#include <noto/transaction.hpp>
|
||||
|
||||
namespace noto::detail {
|
||||
|
||||
struct transaction_result {
|
||||
std::expected<void, modulus::error> status{};
|
||||
std::bool_t active{};
|
||||
};
|
||||
|
||||
struct connection_core : std::enable_shared_from_this<noto::detail::connection_core> {
|
||||
connection_core(boost::asio::any_io_executor executor, noto::connection_config config);
|
||||
~connection_core();
|
||||
auto current_state() const -> noto::connection_state;
|
||||
auto set_state(noto::connection_state state) -> void;
|
||||
auto mark_failed(std::string message) -> void;
|
||||
auto abandon_transaction() -> void;
|
||||
auto async_connect() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_close() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_execute(std::string sql, std::vector<noto::sqlite_value> params, noto::execute_options options) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_prepare(std::string sql) -> boost::asio::awaitable<std::expected<noto::statement, modulus::error>>;
|
||||
auto async_begin_transaction(noto::transaction_mode mode) -> boost::asio::awaitable<std::expected<noto::transaction, modulus::error>>;
|
||||
auto async_commit_transaction() -> boost::asio::awaitable<noto::detail::transaction_result>;
|
||||
auto async_rollback_transaction() -> boost::asio::awaitable<noto::detail::transaction_result>;
|
||||
auto async_backup_to_file(std::string path, noto::backup_options options) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_restore_from_file(std::string path, noto::backup_options options) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_export_sql(noto::export_options options) -> boost::asio::awaitable<std::expected<std::string, modulus::error>>;
|
||||
auto async_export_sql_to_file(std::string path, noto::export_options options) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_set_busy_timeout(std::chrono::milliseconds timeout) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
boost::asio::any_io_executor executor{};
|
||||
boost::asio::strand<boost::asio::any_io_executor> strand;
|
||||
noto::connection_config config{};
|
||||
std::chrono::milliseconds busy_timeout{};
|
||||
sqlite3* database{};
|
||||
std::shared_ptr<std::atomic<noto::connection_state>> state{std::make_shared<std::atomic<noto::connection_state>>(noto::connection_state::IDLE)};
|
||||
std::bool_t busy{};
|
||||
std::bool_t transaction_active{};
|
||||
std::atomic_size_t live_statements{};
|
||||
};
|
||||
|
||||
struct statement_core {
|
||||
statement_core(std::shared_ptr<noto::detail::connection_core> connection, sqlite3_stmt* statement);
|
||||
~statement_core();
|
||||
auto async_reset(std::vector<noto::sqlite_value> params) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_step() -> boost::asio::awaitable<std::expected<std::optional<std::vector<noto::sqlite_value>>, modulus::error>>;
|
||||
auto async_execute(std::vector<noto::sqlite_value> params, noto::execute_options options) -> boost::asio::awaitable<std::expected<noto::query_result, modulus::error>>;
|
||||
auto async_finalize() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
std::shared_ptr<noto::detail::connection_core> connection{};
|
||||
sqlite3_stmt* statement{};
|
||||
std::atomic_bool finalized{};
|
||||
std::bool_t counted{};
|
||||
};
|
||||
|
||||
struct transaction_state {
|
||||
transaction_state(std::shared_ptr<noto::detail::connection_core> core, std::function<void()> on_finish = {});
|
||||
auto finish() noexcept -> void;
|
||||
std::shared_ptr<noto::detail::connection_core> core{};
|
||||
std::function<void()> on_finish{};
|
||||
std::atomic_bool active{false};
|
||||
std::atomic_bool detached{false};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
namespace noto::detail {
|
||||
|
||||
constexpr std::int32_t error_invalid_argument{1};
|
||||
constexpr std::int32_t error_not_found{2};
|
||||
constexpr std::int32_t error_unavailable{3};
|
||||
constexpr std::int32_t error_timeout{4};
|
||||
constexpr std::int32_t error_cancelled{5};
|
||||
constexpr std::int32_t error_unauthenticated{6};
|
||||
constexpr std::int32_t error_permission_denied{7};
|
||||
constexpr std::int32_t error_already_exists{8};
|
||||
constexpr std::int32_t error_constraint_violation{9};
|
||||
constexpr std::int32_t error_syntax_error{10};
|
||||
constexpr std::int32_t error_protocol_error{11};
|
||||
constexpr std::int32_t error_connection_lost{12};
|
||||
constexpr std::int32_t error_data_loss{13};
|
||||
constexpr std::int32_t error_resource_exhausted{14};
|
||||
constexpr std::int32_t error_internal{15};
|
||||
|
||||
auto make_error(std::int32_t code, std::string message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> modulus::error;
|
||||
auto fail_void(modulus::error error) -> std::expected<void, modulus::error>;
|
||||
auto fail_void(std::int32_t code, std::string message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> std::expected<void, modulus::error>;
|
||||
auto fail_void(std::expected<void, modulus::error> const& result) -> std::expected<void, modulus::error>;
|
||||
|
||||
template <typename VALUE>
|
||||
auto fail(modulus::error error) -> std::expected<VALUE, modulus::error>;
|
||||
|
||||
template <typename VALUE>
|
||||
auto fail(std::expected<void, modulus::error> const& result) -> std::expected<VALUE, modulus::error>;
|
||||
|
||||
template <typename VALUE>
|
||||
auto fail(std::int32_t code, std::string message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> std::expected<VALUE, modulus::error>;
|
||||
|
||||
}
|
||||
|
||||
template <typename VALUE>
|
||||
auto noto::detail::fail(modulus::error error) -> std::expected<VALUE, modulus::error> {
|
||||
return modulus::fail<VALUE>(std::move(error));
|
||||
}
|
||||
|
||||
template <typename VALUE>
|
||||
auto noto::detail::fail(std::expected<void, modulus::error> const& result) -> std::expected<VALUE, modulus::error> {
|
||||
assert(!result);
|
||||
return modulus::fail<VALUE>(result.error());
|
||||
}
|
||||
|
||||
template <typename VALUE>
|
||||
auto noto::detail::fail(std::int32_t code, std::string message, modulus::source_location location) -> std::expected<VALUE, modulus::error> {
|
||||
return noto::detail::fail<VALUE>(noto::detail::make_error(code, std::move(message), location));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
namespace noto::detail {
|
||||
|
||||
auto quote_sql_literal(std::string_view value, std::string_view label, std::bool_t allow_empty = true) -> std::expected<std::string, modulus::error>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <string_view>
|
||||
|
||||
#include <modulus.hpp>
|
||||
#include <cstdint>
|
||||
|
||||
struct sqlite3;
|
||||
|
||||
namespace noto {
|
||||
|
||||
auto error_from(std::int32_t code, sqlite3* database = nullptr, std::string_view operation = {}) -> modulus::error;
|
||||
auto result_from(std::int32_t code, sqlite3* database = nullptr, std::string_view operation = {}) -> std::expected<void, modulus::error>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
#include <noto/connection.hpp>
|
||||
#include <noto/schema.hpp>
|
||||
|
||||
namespace noto {
|
||||
|
||||
struct migration {
|
||||
std::string id{};
|
||||
std::string description{};
|
||||
std::string checksum{};
|
||||
std::function<boost::asio::awaitable<std::expected<void, modulus::error>>(noto::connection&)> up{};
|
||||
std::function<boost::asio::awaitable<std::expected<void, modulus::error>>(noto::connection&)> down{};
|
||||
};
|
||||
|
||||
struct migration_record {
|
||||
std::string id{};
|
||||
std::string description{};
|
||||
std::string checksum{};
|
||||
std::string applied_at{};
|
||||
};
|
||||
|
||||
struct migration_options {
|
||||
noto::table_name table{{}, "_noto_schema_migrations"};
|
||||
};
|
||||
|
||||
class migrator {
|
||||
public:
|
||||
migrator(noto::connection& connection, noto::migration_options options = {});
|
||||
auto async_applied_migrations() -> boost::asio::awaitable<std::expected<std::vector<noto::migration_record>, modulus::error>>;
|
||||
auto async_migrate(std::vector<noto::migration> migrations) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_rollback(std::vector<noto::migration> migrations, std::string target_id = {}) -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
private:
|
||||
noto::connection* connection_{};
|
||||
noto::migration_options options_{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
namespace noto {
|
||||
|
||||
class trusted_sql_fragment {
|
||||
public:
|
||||
trusted_sql_fragment() = default;
|
||||
static auto unsafe(std::string sql) -> std::expected<noto::trusted_sql_fragment, modulus::error>;
|
||||
auto value() const -> std::string const&;
|
||||
auto empty() const -> std::bool_t;
|
||||
private:
|
||||
explicit trusted_sql_fragment(std::string sql);
|
||||
std::string sql_{};
|
||||
};
|
||||
|
||||
class column_type {
|
||||
public:
|
||||
column_type() = default;
|
||||
static auto integer() -> noto::column_type;
|
||||
static auto bigint() -> noto::column_type;
|
||||
static auto varchar(std::uint32_t length) -> std::expected<noto::column_type, modulus::error>;
|
||||
static auto text() -> noto::column_type;
|
||||
static auto blob() -> noto::column_type;
|
||||
static auto real() -> noto::column_type;
|
||||
static auto boolean() -> noto::column_type;
|
||||
static auto timestamp() -> noto::column_type;
|
||||
static auto decimal(std::uint32_t precision, std::uint32_t scale) -> std::expected<noto::column_type, modulus::error>;
|
||||
static auto trusted(noto::trusted_sql_fragment fragment) -> noto::column_type;
|
||||
auto sql() const -> std::string const&;
|
||||
auto empty() const -> std::bool_t;
|
||||
private:
|
||||
explicit column_type(std::string sql);
|
||||
std::string sql_{};
|
||||
};
|
||||
|
||||
class default_value {
|
||||
public:
|
||||
default_value() = default;
|
||||
static auto null_value() -> noto::default_value;
|
||||
static auto string(std::string_view value) -> std::expected<noto::default_value, modulus::error>;
|
||||
static auto integer(std::int64_t value) -> noto::default_value;
|
||||
static auto unsigned_integer(std::uint64_t value) -> noto::default_value;
|
||||
static auto real(std::float64_t value) -> std::expected<noto::default_value, modulus::error>;
|
||||
static auto boolean(std::bool_t value) -> noto::default_value;
|
||||
static auto current_timestamp() -> noto::default_value;
|
||||
static auto trusted_expression(noto::trusted_sql_fragment fragment) -> noto::default_value;
|
||||
auto sql() const -> std::string const&;
|
||||
auto empty() const -> std::bool_t;
|
||||
private:
|
||||
explicit default_value(std::string sql);
|
||||
std::string sql_{};
|
||||
};
|
||||
|
||||
struct table_name {
|
||||
std::string database{};
|
||||
std::string name{};
|
||||
};
|
||||
|
||||
enum class generated_column_storage {
|
||||
VIRTUAL,
|
||||
STORED,
|
||||
};
|
||||
|
||||
enum class reference_action {
|
||||
RESTRICT,
|
||||
CASCADE,
|
||||
SET_NULL,
|
||||
NO_ACTION,
|
||||
SET_DEFAULT,
|
||||
};
|
||||
|
||||
struct generated_column_definition {
|
||||
noto::trusted_sql_fragment expression{};
|
||||
noto::generated_column_storage storage{noto::generated_column_storage::VIRTUAL};
|
||||
};
|
||||
|
||||
struct column_definition {
|
||||
std::string name{};
|
||||
noto::column_type type{};
|
||||
std::bool_t nullable{true};
|
||||
std::bool_t auto_increment{false};
|
||||
std::bool_t primary_key{false};
|
||||
std::optional<noto::default_value> default_value{};
|
||||
std::optional<noto::generated_column_definition> generated{};
|
||||
};
|
||||
|
||||
struct unique_constraint_definition {
|
||||
std::string name{};
|
||||
std::vector<std::string> columns{};
|
||||
};
|
||||
|
||||
struct check_constraint_definition {
|
||||
std::string name{};
|
||||
noto::trusted_sql_fragment expression{};
|
||||
std::bool_t enforced{true};
|
||||
};
|
||||
|
||||
struct foreign_key_definition {
|
||||
std::string name{};
|
||||
std::vector<std::string> columns{};
|
||||
noto::table_name referenced_table{};
|
||||
std::vector<std::string> referenced_columns{};
|
||||
std::optional<noto::reference_action> on_delete{};
|
||||
std::optional<noto::reference_action> on_update{};
|
||||
};
|
||||
|
||||
struct table_options {
|
||||
std::bool_t strict{false};
|
||||
std::bool_t without_rowid{false};
|
||||
};
|
||||
|
||||
struct table_definition {
|
||||
noto::table_name name{};
|
||||
std::vector<noto::column_definition> columns{};
|
||||
std::vector<std::string> primary_key{};
|
||||
std::vector<noto::unique_constraint_definition> unique_constraints{};
|
||||
std::vector<noto::check_constraint_definition> check_constraints{};
|
||||
std::vector<noto::foreign_key_definition> foreign_keys{};
|
||||
noto::table_options options{};
|
||||
std::bool_t if_not_exists{true};
|
||||
};
|
||||
|
||||
struct index_definition {
|
||||
noto::table_name table{};
|
||||
std::string name{};
|
||||
std::vector<std::string> columns{};
|
||||
std::bool_t unique{false};
|
||||
};
|
||||
|
||||
auto quote_identifier(std::string_view identifier) -> std::expected<std::string, modulus::error>;
|
||||
auto create_table_sql(noto::table_definition const& definition) -> std::expected<std::string, modulus::error>;
|
||||
auto drop_table_sql(noto::table_name const& table, std::bool_t if_exists = true) -> std::expected<std::string, modulus::error>;
|
||||
auto create_index_sql(noto::index_definition const& definition) -> std::expected<std::string, modulus::error>;
|
||||
auto drop_index_sql(noto::index_definition const& definition) -> std::expected<std::string, modulus::error>;
|
||||
auto table_exists_sql() -> std::string;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <expected>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/awaitable.hpp>
|
||||
|
||||
#include <modulus.hpp>
|
||||
|
||||
namespace noto {
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct connection_core;
|
||||
struct transaction_state;
|
||||
|
||||
}
|
||||
|
||||
enum class transaction_mode {
|
||||
DEFERRED,
|
||||
IMMEDIATE,
|
||||
EXCLUSIVE,
|
||||
};
|
||||
|
||||
class connection;
|
||||
|
||||
class transaction {
|
||||
public:
|
||||
transaction() = default;
|
||||
transaction(noto::transaction const&) = delete;
|
||||
transaction(noto::transaction&& other) noexcept;
|
||||
~transaction();
|
||||
auto operator=(noto::transaction const&) -> noto::transaction& = delete;
|
||||
auto operator=(noto::transaction&& other) noexcept -> noto::transaction& = delete;
|
||||
auto active() const -> std::bool_t;
|
||||
auto async_commit() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
auto async_rollback() -> boost::asio::awaitable<std::expected<void, modulus::error>>;
|
||||
private:
|
||||
friend class noto::connection;
|
||||
friend struct noto::detail::connection_core;
|
||||
explicit transaction(std::shared_ptr<noto::detail::transaction_state> state);
|
||||
explicit transaction(std::shared_ptr<noto::detail::connection_core> core, std::function<void()> on_finish = {});
|
||||
std::shared_ptr<noto::detail::transaction_state> state_{};
|
||||
};
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user