From 46f7c13121e4153af6e8e0c1d6331486fe6dff4f Mon Sep 17 00:00:00 2001 From: khodowany Date: Mon, 14 Sep 2026 23:55:04 -0500 Subject: [PATCH] Formatting --- .gitea/workflows/ci.yml | 4 + README.md | 2 + conanfile.py | 8 - include/noto/config.hpp | 14 +- include/noto/connection.hpp | 21 +-- include/noto/detail/core.hpp | 6 +- include/noto/detail/errors.hpp | 7 +- include/noto/detail/sql.hpp | 1 + include/noto/errors.hpp | 5 +- include/noto/migration.hpp | 8 +- include/noto/schema.hpp | 15 +- include/noto/transaction.hpp | 9 +- modulus.py | 8 +- src/config.cpp | 93 ++++++----- src/connection.cpp | 53 +++--- src/detail/core.cpp | 295 +++++++++++++++++---------------- src/detail/errors.cpp | 3 +- src/detail/sql.cpp | 1 + src/errors.cpp | 7 +- src/migration.cpp | 47 +++--- src/schema.cpp | 54 +++--- src/transaction.cpp | 12 +- tests/test.cpp | 172 ++++--------------- 23 files changed, 391 insertions(+), 454 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index aa41bd7..2b275d9 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -48,6 +48,10 @@ jobs: restore-keys: conan-${{ runner.os }}- - name: Configure native release run: python3 modulus.py configure --target x86_64_gcc12_release + - name: Check code style + run: | + python3 -m pip install --no-cache-dir -r build/build_tools/docker/format-requirements.txt + python3 modulus.py format --check - name: Build native release run: python3 modulus.py build --target x86_64_gcc12_release - name: Test native release diff --git a/README.md b/README.md index 7b10046..8d22a12 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Noto uses Modulus for shared build tooling, Boost.JSON, and SQLite integration. ## Build +Code style is managed by Modulus: run `./modulus.py format` or `./modulus.py format --check`. The generated Neovim integration uses the same pinned container formatter for C++ and Python, including existing format-on-save toggles. Configure first to prepare tooling; customize `tooling.editor.clang_format` and `tooling.format.exclude` in `modulus.json`, not generated editor files. See the shared Modulus README for the compact layout and no-alias policy. + Configure, build, and test the default target: ```sh diff --git a/conanfile.py b/conanfile.py index 0cc3426..b448377 100644 --- a/conanfile.py +++ b/conanfile.py @@ -41,15 +41,12 @@ class noto_conan(conan.ConanFile): "tests/*.cpp", "tests/*.hpp", ) - def config_options(self): if self.settings.os == "Windows": del self.options.fPIC - def configure(self): if self.options.shared: del self.options.fPIC - def requirements(self): self.requires( "modulus/latest", @@ -59,7 +56,6 @@ class noto_conan(conan.ConanFile): "with_sqlite": True, }, ) - def generate(self): deps = conan.tools.cmake.CMakeDeps(self) deps.generate() @@ -69,14 +65,12 @@ class noto_conan(conan.ConanFile): toolchain.variables["BUILD_SHARED_LIBS"] = bool(self.options.shared) toolchain.variables["BUILD_TESTING"] = bool(self.options.with_tests) toolchain.generate() - def build(self): cmake = conan.tools.cmake.CMake(self) cmake.configure() cmake.build() if self.options.with_tests and conan.tools.build.can_run(self): cmake.test(target="check") - def package_id(self): self.info.options.rm_safe("with_tests") for requirement in ( @@ -88,11 +82,9 @@ class noto_conan(conan.ConanFile): ): if requirement in self.info.requires.pkg_names: self.info.requires.remove(requirement) - def package(self): cmake = conan.tools.cmake.CMake(self) cmake.install() - def package_info(self): self.cpp_info.set_property( "cmake_file_name", diff --git a/include/noto/config.hpp b/include/noto/config.hpp index 15283ec..fc141cb 100644 --- a/include/noto/config.hpp +++ b/include/noto/config.hpp @@ -35,14 +35,14 @@ struct connection_config { std::vector pragmas{}; }; -auto validate(noto::connection_config const& config) -> std::expected; +auto validate(noto::connection_config const &config) -> std::expected; 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; -auto parse_database_configs(boost::json::value const& value) -> std::expected, modulus::error>; -auto load_database_configs(std::string const& path) -> std::expected, modulus::error>; -auto to_json(std::variant const& value) -> boost::json::value; -auto to_json(noto::connection_config const& config) -> boost::json::value; -auto to_json(std::vector const& configs) -> boost::json::value; +auto parse_database_config(boost::json::value const &value) -> std::expected; +auto parse_database_configs(boost::json::value const &value) -> std::expected, modulus::error>; +auto load_database_configs(std::string const &path) -> std::expected, modulus::error>; +auto to_json(std::variant const &value) -> boost::json::value; +auto to_json(noto::connection_config const &config) -> boost::json::value; +auto to_json(std::vector const &configs) -> boost::json::value; } diff --git a/include/noto/connection.hpp b/include/noto/connection.hpp index 5bdd712..75b8cb4 100644 --- a/include/noto/connection.hpp +++ b/include/noto/connection.hpp @@ -28,6 +28,7 @@ namespace noto { namespace detail { struct statement_core; + struct connection_core; } @@ -46,7 +47,7 @@ struct sqlite_value { 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::char_t const *value); sqlite_value(std::string value); sqlite_value(std::string_view value); sqlite_value(std::vector value); @@ -84,11 +85,11 @@ class connection; class statement { public: statement() = default; - statement(noto::statement const&) = delete; - statement(noto::statement&& other) noexcept = 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 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 params = {}) -> boost::asio::awaitable>; auto async_step() -> boost::asio::awaitable>, modulus::error>>; @@ -105,11 +106,11 @@ private: 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(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 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>; @@ -168,7 +169,7 @@ auto noto::connection::async_with_transaction(noto::transaction_mode mode, CALLB auto exception_status = std::optional{}; try { callback_status = co_await callback(connection); - } catch (std::exception const& error) { + } 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"); diff --git a/include/noto/detail/core.hpp b/include/noto/detail/core.hpp index b93221e..524e904 100644 --- a/include/noto/detail/core.hpp +++ b/include/noto/detail/core.hpp @@ -51,7 +51,7 @@ struct connection_core : std::enable_shared_from_this strand; noto::connection_config config{}; std::chrono::milliseconds busy_timeout{}; - sqlite3* database{}; + sqlite3 *database{}; std::shared_ptr> state{std::make_shared>(noto::connection_state::IDLE)}; std::bool_t busy{}; std::bool_t transaction_active{}; @@ -59,14 +59,14 @@ struct connection_core : std::enable_shared_from_this connection, sqlite3_stmt* statement); + statement_core(std::shared_ptr connection, sqlite3_stmt *statement); ~statement_core(); auto async_reset(std::vector params) -> boost::asio::awaitable>; auto async_step() -> boost::asio::awaitable>, modulus::error>>; auto async_execute(std::vector params, noto::execute_options options) -> boost::asio::awaitable>; auto async_finalize() -> boost::asio::awaitable>; std::shared_ptr connection{}; - sqlite3_stmt* statement{}; + sqlite3_stmt *statement{}; std::atomic_bool finalized{}; std::bool_t counted{}; }; diff --git a/include/noto/detail/errors.hpp b/include/noto/detail/errors.hpp index 2502fb0..d758123 100644 --- a/include/noto/detail/errors.hpp +++ b/include/noto/detail/errors.hpp @@ -8,6 +8,7 @@ #include + namespace noto::detail { constexpr std::int32_t error_invalid_argument{1}; @@ -29,13 +30,13 @@ 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; 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; -auto fail_void(std::expected const& result) -> std::expected; +auto fail_void(std::expected const &result) -> std::expected; template auto fail(modulus::error error) -> std::expected; template -auto fail(std::expected const& result) -> std::expected; +auto fail(std::expected const &result) -> std::expected; template auto fail(std::int32_t code, std::string message, modulus::source_location location = modulus::source_location(__builtin_FILE(), __builtin_LINE(), __builtin_FUNCTION())) -> std::expected; @@ -48,7 +49,7 @@ auto noto::detail::fail(modulus::error error) -> std::expected -auto noto::detail::fail(std::expected const& result) -> std::expected { +auto noto::detail::fail(std::expected const &result) -> std::expected { assert(!result); return modulus::fail(result.error()); } diff --git a/include/noto/detail/sql.hpp b/include/noto/detail/sql.hpp index 5eaf939..f8db1d1 100644 --- a/include/noto/detail/sql.hpp +++ b/include/noto/detail/sql.hpp @@ -6,6 +6,7 @@ #include + namespace noto::detail { auto quote_sql_literal(std::string_view value, std::string_view label, std::bool_t allow_empty = true) -> std::expected; diff --git a/include/noto/errors.hpp b/include/noto/errors.hpp index be82ef5..f286712 100644 --- a/include/noto/errors.hpp +++ b/include/noto/errors.hpp @@ -6,11 +6,12 @@ #include #include + 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; +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; } diff --git a/include/noto/migration.hpp b/include/noto/migration.hpp index 6caf7b4..42849e1 100644 --- a/include/noto/migration.hpp +++ b/include/noto/migration.hpp @@ -18,8 +18,8 @@ struct migration { std::string id{}; std::string description{}; std::string checksum{}; - std::function>(noto::connection&)> up{}; - std::function>(noto::connection&)> down{}; + std::function>(noto::connection &)> up{}; + std::function>(noto::connection &)> down{}; }; struct migration_record { @@ -35,12 +35,12 @@ struct migration_options { class migrator { public: - migrator(noto::connection& connection, noto::migration_options options = {}); + migrator(noto::connection &connection, noto::migration_options options = {}); auto async_applied_migrations() -> boost::asio::awaitable, modulus::error>>; auto async_migrate(std::vector migrations) -> boost::asio::awaitable>; auto async_rollback(std::vector migrations, std::string target_id = {}) -> boost::asio::awaitable>; private: - noto::connection* connection_{}; + noto::connection *connection_{}; noto::migration_options options_{}; }; diff --git a/include/noto/schema.hpp b/include/noto/schema.hpp index 5aa3bf6..e38a2e9 100644 --- a/include/noto/schema.hpp +++ b/include/noto/schema.hpp @@ -9,13 +9,14 @@ #include + namespace noto { class trusted_sql_fragment { public: trusted_sql_fragment() = default; static auto unsafe(std::string sql) -> std::expected; - auto value() const -> std::string const&; + auto value() const -> std::string const &; auto empty() const -> std::bool_t; private: explicit trusted_sql_fragment(std::string sql); @@ -35,7 +36,7 @@ public: static auto timestamp() -> noto::column_type; static auto decimal(std::uint32_t precision, std::uint32_t scale) -> std::expected; static auto trusted(noto::trusted_sql_fragment fragment) -> noto::column_type; - auto sql() const -> std::string const&; + auto sql() const -> std::string const &; auto empty() const -> std::bool_t; private: explicit column_type(std::string sql); @@ -53,7 +54,7 @@ public: 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 sql() const -> std::string const &; auto empty() const -> std::bool_t; private: explicit default_value(std::string sql); @@ -137,10 +138,10 @@ struct index_definition { }; auto quote_identifier(std::string_view identifier) -> std::expected; -auto create_table_sql(noto::table_definition const& definition) -> std::expected; -auto drop_table_sql(noto::table_name const& table, std::bool_t if_exists = true) -> std::expected; -auto create_index_sql(noto::index_definition const& definition) -> std::expected; -auto drop_index_sql(noto::index_definition const& definition) -> std::expected; +auto create_table_sql(noto::table_definition const &definition) -> std::expected; +auto drop_table_sql(noto::table_name const &table, std::bool_t if_exists = true) -> std::expected; +auto create_index_sql(noto::index_definition const &definition) -> std::expected; +auto drop_index_sql(noto::index_definition const &definition) -> std::expected; auto table_exists_sql() -> std::string; } diff --git a/include/noto/transaction.hpp b/include/noto/transaction.hpp index a293817..96a3a2e 100644 --- a/include/noto/transaction.hpp +++ b/include/noto/transaction.hpp @@ -13,6 +13,7 @@ namespace noto { namespace detail { struct connection_core; + struct transaction_state; } @@ -28,11 +29,11 @@ class connection; class transaction { public: transaction() = default; - transaction(noto::transaction const&) = delete; - transaction(noto::transaction&& other) noexcept; + 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 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>; auto async_rollback() -> boost::asio::awaitable>; diff --git a/modulus.py b/modulus.py index b27d44c..1b69392 100755 --- a/modulus.py +++ b/modulus.py @@ -16,6 +16,7 @@ COMMANDS = ( "package", "lsp", "clangd", + "format", ) COMMON_DIR = pathlib.Path("build") / "build_common" TOOLS_DIR = pathlib.Path("build") / "build_tools" @@ -60,6 +61,9 @@ def runtime_ready(root): and (root / TOOLS_DIR / "clangd.py").is_file() and (root / RUNTIME_DIR / "modulus" / "cli.py").is_file() and (root / RUNTIME_DIR / "modulus" / "export.py").is_file() + and (root / TOOLS_DIR / "format.py").is_file() + and (root / RUNTIME_DIR / "modulus" / "formatting.py").is_file() + and (root / RUNTIME_DIR / "modulus" / "format_layout.py").is_file() ) def optional_credentials(): @@ -340,9 +344,9 @@ def ensure_runtime(root): def delegate(command, argv): root = repo_root() - if command == "clangd": + if command in {"clangd", "format"}: if not runtime_ready(root): - print("Modulus clangd tooling is missing; run ./modulus.py configure first", file=sys.stderr) + print(f"Modulus {command} tooling is missing; run ./modulus.py configure first", file=sys.stderr) return 1 else: ensure_runtime(root) diff --git a/src/config.cpp b/src/config.cpp index f8c1c11..8a0c178 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -19,13 +19,13 @@ #include + namespace { constexpr auto json_options = modulus::json_config::options{ .category = "noto", .invalid_argument_code = noto::detail::error_invalid_argument, - .not_found_code = noto::detail::error_not_found -}; + .not_found_code = noto::detail::error_not_found}; auto invalid(std::string message) -> std::expected { return noto::detail::fail_void(noto::detail::error_invalid_argument, std::move(message)); @@ -72,7 +72,7 @@ auto mode_name(noto::open_mode mode) -> std::string_view { return "read_write_create"; } -auto required_string_field(boost::json::object const& object, std::string_view name) -> std::expected { +auto required_string_field(boost::json::object const &object, std::string_view name) -> std::expected { auto value = modulus::json_config::required_string_field(object, name, "database", json_options); if (!value) { return noto::detail::fail(value.error()); @@ -83,7 +83,7 @@ auto required_string_field(boost::json::object const& object, std::string_view n return std::move(*value); } -auto optional_string_field(boost::json::object const& object, std::string_view name) -> std::expected, modulus::error> { +auto optional_string_field(boost::json::object const &object, std::string_view name) -> std::expected, modulus::error> { auto found = object.find(name); if (found == object.end()) { return std::optional{}; @@ -102,7 +102,7 @@ auto optional_string_field(boost::json::object const& object, std::string_view n return std::optional{std::move(value)}; } -auto parse_pragma_value(boost::json::value const& value, std::string_view name) -> std::expected, modulus::error> { +auto parse_pragma_value(boost::json::value const &value, std::string_view name) -> std::expected, modulus::error> { if (value.is_bool()) { return std::variant{value.as_bool()}; } @@ -123,7 +123,7 @@ auto parse_pragma_value(boost::json::value const& value, std::string_view name) return invalid_result>(std::string{name} + " pragma value must be a string, integer, or boolean"); } -auto parse_pragmas(boost::json::object const& object) -> std::expected, modulus::error> { +auto parse_pragmas(boost::json::object const &object) -> std::expected, modulus::error> { auto found = object.find("pragmas"); if (found == object.end()) { return std::vector{}; @@ -133,7 +133,7 @@ auto parse_pragmas(boost::json::object const& object) -> std::expected>(pragma_object.error()); } auto pragmas = std::vector{}; - for (auto const& item : **pragma_object) { + for (auto const &item : **pragma_object) { auto name = std::string{item.key()}; if (!valid_pragma_name(name)) { return invalid_result>("database pragma names must contain only letters, digits, or underscores"); @@ -152,7 +152,7 @@ auto parse_pragmas(boost::json::object const& object) -> std::expected std::expected { +auto noto::validate(noto::connection_config const &config) -> std::expected { if (config.filename.empty()) { return invalid("database filename must not be empty"); } @@ -168,7 +168,7 @@ auto noto::validate(noto::connection_config const& config) -> std::expected(config.busy_timeout.count()) > sqlite_int_max()) { return invalid("database busy timeout is out of range"); } - for (auto const& pragma : config.pragmas) { + for (auto const &pragma : config.pragmas) { if (!valid_pragma_name(pragma.name)) { return invalid("database pragma names must contain only letters, digits, or underscores"); } @@ -191,20 +191,21 @@ auto noto::file_connection_config(std::string filename, std::string name) -> not }; } -auto noto::parse_database_config(boost::json::value const& value) -> std::expected { +auto noto::parse_database_config(boost::json::value const &value) -> std::expected { auto object = modulus::json_config::require_object(value, "database", json_options); if (!object) { return noto::detail::fail(object.error()); } auto unknown = modulus::json_config::reject_unknown_fields(**object, { - "name", - "filename", - "mode", - "uri", - "busy_timeout_ms", - "foreign_keys", - "pragmas", - }, "database", json_options); + "name", + "filename", + "mode", + "uri", + "busy_timeout_ms", + "foreign_keys", + "pragmas", + }, + "database", json_options); if (!unknown) { return noto::detail::fail(unknown.error()); } @@ -213,23 +214,24 @@ auto noto::parse_database_config(boost::json::value const& value) -> std::expect return noto::detail::fail(name.error()); } auto mode = modulus::json_config::optional_enum_field(**object, "mode", noto::open_mode::READ_WRITE_CREATE, { - { - .name = "read_only", - .value = noto::open_mode::READ_ONLY, - }, - { - .name = "read_write", - .value = noto::open_mode::READ_WRITE, - }, - { - .name = "read_write_create", - .value = noto::open_mode::READ_WRITE_CREATE, - }, - { - .name = "memory", - .value = noto::open_mode::MEMORY, - }, - }, "database mode must be read_only, read_write, read_write_create, or memory", json_options); + { + .name = "read_only", + .value = noto::open_mode::READ_ONLY, + }, + { + .name = "read_write", + .value = noto::open_mode::READ_WRITE, + }, + { + .name = "read_write_create", + .value = noto::open_mode::READ_WRITE_CREATE, + }, + { + .name = "memory", + .value = noto::open_mode::MEMORY, + }, + }, + "database mode must be read_only, read_write, read_write_create, or memory", json_options); if (!mode) { return noto::detail::fail(mode.error()); } @@ -272,7 +274,7 @@ auto noto::parse_database_config(boost::json::value const& value) -> std::expect return config; } -auto noto::parse_database_configs(boost::json::value const& value) -> std::expected, modulus::error> { +auto noto::parse_database_configs(boost::json::value const &value) -> std::expected, modulus::error> { auto object = modulus::json_config::require_object(value, "config root", json_options); if (!object) { return noto::detail::fail>(object.error()); @@ -286,7 +288,7 @@ auto noto::parse_database_configs(boost::json::value const& value) -> std::expec return invalid_result>("config databases array is required"); } auto configs = std::vector{}; - for (auto const& database_value : found->value().as_array()) { + for (auto const &database_value : found->value().as_array()) { auto parsed = noto::parse_database_config(database_value); if (!parsed) { return noto::detail::fail>(parsed.error()); @@ -296,7 +298,7 @@ auto noto::parse_database_configs(boost::json::value const& value) -> std::expec return configs; } -auto noto::load_database_configs(std::string const& path) -> std::expected, modulus::error> { +auto noto::load_database_configs(std::string const &path) -> std::expected, modulus::error> { auto value = modulus::json_config::load_value(path, json_options); if (!value) { return noto::detail::fail>(value.error()); @@ -304,15 +306,16 @@ auto noto::load_database_configs(std::string const& path) -> std::expected const& value) -> boost::json::value { - return std::visit([](auto const& item) -> boost::json::value { +auto noto::to_json(std::variant const &value) -> boost::json::value { + return std::visit([](auto const &item) -> boost::json::value { return boost::json::value{item}; - }, value); + }, + value); } -auto noto::to_json(noto::connection_config const& config) -> boost::json::value { +auto noto::to_json(noto::connection_config const &config) -> boost::json::value { auto pragmas = boost::json::object{}; - for (auto const& pragma : config.pragmas) { + for (auto const &pragma : config.pragmas) { pragmas[pragma.name] = noto::to_json(pragma.value); } return boost::json::object{ @@ -347,9 +350,9 @@ auto noto::to_json(noto::connection_config const& config) -> boost::json::value }; } -auto noto::to_json(std::vector const& configs) -> boost::json::value { +auto noto::to_json(std::vector const &configs) -> boost::json::value { auto databases = boost::json::array{}; - for (auto const& config : configs) { + for (auto const &config : configs) { databases.push_back(noto::to_json(config)); } return boost::json::object{ diff --git a/src/connection.cpp b/src/connection.cpp index 9d957b3..27fa4a6 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -25,6 +25,7 @@ #include #include + namespace { auto invalid_connection() -> std::expected { @@ -61,8 +62,8 @@ auto valid_token(std::string_view value) -> std::bool_t { return true; } -auto pragma_value_sql(std::variant const& value) -> std::expected { - return std::visit([](auto const& current) -> std::expected { +auto pragma_value_sql(std::variant const &value) -> std::expected { + return std::visit([](auto const ¤t) -> std::expected { if constexpr (std::is_same_v, std::bool_t>) { return std::string{current ? "1" : "0"}; } else if constexpr (std::is_same_v, std::int64_t>) { @@ -70,25 +71,26 @@ auto pragma_value_sql(std::variant const } else { return noto::detail::quote_sql_literal(current, "pragma value"); } - }, value); + }, + value); } -auto first_int(noto::query_result const& result) -> std::expected { +auto first_int(noto::query_result const &result) -> std::expected { if (result.rows.empty() || result.rows.front().empty()) { return noto::detail::fail(noto::detail::error_not_found, "query returned no rows"); } - auto const& value = result.rows.front().front().value; + auto const &value = result.rows.front().front().value; if (auto integer = std::get_if(&value)) { return *integer; } return noto::detail::fail(noto::detail::error_invalid_argument, "query did not return an integer"); } -auto first_string(noto::query_result const& result) -> std::expected { +auto first_string(noto::query_result const &result) -> std::expected { if (result.rows.empty() || result.rows.front().empty()) { return noto::detail::fail(noto::detail::error_not_found, "query returned no rows"); } - auto const& value = result.rows.front().front().value; + auto const &value = result.rows.front().front().value; if (auto text = std::get_if(&value)) { return *text; } @@ -243,7 +245,7 @@ auto async_connection_integrity_check(noto::connection connection) -> boost::asi co_return std::unexpected(result.error()); } auto messages = std::vector{}; - for (auto const& row : result->rows) { + for (auto const &row : result->rows) { if (!row.empty()) { if (auto text = std::get_if(&row.front().value)) { messages.push_back(*text); @@ -259,7 +261,7 @@ auto async_connection_quick_check(noto::connection connection) -> boost::asio::a co_return std::unexpected(result.error()); } auto messages = std::vector{}; - for (auto const& row : result->rows) { + for (auto const &row : result->rows) { if (!row.empty()) { if (auto text = std::get_if(&row.front().value)) { messages.push_back(*text); @@ -374,21 +376,29 @@ auto async_connection_wal_checkpoint(noto::connection connection, std::string mo } -noto::sqlite_value::sqlite_value(std::nullptr_t) : value{std::monostate{}} {} +noto::sqlite_value::sqlite_value(std::nullptr_t) + : value{std::monostate{}} {} -noto::sqlite_value::sqlite_value(std::int32_t value) : value{static_cast(value)} {} +noto::sqlite_value::sqlite_value(std::int32_t value) + : value{static_cast(value)} {} -noto::sqlite_value::sqlite_value(std::int64_t current) : value{current} {} +noto::sqlite_value::sqlite_value(std::int64_t current) + : value{current} {} -noto::sqlite_value::sqlite_value(std::float64_t current) : value{current} {} +noto::sqlite_value::sqlite_value(std::float64_t current) + : value{current} {} -noto::sqlite_value::sqlite_value(std::char_t const* current) : value{std::string{current == nullptr ? "" : current}} {} +noto::sqlite_value::sqlite_value(std::char_t const *current) + : value{std::string{current == nullptr ? "" : current}} {} -noto::sqlite_value::sqlite_value(std::string current) : value{std::move(current)} {} +noto::sqlite_value::sqlite_value(std::string current) + : value{std::move(current)} {} -noto::sqlite_value::sqlite_value(std::string_view current) : value{std::string{current}} {} +noto::sqlite_value::sqlite_value(std::string_view current) + : value{std::string{current}} {} -noto::sqlite_value::sqlite_value(std::vector current) : value{std::move(current)} {} +noto::sqlite_value::sqlite_value(std::vector current) + : value{std::move(current)} {} auto noto::sqlite_value::is_null() const -> std::bool_t { return std::holds_alternative(value); @@ -424,9 +434,11 @@ auto noto::statement::async_finalize() -> boost::asio::awaitable core) : core_{std::move(core)} {} +noto::statement::statement(std::shared_ptr core) + : core_{std::move(core)} {} -noto::connection::connection(boost::asio::any_io_executor executor, noto::connection_config config) : core_{std::make_shared(std::move(executor), std::move(config))} {} +noto::connection::connection(boost::asio::any_io_executor executor, noto::connection_config config) + : core_{std::make_shared(std::move(executor), std::move(config))} {} noto::connection::~connection() = default; @@ -586,7 +598,8 @@ auto noto::connection::async_wal_checkpoint(std::string mode) -> boost::asio::aw return async_connection_wal_checkpoint(noto::connection{core_}, std::move(mode)); } -noto::connection::connection(std::shared_ptr core) : core_{std::move(core)} {} +noto::connection::connection(std::shared_ptr core) + : core_{std::move(core)} {} auto noto::connection::core() const -> std::shared_ptr { return core_; diff --git a/src/detail/core.cpp b/src/detail/core.cpp index 893430a..88a387a 100644 --- a/src/detail/core.cpp +++ b/src/detail/core.cpp @@ -56,23 +56,24 @@ #include + namespace { struct pending_statement_finalize { - pending_statement_finalize(std::shared_ptr owner_value, sqlite3_stmt* statement_value, std::bool_t counted_value); - pending_statement_finalize(pending_statement_finalize const&) = delete; - auto operator=(pending_statement_finalize const&) -> pending_statement_finalize& = delete; + pending_statement_finalize(std::shared_ptr owner_value, sqlite3_stmt *statement_value, std::bool_t counted_value); + pending_statement_finalize(pending_statement_finalize const &) = delete; + auto operator=(pending_statement_finalize const &) -> pending_statement_finalize & = delete; ~pending_statement_finalize(); auto finish() noexcept -> void; std::shared_ptr owner{}; - sqlite3_stmt* statement{}; + sqlite3_stmt *statement{}; std::bool_t counted{}; }; struct pending_transaction_rollback { explicit pending_transaction_rollback(std::shared_ptr owner_value); - pending_transaction_rollback(pending_transaction_rollback const&) = delete; - auto operator=(pending_transaction_rollback const&) -> pending_transaction_rollback& = delete; + pending_transaction_rollback(pending_transaction_rollback const &) = delete; + auto operator=(pending_transaction_rollback const &) -> pending_transaction_rollback & = delete; ~pending_transaction_rollback(); auto finish() noexcept -> void; std::shared_ptr owner{}; @@ -167,12 +168,13 @@ auto validate_path(std::string_view path, std::string_view label) -> std::expect auto errno_status(std::string operation, std::int32_t code) -> std::expected { return noto::detail::fail_void(noto::detail::error_unavailable, std::move(operation) + ": " + std::error_code{ - code, - std::generic_category(), - }.message()); + code, + std::generic_category(), + } + .message()); } -auto filesystem_status(std::string operation, std::error_code const& code) -> std::expected { +auto filesystem_status(std::string operation, std::error_code const &code) -> std::expected { return noto::detail::fail_void(noto::detail::error_unavailable, std::move(operation) + ": " + code.message()); } @@ -183,7 +185,7 @@ auto close_fd(std::int32_t fd, std::string_view operation) -> std::expected std::expected { +auto sync_file_path(std::string const &path, std::string_view operation) -> std::expected { auto fd = ::open(path.c_str(), O_RDONLY | O_CLOEXEC); if (fd < 0) { return errno_status(std::string{operation}, errno); @@ -196,7 +198,7 @@ auto sync_file_path(std::string const& path, std::string_view operation) -> std: return close_fd(fd, operation); } -auto sync_parent_directory(std::string const& path, std::string_view operation) -> std::expected { +auto sync_parent_directory(std::string const &path, std::string_view operation) -> std::expected { auto parent = std::filesystem::path{path}.parent_path(); if (parent.empty()) { parent = "."; @@ -214,7 +216,7 @@ auto sync_parent_directory(std::string const& path, std::string_view operation) return close_fd(fd, operation); } -auto make_temporary_path(std::string const& target, std::string_view operation) -> std::expected { +auto make_temporary_path(std::string const &target, std::string_view operation) -> std::expected { auto target_path = std::filesystem::path{target}; auto parent = target_path.parent_path(); if (parent.empty()) { @@ -243,20 +245,20 @@ auto make_temporary_path(std::string const& target, std::string_view operation) return path; } -auto remove_file_noexcept(std::string const& path) noexcept -> void { +auto remove_file_noexcept(std::string const &path) noexcept -> void { if (!path.empty()) { static_cast(::unlink(path.c_str())); } } -auto remove_file_if_exists(std::string const& path, std::string_view operation) -> std::expected { +auto remove_file_if_exists(std::string const &path, std::string_view operation) -> std::expected { if (::unlink(path.c_str()) == 0 || errno == ENOENT) { return {}; } return errno_status(std::string{operation}, errno); } -auto remove_database_sidecars(std::string const& path, std::string_view operation) -> std::expected { +auto remove_database_sidecars(std::string const &path, std::string_view operation) -> std::expected { for (auto suffix : sqlite_sidecar_suffixes) { auto status = remove_file_if_exists(path + std::string{suffix}, operation); if (!status) { @@ -266,14 +268,14 @@ auto remove_database_sidecars(std::string const& path, std::string_view operatio return {}; } -auto remove_database_files_noexcept(std::string const& path) noexcept -> void { +auto remove_database_files_noexcept(std::string const &path) noexcept -> void { remove_file_noexcept(path); for (auto suffix : sqlite_sidecar_suffixes) { remove_file_noexcept(path + std::string{suffix}); } } -auto replace_file_atomically(std::string const& temporary, std::string const& destination, std::string_view operation) -> std::expected { +auto replace_file_atomically(std::string const &temporary, std::string const &destination, std::string_view operation) -> std::expected { auto status = sync_file_path(temporary, operation); if (!status) { return status; @@ -294,7 +296,7 @@ auto validate_database_name(std::string_view name, std::string_view label) -> st return {}; } -auto open_flags(noto::connection_config const& config) -> std::int32_t { +auto open_flags(noto::connection_config const &config) -> std::int32_t { auto flags = SQLITE_OPEN_FULLMUTEX; switch (config.mode) { case noto::open_mode::READ_ONLY: @@ -340,7 +342,7 @@ auto transaction_mode_sql(noto::transaction_mode mode) -> std::string_view { return "BEGIN IMMEDIATE"; } -auto begin_operation(noto::detail::connection_core& core, std::string_view operation) -> std::expected { +auto begin_operation(noto::detail::connection_core &core, std::string_view operation) -> std::expected { if (core.current_state() != noto::connection_state::CONNECTED || core.database == nullptr) { return unavailable(std::string{operation} + " requires an open database connection"); } @@ -351,7 +353,7 @@ auto begin_operation(noto::detail::connection_core& core, std::string_view opera return {}; } -auto finish_operation(noto::detail::connection_core& core, std::expected status) -> std::expected { +auto finish_operation(noto::detail::connection_core &core, std::expected status) -> std::expected { core.busy = false; if (!status && status.error().code() == noto::detail::error_data_loss) { core.set_state(noto::connection_state::FAILED); @@ -359,7 +361,7 @@ auto finish_operation(noto::detail::connection_core& core, std::expected std::expected { +auto validate_backup_options(noto::backup_options const &options) -> std::expected { if (options.pages_per_step <= 0) { return invalid("backup pages per step must be positive"); } @@ -384,15 +386,15 @@ auto validate_backup_options(noto::backup_options const& options) -> std::expect return {}; } -auto validate_export_options(noto::export_options const& options) -> std::expected { +auto validate_export_options(noto::export_options const &options) -> std::expected { if (options.rows_per_yield == 0) { return invalid("SQL export rows per yield must be positive"); } return {}; } -auto exec_direct(sqlite3* database, std::string const& sql, std::string_view operation) -> std::expected { - auto* error = static_cast(nullptr); +auto exec_direct(sqlite3 *database, std::string const &sql, std::string_view operation) -> std::expected { + auto *error = static_cast(nullptr); auto code = sqlite3_exec(database, sql.c_str(), nullptr, nullptr, &error); if (code == SQLITE_OK) { return {}; @@ -408,12 +410,12 @@ auto exec_direct(sqlite3* database, std::string const& sql, std::string_view ope return noto::detail::fail_void(mapped.code(), std::move(message)); } -auto configure_busy_timeout(sqlite3* database, std::chrono::milliseconds timeout, std::string_view operation) -> std::expected { +auto configure_busy_timeout(sqlite3 *database, std::chrono::milliseconds timeout, std::string_view operation) -> std::expected { return noto::result_from(sqlite3_busy_timeout(database, static_cast(timeout.count())), database, operation); } -auto pragma_value_sql(std::variant const& value) -> std::expected { - return std::visit([](auto const& current) -> std::expected { +auto pragma_value_sql(std::variant const &value) -> std::expected { + return std::visit([](auto const ¤t) -> std::expected { if constexpr (std::is_same_v, std::bool_t>) { return std::string{current ? "1" : "0"}; } else if constexpr (std::is_same_v, std::int64_t>) { @@ -421,10 +423,11 @@ auto pragma_value_sql(std::variant const } else { return noto::detail::quote_sql_literal(current, "pragma value"); } - }, value); + }, + value); } -auto set_pragma(sqlite3* database, std::string const& name, std::variant const& value) -> std::expected { +auto set_pragma(sqlite3 *database, std::string const &name, std::variant const &value) -> std::expected { if (!valid_token(name)) { return invalid("database pragma names must contain only letters, digits, or underscores"); } @@ -435,9 +438,9 @@ auto set_pragma(sqlite3* database, std::string const& name, std::variant std::expected { +auto open_configured_database(noto::connection_config const &config, std::chrono::milliseconds busy_timeout, sqlite3 **output) -> std::expected { *output = nullptr; - auto* next_database = static_cast(nullptr); + auto *next_database = static_cast(nullptr); auto filename = config.mode == noto::open_mode::MEMORY ? std::string{":memory:"} : config.filename; auto code = sqlite3_open_v2(filename.c_str(), &next_database, open_flags(config), nullptr); if (code != SQLITE_OK) { @@ -452,7 +455,7 @@ auto open_configured_database(noto::connection_config const& config, std::chrono if (status) { status = exec_direct(next_database, std::string{"PRAGMA foreign_keys = "} + (config.foreign_keys ? "1" : "0"), "configure foreign keys"); } - for (auto const& pragma : config.pragmas) { + for (auto const &pragma : config.pragmas) { if (!status) { break; } @@ -466,11 +469,11 @@ auto open_configured_database(noto::connection_config const& config, std::chrono return {}; } -auto retry_timeout(noto::detail::connection_core const& core, noto::backup_options const& options) -> std::chrono::milliseconds { +auto retry_timeout(noto::detail::connection_core const &core, noto::backup_options const &options) -> std::chrono::milliseconds { return options.retry_timeout.value_or(core.busy_timeout); } -auto async_step_backup(noto::detail::connection_core& core, sqlite3_backup* backup, noto::backup_options const& options, std::string_view operation) -> boost::asio::awaitable> { +auto async_step_backup(noto::detail::connection_core &core, sqlite3_backup *backup, noto::backup_options const &options, std::string_view operation) -> boost::asio::awaitable> { auto const deadline = std::chrono::steady_clock::now() + retry_timeout(core, options); auto timer = boost::asio::steady_timer{core.strand}; while (true) { @@ -482,7 +485,6 @@ auto async_step_backup(noto::detail::connection_core& core, sqlite3_backup* back co_await boost::asio::post(core.strand, boost::asio::use_awaitable); continue; } - auto now = std::chrono::steady_clock::now(); if (now >= deadline) { co_return std::unexpected(timeout(std::string{operation} + " timed out while waiting for SQLite locks").error()); @@ -500,7 +502,7 @@ auto async_step_backup(noto::detail::connection_core& core, sqlite3_backup* back } } -auto close_sqlite_handle(sqlite3* handle, std::string_view operation, std::expected status) -> std::expected { +auto close_sqlite_handle(sqlite3 *handle, std::string_view operation, std::expected status) -> std::expected { auto close_code = sqlite3_close(handle); if (close_code == SQLITE_OK) { return status; @@ -512,8 +514,8 @@ auto close_sqlite_handle(sqlite3* handle, std::string_view operation, std::expec return status; } -auto async_copy_restore_source_to_file(noto::detail::connection_core& core, std::string const& path, std::string const& temporary, noto::backup_options const& options) -> boost::asio::awaitable> { - auto* source = static_cast(nullptr); +auto async_copy_restore_source_to_file(noto::detail::connection_core &core, std::string const &path, std::string const &temporary, noto::backup_options const &options) -> boost::asio::awaitable> { + auto *source = static_cast(nullptr); auto code = sqlite3_open_v2(path.c_str(), &source, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr); if (code != SQLITE_OK) { auto status = noto::result_from(code, source, "open restore source"); @@ -527,8 +529,7 @@ auto async_copy_restore_source_to_file(noto::detail::connection_core& core, std: sqlite3_close(source); co_return status; } - - auto* destination = static_cast(nullptr); + auto *destination = static_cast(nullptr); code = sqlite3_open_v2(temporary.c_str(), &destination, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr); if (code != SQLITE_OK) { status = noto::result_from(code, destination, "open restore temporary destination"); @@ -544,8 +545,7 @@ auto async_copy_restore_source_to_file(noto::detail::connection_core& core, std: sqlite3_close(source); co_return status; } - - auto* backup = sqlite3_backup_init(destination, "main", source, options.source_name.c_str()); + auto *backup = sqlite3_backup_init(destination, "main", source, options.source_name.c_str()); if (backup == nullptr) { status = noto::result_from(sqlite3_errcode(destination), destination, "initialize restore"); status = close_sqlite_handle(destination, "close restore temporary destination", status); @@ -560,12 +560,12 @@ auto async_copy_restore_source_to_file(noto::detail::connection_core& core, std: co_return status; } -auto close_database_for_atomic_restore(noto::detail::connection_core& core) -> std::expected { +auto close_database_for_atomic_restore(noto::detail::connection_core &core) -> std::expected { auto checkpoint = noto::result_from(sqlite3_wal_checkpoint_v2(core.database, nullptr, SQLITE_CHECKPOINT_TRUNCATE, nullptr, nullptr), core.database, "checkpoint database before restore"); if (!checkpoint) { return checkpoint; } - auto* closing = std::exchange(core.database, nullptr); + auto *closing = std::exchange(core.database, nullptr); auto code = sqlite3_close(closing); if (code == SQLITE_OK) { return {}; @@ -574,8 +574,8 @@ auto close_database_for_atomic_restore(noto::detail::connection_core& core) -> s return noto::result_from(code, core.database, "close database before restore"); } -auto reopen_database_after_restore(noto::detail::connection_core& core) -> std::expected { - auto* reopened = static_cast(nullptr); +auto reopen_database_after_restore(noto::detail::connection_core &core) -> std::expected { + auto *reopened = static_cast(nullptr); auto status = open_configured_database(core.config, core.busy_timeout, &reopened); if (!status) { core.set_state(noto::connection_state::FAILED); @@ -586,7 +586,7 @@ auto reopen_database_after_restore(noto::detail::connection_core& core) -> std:: return {}; } -auto restore_atomic_failure(noto::detail::connection_core& core, std::expected status) -> std::expected { +auto restore_atomic_failure(noto::detail::connection_core &core, std::expected status) -> std::expected { if (core.database != nullptr) { core.set_state(noto::connection_state::CONNECTED); return status; @@ -595,11 +595,11 @@ auto restore_atomic_failure(noto::detail::connection_core& core, std::expected std::bool_t { +auto statement_is_open(noto::detail::statement_core const &core) -> std::bool_t { return core.statement != nullptr && !core.finalized.load(std::memory_order_acquire); } -auto untrack_statement(noto::detail::statement_core& statement) -> void { +auto untrack_statement(noto::detail::statement_core &statement) -> void { if (!statement.counted) { return; } @@ -609,19 +609,19 @@ auto untrack_statement(noto::detail::statement_core& statement) -> void { } } - -auto bind_value(sqlite3_stmt* statement, std::int32_t index, noto::sqlite_value const& value, sqlite3* database) -> std::expected { - auto size = std::visit([](auto const& current) -> std::size_t { +auto bind_value(sqlite3_stmt *statement, std::int32_t index, noto::sqlite_value const &value, sqlite3 *database) -> std::expected { + auto size = std::visit([](auto const ¤t) -> std::size_t { if constexpr (std::is_same_v, std::string> || std::is_same_v, std::vector>) { return current.size(); } else { return std::size_t{}; } - }, value.value); + }, + value.value); if (size > sqlite_int_max()) { return invalid("bound SQLite text/blob value is too large"); } - auto code = std::visit([statement, index](auto const& current) -> std::int32_t { + auto code = std::visit([statement, index](auto const ¤t) -> std::int32_t { if constexpr (std::is_same_v, std::monostate>) { return sqlite3_bind_null(statement, index); } else if constexpr (std::is_same_v, std::int64_t>) { @@ -633,11 +633,12 @@ auto bind_value(sqlite3_stmt* statement, std::int32_t index, noto::sqlite_value } else { return sqlite3_bind_blob(statement, index, current.data(), static_cast(current.size()), SQLITE_TRANSIENT); } - }, value.value); + }, + value.value); return noto::result_from(code, database, "bind parameter"); } -auto bind_values(sqlite3_stmt* statement, std::vector const& params, sqlite3* database) -> std::expected { +auto bind_values(sqlite3_stmt *statement, std::vector const ¶ms, sqlite3 *database) -> std::expected { if (params.size() > sqlite_int_max()) { return invalid("prepared statement parameter count is too large"); } @@ -654,24 +655,24 @@ auto bind_values(sqlite3_stmt* statement, std::vector const& return {}; } -auto column_value(sqlite3_stmt* statement, std::int32_t index) -> noto::sqlite_value { +auto column_value(sqlite3_stmt *statement, std::int32_t index) -> noto::sqlite_value { switch (sqlite3_column_type(statement, index)) { case SQLITE_INTEGER: return noto::sqlite_value{static_cast(sqlite3_column_int64(statement, index))}; case SQLITE_FLOAT: return noto::sqlite_value{sqlite3_column_double(statement, index)}; case SQLITE_TEXT: { - auto const* text = reinterpret_cast(sqlite3_column_text(statement, index)); + auto const *text = reinterpret_cast(sqlite3_column_text(statement, index)); auto bytes = sqlite3_column_bytes(statement, index); return noto::sqlite_value{ text == nullptr ? std::string{} : std::string{ - text, - static_cast(bytes), - }, + text, + static_cast(bytes), + }, }; } case SQLITE_BLOB: { - auto const* data = static_cast(sqlite3_column_blob(statement, index)); + auto const *data = static_cast(sqlite3_column_blob(statement, index)); auto bytes = sqlite3_column_bytes(statement, index); auto blob = std::vector{}; if (data != nullptr && bytes > 0) { @@ -685,7 +686,7 @@ auto column_value(sqlite3_stmt* statement, std::int32_t index) -> noto::sqlite_v } } -auto current_row(sqlite3_stmt* statement) -> std::vector { +auto current_row(sqlite3_stmt *statement) -> std::vector { auto count = sqlite3_column_count(statement); auto row = std::vector{}; row.reserve(static_cast(count)); @@ -695,19 +696,19 @@ auto current_row(sqlite3_stmt* statement) -> std::vector { return row; } -auto column_names(sqlite3_stmt* statement) -> std::vector { +auto column_names(sqlite3_stmt *statement) -> std::vector { auto count = sqlite3_column_count(statement); auto names = std::vector{}; names.reserve(static_cast(count)); for (auto index = 0; index < count; ++index) { - auto const* name = sqlite3_column_name(statement, index); + auto const *name = sqlite3_column_name(statement, index); names.push_back(name == nullptr ? std::string{} : std::string{name}); } return names; } -auto value_result_bytes(noto::sqlite_value const& value) -> std::size_t { - return std::visit([](auto const& current) -> std::size_t { +auto value_result_bytes(noto::sqlite_value const &value) -> std::size_t { + return std::visit([](auto const ¤t) -> std::size_t { if constexpr (std::is_same_v, std::monostate>) { return {}; } else if constexpr (std::is_same_v, std::int64_t>) { @@ -717,10 +718,11 @@ auto value_result_bytes(noto::sqlite_value const& value) -> std::size_t { } else { return current.size(); } - }, value.value); + }, + value.value); } -auto add_result_bytes(std::size_t& current, std::size_t addition, noto::execute_options const& options) -> std::expected { +auto add_result_bytes(std::size_t ¤t, std::size_t addition, noto::execute_options const &options) -> std::expected { if (!options.max_result_bytes) { if (addition > std::numeric_limits::max() - current) { current = std::numeric_limits::max(); @@ -736,8 +738,8 @@ auto add_result_bytes(std::size_t& current, std::size_t addition, noto::execute_ return {}; } -auto track_result_columns(std::vector const& columns, std::size_t& result_bytes, noto::execute_options const& options) -> std::expected { - for (auto const& column : columns) { +auto track_result_columns(std::vector const &columns, std::size_t &result_bytes, noto::execute_options const &options) -> std::expected { + for (auto const &column : columns) { auto status = add_result_bytes(result_bytes, column.size(), options); if (!status) { return status; @@ -746,11 +748,11 @@ auto track_result_columns(std::vector const& columns, std::size_t& return {}; } -auto append_result_row(noto::query_result& result, std::vector row, std::size_t& result_bytes, noto::execute_options const& options) -> std::expected { +auto append_result_row(noto::query_result &result, std::vector row, std::size_t &result_bytes, noto::execute_options const &options) -> std::expected { if (options.max_rows && result.rows.size() >= *options.max_rows) { return resource_exhausted("query returned more rows than configured limit"); } - for (auto const& value : row) { + for (auto const &value : row) { auto status = add_result_bytes(result_bytes, value_result_bytes(value), options); if (!status) { return status; @@ -760,12 +762,12 @@ auto append_result_row(noto::query_result& result, std::vector status) -> std::expected { +auto reset_statement_after_execute_failure(sqlite3_stmt *statement, sqlite3 *database, std::expected status) -> std::expected { auto reset = noto::result_from(sqlite3_reset(statement), database, "reset statement after execute failure"); return reset ? status : reset; } -auto trailing_sql_is_empty(std::char_t const* tail) -> std::bool_t { +auto trailing_sql_is_empty(std::char_t const *tail) -> std::bool_t { if (tail == nullptr) { return true; } @@ -778,8 +780,8 @@ auto trailing_sql_is_empty(std::char_t const* tail) -> std::bool_t { return true; } -auto prepare_statement(sqlite3* database, std::string const& sql, sqlite3_stmt** statement, std::string_view operation) -> std::expected { - auto const* tail = static_cast(nullptr); +auto prepare_statement(sqlite3 *database, std::string const &sql, sqlite3_stmt **statement, std::string_view operation) -> std::expected { + auto const *tail = static_cast(nullptr); auto code = sqlite3_prepare_v2(database, sql.c_str(), -1, statement, &tail); auto status = noto::result_from(code, database, operation); if (!status) { @@ -796,11 +798,11 @@ auto prepare_statement(sqlite3* database, std::string const& sql, sqlite3_stmt** return {}; } -auto same_identity(file_identity const& left, file_identity const& right) -> std::bool_t { +auto same_identity(file_identity const &left, file_identity const &right) -> std::bool_t { return left.device == right.device && left.inode == right.inode; } -auto normalized_path(std::string const& path, std::string_view operation) -> std::expected { +auto normalized_path(std::string const &path, std::string_view operation) -> std::expected { auto error = std::error_code{}; auto absolute = std::filesystem::absolute(path, error); if (error) { @@ -813,8 +815,9 @@ auto normalized_path(std::string const& path, std::string_view operation) -> std return normalized.lexically_normal(); } -auto identity_for_path(std::filesystem::path const& path, std::string_view operation) -> std::expected, modulus::error> { - struct stat data {}; +auto identity_for_path(std::filesystem::path const &path, std::string_view operation) -> std::expected, modulus::error> { + struct stat data{}; + auto text = path.string(); if (::stat(text.c_str(), &data) == 0) { auto identity = file_identity{}; @@ -828,7 +831,7 @@ auto identity_for_path(std::filesystem::path const& path, std::string_view opera return std::unexpected(errno_status(std::string{operation}, errno).error()); } -auto protected_path_for(std::string const& path, std::string_view operation) -> std::expected { +auto protected_path_for(std::string const &path, std::string_view operation) -> std::expected { auto normalized = normalized_path(path, operation); if (!normalized) { return std::unexpected(normalized.error()); @@ -843,15 +846,15 @@ auto protected_path_for(std::string const& path, std::string_view operation) -> }; } -auto same_protected_path(protected_path const& left, protected_path const& right) -> std::bool_t { +auto same_protected_path(protected_path const &left, protected_path const &right) -> std::bool_t { if (left.normalized == right.normalized) { return true; } return left.identity && right.identity && same_identity(*left.identity, *right.identity); } -auto live_database_paths(sqlite3* database) -> std::expected, modulus::error> { - auto* raw_statement = static_cast(nullptr); +auto live_database_paths(sqlite3 *database) -> std::expected, modulus::error> { + auto *raw_statement = static_cast(nullptr); auto prepared = prepare_statement(database, "PRAGMA database_list", &raw_statement, "prepare open database list"); if (!prepared) { return failure_from>(prepared); @@ -872,19 +875,19 @@ auto live_database_paths(sqlite3* database) -> std::expected(sqlite3_column_text(statement.get(), 2)); + auto const *text = reinterpret_cast(sqlite3_column_text(statement.get(), 2)); auto bytes = sqlite3_column_bytes(statement.get(), 2); auto path = text == nullptr ? std::string{} : std::string{ - text, - static_cast(bytes), - }; + text, + static_cast(bytes), + }; if (!path.empty()) { paths.push_back(std::move(path)); } } } -auto validate_output_not_live_database(sqlite3* database, std::string const& path, std::string_view label) -> std::expected { +auto validate_output_not_live_database(sqlite3 *database, std::string const &path, std::string_view label) -> std::expected { auto target = protected_path_for(path, std::string{"check "} + std::string{label}); if (!target) { return std::unexpected(target.error()); @@ -893,7 +896,7 @@ auto validate_output_not_live_database(sqlite3* database, std::string const& pat if (!live_paths) { return std::unexpected(live_paths.error()); } - for (auto const& live_path : *live_paths) { + for (auto const &live_path : *live_paths) { auto protected_live = protected_path_for(live_path, "check open database path"); if (!protected_live) { return std::unexpected(protected_live.error()); @@ -914,8 +917,8 @@ auto validate_output_not_live_database(sqlite3* database, std::string const& pat return {}; } -auto execute_prepared(sqlite3* database, std::string const& sql, std::vector const& params, noto::execute_options const& options) -> std::expected { - auto* raw_statement = static_cast(nullptr); +auto execute_prepared(sqlite3 *database, std::string const &sql, std::vector const ¶ms, noto::execute_options const &options) -> std::expected { + auto *raw_statement = static_cast(nullptr); auto prepared = prepare_statement(database, sql, &raw_statement, "prepare statement"); if (!prepared) { return failure_from(prepared); @@ -959,7 +962,7 @@ auto execute_prepared(sqlite3* database, std::string const& sql, std::vector std::string { +auto hex_literal(std::byte const *data, std::size_t size) -> std::string { auto sql = std::ostringstream{}; sql << "X'"; for (auto index = std::size_t{}; index < size; ++index) { @@ -969,13 +972,13 @@ auto hex_literal(std::byte const* data, std::size_t size) -> std::string { return sql.str(); } -auto text_hex_literal(std::string const& value) -> std::string { - auto const* bytes = reinterpret_cast(value.data()); +auto text_hex_literal(std::string const &value) -> std::string { + auto const *bytes = reinterpret_cast(value.data()); return "CAST(" + hex_literal(bytes, value.size()) + " AS TEXT)"; } -auto literal_sql(noto::sqlite_value const& value) -> std::expected { - return std::visit([](auto const& current) -> std::expected { +auto literal_sql(noto::sqlite_value const &value) -> std::expected { + return std::visit([](auto const ¤t) -> std::expected { if constexpr (std::is_same_v, std::monostate>) { return "NULL"; } else if constexpr (std::is_same_v, std::int64_t>) { @@ -999,10 +1002,11 @@ auto literal_sql(noto::sqlite_value const& value) -> std::expected std::expected { +auto write_limited(std::ostream &output, std::string_view text, noto::export_options const &options, std::size_t &written) -> std::expected { if (text.size() > static_cast(std::numeric_limits::max())) { return resource_exhausted("SQL export chunk is too large"); } @@ -1021,16 +1025,16 @@ auto write_limited(std::ostream& output, std::string_view text, noto::export_opt return {}; } -auto column_text(sqlite3_stmt* statement, std::int32_t index) -> std::optional { +auto column_text(sqlite3_stmt *statement, std::int32_t index) -> std::optional { if (sqlite3_column_type(statement, index) != SQLITE_TEXT) { return std::nullopt; } - auto const* text = reinterpret_cast(sqlite3_column_text(statement, index)); + auto const *text = reinterpret_cast(sqlite3_column_text(statement, index)); auto bytes = sqlite3_column_bytes(statement, index); return text == nullptr ? std::string{} : std::string{ - text, - static_cast(bytes), - }; + text, + static_cast(bytes), + }; } auto identifier_character(std::char_t character) -> std::bool_t { @@ -1038,7 +1042,7 @@ auto identifier_character(std::char_t character) -> std::bool_t { return std::isalnum(byte) || character == '_'; } -auto consume_keyword(std::string_view sql, std::size_t& position, std::string_view keyword) -> std::bool_t { +auto consume_keyword(std::string_view sql, std::size_t &position, std::string_view keyword) -> std::bool_t { auto current = position; while (current < sql.size() && std::isspace(static_cast(sql[current]))) { ++current; @@ -1073,8 +1077,8 @@ auto create_virtual_table_sql(std::string_view sql) -> std::bool_t { return consume_keyword(sql, position, "VIRTUAL") && consume_keyword(sql, position, "TABLE"); } -auto table_exists(sqlite3* database, std::string const& table_name) -> std::expected { - auto* raw_statement = static_cast(nullptr); +auto table_exists(sqlite3 *database, std::string const &table_name) -> std::expected { + auto *raw_statement = static_cast(nullptr); auto prepared = prepare_statement(database, "SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name = ?1", &raw_statement, "prepare table existence check"); if (!prepared) { return failure_from(prepared); @@ -1098,12 +1102,12 @@ auto table_exists(sqlite3* database, std::string const& table_name) -> std::expe return failure_from(noto::result_from(code, database, "check table existence")); } -auto exportable_columns(sqlite3* database, std::string const& table_name) -> std::expected, modulus::error> { +auto exportable_columns(sqlite3 *database, std::string const &table_name) -> std::expected, modulus::error> { auto quoted_table = noto::quote_identifier(table_name); if (!quoted_table) { return failure_from>(quoted_table.error()); } - auto* raw_statement = static_cast(nullptr); + auto *raw_statement = static_cast(nullptr); auto prepared = prepare_statement(database, "PRAGMA table_xinfo(" + *quoted_table + ")", &raw_statement, "prepare export columns"); if (!prepared) { return failure_from>(prepared); @@ -1142,15 +1146,11 @@ auto exportable_columns(sqlite3* database, std::string const& table_name) -> std } } -auto collect_export_schema(sqlite3* database) -> std::expected { - auto* raw_statement = static_cast(nullptr); - auto prepared = prepare_statement( - database, - "SELECT type, name, sql FROM sqlite_schema WHERE sql IS NOT NULL AND name NOT GLOB 'sqlite_*' " - "ORDER BY CASE type WHEN 'table' THEN 0 WHEN 'index' THEN 1 WHEN 'trigger' THEN 2 WHEN 'view' THEN 3 ELSE 4 END, name", - &raw_statement, - "prepare export schema" - ); +auto collect_export_schema(sqlite3 *database) -> std::expected { + auto *raw_statement = static_cast(nullptr); + auto prepared = prepare_statement(database, "SELECT type, name, sql FROM sqlite_schema WHERE sql IS NOT NULL AND name NOT GLOB 'sqlite_*' " + "ORDER BY CASE type WHEN 'table' THEN 0 WHEN 'index' THEN 1 WHEN 'trigger' THEN 2 WHEN 'view' THEN 3 ELSE 4 END, name", + &raw_statement, "prepare export schema"); if (!prepared) { return failure_from(prepared); } @@ -1202,8 +1202,8 @@ auto collect_export_schema(sqlite3* database) -> std::expected(std::string_view)> const& write, noto::export_options const& options) -> boost::asio::awaitable> { - auto* raw_statement = static_cast(nullptr); +auto export_table_rows(noto::detail::connection_core &core, export_table const &table, std::function(std::string_view)> const &write, noto::export_options const &options) -> boost::asio::awaitable> { + auto *raw_statement = static_cast(nullptr); auto select_sql = std::string{}; if (table.columns.empty()) { select_sql = "SELECT 1 FROM " + table.quoted_name; @@ -1271,12 +1271,12 @@ auto export_table_rows(noto::detail::connection_core& core, export_table const& } } -auto export_sqlite_sequence(noto::detail::connection_core& core, std::function(std::string_view)> const& write, noto::export_options const& options) -> boost::asio::awaitable> { +auto export_sqlite_sequence(noto::detail::connection_core &core, std::function(std::string_view)> const &write, noto::export_options const &options) -> boost::asio::awaitable> { auto status = write("DELETE FROM \"sqlite_sequence\";\n"); if (!status) { co_return status; } - auto* raw_statement = static_cast(nullptr); + auto *raw_statement = static_cast(nullptr); auto prepared = prepare_statement(core.database, "SELECT name, seq FROM sqlite_sequence", &raw_statement, "prepare sqlite_sequence export"); if (!prepared) { co_return prepared; @@ -1316,7 +1316,7 @@ auto export_sqlite_sequence(noto::detail::connection_core& core, std::function(std::string_view)> const& write, noto::export_options const& options) -> boost::asio::awaitable> { +auto write_export(noto::detail::connection_core &core, std::function(std::string_view)> const &write, noto::export_options const &options) -> boost::asio::awaitable> { auto schema = collect_export_schema(core.database); if (!schema) { co_return std::unexpected(schema.error()); @@ -1325,7 +1325,7 @@ auto write_export(noto::detail::connection_core& core, std::functiontable_sql) { + for (auto const &sql : schema->table_sql) { status = write(sql); if (status) { status = write(";\n"); @@ -1334,7 +1334,7 @@ auto write_export(noto::detail::connection_core& core, std::functiontables) { + for (auto const &table : schema->tables) { status = co_await export_table_rows(core, table, write, options); if (!status) { co_return status; @@ -1346,7 +1346,7 @@ auto write_export(noto::detail::connection_core& core, std::functionpost_data_sql) { + for (auto const &sql : schema->post_data_sql) { status = write(sql); if (status) { status = write(";\n"); @@ -1358,14 +1358,15 @@ auto write_export(noto::detail::connection_core& core, std::function owner_value, sqlite3_stmt* statement_value, std::bool_t counted_value) : owner{std::move(owner_value)}, statement{statement_value}, counted{counted_value} {} +pending_statement_finalize::pending_statement_finalize(std::shared_ptr owner_value, sqlite3_stmt *statement_value, std::bool_t counted_value) + : owner{std::move(owner_value)}, statement{statement_value}, counted{counted_value} {} pending_statement_finalize::~pending_statement_finalize() { finish(); } auto pending_statement_finalize::finish() noexcept -> void { - auto* current = std::exchange(statement, nullptr); + auto *current = std::exchange(statement, nullptr); if (current != nullptr) { static_cast(sqlite3_finalize(current)); } @@ -1375,7 +1376,8 @@ auto pending_statement_finalize::finish() noexcept -> void { counted = false; } -pending_transaction_rollback::pending_transaction_rollback(std::shared_ptr owner_value) : owner{std::move(owner_value)} {} +pending_transaction_rollback::pending_transaction_rollback(std::shared_ptr owner_value) + : owner{std::move(owner_value)} {} pending_transaction_rollback::~pending_transaction_rollback() { finish(); @@ -1394,7 +1396,8 @@ auto pending_transaction_rollback::finish() noexcept -> void { } -noto::detail::connection_core::connection_core(boost::asio::any_io_executor executor_value, noto::connection_config config_value) : executor{std::move(executor_value)}, strand{boost::asio::make_strand(executor)}, config{std::move(config_value)}, busy_timeout{config.busy_timeout} {} +noto::detail::connection_core::connection_core(boost::asio::any_io_executor executor_value, noto::connection_config config_value) + : executor{std::move(executor_value)}, strand{boost::asio::make_strand(executor)}, config{std::move(config_value)}, busy_timeout{config.busy_timeout} {} noto::detail::connection_core::~connection_core() { if (database != nullptr) { @@ -1447,7 +1450,7 @@ auto noto::detail::connection_core::async_connect() -> boost::asio::awaitable(nullptr); + auto *next_database = static_cast(nullptr); auto status = open_configured_database(config, busy_timeout, &next_database); if (!status) { busy = false; @@ -1482,7 +1485,7 @@ auto noto::detail::connection_core::async_close() -> boost::asio::awaitable boost::asi if (!ready) { co_return std::unexpected(ready.error()); } - auto* raw_statement = static_cast(nullptr); + auto *raw_statement = static_cast(nullptr); auto prepared = prepare_statement(database, sql, &raw_statement, "prepare statement"); auto status = finish_operation(*this, prepared); if (!status) { @@ -1631,7 +1634,7 @@ auto noto::detail::connection_core::async_backup_to_file(std::string path, noto: if (!temporary) { co_return finish_operation(*this, noto::detail::fail_void(temporary.error())); } - auto* destination = static_cast(nullptr); + auto *destination = static_cast(nullptr); auto code = sqlite3_open_v2(temporary->c_str(), &destination, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr); if (code != SQLITE_OK) { auto status = noto::result_from(code, destination, "open backup destination"); @@ -1651,7 +1654,7 @@ auto noto::detail::connection_core::async_backup_to_file(std::string path, noto: auto restored = configure_busy_timeout(database, busy_timeout, "restore source busy timeout after backup"); return status ? restored : status; }; - auto* backup = sqlite3_backup_init(destination, options.destination_name.c_str(), database, options.source_name.c_str()); + auto *backup = sqlite3_backup_init(destination, options.destination_name.c_str(), database, options.source_name.c_str()); if (backup == nullptr) { auto status = noto::result_from(sqlite3_errcode(destination), destination, "initialize backup"); sqlite3_close(destination); @@ -1759,9 +1762,7 @@ auto noto::detail::connection_core::async_export_sql(noto::export_options option } auto output = std::ostringstream{}; auto written = std::size_t{}; - auto status = co_await write_export(*this, [&output, &options, &written](std::string_view text) { - return write_limited(output, text, options, written); - }, options); + auto status = co_await write_export(*this, [&output, &options, &written](std::string_view text) { return write_limited(output, text, options, written); }, options); if (!status) { status = finish_operation(*this, status); co_return std::unexpected(status.error()); @@ -1802,9 +1803,7 @@ auto noto::detail::connection_core::async_export_sql_to_file(std::string path, n co_return finish_operation(*this, unavailable("failed to open export file")); } auto written = std::size_t{}; - auto status = co_await write_export(*this, [&output, &options, &written](std::string_view text) { - return write_limited(output, text, options, written); - }, options); + auto status = co_await write_export(*this, [&output, &options, &written](std::string_view text) { return write_limited(output, text, options, written); }, options); if (status) { output.flush(); if (!output) { @@ -1842,7 +1841,8 @@ auto noto::detail::connection_core::async_set_busy_timeout(std::chrono::millisec co_return finish_operation(*this, status); } -noto::detail::statement_core::statement_core(std::shared_ptr connection_value, sqlite3_stmt* statement_value) : connection{std::move(connection_value)}, statement{statement_value}, counted{statement_value != nullptr} { +noto::detail::statement_core::statement_core(std::shared_ptr connection_value, sqlite3_stmt *statement_value) + : connection{std::move(connection_value)}, statement{statement_value}, counted{statement_value != nullptr} { if (connection && counted) { connection->live_statements.fetch_add(1, std::memory_order_acq_rel); } @@ -1852,7 +1852,7 @@ noto::detail::statement_core::~statement_core() { if (finalized.exchange(true, std::memory_order_acq_rel)) { return; } - auto* current = std::exchange(statement, nullptr); + auto *current = std::exchange(statement, nullptr); auto owner = connection; auto was_counted = counted; counted = false; @@ -2005,14 +2005,15 @@ auto noto::detail::statement_core::async_finalize() -> boost::asio::awaitabledatabase, "finalize statement"); untrack_statement(*this); co_return finish_operation(*owner, status); } -noto::detail::transaction_state::transaction_state(std::shared_ptr core_value, std::function on_finish_value) : core{std::move(core_value)}, on_finish{std::move(on_finish_value)}, active{true} {} +noto::detail::transaction_state::transaction_state(std::shared_ptr core_value, std::function on_finish_value) + : core{std::move(core_value)}, on_finish{std::move(on_finish_value)}, active{true} {} auto noto::detail::transaction_state::finish() noexcept -> void { if (!on_finish) { diff --git a/src/detail/errors.cpp b/src/detail/errors.cpp index f5356e3..e795b17 100644 --- a/src/detail/errors.cpp +++ b/src/detail/errors.cpp @@ -8,6 +8,7 @@ #include + auto noto::detail::make_error(std::int32_t code, std::string message, modulus::source_location location) -> modulus::error { return modulus::make_error("noto", code, std::move(message), location); } @@ -20,7 +21,7 @@ auto noto::detail::fail_void(std::int32_t code, std::string message, modulus::so return noto::detail::fail_void(noto::detail::make_error(code, std::move(message), location)); } -auto noto::detail::fail_void(std::expected const& result) -> std::expected { +auto noto::detail::fail_void(std::expected const &result) -> std::expected { assert(!result); return noto::detail::fail_void(result.error()); } diff --git a/src/detail/sql.cpp b/src/detail/sql.cpp index e5d86c7..e878eec 100644 --- a/src/detail/sql.cpp +++ b/src/detail/sql.cpp @@ -9,6 +9,7 @@ #include + auto noto::detail::quote_sql_literal(std::string_view value, std::string_view label, std::bool_t allow_empty) -> std::expected { if (value.empty() && !allow_empty) { return noto::detail::fail(noto::detail::error_invalid_argument, std::string{label} + " must not be empty"); diff --git a/src/errors.cpp b/src/errors.cpp index e21f22b..8bdbfb4 100644 --- a/src/errors.cpp +++ b/src/errors.cpp @@ -11,6 +11,7 @@ #include + namespace { auto code_from(std::int32_t code) -> std::int32_t { @@ -51,7 +52,7 @@ auto code_from(std::int32_t code) -> std::int32_t { } } -auto message_from(std::int32_t code, sqlite3* database, std::string_view operation) -> std::string { +auto message_from(std::int32_t code, sqlite3 *database, std::string_view operation) -> std::string { auto message = std::string{}; if (!operation.empty()) { message += operation; @@ -67,11 +68,11 @@ auto message_from(std::int32_t code, sqlite3* database, std::string_view operati } -auto noto::error_from(std::int32_t code, sqlite3* database, std::string_view operation) -> modulus::error { +auto noto::error_from(std::int32_t code, sqlite3 *database, std::string_view operation) -> modulus::error { return noto::detail::make_error(code_from(code), message_from(code, database, operation)); } -auto noto::result_from(std::int32_t code, sqlite3* database, std::string_view operation) -> std::expected { +auto noto::result_from(std::int32_t code, sqlite3 *database, std::string_view operation) -> std::expected { if (code == SQLITE_OK || code == SQLITE_ROW || code == SQLITE_DONE) { return {}; } diff --git a/src/migration.cpp b/src/migration.cpp index 8d10114..897b7a4 100644 --- a/src/migration.cpp +++ b/src/migration.cpp @@ -23,6 +23,7 @@ #include #include + namespace { auto invalid(std::string message) -> std::expected { @@ -37,7 +38,7 @@ auto contains_null(std::string_view value) -> std::bool_t { return value.find('\0') != std::string_view::npos; } -auto qualified_table(noto::table_name const& table) -> std::expected { +auto qualified_table(noto::table_name const &table) -> std::expected { auto name = noto::quote_identifier(table.name); if (!name) { return std::unexpected(name.error()); @@ -52,15 +53,18 @@ auto qualified_table(noto::table_name const& table) -> std::expected std::expected { +auto migration_table_sql(noto::migration_options const &options) -> std::expected { auto table = qualified_table(options.table); if (!table) { return std::unexpected(table.error()); } - return "CREATE TABLE IF NOT EXISTS " + *table + " (\"id\" TEXT NOT NULL PRIMARY KEY, " "\"description\" TEXT NOT NULL, " "\"checksum\" TEXT NOT NULL, " "\"applied_at\" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP) STRICT"; + return "CREATE TABLE IF NOT EXISTS " + *table + " (\"id\" TEXT NOT NULL PRIMARY KEY, " + "\"description\" TEXT NOT NULL, " + "\"checksum\" TEXT NOT NULL, " + "\"applied_at\" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP) STRICT"; } -auto select_records_sql(noto::migration_options const& options) -> std::expected { +auto select_records_sql(noto::migration_options const &options) -> std::expected { auto table = qualified_table(options.table); if (!table) { return std::unexpected(table.error()); @@ -68,7 +72,7 @@ auto select_records_sql(noto::migration_options const& options) -> std::expected return "SELECT \"id\", \"description\", \"checksum\", \"applied_at\" FROM " + *table + " ORDER BY \"id\""; } -auto insert_record_sql(noto::migration_options const& options, noto::migration const& migration) -> std::expected { +auto insert_record_sql(noto::migration_options const &options, noto::migration const &migration) -> std::expected { auto table = qualified_table(options.table); if (!table) { return std::unexpected(table.error()); @@ -88,7 +92,7 @@ auto insert_record_sql(noto::migration_options const& options, noto::migration c return "INSERT INTO " + *table + " (\"id\", \"description\", \"checksum\") VALUES (" + *id + ", " + *description + ", " + *checksum + ")"; } -auto delete_record_sql(noto::migration_options const& options, std::string_view id_value) -> std::expected { +auto delete_record_sql(noto::migration_options const &options, std::string_view id_value) -> std::expected { auto table = qualified_table(options.table); if (!table) { return std::unexpected(table.error()); @@ -102,7 +106,7 @@ auto delete_record_sql(noto::migration_options const& options, std::string_view auto validate_and_sort(std::vector migrations, std::bool_t require_up) -> std::expected, modulus::error> { auto ids = std::set{}; - for (auto const& migration : migrations) { + for (auto const &migration : migrations) { if (migration.id.empty()) { return std::unexpected(invalid("migration id must not be empty").error()); } @@ -126,7 +130,7 @@ auto validate_and_sort(std::vector migrations, std::bool_t requ return migrations; } -auto as_string(noto::sqlite_value const& value) -> std::string { +auto as_string(noto::sqlite_value const &value) -> std::string { if (auto text = std::get_if(&value.value)) { return *text; } @@ -136,7 +140,7 @@ auto as_string(noto::sqlite_value const& value) -> std::string { return {}; } -auto execute_status(noto::connection& connection, std::string sql) -> boost::asio::awaitable> { +auto execute_status(noto::connection &connection, std::string sql) -> boost::asio::awaitable> { auto result = co_await connection.async_execute(std::move(sql)); if (!result) { co_return std::unexpected(result.error()); @@ -144,7 +148,7 @@ auto execute_status(noto::connection& connection, std::string sql) -> boost::asi co_return std::expected{}; } -auto ensure_migration_table(noto::connection& connection, noto::migration_options const& options) -> boost::asio::awaitable> { +auto ensure_migration_table(noto::connection &connection, noto::migration_options const &options) -> boost::asio::awaitable> { auto sql = migration_table_sql(options); if (!sql) { co_return std::unexpected(sql.error()); @@ -152,7 +156,7 @@ auto ensure_migration_table(noto::connection& connection, noto::migration_option co_return co_await execute_status(connection, *sql); } -auto load_records(noto::connection& connection, noto::migration_options const& options) -> boost::asio::awaitable, modulus::error>> { +auto load_records(noto::connection &connection, noto::migration_options const &options) -> boost::asio::awaitable, modulus::error>> { auto sql = select_records_sql(options); if (!sql) { co_return std::unexpected(sql.error()); @@ -163,7 +167,7 @@ auto load_records(noto::connection& connection, noto::migration_options const& o } auto records = std::vector{}; records.reserve(result->rows.size()); - for (auto const& row : result->rows) { + for (auto const &row : result->rows) { if (row.size() < 4) { co_return std::unexpected(internal("migration table returned an incomplete row").error()); } @@ -177,8 +181,8 @@ auto load_records(noto::connection& connection, noto::migration_options const& o co_return records; } -auto applied_record(std::vector const& records, std::string const& id) -> noto::migration_record const* { - for (auto const& record : records) { +auto applied_record(std::vector const &records, std::string const &id) -> noto::migration_record const * { + for (auto const &record : records) { if (record.id == id) { return &record; } @@ -186,7 +190,7 @@ auto applied_record(std::vector const& records, std::str return nullptr; } -auto rollback_and_return(noto::transaction& transaction, std::expected status) -> boost::asio::awaitable> { +auto rollback_and_return(noto::transaction &transaction, std::expected status) -> boost::asio::awaitable> { auto rollback = co_await transaction.async_rollback(); if (!rollback) { co_return rollback; @@ -194,13 +198,14 @@ auto rollback_and_return(noto::transaction& transaction, std::expected boost::asio::awaitable> { +auto rollback_and_return(noto::transaction &transaction, modulus::error error) -> boost::asio::awaitable> { co_return co_await rollback_and_return(transaction, noto::detail::fail_void(std::move(error))); } } -noto::migrator::migrator(noto::connection& connection, noto::migration_options options) : connection_{&connection}, options_{std::move(options)} {} +noto::migrator::migrator(noto::connection &connection, noto::migration_options options) + : connection_{&connection}, options_{std::move(options)} {} auto noto::migrator::async_applied_migrations() -> boost::asio::awaitable, modulus::error>> { if (connection_ == nullptr) { @@ -235,8 +240,8 @@ auto noto::migrator::async_migrate(std::vector migrations) -> b if (!records) { co_return co_await rollback_and_return(transaction, records.error()); } - for (auto const& migration : *sorted) { - auto const* applied = applied_record(*records, migration.id); + for (auto const &migration : *sorted) { + auto const *applied = applied_record(*records, migration.id); if (applied != nullptr) { if (applied->checksum != migration.checksum) { co_return co_await rollback_and_return(transaction, invalid("applied migration checksum mismatch: " + migration.id)); @@ -277,7 +282,7 @@ auto noto::migrator::async_rollback(std::vector migrations, std co_return std::unexpected(sorted.error()); } auto by_id = std::map{}; - for (auto& migration : *sorted) { + for (auto &migration : *sorted) { by_id.emplace(migration.id, std::move(migration)); } auto ensured = co_await ensure_migration_table(*connection_, options_); @@ -294,7 +299,7 @@ auto noto::migrator::async_rollback(std::vector migrations, std co_return co_await rollback_and_return(transaction, records.error()); } std::ranges::sort(*records, std::greater<>{}, ¬o::migration_record::id); - for (auto const& record : *records) { + for (auto const &record : *records) { if (!target_id.empty() && record.id <= target_id) { continue; } diff --git a/src/schema.cpp b/src/schema.cpp index 0d6ceb0..665ef95 100644 --- a/src/schema.cpp +++ b/src/schema.cpp @@ -15,6 +15,7 @@ #include #include + namespace { auto invalid_sql(std::string message) -> std::expected { @@ -50,7 +51,7 @@ auto validate_fragment(std::string_view value, std::string_view label) -> std::e return {}; } -auto qualified_table(noto::table_name const& table) -> std::expected { +auto qualified_table(noto::table_name const &table) -> std::expected { auto name = noto::quote_identifier(table.name); if (!name) { return std::unexpected(name.error()); @@ -91,7 +92,7 @@ auto reference_action_sql(noto::reference_action action) -> std::string_view { return "RESTRICT"; } -auto append_identifier_list(std::ostringstream& sql, std::vector const& identifiers) -> std::expected { +auto append_identifier_list(std::ostringstream &sql, std::vector const &identifiers) -> std::expected { if (identifiers.empty()) { return noto::detail::fail_void(noto::detail::error_invalid_argument, "column list must not be empty"); } @@ -108,7 +109,7 @@ auto append_identifier_list(std::ostringstream& sql, std::vector co return {}; } -auto append_constraint_name(std::ostringstream& sql, std::string const& name) -> std::expected { +auto append_constraint_name(std::ostringstream &sql, std::string const &name) -> std::expected { if (name.empty()) { return {}; } @@ -120,7 +121,7 @@ auto append_constraint_name(std::ostringstream& sql, std::string const& name) -> return {}; } -auto column_sql(noto::column_definition const& column) -> std::expected { +auto column_sql(noto::column_definition const &column) -> std::expected { auto name = noto::quote_identifier(column.name); if (!name) { return std::unexpected(name.error()); @@ -167,7 +168,7 @@ auto column_sql(noto::column_definition const& column) -> std::expected const& columns) -> std::expected { +auto primary_key_sql(std::vector const &columns) -> std::expected { auto sql = std::ostringstream{}; sql << "PRIMARY KEY ("; auto status = append_identifier_list(sql, columns); @@ -178,7 +179,7 @@ auto primary_key_sql(std::vector const& columns) -> std::expected std::expected { +auto unique_constraint_sql(noto::unique_constraint_definition const &definition) -> std::expected { auto sql = std::ostringstream{}; auto name_status = append_constraint_name(sql, definition.name); if (!name_status) { @@ -193,7 +194,7 @@ auto unique_constraint_sql(noto::unique_constraint_definition const& definition) return sql.str(); } -auto check_constraint_sql(noto::check_constraint_definition const& definition) -> std::expected { +auto check_constraint_sql(noto::check_constraint_definition const &definition) -> std::expected { if (!definition.enforced) { return invalid_sql("SQLite check constraints cannot be marked not enforced"); } @@ -210,7 +211,7 @@ auto check_constraint_sql(noto::check_constraint_definition const& definition) - return sql.str(); } -auto foreign_key_sql(noto::foreign_key_definition const& definition) -> std::expected { +auto foreign_key_sql(noto::foreign_key_definition const &definition) -> std::expected { auto target = qualified_table(definition.referenced_table); if (!target) { return std::unexpected(target.error()); @@ -240,7 +241,7 @@ auto foreign_key_sql(noto::foreign_key_definition const& definition) -> std::exp return sql.str(); } -auto append_table_options(std::ostringstream& sql, noto::table_options const& options) -> void { +auto append_table_options(std::ostringstream &sql, noto::table_options const &options) -> void { auto first = true; auto append = [&sql, &first](std::string_view value) { sql << (first ? " " : ", ") << value; @@ -264,7 +265,7 @@ auto noto::trusted_sql_fragment::unsafe(std::string sql) -> std::expected std::string const& { +auto noto::trusted_sql_fragment::value() const -> std::string const & { return sql_; } @@ -272,7 +273,8 @@ auto noto::trusted_sql_fragment::empty() const -> std::bool_t { return sql_.empty(); } -noto::trusted_sql_fragment::trusted_sql_fragment(std::string sql) : sql_{std::move(sql)} {} +noto::trusted_sql_fragment::trusted_sql_fragment(std::string sql) + : sql_{std::move(sql)} {} auto noto::column_type::integer() -> noto::column_type { return noto::column_type{"INTEGER"}; @@ -323,7 +325,7 @@ auto noto::column_type::trusted(noto::trusted_sql_fragment fragment) -> noto::co return noto::column_type{fragment.value()}; } -auto noto::column_type::sql() const -> std::string const& { +auto noto::column_type::sql() const -> std::string const & { return sql_; } @@ -331,7 +333,8 @@ auto noto::column_type::empty() const -> std::bool_t { return sql_.empty(); } -noto::column_type::column_type(std::string sql) : sql_{std::move(sql)} {} +noto::column_type::column_type(std::string sql) + : sql_{std::move(sql)} {} auto noto::default_value::null_value() -> noto::default_value { return noto::default_value{"NULL"}; @@ -374,7 +377,7 @@ auto noto::default_value::trusted_expression(noto::trusted_sql_fragment fragment return noto::default_value{fragment.value()}; } -auto noto::default_value::sql() const -> std::string const& { +auto noto::default_value::sql() const -> std::string const & { return sql_; } @@ -382,7 +385,8 @@ auto noto::default_value::empty() const -> std::bool_t { return sql_.empty(); } -noto::default_value::default_value(std::string sql) : sql_{std::move(sql)} {} +noto::default_value::default_value(std::string sql) + : sql_{std::move(sql)} {} auto noto::quote_identifier(std::string_view identifier) -> std::expected { auto valid = validate_identifier(identifier); @@ -402,7 +406,7 @@ auto noto::quote_identifier(std::string_view identifier) -> std::expected std::expected { +auto noto::create_table_sql(noto::table_definition const &definition) -> std::expected { auto table = qualified_table(definition.name); if (!table) { return std::unexpected(table.error()); @@ -410,17 +414,17 @@ auto noto::create_table_sql(noto::table_definition const& definition) -> std::ex if (definition.columns.empty()) { return invalid_sql("table must define at least one column"); } - auto inline_primary_key_count = std::ranges::count_if(definition.columns, [](noto::column_definition const& column) { + auto inline_primary_key_count = std::ranges::count_if(definition.columns, [](noto::column_definition const &column) { return column.primary_key; }); if (!definition.primary_key.empty() && inline_primary_key_count != 0) { return invalid_sql("table must not mix column primary keys with a table primary key"); } if (definition.primary_key.empty() && inline_primary_key_count > 1) { - return invalid_sql("table must not define multiple column-level primary keys"); + return invalid_sql("table must not define multiple column-level primary keys"); } auto definitions = std::vector{}; - for (auto const& column_definition : definition.columns) { + for (auto const &column_definition : definition.columns) { auto column = column_sql(column_definition); if (!column) { return std::unexpected(column.error()); @@ -434,21 +438,21 @@ auto noto::create_table_sql(noto::table_definition const& definition) -> std::ex } definitions.push_back(std::move(*constraint)); } - for (auto const& unique_constraint : definition.unique_constraints) { + for (auto const &unique_constraint : definition.unique_constraints) { auto constraint = unique_constraint_sql(unique_constraint); if (!constraint) { return std::unexpected(constraint.error()); } definitions.push_back(std::move(*constraint)); } - for (auto const& check_constraint : definition.check_constraints) { + for (auto const &check_constraint : definition.check_constraints) { auto constraint = check_constraint_sql(check_constraint); if (!constraint) { return std::unexpected(constraint.error()); } definitions.push_back(std::move(*constraint)); } - for (auto const& foreign_key : definition.foreign_keys) { + for (auto const &foreign_key : definition.foreign_keys) { auto constraint = foreign_key_sql(foreign_key); if (!constraint) { return std::unexpected(constraint.error()); @@ -468,7 +472,7 @@ auto noto::create_table_sql(noto::table_definition const& definition) -> std::ex return sql.str(); } -auto noto::drop_table_sql(noto::table_name const& table, std::bool_t if_exists) -> std::expected { +auto noto::drop_table_sql(noto::table_name const &table, std::bool_t if_exists) -> std::expected { auto name = qualified_table(table); if (!name) { return std::unexpected(name.error()); @@ -476,7 +480,7 @@ auto noto::drop_table_sql(noto::table_name const& table, std::bool_t if_exists) return std::string{"DROP TABLE "} + (if_exists ? "IF EXISTS " : "") + *name; } -auto noto::create_index_sql(noto::index_definition const& definition) -> std::expected { +auto noto::create_index_sql(noto::index_definition const &definition) -> std::expected { auto table = qualified_table(definition.table); if (!table) { return std::unexpected(table.error()); @@ -503,7 +507,7 @@ auto noto::create_index_sql(noto::index_definition const& definition) -> std::ex return sql.str(); } -auto noto::drop_index_sql(noto::index_definition const& definition) -> std::expected { +auto noto::drop_index_sql(noto::index_definition const &definition) -> std::expected { auto index = noto::quote_identifier(definition.name); if (!index) { return std::unexpected(index.error()); diff --git a/src/transaction.cpp b/src/transaction.cpp index ac07ebc..222f95e 100644 --- a/src/transaction.cpp +++ b/src/transaction.cpp @@ -14,6 +14,7 @@ #include + namespace { auto inactive_transaction() -> std::expected { @@ -24,7 +25,7 @@ auto async_status(std::expected status) -> boost::asio::aw co_return status; } -auto finish_started_transaction(std::shared_ptr& state, noto::detail::transaction_result const& result) -> void { +auto finish_started_transaction(std::shared_ptr &state, noto::detail::transaction_result const &result) -> void { if (result.status) { state->core.reset(); state->finish(); @@ -76,7 +77,8 @@ auto async_rollback_impl(std::shared_ptr state) } -noto::transaction::transaction(noto::transaction&& other) noexcept : state_{std::move(other.state_)} {} +noto::transaction::transaction(noto::transaction &&other) noexcept + : state_{std::move(other.state_)} {} noto::transaction::~transaction() { if (!state_) { @@ -106,6 +108,8 @@ auto noto::transaction::async_rollback() -> boost::asio::awaitable state) : state_{std::move(state)} {} +noto::transaction::transaction(std::shared_ptr state) + : state_{std::move(state)} {} -noto::transaction::transaction(std::shared_ptr core, std::function on_finish) : state_{std::make_shared(std::move(core), std::move(on_finish))} {} +noto::transaction::transaction(std::shared_ptr core, std::function on_finish) + : state_{std::make_shared(std::move(core), std::move(on_finish))} {} diff --git a/tests/test.cpp b/tests/test.cpp index d6cc693..09d0e04 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -34,6 +34,7 @@ #include #include + BOOST_AUTO_TEST_CASE(noto_asserts_follow_build_type) { #if defined(NOTO_EXPECT_NDEBUG) && !defined(NDEBUG) BOOST_FAIL("release-style builds must define NDEBUG"); @@ -43,11 +44,10 @@ BOOST_AUTO_TEST_CASE(noto_asserts_follow_build_type) { #endif } - namespace { template -auto run(boost::asio::io_context& io, AWAITABLE&& awaitable) { +auto run(boost::asio::io_context &io, AWAITABLE &&awaitable) { auto future = boost::asio::co_spawn(io, std::forward(awaitable), boost::asio::use_future); io.run(); io.restart(); @@ -59,24 +59,24 @@ auto temp_database_path(std::string_view name) -> std::string { return path.string(); } -auto remove_database_files(std::string const& path) -> void { +auto remove_database_files(std::string const &path) -> void { std::filesystem::remove(path); std::filesystem::remove(path + "-wal"); std::filesystem::remove(path + "-shm"); std::filesystem::remove(path + "-journal"); } -auto text_value(noto::sqlite_value const& value) -> std::string { +auto text_value(noto::sqlite_value const &value) -> std::string { auto text = std::get_if(&value.value); return text == nullptr ? std::string{} : *text; } -auto int_value(noto::sqlite_value const& value) -> std::int64_t { +auto int_value(noto::sqlite_value const &value) -> std::int64_t { auto integer = std::get_if(&value.value); return integer == nullptr ? 0 : *integer; } -auto execute_status(noto::connection& connection, std::string sql) -> boost::asio::awaitable> { +auto execute_status(noto::connection &connection, std::string sql) -> boost::asio::awaitable> { auto result = co_await connection.async_execute(std::move(sql)); if (!result) { co_return std::unexpected(result.error()); @@ -84,15 +84,15 @@ auto execute_status(noto::connection& connection, std::string sql) -> boost::asi co_return std::expected{}; } -auto sqlite_exec_ok(sqlite3* database, std::string const& sql) -> std::bool_t { - auto* error = static_cast(nullptr); +auto sqlite_exec_ok(sqlite3 *database, std::string const &sql) -> std::bool_t { + auto *error = static_cast(nullptr); auto code = sqlite3_exec(database, sql.c_str(), nullptr, nullptr, &error); sqlite3_free(error); return code == SQLITE_OK; } -auto create_sqlite_database(std::string const& path, std::string const& sql) -> std::bool_t { - auto* database = static_cast(nullptr); +auto create_sqlite_database(std::string const &path, std::string const &sql) -> std::bool_t { + auto *database = static_cast(nullptr); if (sqlite3_open_v2(path.c_str(), &database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) { if (database != nullptr) { sqlite3_close(database); @@ -103,7 +103,7 @@ auto create_sqlite_database(std::string const& path, std::string const& sql) -> return sqlite3_close(database) == SQLITE_OK && ok; } -auto write_file(std::string const& path, std::string_view text) -> void { +auto write_file(std::string const &path, std::string_view text) -> void { auto output = std::ofstream{ path, std::ios::binary | std::ios::trunc, @@ -111,7 +111,7 @@ auto write_file(std::string const& path, std::string_view text) -> void { output << text; } -auto read_file(std::string const& path) -> std::string { +auto read_file(std::string const &path) -> std::string { auto input = std::ifstream{ path, std::ios::binary, @@ -121,8 +121,8 @@ auto read_file(std::string const& path) -> std::string { return output.str(); } -auto sqlite_scalar_int64(sqlite3* database, std::string const& sql) -> std::int64_t { - auto* statement = static_cast(nullptr); +auto sqlite_scalar_int64(sqlite3 *database, std::string const &sql) -> std::int64_t { + auto *statement = static_cast(nullptr); if (sqlite3_prepare_v2(database, sql.c_str(), -1, &statement, nullptr) != SQLITE_OK) { return 0; } @@ -146,7 +146,6 @@ BOOST_AUTO_TEST_CASE(noto_status_defaults_to_ok) { BOOST_AUTO_TEST_CASE(noto_result_carries_failure_status) { auto ok = std::expected{}; BOOST_TEST(static_cast(ok.has_value())); - auto result = noto::detail::fail(noto::detail::error_invalid_argument, "bad value"); BOOST_REQUIRE(!result); BOOST_TEST(static_cast(result.error().code()) == static_cast(noto::detail::error_invalid_argument)); @@ -163,10 +162,8 @@ BOOST_AUTO_TEST_CASE(noto_status_maps_sqlite_errors) { BOOST_AUTO_TEST_CASE(noto_config_validates_and_parses_json) { auto config = noto::connection_config{}; BOOST_TEST(static_cast(noto::validate(config))); - config.filename = ""; BOOST_TEST(!static_cast(noto::validate(config))); - auto value = boost::json::parse(R"json({ "databases": [ { @@ -196,27 +193,19 @@ BOOST_AUTO_TEST_CASE(noto_config_validates_and_parses_json) { BOOST_TEST(round_trip->at(0).name == "main"); BOOST_TEST(static_cast(round_trip->at(0).mode) == static_cast(noto::open_mode::MEMORY)); BOOST_TEST(round_trip->at(0).pragmas.size() == 2U); - auto bad = noto::parse_database_configs(boost::json::parse(R"json({"databases":[{"name":"bad","filename":":memory:","mode":"server"}]})json")); BOOST_TEST(!static_cast(bad.has_value())); - auto missing_file = noto::parse_database_configs(boost::json::parse(R"json({"databases":[{"name":"bad"}]})json")); BOOST_TEST(!static_cast(missing_file.has_value())); - config.filename = ":memory:"; config.busy_timeout = std::chrono::milliseconds{static_cast(std::numeric_limits::max()) + 1}; BOOST_TEST(!static_cast(noto::validate(config))); - - auto oversized_timeout_json = std::string{R"json({"databases":[{"name":"bad","filename":":memory:","busy_timeout_ms":)json"} - + std::to_string(static_cast(std::numeric_limits::max()) + 1) - + R"json(}]})json"; + auto oversized_timeout_json = std::string{R"json({"databases":[{"name":"bad","filename":":memory:","busy_timeout_ms":)json"} + std::to_string(static_cast(std::numeric_limits::max()) + 1) + R"json(}]})json"; auto oversized_timeout = noto::parse_database_configs(boost::json::parse(oversized_timeout_json)); BOOST_TEST(!static_cast(oversized_timeout.has_value())); - auto unknown_root = noto::parse_database_configs(boost::json::parse(R"json({"databases":[],"databasez":[]})json")); BOOST_REQUIRE(!unknown_root); BOOST_TEST(unknown_root.error().describe().find("config root has unknown key: databasez") != std::string::npos); - auto unknown_database = noto::parse_database_configs(boost::json::parse(R"json({"databases":[{"name":"bad","filename":":memory:","busy_timout_ms":250}]})json")); BOOST_REQUIRE(!unknown_database); BOOST_TEST(unknown_database.error().describe().find("database has unknown key: busy_timout_ms") != std::string::npos); @@ -226,7 +215,6 @@ BOOST_AUTO_TEST_CASE(noto_schema_quotes_identifiers_and_builds_sqlite_table_sql) auto quoted = noto::quote_identifier("with\"quote"); BOOST_REQUIRE(quoted); BOOST_TEST(*quoted == "\"with\"\"quote\""); - auto email_type = noto::column_type::varchar(255); BOOST_REQUIRE(email_type); auto sql = noto::create_table_sql({ @@ -257,7 +245,6 @@ BOOST_AUTO_TEST_CASE(noto_schema_quotes_identifiers_and_builds_sqlite_table_sql) .strict = true, }, }); - BOOST_REQUIRE(sql); BOOST_TEST(*sql == "CREATE TABLE IF NOT EXISTS \"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT, \"email\" TEXT NOT NULL, \"created_at\" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP) STRICT"); } @@ -335,7 +322,6 @@ BOOST_AUTO_TEST_CASE(noto_schema_builds_constraints_generated_columns_and_option .without_rowid = true, }, }); - BOOST_REQUIRE(sql); BOOST_TEST(*sql == "CREATE TABLE IF NOT EXISTS \"accounts\" (\"tenant_id\" INTEGER NOT NULL, \"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, \"email_key\" TEXT GENERATED ALWAYS AS (lower(\"email\")) STORED NOT NULL, PRIMARY KEY (\"tenant_id\", \"id\"), CONSTRAINT \"uq_accounts_email\" UNIQUE (\"tenant_id\", \"email_key\"), CONSTRAINT \"ck_accounts_email\" CHECK (\"email\" <> ''), CONSTRAINT \"fk_accounts_tenant\" FOREIGN KEY (\"tenant_id\") REFERENCES \"tenants\" (\"id\") ON DELETE CASCADE) WITHOUT ROWID"); } @@ -346,29 +332,24 @@ BOOST_AUTO_TEST_CASE(noto_connection_executes_queries_and_prepared_statements) { io.get_executor(), noto::memory_connection_config("test"), }; - auto connected = run(io, connection.async_connect()); BOOST_TEST(static_cast(connected)); BOOST_TEST(static_cast(connection.state()) == static_cast(noto::connection_state::CONNECTED)); - auto created = run(io, connection.async_execute("CREATE TABLE items(id INTEGER PRIMARY KEY, value TEXT NOT NULL)")); BOOST_REQUIRE(created); - auto statement = run(io, connection.async_prepare("INSERT INTO items(value) VALUES(?1)")); BOOST_REQUIRE(statement); auto inserted = run(io, statement->async_execute({ - noto::sqlite_value{"alpha"}, - })); + noto::sqlite_value{"alpha"}, + })); BOOST_REQUIRE(inserted); BOOST_TEST(inserted->changes == 1); - auto selected = run(io, connection.async_execute("SELECT value FROM items WHERE id = ?1", { - noto::sqlite_value{inserted->last_insert_rowid}, - })); + noto::sqlite_value{inserted->last_insert_rowid}, + })); BOOST_REQUIRE(selected); BOOST_REQUIRE(selected->rows.size() == 1U); BOOST_TEST(text_value(selected->rows.at(0).at(0)) == "alpha"); - auto finalized = run(io, statement->async_finalize()); BOOST_TEST(static_cast(finalized)); auto closed = run(io, connection.async_close()); @@ -382,14 +363,12 @@ BOOST_AUTO_TEST_CASE(noto_close_with_live_statement_is_recoverable) { noto::memory_connection_config("live_statement"), }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); - auto statement = run(io, connection.async_prepare("SELECT 1")); BOOST_REQUIRE(statement); auto blocked = run(io, connection.async_close()); BOOST_TEST(!static_cast(blocked)); BOOST_TEST(static_cast(blocked.error().code()) == static_cast(noto::detail::error_unavailable)); BOOST_TEST(static_cast(connection.state()) == static_cast(noto::connection_state::CONNECTED)); - BOOST_TEST(static_cast(run(io, statement->async_finalize()))); BOOST_TEST(static_cast(run(io, connection.async_close()))); } @@ -403,14 +382,12 @@ BOOST_AUTO_TEST_CASE(noto_close_rolls_back_active_transaction) { }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); - { auto transaction = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(transaction); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('transient')"))); BOOST_TEST(static_cast(run(io, connection.async_close()))); } - auto reopened = noto::connection{ io.get_executor(), noto::file_connection_config(path), @@ -425,33 +402,29 @@ BOOST_AUTO_TEST_CASE(noto_close_rolls_back_active_transaction) { BOOST_AUTO_TEST_CASE(noto_async_methods_capture_state_from_temporary_wrappers) { auto io = boost::asio::io_context{}; - auto connect_future = boost::asio::co_spawn( io, noto::connection{ io.get_executor(), noto::memory_connection_config("temporary_connection"), - }.async_connect(), - boost::asio::use_future - ); + } + .async_connect(), + boost::asio::use_future); io.run(); io.restart(); BOOST_TEST(static_cast(connect_future.get())); - auto connection = noto::connection{ io.get_executor(), noto::memory_connection_config("temporary_wrappers"), }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); - auto statement = run(io, connection.async_prepare("SELECT 1")); BOOST_REQUIRE(statement); auto finalize_future = boost::asio::co_spawn(io, noto::statement{std::move(*statement)}.async_finalize(), boost::asio::use_future); io.run(); io.restart(); BOOST_TEST(static_cast(finalize_future.get())); - auto rolled_back = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(rolled_back); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('rolled back')"))); @@ -459,7 +432,6 @@ BOOST_AUTO_TEST_CASE(noto_async_methods_capture_state_from_temporary_wrappers) { io.run(); io.restart(); BOOST_TEST(static_cast(rollback_future.get())); - auto committed = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(committed); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('committed')"))); @@ -467,7 +439,6 @@ BOOST_AUTO_TEST_CASE(noto_async_methods_capture_state_from_temporary_wrappers) { io.run(); io.restart(); BOOST_TEST(static_cast(commit_future.get())); - auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM items")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 1); @@ -483,22 +454,18 @@ BOOST_AUTO_TEST_CASE(noto_detached_transaction_rolls_back_when_commit_cannot_sta }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value INTEGER NOT NULL)"))); - auto transaction = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(transaction); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES(1), (2), (3), (4), (5)"))); - auto options = noto::export_options{}; options.rows_per_yield = 1; auto export_future = boost::asio::co_spawn(io, connection.async_export_sql_to_file(export_path, options), boost::asio::use_future); auto commit_future = boost::asio::co_spawn(io, noto::transaction{std::move(*transaction)}.async_commit(), boost::asio::use_future); io.run(); io.restart(); - BOOST_TEST(static_cast(export_future.get())); auto commit = commit_future.get(); BOOST_TEST(static_cast(commit.error().code()) == static_cast(noto::detail::error_unavailable)); - auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM items")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 0); @@ -513,12 +480,10 @@ BOOST_AUTO_TEST_CASE(noto_statement_destructor_cleanup_allows_close) { noto::memory_connection_config("statement_destructor"), }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); - { auto statement = run(io, connection.async_prepare("SELECT 1")); BOOST_REQUIRE(statement); } - BOOST_TEST(static_cast(run(io, connection.async_close()))); } @@ -530,7 +495,6 @@ BOOST_AUTO_TEST_CASE(noto_transactions_commit_and_rollback) { }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); - { auto transaction = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(transaction); @@ -540,7 +504,6 @@ BOOST_AUTO_TEST_CASE(noto_transactions_commit_and_rollback) { auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM items")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 0); - { auto transaction = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(transaction); @@ -561,21 +524,17 @@ BOOST_AUTO_TEST_CASE(noto_failed_commit_remains_rollbackable) { BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE parents(id INTEGER PRIMARY KEY)"))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE children(parent_id INTEGER NOT NULL, FOREIGN KEY(parent_id) REFERENCES parents(id) DEFERRABLE INITIALLY DEFERRED)"))); - auto transaction = run(io, connection.async_begin_transaction(noto::transaction_mode::DEFERRED)); BOOST_REQUIRE(transaction); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO children(parent_id) VALUES(42)"))); - auto committed = run(io, transaction->async_commit()); BOOST_TEST(!static_cast(committed)); BOOST_TEST(static_cast(committed.error().code()) == static_cast(noto::detail::error_constraint_violation)); BOOST_TEST(static_cast(transaction->active())); BOOST_TEST(static_cast(run(io, transaction->async_rollback()))); - auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM children")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 0); - auto next = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(next); BOOST_TEST(static_cast(run(io, next->async_rollback()))); @@ -591,22 +550,18 @@ BOOST_AUTO_TEST_CASE(noto_detached_failed_commit_rolls_back) { BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE parents(id INTEGER PRIMARY KEY)"))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE children(parent_id INTEGER NOT NULL, FOREIGN KEY(parent_id) REFERENCES parents(id) DEFERRABLE INITIALLY DEFERRED)"))); - auto transaction = run(io, connection.async_begin_transaction(noto::transaction_mode::DEFERRED)); BOOST_REQUIRE(transaction); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO children(parent_id) VALUES(42)"))); - auto commit_future = boost::asio::co_spawn(io, noto::transaction{std::move(*transaction)}.async_commit(), boost::asio::use_future); io.run(); io.restart(); auto committed = commit_future.get(); BOOST_TEST(!static_cast(committed)); BOOST_TEST(static_cast(committed.error().code()) == static_cast(noto::detail::error_constraint_violation)); - auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM children")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 0); - auto next = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(next); BOOST_TEST(static_cast(run(io, next->async_rollback()))); @@ -621,13 +576,11 @@ BOOST_AUTO_TEST_CASE(noto_abandoned_transaction_rolls_back_before_next_operation }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); - { auto transaction = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(transaction); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('abandoned')"))); } - auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM items")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 0); @@ -642,8 +595,7 @@ BOOST_AUTO_TEST_CASE(noto_transaction_callback_exceptions_return_status_and_roll }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); - - auto status = run(io, connection.async_with_transaction(noto::transaction_mode::IMMEDIATE, [](noto::connection& active) -> boost::asio::awaitable> { + auto status = run(io, connection.async_with_transaction(noto::transaction_mode::IMMEDIATE, [](noto::connection &active) -> boost::asio::awaitable> { auto inserted = co_await active.async_execute("INSERT INTO items(value) VALUES('temporary')"); if (!inserted) { co_return std::unexpected(inserted.error()); @@ -652,7 +604,6 @@ BOOST_AUTO_TEST_CASE(noto_transaction_callback_exceptions_return_status_and_roll })); BOOST_TEST(!static_cast(status)); BOOST_TEST(static_cast(status.error().code()) == static_cast(noto::detail::error_internal)); - auto count = run(io, connection.async_execute("SELECT COUNT(*) FROM items")); BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 0); @@ -667,19 +618,16 @@ BOOST_AUTO_TEST_CASE(noto_execute_options_limit_collected_results) { BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('one'), ('two')"))); - auto row_options = noto::execute_options{}; row_options.max_rows = 1; auto too_many_rows = run(io, connection.async_execute("SELECT value FROM items ORDER BY value", {}, row_options)); BOOST_REQUIRE(!too_many_rows); BOOST_TEST(static_cast(too_many_rows.error().code()) == static_cast(noto::detail::error_resource_exhausted)); - auto byte_options = noto::execute_options{}; byte_options.max_result_bytes = 4; auto too_many_bytes = run(io, connection.async_execute("SELECT value FROM items WHERE value = 'one'", {}, byte_options)); BOOST_REQUIRE(!too_many_bytes); BOOST_TEST(static_cast(too_many_bytes.error().code()) == static_cast(noto::detail::error_resource_exhausted)); - auto statement = run(io, connection.async_prepare("SELECT value FROM items ORDER BY value")); BOOST_REQUIRE(statement); auto statement_options = noto::execute_options{}; @@ -700,16 +648,15 @@ BOOST_AUTO_TEST_CASE(noto_migrations_apply_validate_and_rollback) { noto::memory_connection_config(), }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); - auto migrations = std::vector{ { .id = "001_create_table", .description = "create items", .checksum = "checksum-001", - .up = [](noto::connection& active) -> boost::asio::awaitable> { + .up = [](noto::connection &active) -> boost::asio::awaitable> { co_return co_await execute_status(active, "CREATE TABLE items(id INTEGER PRIMARY KEY, value TEXT NOT NULL)"); }, - .down = [](noto::connection& active) -> boost::asio::awaitable> { + .down = [](noto::connection &active) -> boost::asio::awaitable> { co_return co_await execute_status(active, "DROP TABLE IF EXISTS items"); }, }, @@ -717,18 +664,15 @@ BOOST_AUTO_TEST_CASE(noto_migrations_apply_validate_and_rollback) { auto migrator = noto::migrator{connection}; BOOST_TEST(static_cast(run(io, migrator.async_migrate(migrations)))); BOOST_TEST(static_cast(run(io, migrator.async_migrate(migrations)))); - auto changed = migrations; changed.at(0).checksum = "changed"; auto mismatch = run(io, migrator.async_migrate(changed)); BOOST_TEST(!static_cast(mismatch)); BOOST_TEST(static_cast(mismatch.error().code()) == static_cast(noto::detail::error_invalid_argument)); - auto records = run(io, migrator.async_applied_migrations()); BOOST_REQUIRE(records); BOOST_TEST(records->size() == 1U); BOOST_TEST(records->at(0).id == "001_create_table"); - BOOST_TEST(static_cast(run(io, migrator.async_rollback(migrations)))); auto missing = run(io, connection.async_execute("SELECT name FROM sqlite_schema WHERE type = 'table' AND name = 'items'")); BOOST_REQUIRE(missing); @@ -742,7 +686,6 @@ BOOST_AUTO_TEST_CASE(noto_backup_export_integrity_and_pragmas_work) { auto failed_backup_path = temp_database_path("failed_backup"); auto oversized_retry_path = temp_database_path("oversized_retry"); auto export_path = temp_database_path("export_sql"); - auto connection = noto::connection{ io.get_executor(), noto::file_connection_config(source_path), @@ -759,14 +702,12 @@ BOOST_AUTO_TEST_CASE(noto_backup_export_integrity_and_pragmas_work) { auto busy_timeout = run(io, connection.async_busy_timeout()); BOOST_REQUIRE(busy_timeout); BOOST_TEST(*busy_timeout == std::chrono::milliseconds{37}); - BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(id INTEGER PRIMARY KEY, value TEXT NOT NULL)"))); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('one'), ('two')"))); auto integrity = run(io, connection.async_integrity_check()); BOOST_REQUIRE(integrity); BOOST_REQUIRE(integrity->size() == 1U); BOOST_TEST(integrity->at(0) == "ok"); - BOOST_TEST(static_cast(run(io, connection.async_backup_to_file(backup_path)))); auto exported = run(io, connection.async_export_sql()); BOOST_REQUIRE(exported); @@ -776,8 +717,7 @@ BOOST_AUTO_TEST_CASE(noto_backup_export_integrity_and_pragmas_work) { auto limited_export = run(io, connection.async_export_sql(limited_options)); BOOST_REQUIRE(!limited_export); BOOST_TEST(static_cast(limited_export.error().code()) == static_cast(noto::detail::error_resource_exhausted)); - - auto* preserved = static_cast(nullptr); + auto *preserved = static_cast(nullptr); BOOST_REQUIRE(sqlite3_open_v2(failed_backup_path.c_str(), &preserved, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, nullptr) == SQLITE_OK); BOOST_REQUIRE(sqlite_exec_ok(preserved, "CREATE TABLE preserved(value INTEGER); INSERT INTO preserved(value) VALUES(42)")); sqlite3_close(preserved); @@ -789,17 +729,14 @@ BOOST_AUTO_TEST_CASE(noto_backup_export_integrity_and_pragmas_work) { BOOST_REQUIRE(sqlite3_open_v2(failed_backup_path.c_str(), &preserved, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) == SQLITE_OK); BOOST_TEST(sqlite_scalar_int64(preserved, "SELECT value FROM preserved") == 42); sqlite3_close(preserved); - auto oversized_retry = noto::backup_options{}; oversized_retry.retry_delay = std::chrono::milliseconds{static_cast(std::numeric_limits::max()) + 1}; auto failed_delay = run(io, connection.async_backup_to_file(oversized_retry_path, oversized_retry)); BOOST_TEST(static_cast(failed_delay.error().code()) == static_cast(noto::detail::error_invalid_argument)); - oversized_retry = noto::backup_options{}; oversized_retry.retry_timeout = std::chrono::milliseconds{static_cast(std::numeric_limits::max()) + 1}; auto failed_timeout = run(io, connection.async_backup_to_file(oversized_retry_path, oversized_retry)); BOOST_TEST(static_cast(failed_timeout.error().code()) == static_cast(noto::detail::error_invalid_argument)); - write_file(export_path, "preserved export"); auto limited_file_options = noto::export_options{}; limited_file_options.max_output_bytes = 16; @@ -807,11 +744,9 @@ BOOST_AUTO_TEST_CASE(noto_backup_export_integrity_and_pragmas_work) { BOOST_REQUIRE(!limited_file_export); BOOST_TEST(static_cast(limited_file_export.error().code()) == static_cast(noto::detail::error_resource_exhausted)); BOOST_TEST(read_file(export_path) == "preserved export"); - BOOST_TEST(static_cast(run(io, connection.async_export_sql_to_file(export_path)))); BOOST_TEST(static_cast(std::filesystem::exists(export_path))); BOOST_TEST(static_cast(run(io, connection.async_close()))); - auto restored = noto::connection{ io.get_executor(), noto::file_connection_config(backup_path), @@ -821,7 +756,6 @@ BOOST_AUTO_TEST_CASE(noto_backup_export_integrity_and_pragmas_work) { BOOST_REQUIRE(count); BOOST_TEST(int_value(count->rows.at(0).at(0)) == 2); BOOST_TEST(static_cast(run(io, restored.async_close()))); - std::filesystem::remove(source_path); std::filesystem::remove(backup_path); std::filesystem::remove(failed_backup_path); @@ -840,19 +774,16 @@ BOOST_AUTO_TEST_CASE(noto_backup_and_export_reject_live_database_destinations) { BOOST_TEST(static_cast(run(io, connection.async_connect()))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('safe')"))); - auto self_backup = run(io, connection.async_backup_to_file(source_path)); BOOST_TEST(static_cast(self_backup.error().code()) == static_cast(noto::detail::error_invalid_argument)); auto self_export = run(io, connection.async_export_sql_to_file(source_path)); BOOST_TEST(static_cast(self_export.error().code()) == static_cast(noto::detail::error_invalid_argument)); - for (auto suffix : std::array{"-wal", "-shm", "-journal"}) { auto backup = run(io, connection.async_backup_to_file(source_path + std::string{suffix})); BOOST_TEST(static_cast(backup.error().code()) == static_cast(noto::detail::error_invalid_argument)); auto exported = run(io, connection.async_export_sql_to_file(source_path + std::string{suffix})); BOOST_TEST(static_cast(exported.error().code()) == static_cast(noto::detail::error_invalid_argument)); } - auto error = std::error_code{}; std::filesystem::create_symlink(source_path, link_path, error); if (!error) { @@ -861,7 +792,6 @@ BOOST_AUTO_TEST_CASE(noto_backup_and_export_reject_live_database_destinations) { auto link_export = run(io, connection.async_export_sql_to_file(link_path)); BOOST_TEST(static_cast(link_export.error().code()) == static_cast(noto::detail::error_invalid_argument)); } - auto rows = run(io, connection.async_execute("SELECT value FROM items")); BOOST_REQUIRE(rows); BOOST_REQUIRE(rows->rows.size() == 1U); @@ -875,9 +805,7 @@ BOOST_AUTO_TEST_CASE(noto_restore_to_file_is_atomic_and_reopens_connection) { auto io = boost::asio::io_context{}; auto source_path = temp_database_path("restore_source"); auto target_path = temp_database_path("restore_target"); - BOOST_REQUIRE(create_sqlite_database(source_path, "CREATE TABLE items(value TEXT NOT NULL); INSERT INTO items(value) VALUES('new')")); - auto connection = noto::connection{ io.get_executor(), noto::file_connection_config(target_path), @@ -890,13 +818,11 @@ BOOST_AUTO_TEST_CASE(noto_restore_to_file_is_atomic_and_reopens_connection) { BOOST_TEST(static_cast(run(io, connection.async_set_busy_timeout(std::chrono::milliseconds{37})))); write_file(target_path + "-journal", "stale rollback journal"); BOOST_TEST(static_cast(std::filesystem::exists(target_path + "-journal"))); - BOOST_TEST(static_cast(run(io, connection.async_restore_from_file(source_path)))); BOOST_TEST(static_cast(connection.state()) == static_cast(noto::connection_state::CONNECTED)); auto timeout = run(io, connection.async_busy_timeout()); BOOST_REQUIRE(timeout); BOOST_TEST(*timeout == std::chrono::milliseconds{37}); - auto rows = run(io, connection.async_execute("SELECT value FROM items")); BOOST_REQUIRE(rows); BOOST_REQUIRE(rows->rows.size() == 1U); @@ -905,12 +831,10 @@ BOOST_AUTO_TEST_CASE(noto_restore_to_file_is_atomic_and_reopens_connection) { BOOST_TEST(!static_cast(std::filesystem::exists(target_path + "-wal"))); BOOST_TEST(!static_cast(std::filesystem::exists(target_path + "-shm"))); BOOST_TEST(!static_cast(std::filesystem::exists(target_path + "-journal"))); - - auto* restored = static_cast(nullptr); + auto *restored = static_cast(nullptr); BOOST_REQUIRE(sqlite3_open_v2(target_path.c_str(), &restored, SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) == SQLITE_OK); BOOST_TEST(sqlite_scalar_int64(restored, "SELECT COUNT(*) FROM items WHERE value = 'new'") == 1); sqlite3_close(restored); - remove_database_files(source_path); remove_database_files(target_path); } @@ -919,10 +843,8 @@ BOOST_AUTO_TEST_CASE(noto_failed_restore_leaves_existing_database_intact) { auto io = boost::asio::io_context{}; auto source_path = temp_database_path("restore_failure_source"); auto target_path = temp_database_path("restore_failure_target"); - BOOST_REQUIRE(create_sqlite_database(source_path, "CREATE TABLE items(value TEXT NOT NULL); INSERT INTO items(value) VALUES('new')")); BOOST_REQUIRE(create_sqlite_database(target_path, "CREATE TABLE items(value TEXT NOT NULL); INSERT INTO items(value) VALUES('old')")); - auto connection = noto::connection{ io.get_executor(), noto::file_connection_config(target_path), @@ -932,13 +854,11 @@ BOOST_AUTO_TEST_CASE(noto_failed_restore_leaves_existing_database_intact) { options.source_name = "missing"; auto restored = run(io, connection.async_restore_from_file(source_path, options)); BOOST_TEST(!static_cast(restored)); - auto rows = run(io, connection.async_execute("SELECT value FROM items")); BOOST_REQUIRE(rows); BOOST_REQUIRE(rows->rows.size() == 1U); BOOST_TEST(text_value(rows->rows.at(0).at(0)) == "old"); BOOST_TEST(static_cast(run(io, connection.async_close()))); - remove_database_files(source_path); remove_database_files(target_path); } @@ -947,10 +867,8 @@ BOOST_AUTO_TEST_CASE(noto_restore_rejects_unsupported_targets_and_active_state) auto io = boost::asio::io_context{}; auto source_path = temp_database_path("restore_reject_source"); auto target_path = temp_database_path("restore_reject_target"); - BOOST_REQUIRE(create_sqlite_database(source_path, "CREATE TABLE items(value TEXT NOT NULL); INSERT INTO items(value) VALUES('new')")); BOOST_REQUIRE(create_sqlite_database(target_path, "CREATE TABLE items(value TEXT NOT NULL); INSERT INTO items(value) VALUES('old')")); - auto memory = noto::connection{ io.get_executor(), noto::memory_connection_config("restore_reject_memory"), @@ -959,31 +877,26 @@ BOOST_AUTO_TEST_CASE(noto_restore_rejects_unsupported_targets_and_active_state) auto memory_restore = run(io, memory.async_restore_from_file(source_path)); BOOST_TEST(static_cast(memory_restore.error().code()) == static_cast(noto::detail::error_invalid_argument)); BOOST_TEST(static_cast(run(io, memory.async_close()))); - auto connection = noto::connection{ io.get_executor(), noto::file_connection_config(target_path), }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); - auto options = noto::backup_options{}; options.destination_name = "attached"; auto non_main = run(io, connection.async_restore_from_file(source_path, options)); BOOST_TEST(static_cast(non_main.error().code()) == static_cast(noto::detail::error_invalid_argument)); - auto transaction = run(io, connection.async_begin_transaction()); BOOST_REQUIRE(transaction); auto active_transaction = run(io, connection.async_restore_from_file(source_path)); BOOST_TEST(static_cast(active_transaction.error().code()) == static_cast(noto::detail::error_unavailable)); BOOST_TEST(static_cast(run(io, transaction->async_rollback()))); - auto statement = run(io, connection.async_prepare("SELECT value FROM items")); BOOST_REQUIRE(statement); auto live_statement = run(io, connection.async_restore_from_file(source_path)); BOOST_TEST(static_cast(live_statement.error().code()) == static_cast(noto::detail::error_unavailable)); BOOST_TEST(static_cast(run(io, statement->async_finalize()))); BOOST_TEST(static_cast(run(io, connection.async_close()))); - remove_database_files(source_path); remove_database_files(target_path); } @@ -992,7 +905,6 @@ BOOST_AUTO_TEST_CASE(noto_backup_lock_timeout_and_statement_finalize_retry_work) auto io = boost::asio::io_context{}; auto source_path = temp_database_path("locked_source"); auto backup_path = temp_database_path("locked_backup"); - auto connection = noto::connection{ io.get_executor(), noto::file_connection_config(source_path), @@ -1002,25 +914,20 @@ BOOST_AUTO_TEST_CASE(noto_backup_lock_timeout_and_statement_finalize_retry_work) BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('one')"))); auto statement = run(io, connection.async_prepare("SELECT value FROM items")); BOOST_REQUIRE(statement); - - auto* blocker = static_cast(nullptr); + auto *blocker = static_cast(nullptr); BOOST_REQUIRE(sqlite3_open_v2(source_path.c_str(), &blocker, SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, nullptr) == SQLITE_OK); BOOST_REQUIRE(sqlite_exec_ok(blocker, "BEGIN EXCLUSIVE")); - auto options = noto::backup_options{}; options.pages_per_step = 1; options.retry_delay = std::chrono::milliseconds{1}; options.retry_timeout = std::chrono::milliseconds{25}; - auto backup_future = boost::asio::co_spawn(io, connection.async_backup_to_file(backup_path, options), boost::asio::use_future); auto finalize_future = boost::asio::co_spawn(io, statement->async_finalize(), boost::asio::use_future); io.run(); io.restart(); - auto backup = backup_future.get(); auto finalized_while_busy = finalize_future.get(); BOOST_TEST(static_cast(backup.error().code()) == static_cast(noto::detail::error_timeout)); - BOOST_TEST(static_cast(sqlite_exec_ok(blocker, "ROLLBACK"))); sqlite3_close(blocker); if (!finalized_while_busy) { @@ -1028,7 +935,6 @@ BOOST_AUTO_TEST_CASE(noto_backup_lock_timeout_and_statement_finalize_retry_work) BOOST_TEST(static_cast(run(io, statement->async_finalize()))); } BOOST_TEST(static_cast(run(io, connection.async_close()))); - std::filesystem::remove(source_path); std::filesystem::remove(backup_path); } @@ -1044,7 +950,6 @@ BOOST_AUTO_TEST_CASE(noto_export_preserves_nul_text_and_defers_triggers_until_af BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO audit(count) VALUES(0)"))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(value TEXT NOT NULL)"))); BOOST_REQUIRE(run(io, connection.async_execute("CREATE TRIGGER items_ai AFTER INSERT ON items BEGIN UPDATE audit SET count = count + 1; END"))); - auto insert = run(io, connection.async_prepare("INSERT INTO items(value) VALUES(?1)")); BOOST_REQUIRE(insert); auto embedded = std::string{ @@ -1052,23 +957,20 @@ BOOST_AUTO_TEST_CASE(noto_export_preserves_nul_text_and_defers_triggers_until_af 3, }; BOOST_REQUIRE(run(io, insert->async_execute({ - noto::sqlite_value{embedded}, - }))); + noto::sqlite_value{embedded}, + }))); BOOST_TEST(static_cast(run(io, insert->async_finalize()))); - auto exported = run(io, connection.async_export_sql()); BOOST_REQUIRE(exported); BOOST_TEST(exported->find("CAST(X'610062' AS TEXT)") != std::string::npos); BOOST_TEST(exported->find("INSERT INTO \"items\"") < exported->find("CREATE TRIGGER")); - - auto* restored = static_cast(nullptr); + auto *restored = static_cast(nullptr); BOOST_REQUIRE(sqlite3_open(":memory:", &restored) == SQLITE_OK); BOOST_REQUIRE(sqlite_exec_ok(restored, *exported)); - - auto* statement = static_cast(nullptr); + auto *statement = static_cast(nullptr); BOOST_REQUIRE(sqlite3_prepare_v2(restored, "SELECT value FROM items", -1, &statement, nullptr) == SQLITE_OK); BOOST_REQUIRE(sqlite3_step(statement) == SQLITE_ROW); - auto const* text = reinterpret_cast(sqlite3_column_text(statement, 0)); + auto const *text = reinterpret_cast(sqlite3_column_text(statement, 0)); auto bytes = sqlite3_column_bytes(statement, 0); auto restored_text = std::string{ text, @@ -1076,7 +978,6 @@ BOOST_AUTO_TEST_CASE(noto_export_preserves_nul_text_and_defers_triggers_until_af }; sqlite3_finalize(statement); BOOST_TEST(restored_text == embedded); - statement = nullptr; BOOST_REQUIRE(sqlite3_prepare_v2(restored, "SELECT count FROM audit", -1, &statement, nullptr) == SQLITE_OK); BOOST_REQUIRE(sqlite3_step(statement) == SQLITE_ROW); @@ -1095,14 +996,12 @@ BOOST_AUTO_TEST_CASE(noto_export_preserves_autoincrement_sequence_and_generated_ BOOST_REQUIRE(run(io, connection.async_execute("CREATE TABLE items(id INTEGER PRIMARY KEY AUTOINCREMENT, value TEXT NOT NULL, value_size INTEGER GENERATED ALWAYS AS (length(value)) STORED)"))); BOOST_REQUIRE(run(io, connection.async_execute("INSERT INTO items(value) VALUES('one'), ('two')"))); BOOST_REQUIRE(run(io, connection.async_execute("DELETE FROM items WHERE value = 'two'"))); - auto exported = run(io, connection.async_export_sql()); BOOST_REQUIRE(exported); BOOST_TEST(exported->find("sqlite_sequence") != std::string::npos); BOOST_TEST(exported->find("value_size") != std::string::npos); BOOST_TEST(exported->find("INSERT INTO \"items\"(\"id\", \"value\")") != std::string::npos); - - auto* restored = static_cast(nullptr); + auto *restored = static_cast(nullptr); BOOST_REQUIRE(sqlite3_open(":memory:", &restored) == SQLITE_OK); BOOST_REQUIRE(sqlite_exec_ok(restored, *exported)); BOOST_REQUIRE(sqlite_exec_ok(restored, "INSERT INTO items(value) VALUES('three')")); @@ -1122,7 +1021,6 @@ BOOST_AUTO_TEST_CASE(noto_export_rejects_virtual_tables) { if (!created) { return; } - auto exported = run(io, connection.async_export_sql()); BOOST_REQUIRE(!exported); BOOST_TEST(static_cast(exported.error().code()) == static_cast(noto::detail::error_invalid_argument)); @@ -1135,7 +1033,6 @@ BOOST_AUTO_TEST_CASE(noto_file_helpers_reject_null_byte_paths) { noto::memory_connection_config("path_validation"), }; BOOST_TEST(static_cast(run(io, connection.async_connect()))); - auto bad_path = std::string{ "bad\0path", 8, @@ -1144,6 +1041,5 @@ BOOST_AUTO_TEST_CASE(noto_file_helpers_reject_null_byte_paths) { BOOST_TEST(static_cast(backup.error().code()) == static_cast(noto::detail::error_invalid_argument)); auto exported = run(io, connection.async_export_sql_to_file(bad_path)); BOOST_TEST(static_cast(exported.error().code()) == static_cast(noto::detail::error_invalid_argument)); - BOOST_TEST(static_cast(run(io, connection.async_close()))); }