From f63d2b2fe28d7acfe697240b872d1db47e1e9e2a Mon Sep 17 00:00:00 2001 From: Folling Date: Wed, 27 Dec 2023 12:52:27 +0100 Subject: [PATCH] simplify blueprint implementation with helpers Signed-off-by: Folling --- src/objects/blueprint.cpp | 240 ++++++++------------------- src/objects/util.hpp | 152 +++++++++++++---- src/persistence/function_context.cpp | 4 +- src/persistence/function_context.hpp | 14 +- src/persistence/project.cpp | 2 +- src/persistence/project.hpp | 6 +- 6 files changed, 200 insertions(+), 218 deletions(-) diff --git a/src/objects/blueprint.cpp b/src/objects/blueprint.cpp index fc49c71..af5fc08 100644 --- a/src/objects/blueprint.cpp +++ b/src/objects/blueprint.cpp @@ -1,16 +1,15 @@ #include "ikarus/objects/blueprint.h" #include "objects/blueprint.hpp" - -#include +#include "util.hpp" #include #include #include #include -#include #include +#include #include #include @@ -18,199 +17,92 @@ IkarusBlueprint::IkarusBlueprint(IkarusProject * project, IkarusId id): IkarusObject{project, id} {} IkarusBlueprint * ikarus_blueprint_create(struct IkarusProject * project, char const * name) { - LOG_INFO("creating new blueprint"); - - LOG_DEBUG("project={}; name={}", project->get_path().c_str(), name); - - auto * ctx = project->get_function_context(); - - if (cppbase::is_empty_or_blank(name)) { - ctx->set_error("name is empty or blank", true, IkarusErrorInfo_Source_Client, IkarusErrorInfo_Type_Client_Input); - return nullptr; - } - - VTRYRV(auto const id, nullptr, insert_object(project, ctx, IkarusObjectType_Blueprint, name, [](auto * db, IkarusId id) { - return db->execute("INSERT INTO `blueprints`(`id`) VALUES(?)", id); - })); - - return project->get_blueprint(id); + return ikarus::util::insert_object( + project, + IkarusObjectType_Blueprint, + name, + [](auto * db, IkarusId id) { return db->execute("INSERT INTO `blueprints`(`id`) VALUES(?)", id); }, + [project](IkarusId id) { return project->get_blueprint(id); } + ).unwrap_value_or(nullptr); } void ikarus_blueprint_delete(IkarusBlueprint * blueprint) { - LOG_INFO("deleting blueprint"); - - LOG_DEBUG("project={}; blueprint={}", blueprint->project->get_path().c_str(), blueprint->id); - - auto * ctx = blueprint->project->get_function_context(); - - delete_object(blueprint->project, ctx, blueprint); + ikarus::util::delete_object(blueprint->project, blueprint); } void ikarus_blueprint_get_properties( IkarusBlueprint const * blueprint, struct IkarusProperty ** properties_out, size_t properties_out_size ) { - LOG_VERBOSE("fetching blueprint properties"); + ikarus::util::fetch_multiple_buffered( + blueprint, + ikarus::util::MultipleBufferQueryData{ + .table_name = "properties", + .select_field_name = "id", + .where_field_name = "blueprint", + .relation_desc = "properties" + }, + properties_out, + properties_out_size, + [&](IkarusProject * project, IkarusFunctionContext * ctx, IkarusId id + ) -> cppbase::Result { + VTRY( + auto const type, IkarusProperty::get_property_type(blueprint->project, id).on_error([ctx, id](auto const& err) { + ctx->set_error( + fmt::format("failed to fetch property {}'s type: {}", id, err), + true, + IkarusErrorInfo_Source_SubSystem, + IkarusErrorInfo_Type_SubSystem_Database + ); + }) + ); - LOG_DEBUG( - "project={}; blueprint={}; properties_out_size={}", - blueprint->project->get_path().c_str(), - blueprint->id, - properties_out_size + return cppbase::ok(project->get_property(id, type)); + } ); - - auto * ctx = blueprint->project->get_function_context(); - - IkarusId ids[properties_out_size]; - - TRYRV( - , - blueprint->project - ->get_db() - ->query_many_buffered( - "SELECT `id` FROM `properties` WHERE `source` = ?", - static_cast(ids), - properties_out_size, - blueprint->id - ) - .on_error([ctx](auto const& err) { - ctx->set_error( - fmt::format("failed to fetch blueprint property ids: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); - - LOG_DEBUG("blueprint properties: [{}]", fmt::join(ids, ids + properties_out_size, ", ")); - - for (size_t i = 0; i < properties_out_size; ++i) { - auto const id = ids[i]; - - VTRYRV( - auto const type, - , - IkarusProperty::get_property_type(blueprint->project, id).on_error([ctx, id](auto const& err) { - ctx->set_error( - fmt::format("failed to fetch property {}'s type: {}", id, err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); - - /// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - properties_out[i] = blueprint->project->get_property(id, type); - } - - LOG_VERBOSE("successfully fetched blueprint properties"); } size_t ikarus_blueprint_get_property_count(IkarusBlueprint const * blueprint) { - LOG_VERBOSE("fetching blueprint property count"); - - auto * ctx = blueprint->project->get_function_context(); - - LOG_DEBUG("blueprint={}", blueprint->id); - - VTRYRV( - auto count, - 0, - blueprint->project - ->get_db() - ->query_one("SELECT COUNT(*) FROM `blueprint_properties` WHERE `blueprint` = ?;", blueprint->id) - .on_error([ctx](auto const& err) { - ctx->set_error( - fmt::format("failed to fetch blueprint property count: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); - - LOG_DEBUG("blueprint property count: {}", count); - - LOG_VERBOSE("successfully fetched blueprint property count"); - - return static_cast(count); + return ikarus::util::fetch_count( + blueprint, + ikarus::util::CountQueryData{ + .table_name = "blueprint_properties", + .select_field_name = "property", + .where_field_name = "blueprint", + .relation_desc = "properties" + } + ) + .unwrap_value_or(0); } void ikarus_blueprint_get_linked_entities( IkarusBlueprint const * blueprint, struct IkarusEntity ** entities_out, size_t entities_out_size ) { - LOG_VERBOSE("fetching blueprint linked entities"); - - LOG_DEBUG( - "project={}; blueprint={}; entities_out_size={}", - blueprint->project->get_path().c_str(), - blueprint->id, - entities_out_size + ikarus::util::fetch_multiple_buffered( + blueprint, + ikarus::util::MultipleBufferQueryData{ + .table_name = "entity_blueprint_links", + .select_field_name = "entity", + .where_field_name = "blueprint", + .relation_desc = "linked entities" + }, + entities_out, + entities_out_size, + [&](IkarusProject * project, [[maybe_unused]] IkarusFunctionContext * ctx, IkarusId id + ) -> cppbase::Result { return cppbase::ok(project->get_entity(id)); } ); - - auto * ctx = blueprint->project->get_function_context(); - - IkarusId ids[entities_out_size]; - - TRYRV( - , - blueprint->project - ->get_db() - ->query_many_buffered( - "SELECT `entity` FROM `entity_blueprint_links` WHERE `blueprint` = ?", - static_cast(ids), - entities_out_size, - blueprint->id - ) - .on_error([ctx](auto const& err) { - ctx->set_error( - fmt::format("failed to fetch blueprint linked entity ids: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); - - LOG_DEBUG("blueprint linked entities: [{}]", fmt::join(ids, ids + entities_out_size, ", ")); - - for (size_t i = 0; i < entities_out_size; ++i) { - /// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) - entities_out[i] = blueprint->project->get_entity(ids[i]); - } - - LOG_VERBOSE("successfully fetched blueprint linked entities"); } size_t ikarus_blueprint_get_linked_entity_count(IkarusBlueprint const * blueprint) { - LOG_VERBOSE("fetching blueprint linked entity count"); - - LOG_DEBUG("project={}; blueprint={}", blueprint->project->get_path().c_str(), blueprint->id); - - auto * ctx = blueprint->project->get_function_context(); - - VTRYRV( - auto count, - 0, - blueprint->project - ->get_db() - ->query_one("SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?;", blueprint->id) - .on_error([ctx](auto const& err) { - ctx->set_error( - fmt::format("failed to fetch blueprint linked entity count: {}", err), - true, - IkarusErrorInfo_Source_SubSystem, - IkarusErrorInfo_Type_SubSystem_Database - ); - }) - ); - - LOG_DEBUG("blueprint linked entity count: {}", count); - - LOG_VERBOSE("successfully fetched blueprint linked entity count: {}", count); - - return static_cast(count); + return ikarus::util::fetch_count( + blueprint, + ikarus::util::CountQueryData{ + .table_name = "entity_blueprint_links", + .select_field_name = "entity", + .where_field_name = "blueprint", + .relation_desc = "linked entities" + } + ) + .unwrap_value_or(0); } IkarusObject * ikarus_blueprint_to_object(IkarusBlueprint * blueprint) { diff --git a/src/objects/util.hpp b/src/objects/util.hpp index 963701b..f192b7a 100644 --- a/src/objects/util.hpp +++ b/src/objects/util.hpp @@ -1,20 +1,43 @@ #pragma once +#include "util.hpp" + #include #include +#include #include #include #include -namespace util { -template F> -[[nodiscard]] cppbase::Result insert_object( - IkarusProject * project, FunctionContext * ctx, IkarusObjectType type, std::string_view name, F insert_function +namespace ikarus::util { + +struct EmptyNameError {}; + +COMPOUND_ERROR(InsertObjectError, EmptyNameError, sqlitecpp::TransactionError); + +template +[[nodiscard]] cppbase::Result, InsertObjectError> insert_object( + IkarusProject * project, + IkarusObjectType type, + std::string_view name, + InsertFunction insert_function, + ObjectFactory object_factory ) { - char const * object_type_str = ikarus_object_type_to_string(type); + auto const * object_type_str = ikarus_object_type_to_string(type); + + LOG_INFO("creating new {}", object_type_str); + + LOG_DEBUG("project={}; name={}", project->get_path().c_str(), name); + + auto * ctx = project->get_function_context(); + + if (cppbase::is_empty_or_blank(name)) { + ctx->set_error("name is empty or blank", true, IkarusErrorInfo_Source_Client, IkarusErrorInfo_Type_Client_Input); + return cppbase::err(EmptyNameError{}); + } VTRY( auto const id, @@ -42,17 +65,23 @@ template F> }) ); - LOG_VERBOSE("successfully created blueprint"); + LOG_VERBOSE("successfully created {}", object_type_str); - return cppbase::ok(id); + return cppbase::ok(object_factory(id)); } -template -void delete_object(IkarusProject * project, FunctionContext * ctx, T * object) { - auto id = object->id; - auto object_type_str = ikarus_object_type_to_string(ikarus_id_get_object_type(id)); +template + requires std::derived_from +void delete_object(IkarusProject * project, Object * object) { + auto object_type_str = ikarus_object_type_to_string(ikarus_id_get_object_type(object->id)); - TRYRV(, project->get_db()->execute("DELETE FROM `objects` WHERE `id` = ?", id).on_error([&](auto const& err) { + LOG_INFO("deleting {}", object_type_str); + + LOG_DEBUG("project={}; {}={}", object_type_str, object->project->get_path().c_str(), object->id); + + auto * ctx = object->project->get_function_context(); + + TRYRV(, project->get_db()->execute("DELETE FROM `objects` WHERE `id` = ?", object->id).on_error([&](auto const& err) { ctx->set_error( fmt::format("failed to delete {} from objects table: {}", object_type_str, err), true, @@ -73,8 +102,9 @@ struct SingleQueryData { std::string_view select_field_name; }; -template -cppbase::Result fetch_single_field(IkarusObject * object, SingleQueryData const& query_data) { +template + requires std::derived_from +cppbase::Result fetch_single_field(Object const * object, SingleQueryData const& query_data) { auto object_type_str = ikarus_object_type_to_string(ikarus_id_get_object_type(object->id)); LOG_VERBOSE("fetching property default value"); @@ -86,7 +116,7 @@ cppbase::Result fetch_single_field(IkarusObject VTRY( T value, object->project->get_db() - ->query_one( + ->template query_one( fmt::format("SELECT `{}` FROM `{}` WHERE `id` = ?", query_data.select_field_name, query_data.table_name), object->id ) @@ -107,45 +137,105 @@ struct MultipleBufferQueryData { std::string_view table_name; std::string_view select_field_name; std::string_view where_field_name; + std::string_view relation_desc; }; -template -cppbase::Result fetch_multiple_buffered( - IkarusObject * object, - MultipleBufferQueryData const& query_data, - std::string_view relation_desc, - T * buffer, - size_t buffer_size -) { +template + requires std::derived_from +void fetch_multiple_buffered( + Object const * object, MultipleBufferQueryData const& query_data, Mapped * mapped_buffer, size_t buffer_size, F transformer +) + requires cppbase:: + is_result_with_value_type_v> +{ + auto * ctx = object->project->get_function_context(); + auto object_type_str = ikarus_object_type_to_string(ikarus_id_get_object_type(object->id)); - LOG_VERBOSE("fetching {} {}", object_type_str, relation_desc); + LOG_VERBOSE("fetching {} {}", object_type_str, query_data.relation_desc); LOG_VERBOSE("project={};{}={}", object->project->get_path().c_str(), object_type_str, object->id); - auto * ctx = object->project->get_function_context(); + Selected select_buffer[buffer_size]; - TRY(object->project->get_db() - ->query_many_buffered( + TRYRV( + , + object->project->get_db() + ->template query_many_buffered( fmt::format( "SELECT `{}` FROM `{}` WHERE `{}` = ?", query_data.select_field_name, query_data.table_name, query_data.where_field_name ), - buffer, + select_buffer, buffer_size, object->id ) .on_error([&](auto const& err) { ctx->set_error( - fmt::format("failed to fetch {} {} from database: {}", object_type_str, relation_desc, err), + fmt::format("failed to fetch {} {} from database: {}", object_type_str, query_data.relation_desc, err), true, IkarusErrorInfo_Source_SubSystem, IkarusErrorInfo_Type_SubSystem_Database ); - })); + }) + ); - return cppbase::ok(); + LOG_DEBUG( + "{} {}: [{}]", object_type_str, query_data.relation_desc, fmt::join(select_buffer, select_buffer + buffer_size, ", ") + ); + + for (size_t i = 0; i < buffer_size; ++i) { + VTRYRV(mapped_buffer[i], , transformer(object->project, ctx, select_buffer[i])); + } } + +struct CountQueryData { + std::string_view table_name; + std::string_view select_field_name; + std::string_view where_field_name; + std::string_view relation_desc; +}; + +template + requires std::derived_from +cppbase::Result fetch_count(Object const * object, CountQueryData const& query_data) { + auto * object_type_str = ikarus_object_type_to_string(ikarus_id_get_object_type(object->id)); + + LOG_VERBOSE("fetching {} {} count", object_type_str, query_data.relation_desc); + + auto * ctx = object->project->get_function_context(); + + LOG_DEBUG("{}={}", object_type_str, object->id); + + VTRY( + auto count, + object->project->get_db() + ->template query_one( + fmt::format( + "SELECT COUNT(`{}`) FROM `{}` WHERE `{}` = ?;", + query_data.select_field_name, + query_data.table_name, + query_data.where_field_name + ), + object->id + ) + .on_error([&](auto const& err) { + ctx->set_error( + fmt::format("failed to fetch {} {} count: {}", object_type_str, query_data.relation_desc, err), + true, + IkarusErrorInfo_Source_SubSystem, + IkarusErrorInfo_Type_SubSystem_Database + ); + }) + ); + + LOG_DEBUG("{} {} count: {}", object_type_str, query_data.relation_desc, count); + + LOG_VERBOSE("successfully fetched {} {} count", object_type_str, query_data.relation_desc); + + return cppbase::ok(static_cast(count)); +} + } diff --git a/src/persistence/function_context.cpp b/src/persistence/function_context.cpp index 96edddf..f056308 100644 --- a/src/persistence/function_context.cpp +++ b/src/persistence/function_context.cpp @@ -1,9 +1,9 @@ #include "function_context.hpp" -FunctionContext::FunctionContext(IkarusProject * project): +IkarusFunctionContext::IkarusFunctionContext(IkarusProject * project): _project{project} {} -FunctionContext::~FunctionContext() { +IkarusFunctionContext::~IkarusFunctionContext() { if (_project->_function_contexts.size() == 1) { if (_project->error_message_buffer.empty()) { _project->error_message_buffer.push_back('\0'); diff --git a/src/persistence/function_context.hpp b/src/persistence/function_context.hpp index 3dc64b3..769006b 100644 --- a/src/persistence/function_context.hpp +++ b/src/persistence/function_context.hpp @@ -12,17 +12,17 @@ #include -struct FunctionContext { +struct IkarusFunctionContext { public: - explicit FunctionContext(struct IkarusProject * project); + explicit IkarusFunctionContext(struct IkarusProject * project); - FunctionContext(FunctionContext const&) noexcept = default; - FunctionContext(FunctionContext&&) noexcept = default; + IkarusFunctionContext(IkarusFunctionContext const&) noexcept = default; + IkarusFunctionContext(IkarusFunctionContext&&) noexcept = default; - auto operator=(FunctionContext const&) noexcept -> FunctionContext& = default; - auto operator=(FunctionContext&&) noexcept -> FunctionContext& = default; + auto operator=(IkarusFunctionContext const&) noexcept -> IkarusFunctionContext& = default; + auto operator=(IkarusFunctionContext&&) noexcept -> IkarusFunctionContext& = default; - ~FunctionContext(); + ~IkarusFunctionContext(); public: template diff --git a/src/persistence/project.cpp b/src/persistence/project.cpp index 163bc93..bf1e51a 100644 --- a/src/persistence/project.cpp +++ b/src/persistence/project.cpp @@ -26,7 +26,7 @@ auto IkarusProject::get_db() const -> sqlitecpp::Connection const * { return _db.get(); } -auto IkarusProject::get_function_context() -> FunctionContext * { +auto IkarusProject::get_function_context() -> IkarusFunctionContext * { return &_function_contexts.emplace_back(this); } diff --git a/src/persistence/project.hpp b/src/persistence/project.hpp index 2af1fff..7d9d5b0 100644 --- a/src/persistence/project.hpp +++ b/src/persistence/project.hpp @@ -24,7 +24,7 @@ public: [[nodiscard]] auto get_db() const -> sqlitecpp::Connection const *; public: - [[nodiscard]] auto get_function_context() -> struct FunctionContext *; + [[nodiscard]] auto get_function_context() -> struct IkarusFunctionContext *; public: [[nodiscard]] auto get_blueprint(IkarusId id) -> struct IkarusBlueprint *; @@ -54,7 +54,7 @@ private: } private: - friend struct FunctionContext; + friend struct IkarusFunctionContext; std::string _name; std::filesystem::path _path; @@ -67,5 +67,5 @@ private: std::unordered_map> _properties; std::unordered_map> _entities; - std::vector _function_contexts; + std::vector _function_contexts; };