#include "entity.hpp" #include #include #include #include #include IkarusEntity::IkarusEntity(struct IkarusProject * project, int64_t id): project{project}, id{id} {} IkarusEntity * ikarus_entity_create( struct IkarusProject * project, char const * name, IkarusEntityCreateFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(project, nullptr); IKARUS_FAIL_IF_NAME_INVALID(name, nullptr); IKARUS_TRYRV_OR_FAIL( nullptr, "failed to create entity: {}", IkarusErrorInfo_Database_QueryFailed, project->db->execute("INSERT INTO `entities`(`name`) VALUES(?)", name) ); auto const id = project->db->last_insert_rowid(); return new IkarusEntity{project, id}; } void ikarus_entity_delete( IkarusEntity * entity, IkarusEntityDeleteFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to delete entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db ->execute("DELETE FROM `entities` WHERE `id` = ?", entity->id) ); delete entity; } IkarusEntity * ikarus_entity_copy( IkarusEntity * entity, IkarusEntityCopyFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_VTRYRV_OR_FAIL( auto id, nullptr, "failed to copy entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->transact( [entity](auto * db) -> cppbase::Result { TRY(entity->project->db->execute( "INSERT INTO `entities`(`name`) " "SELECT `name` FROM `entities` WHERE `id` = ?", entity->id )); TRY(entity->project->db->execute( "INSERT INTO `entity_values`(`entity`, `name`, `value`) " "SELECT ?1, `name`, `value` FROM `entity_values` WHERE " "`entity` = ?1", entity->id )) TRY(entity->project->db->execute( "INSERT INTO `entity_property_values`(`entity`, `property`, `value`) " "SELECT ?1, `property`, `value` FROM `entity_property_values` " "WHERE `entity` = ?1", entity->id )) TRY(entity->project->db->execute( "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) " "SELECT ?1, `property`, `value` FROM `entity_property_values` " "WHERE `entity` = ?1", entity->id )) return cppbase::ok(entity->project->db->last_insert_rowid()); } ) ); return new IkarusEntity{entity->project, id}; } IkarusProject * ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); return entity->project; } char const * ikarus_entity_get_name(IkarusEntity * entity, IkarusErrorData * error_out) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_VTRYRV_OR_FAIL( auto name, nullptr, "failed to get name for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT `name` FROM `entities` WHERE `id` = ?", entity->id ) ); return name; } void ikarus_entity_set_name( IkarusEntity * entity, char const * name, IkarusEntitySetNameFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NAME_INVALID(name, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to set name for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "UPDATE `entities` SET `name` = ? WHERE `id` = ?", name, entity->id ) ); } struct IkarusBlueprint ** ikarus_entity_get_linked_blueprints( IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_FAIL_IF_NULL(size_out, nullptr); auto count = ikarus_entity_get_linked_blueprints_count(entity, error_out); IKARUS_FAIL_IF_ERROR(nullptr); std::int64_t ids[count]; IKARUS_TRYRV_OR_FAIL( nullptr, "failed to get linked blueprints for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many_buffered( "SELECT `blueprint` FROM `entity_blueprint_links` WHERE `entity` = ?", ids, count, entity->id ) ); auto blueprints = new IkarusBlueprint *[count]; std::transform(ids, ids + count, blueprints, [entity](auto id) { return new IkarusBlueprint{entity->project, id}; }); if (size_out) { *size_out = count; } return blueprints; } size_t ikarus_entity_get_linked_blueprints_count( IkarusEntity * entity, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, 0); IKARUS_FAIL_IF_NOT_EXIST(entity, 0); IKARUS_VTRYRV_OR_FAIL( auto count, 0, "failed to get linked blueprints count for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `entity` = ?", entity->id ) ); return count; } void ikarus_entity_link_blueprint( IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusEntityLinkBlueprintFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(blueprint, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(blueprint, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to link blueprint to entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) VALUES(?, ?)", entity->id, blueprint->id ) ); } void ikarus_entity_unlink_blueprint( IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusEntityUnlinkBlueprintFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(blueprint, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(blueprint, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to unlink blueprint from entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "DELETE FROM `entity_blueprint_links` WHERE `entity` = ? AND `blueprint` = ?", entity->id, blueprint->id ) ); } IkarusEntityValue * ikarus_entity_get_values( IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_VTRYRV_OR_FAIL( auto values_plain, nullptr, "failed to get values for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many( "SELECT `name`, `value` FROM `entity_values` WHERE `entity` = ?", entity->id ) ); IkarusEntityValue * values = new IkarusEntityValue[values_plain.size()]; std::transform( std::cbegin(values_plain), std::cend(values_plain), values, [](auto const & tuple) { return IkarusEntityValue{ tuple.template get<0>(), tuple.template get<1>() }; } ); if (size_out) { *size_out = values_plain.size(); } return values; } char const * ikarus_entity_get_value( IkarusEntity * entity, char const * name, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_FAIL_IF_NULL(name, nullptr); IKARUS_VTRYRV_OR_FAIL( auto value, nullptr, "failed to get value for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT `value` FROM `entity_values` WHERE `entity` = ? AND `name` = ?", entity->id, name ) ); return value; } void ikarus_entity_set_value( IkarusEntity * entity, char const * name, char const * value, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(name, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); // parsing from & to here to ensure values are valid JSON & formatted // uniformly IKARUS_VTRYRV_OR_FAIL( auto value_parsed, IKARUS_VOID_RETURN, "cannot parse value as JSON: {}", IkarusErrorInfo_Client_InvalidInput, IkarusValue::from_json(value) ); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to set value for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "INSERT INTO `entity_values`(`entity`, `name`, `value`) VALUES(?, ?, ?) " "ON CONFLICT(`entity`, `name`) DO UPDATE SET `value` = excluded.`value`", entity->id, name, IkarusValue::to_json(value_parsed).dump() ) ); } void ikarus_entity_delete_value( IkarusEntity * entity, char const * name, IkarusEntityDeleteValueFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(name, IKARUS_VOID_RETURN); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to delete value for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "DELETE FROM `entity_values` WHERE `entity` = ? AND `name` = ?", entity->id, name ) ); } IkarusEntityPropertyValue * ikarus_entity_get_property_values( IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_VTRYRV_OR_FAIL( auto values_plain, nullptr, "failed to get property values for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many( "SELECT `property`, `value` FROM `entity_property_values` WHERE `entity` = ?", entity->id ) ); IkarusEntityPropertyValue * values = new IkarusEntityPropertyValue[values_plain.size()]; std::transform( std::cbegin(values_plain), std::cend(values_plain), values, [entity](auto const & tuple) { return IkarusEntityPropertyValue{ new IkarusProperty{entity->project, tuple.template get<0>()}, tuple.template get<1>() }; } ); if (size_out) { *size_out = values_plain.size(); } return values; } char const * ikarus_entity_get_property_value( IkarusEntity * entity, struct IkarusProperty * property, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_FAIL_IF_NULL(property, nullptr); IKARUS_FAIL_IF_NOT_EXIST(property, nullptr); IKARUS_VTRYRV_OR_FAIL( auto value, nullptr, "failed to get property value for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT `value` FROM `entity_property_values` WHERE `entity` = ? AND `property` = ?", entity->id, property->id ) ); return value; } void ikarus_entity_set_property_value( IkarusEntity * entity, struct IkarusProperty * property, char const * value, IkarusEntitySetPropertyValueFlags flags, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(property, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NOT_EXIST(property, IKARUS_VOID_RETURN); IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); // parsing from & to here to ensure values are valid JSON & formatted // uniformly IKARUS_VTRYRV_OR_FAIL( auto value_parsed, IKARUS_VOID_RETURN, "cannot parse value as JSON: {}", IkarusErrorInfo_Client_InvalidInput, IkarusValue::from_json(value) ); IKARUS_TRYRV_OR_FAIL( IKARUS_VOID_RETURN, "failed to set property value for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "INSERT INTO `entity_property_values`(`entity`, `property`, `value`) VALUES(?, ?, ?) " "ON CONFLICT(`entity`, `property`) DO UPDATE SET `value` = excluded.`value`", entity->id, property->id, IkarusValue::to_json(value_parsed).dump() ) ); }