finalize entity functions

This commit is contained in:
Folling 2025-01-02 09:39:08 +01:00
parent ba62206c0c
commit e6233cf3f2
No known key found for this signature in database
18 changed files with 523 additions and 181 deletions

View file

@ -64,7 +64,7 @@ BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTernaryOperators: false BreakBeforeTernaryOperators: false
BreakConstructorInitializers: AfterColon BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon BreakInheritanceList: AfterColon
BreakStringLiterals: true BreakStringLiterals: false
ColumnLimit: 80 ColumnLimit: 80
CommentPragmas: '^\\.+' CommentPragmas: '^\\.+'

View file

@ -181,8 +181,6 @@ IKA_API void ikarus_entity_link_blueprint(
enum IkarusEntityUnlinkBlueprintFlags { enum IkarusEntityUnlinkBlueprintFlags {
/// \brief No flags. /// \brief No flags.
IkarusEntityUnlinkBlueprintFlags_None = 0, IkarusEntityUnlinkBlueprintFlags_None = 0,
/// \brief Keep the values associated with the blueprint, transforming them into entity values.
IkarusEntityUnlinkBlueprintFlags_KeepValues = 1,
}; };
/// \brief Unlinks an entity from a blueprint. /// \brief Unlinks an entity from a blueprint.
@ -203,17 +201,27 @@ IKA_API void ikarus_entity_unlink_blueprint(
IkarusErrorData * error_out IkarusErrorData * error_out
); );
/// \brief Struct for an entity value.
struct IkarusEntityValue {
/// \brief The name of the value.
char const * name;
/// \brief The value in json format. \see value.h
char const * value;
};
/// \brief Gets the values of an entity. /// \brief Gets the values of an entity.
/// \param entity The entity to get the values of. /// \param entity The entity to get the values of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param size_out An out parameter for the number of values in the returned array
/// or undefined if an error occurs.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The values, in json format of or null if an error occurs. /// \return The values or null if an error occurs.
/// The json representation is an array of objects with the following keys: IKA_API IkarusEntityValue * ikarus_entity_get_values(
/// - `name`: The name of the value. IkarusEntity * entity,
/// - `value`: The value itself. \see value.h size_t * size_out,
IKA_API char const * IkarusErrorData * error_out
ikarus_entity_get_values(IkarusEntity * entity, IkarusErrorData * error_out); );
/// \brief Gets a value of an entity. /// \brief Gets a value of an entity.
/// \param entity The entity to get the value of. /// \param entity The entity to get the value of.
@ -279,16 +287,25 @@ IKA_API void ikarus_entity_delete_value(
IkarusErrorData * error_out IkarusErrorData * error_out
); );
/// \brief Struct for an entity property value.
struct IkarusEntityPropertyValue {
/// \brief The property.
struct IkarusProperty * property;
/// \brief The value in json format. \see value.h
char const * value;
};
/// \brief Gets the property values of an entity. /// \brief Gets the property values of an entity.
/// \param entity The entity to get the property values of. /// \param entity The entity to get the property values of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param size_out An out parameter for the number of property values in the
/// returned array or undefined if an error occurs.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The property values, in msgpack format of or null if an error occurs. /// \return The property values, or null if an error occurs.
/// The format is a map of property pointers (as integers) to values. \see IKA_API IkarusEntityPropertyValue * ikarus_entity_get_property_values(
/// value.h
IKA_API char const * ikarus_entity_get_property_values(
IkarusEntity * entity, IkarusEntity * entity,
size_t * size_out,
IkarusErrorData * error_out IkarusErrorData * error_out
); );

View file

@ -11,6 +11,8 @@ void safe_strcpy(
size_t const dest_size size_t const dest_size
); );
#define IKARUS_VOID_RETURN
#define IKARUS_SET_ERROR(msg, err_info) \ #define IKARUS_SET_ERROR(msg, err_info) \
if (error_out != nullptr) { \ if (error_out != nullptr) { \
safe_strcpy( \ safe_strcpy( \
@ -140,9 +142,9 @@ void safe_strcpy(
IkarusErrorInfo_Client_InvalidInput \ IkarusErrorInfo_Client_InvalidInput \
); );
#define IKARUS_FAIL_IF_NOT_EXIST(object, ret) \ #define IKARUS_FAIL_IF_NOT_EXIST_IMPL(exists_name, object, ret) \
IKARUS_VTRYRV_OR_FAIL( \ IKARUS_VTRYRV_OR_FAIL( \
auto exists, \ auto exists_name, \
ret, \ ret, \
fmt::format( \ fmt::format( \
"failed to check if {} exists", \ "failed to check if {} exists", \
@ -159,7 +161,7 @@ void safe_strcpy(
); \ ); \
\ \
IKARUS_FAIL_IF( \ IKARUS_FAIL_IF( \
!exists, \ !exists_name, \
ret, \ ret, \
fmt::format( \ fmt::format( \
"{} doesn't exist", \ "{} doesn't exist", \
@ -167,3 +169,6 @@ void safe_strcpy(
), \ ), \
IkarusErrorInfo_Client_NonExistent \ IkarusErrorInfo_Client_NonExistent \
) )
#define IKARUS_FAIL_IF_NOT_EXIST(object, ret) \
IKARUS_FAIL_IF_NOT_EXIST_IMPL(CPPBASE_UNIQUE_NAME(exists), object, ret)

View file

@ -3,3 +3,7 @@
#include <ikarus/errors.hpp> #include <ikarus/errors.hpp>
#include <ikarus/objects/blueprint.hpp> #include <ikarus/objects/blueprint.hpp>
#include <ikarus/persistence/project.hpp> #include <ikarus/persistence/project.hpp>
IkarusBlueprint::IkarusBlueprint(struct IkarusProject * project, int64_t id):
project{project},
id{id} {}

View file

@ -3,21 +3,11 @@
#include <string> #include <string>
struct IkarusBlueprint { struct IkarusBlueprint {
consteval static inline auto OBJECT_NAME() -> std::string_view { constinit static inline auto object_name = "blueprint";
return "blueprint"; constinit static inline auto table_name = "blueprints";
}
consteval static inline auto TABLE_NAME() -> std::string_view { IkarusBlueprint(struct IkarusProject * project, int64_t id);
return "blueprints";
}
IkarusBlueprint(
struct IkarusProject * project,
int64_t id,
std::string_view name
);
struct IkarusProject * project; struct IkarusProject * project;
int64_t id; int64_t id;
std::string name;
}; };

View file

@ -1,18 +1,15 @@
#include "entity.hpp" #include "entity.hpp"
#include <algorithm>
#include <ikarus/errors.h> #include <ikarus/errors.h>
#include <ikarus/errors.hpp> #include <ikarus/errors.hpp>
#include <ikarus/objects/entity.h> #include <ikarus/objects/entity.h>
#include <ikarus/persistence/project.hpp> #include <ikarus/persistence/project.hpp>
IkarusEntity::IkarusEntity( IkarusEntity::IkarusEntity(struct IkarusProject * project, int64_t id):
struct IkarusProject * project,
int64_t id,
std::string_view name
):
project{project}, project{project},
id{id}, id{id} {}
name{name} {}
IkarusEntity * ikarus_entity_create( IkarusEntity * ikarus_entity_create(
struct IkarusProject * project, struct IkarusProject * project,
@ -31,7 +28,7 @@ IkarusEntity * ikarus_entity_create(
); );
auto const id = project->db->last_insert_rowid(); auto const id = project->db->last_insert_rowid();
return new IkarusEntity{project, id, name}; return new IkarusEntity{project, id};
} }
void ikarus_entity_delete( void ikarus_entity_delete(
@ -39,12 +36,11 @@ void ikarus_entity_delete(
IkarusEntityDeleteFlags flags, IkarusEntityDeleteFlags flags,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NOT_EXIST(entity, ); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(entity->project, );
IKARUS_TRYRV_OR_FAIL( IKARUS_TRYRV_OR_FAIL(
, IKARUS_VOID_RETURN,
"failed to delete entity: {}", "failed to delete entity: {}",
IkarusErrorInfo_Database_QueryFailed, IkarusErrorInfo_Database_QueryFailed,
entity->project->db entity->project->db
@ -61,7 +57,6 @@ IkarusEntity * ikarus_entity_copy(
) { ) {
IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NULL(entity, nullptr);
IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr);
IKARUS_FAIL_IF_NULL(entity->project, nullptr);
IKARUS_VTRYRV_OR_FAIL( IKARUS_VTRYRV_OR_FAIL(
auto id, auto id,
@ -72,34 +67,28 @@ IkarusEntity * ikarus_entity_copy(
[entity](auto * db) [entity](auto * db)
-> cppbase::Result<int64_t, sqlitecpp::TransactionError> { -> cppbase::Result<int64_t, sqlitecpp::TransactionError> {
TRY(entity->project->db->execute( TRY(entity->project->db->execute(
"INSERT INTO `entities`(`name`) VALUES(?)", "INSERT INTO `entities`(`name`) "
entity->name.data() "SELECT `name` FROM `entities` WHERE `id` = ?",
entity->id
)); ));
TRY(entity->project->db->execute( TRY(entity->project->db->execute(
"INSERT INTO `entity_values`(`entity`, `name`, `value`)" "INSERT INTO `entity_values`(`entity`, `name`, `value`) "
" SELECT ?1, `name`, `value` FROM `entity_values` WHERE " "SELECT ?1, `name`, `value` FROM `entity_values` WHERE "
"`entity` = ?1", "`entity` = ?1",
entity->id entity->id
)) ))
TRY(entity->project->db->execute( TRY(entity->project->db->execute(
"INSERT INTO `entity_property_values`(" "INSERT INTO `entity_property_values`(`entity`, `property`, `value`) "
" `entity`, " "SELECT ?1, `property`, `value` FROM `entity_property_values` "
" `property`,"
" `value`"
") "
"SELECT ?1, `property`, `value` FROM "
"`entity_property_values` "
"WHERE `entity` = ?1", "WHERE `entity` = ?1",
entity->id entity->id
)) ))
TRY(entity->project->db->execute( TRY(entity->project->db->execute(
"INSERT INTO `entity_blueprint_links`(`entity`, " "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) "
"`blueprint`)" "SELECT ?1, `property`, `value` FROM `entity_property_values` "
"SELECT ?1, `property`, `value` FROM "
"`entity_property_values` "
"WHERE `entity` = ?1", "WHERE `entity` = ?1",
entity->id entity->id
)) ))
@ -109,13 +98,13 @@ IkarusEntity * ikarus_entity_copy(
) )
); );
return new IkarusEntity{entity->project, id, entity->name}; return new IkarusEntity{entity->project, id};
} }
IkarusProject * IkarusProject *
ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) { ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NULL(entity, nullptr);
IKARUS_FAIL_IF_NULL(entity->project, nullptr); IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr);
return entity->project; return entity->project;
} }
@ -123,8 +112,20 @@ ikarus_entity_get_project(IkarusEntity * entity, IkarusErrorData * error_out) {
char const * char const *
ikarus_entity_get_name(IkarusEntity * entity, IkarusErrorData * error_out) { ikarus_entity_get_name(IkarusEntity * entity, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NULL(entity, nullptr);
IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr);
return entity->name.data(); IKARUS_VTRYRV_OR_FAIL(
auto name,
nullptr,
"failed to get name for entity: {}",
IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_one<char const *>(
"SELECT `name` FROM `entities` WHERE `id` = ?",
entity->id
)
);
return name;
} }
void ikarus_entity_set_name( void ikarus_entity_set_name(
@ -133,11 +134,12 @@ void ikarus_entity_set_name(
IkarusEntitySetNameFlags flags, IkarusEntitySetNameFlags flags,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NAME_INVALID(name, ); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NAME_INVALID(name, IKARUS_VOID_RETURN);
IKARUS_TRYRV_OR_FAIL( IKARUS_TRYRV_OR_FAIL(
, IKARUS_VOID_RETURN,
"failed to set name for entity: {}", "failed to set name for entity: {}",
IkarusErrorInfo_Database_QueryFailed, IkarusErrorInfo_Database_QueryFailed,
entity->project->db->execute( entity->project->db->execute(
@ -146,8 +148,152 @@ void ikarus_entity_set_name(
entity->id entity->id
) )
); );
}
entity->name = name; 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<std::int64_t>(
"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<std::int64_t>(
"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<char const *, char const *>(
"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( char const * ikarus_entity_get_value(
@ -156,7 +302,22 @@ char const * ikarus_entity_get_value(
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_NULL(entity, nullptr);
IKARUS_FAIL_IF_NOT_EXIST(entity, nullptr);
IKARUS_FAIL_IF_NULL(name, 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<char const *>(
"SELECT `value` FROM `entity_values` WHERE `entity` = ? AND `name` = ?",
entity->id,
name
)
);
return value;
} }
void ikarus_entity_set_value( void ikarus_entity_set_value(
@ -165,7 +326,155 @@ void ikarus_entity_set_value(
char const * value, char const * value,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_NULL(entity, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(name, ); IKARUS_FAIL_IF_NOT_EXIST(entity, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(value, ); 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<int64_t, char const *>(
"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<char const *>(
"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()
)
);
} }

View file

@ -9,16 +9,8 @@ struct IkarusEntity {
constinit static inline auto object_name = "entity"; constinit static inline auto object_name = "entity";
constinit static inline auto table_name = "entities"; constinit static inline auto table_name = "entities";
IkarusEntity( IkarusEntity(struct IkarusProject * project, int64_t id);
struct IkarusProject * project,
int64_t id,
std::string_view name
);
struct IkarusProject * project; struct IkarusProject * project;
int64_t id; int64_t id;
std::string name;
std::vector<std::pair<std::string_view, IkarusValue *>> values_ordered;
std::unordered_map<std::string, IkarusValue> values;
}; };

View file

@ -5,11 +5,6 @@
#include <ikarus/objects/property.h> #include <ikarus/objects/property.h>
#include <ikarus/persistence/project.hpp> #include <ikarus/persistence/project.hpp>
IkarusProperty::IkarusProperty( IkarusProperty::IkarusProperty(struct IkarusProject * project, int64_t id):
struct IkarusProject * project,
int64_t id,
std::string_view name
):
project{project}, project{project},
id{id}, id{id} {}
name{name} {}

View file

@ -3,21 +3,11 @@
#include <string> #include <string>
struct IkarusProperty { struct IkarusProperty {
consteval static inline auto OBJECT_NAME() -> std::string_view { constinit static inline auto object_name = "property";
return "property"; constinit static inline auto table_name = "properties";
}
consteval static inline auto TABLE_NAME() -> std::string_view { IkarusProperty(struct IkarusProject * project, int64_t id);
return "properties";
}
IkarusProperty(
struct IkarusProject * project,
int64_t id,
std::string_view name
);
struct IkarusProject * project; struct IkarusProject * project;
int64_t id; int64_t id;
std::string name;
}; };

View file

@ -6,7 +6,6 @@ CREATE TABLE `entities`
CREATE TABLE `entity_values` CREATE TABLE `entity_values`
( (
`id` INTEGER PRIMARY KEY,
`entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE, `entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE,
`name` TEXT NOT NULL, `name` TEXT NOT NULL,
`value` TEXT NOT NULL, `value` TEXT NOT NULL,
@ -36,12 +35,12 @@ CREATE TABLE `entity_blueprint_links`
`entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE, `entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE,
`blueprint` INTEGER NOT NULL REFERENCES `blueprints` (`id`) ON DELETE CASCADE, `blueprint` INTEGER NOT NULL REFERENCES `blueprints` (`id`) ON DELETE CASCADE,
PRIMARY KEY (`entity`, `blueprint`) PRIMARY KEY (`entity`, `blueprint`),
FOREIGN KEY (`entity`, `blueprint`) REFERENCES `entity_blueprint_links` (`entity`, `blueprint`) ON DELETE CASCADE
) STRICT; ) STRICT;
CREATE TABLE `entity_property_values` CREATE TABLE `entity_property_values`
( (
`id` INTEGER PRIMARY KEY,
`entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE, `entity` INTEGER NOT NULL REFERENCES `entities` (`id`) ON DELETE CASCADE,
`property` INTEGER NOT NULL REFERENCES `properties` (`id`) ON DELETE CASCADE, `property` INTEGER NOT NULL REFERENCES `properties` (`id`) ON DELETE CASCADE,
`value` TEXT NOT NULL, `value` TEXT NOT NULL,

View file

@ -232,18 +232,18 @@ void ikarus_project_set_name(
char const * new_name, char const * new_name,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(project, ); IKARUS_FAIL_IF_NULL(project, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(new_name, ); IKARUS_FAIL_IF_NULL(new_name, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF( IKARUS_FAIL_IF(
cppbase::is_empty_or_blank(new_name), cppbase::is_empty_or_blank(new_name),
, IKARUS_VOID_RETURN,
"name must not be empty", "name must not be empty",
IkarusErrorInfo_Client_InvalidInput IkarusErrorInfo_Client_InvalidInput
); );
IKARUS_TRYRV_OR_FAIL( IKARUS_TRYRV_OR_FAIL(
, IKARUS_VOID_RETURN,
"failed to update project name: {}", "failed to update project name: {}",
IkarusErrorInfo_Database_QueryFailed, IkarusErrorInfo_Database_QueryFailed,
project->db->execute( project->db->execute(
@ -269,15 +269,15 @@ void ikarus_project_get_entities(
uint64_t ids_out_size, uint64_t ids_out_size,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(project, ); IKARUS_FAIL_IF_NULL(project, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(ids_out, ); IKARUS_FAIL_IF_NULL(ids_out, IKARUS_VOID_RETURN);
if (ids_out == 0) { if (ids_out == 0) {
return; return;
} }
IKARUS_TRYRV_OR_FAIL( IKARUS_TRYRV_OR_FAIL(
, IKARUS_VOID_RETURN,
"unable to fetch project entities from database: {}", "unable to fetch project entities from database: {}",
IkarusErrorInfo_Database_QueryFailed, IkarusErrorInfo_Database_QueryFailed,
project->db->query_many_buffered<int64_t>( project->db->query_many_buffered<int64_t>(
@ -311,8 +311,8 @@ void ikarus_project_get_blueprints(
uint64_t ids_out_size, uint64_t ids_out_size,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(project, ); IKARUS_FAIL_IF_NULL(project, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(ids_out, ); IKARUS_FAIL_IF_NULL(ids_out, IKARUS_VOID_RETURN);
if (ids_out == 0) { if (ids_out == 0) {
return; return;

View file

@ -152,10 +152,9 @@ auto IkarusValueData::from_json(nlohmann::json const & json)
return cppbase::ok(value); return cppbase::ok(value);
} }
auto IkarusValueData::to_json( auto IkarusValueData::to_json(IkarusValueData const & value) -> nlohmann::json {
nlohmann::json & json, nlohmann::json json = nlohmann::json::object();
IkarusValueData const & value
) -> void {
std::visit( std::visit(
cppbase::overloaded{ cppbase::overloaded{
[&](IkarusValueDataPrimitive const & primitive) { [&](IkarusValueDataPrimitive const & primitive) {
@ -184,9 +183,7 @@ auto IkarusValueData::to_json(
json["type"] = IkarusValueDataType_List; json["type"] = IkarusValueDataType_List;
json["data"] = list.values | json["data"] = list.values |
std::views::transform([](auto const & data) { std::views::transform([](auto const & data) {
nlohmann::json j; return IkarusValueData::to_json(*data);
IkarusValueData::to_json(j, *data);
return j;
}) | }) |
std::ranges::to<std::vector<nlohmann::json>>(); std::ranges::to<std::vector<nlohmann::json>>();
}, },
@ -195,8 +192,8 @@ auto IkarusValueData::to_json(
json["data"] = json["data"] =
map.values | std::views::transform([](auto const & pair) { map.values | std::views::transform([](auto const & pair) {
nlohmann::json j; nlohmann::json j;
IkarusValueData::to_json(j["key"], *pair.first); j["key"] = IkarusValueData::to_json(*pair.first);
IkarusValueData::to_json(j["value"], *pair.second); j["value"] = IkarusValueData::to_json(*pair.second);
return j; return j;
}) | }) |
std::ranges::to<std::vector<nlohmann::json>>(); std::ranges::to<std::vector<nlohmann::json>>();
@ -205,13 +202,13 @@ auto IkarusValueData::to_json(
json["type"] = IkarusValueDataType_Tuple; json["type"] = IkarusValueDataType_Tuple;
json["data"] = tuple.values | json["data"] = tuple.values |
std::views::transform([](auto const & data) { std::views::transform([](auto const & data) {
nlohmann::json j; return IkarusValueData::to_json(*data);
IkarusValueData::to_json(j, *data);
return j;
}) | }) |
std::ranges::to<std::vector<nlohmann::json>>(); std::ranges::to<std::vector<nlohmann::json>>();
} }
}, },
value.variant value.variant
); );
return json;
} }

View file

@ -55,8 +55,7 @@ struct IkarusValueData {
static auto from_json(nlohmann::json const & json) static auto from_json(nlohmann::json const & json)
-> cppbase::Result<IkarusValueData, IkarusValueDataParseError>; -> cppbase::Result<IkarusValueData, IkarusValueDataParseError>;
static auto to_json(nlohmann::json & json, IkarusValueData const & value) static auto to_json(IkarusValueData const & value) -> nlohmann::json;
-> void;
IkarusValueDataVariant variant; IkarusValueDataVariant variant;
}; };

View file

@ -2,6 +2,8 @@
#include <variant> #include <variant>
#include <cppbase/format.hpp>
struct IkarusJsonMissingKeyError {}; struct IkarusJsonMissingKeyError {};
struct IkarusJsonInvalidTypeError {}; struct IkarusJsonInvalidTypeError {};
@ -34,3 +36,82 @@ using IkarusValueParseError = std::variant<
using IkarusValuesParseError = using IkarusValuesParseError =
std::variant<IkarusJsonMissingKeyError, IkarusValueParseError>; std::variant<IkarusJsonMissingKeyError, IkarusValueParseError>;
template<>
struct fmt::formatter<IkarusJsonMissingKeyError> : formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusJsonMissingKeyError const & error,
fmt::format_context & ctx
) {
return fmt::format_to(ctx.out(), "missing JSON key");
}
};
template<>
struct fmt::formatter<IkarusJsonInvalidTypeError> : formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusJsonInvalidTypeError const & error,
fmt::format_context & ctx
) {
return fmt::format_to(ctx.out(), "invalid JSON type");
}
};
template<>
struct fmt::formatter<IkarusJsonEnumOutOfBoundsError> : formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusJsonEnumOutOfBoundsError const & error,
fmt::format_context & ctx
) {
return fmt::format_to(ctx.out(), "JSON enum is out of bounds");
}
};
template<>
struct fmt::formatter<IkarusJsonUnknownError> : formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusJsonUnknownError const & error,
fmt::format_context & ctx
) {
return fmt::format_to(ctx.out(), "unknown JSON error");
}
};
template<>
struct fmt::formatter<IkarusValueSchemaParseError> : formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusValueSchemaParseError const & error,
fmt::format_context & ctx
) {
return fmt::format_to(
ctx.out(),
"failed to parse value schema: {}",
error.error
);
}
};
template<>
struct fmt::formatter<IkarusValueDataParseError> : formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusValueDataParseError const & error,
fmt::format_context & ctx
) {
return fmt::format_to(
ctx.out(),
"failed to parse value data: {}",
error.error
);
}
};
template<>
struct fmt::formatter<IkarusValueParseErrorDataSchemaMismatch> :
formatter<string_view> {
constexpr static auto format(
[[maybe_unused]] IkarusValueParseErrorDataSchemaMismatch const & error,
fmt::format_context & ctx
) {
return fmt::format_to(ctx.out(), "value data and schema mismatched");
}
};

View file

@ -85,10 +85,10 @@ auto IkarusValueSchema::from_json(nlohmann::json const & json)
return cppbase::ok(std::move(schema)); return cppbase::ok(std::move(schema));
} }
auto IkarusValueSchema::to_json( auto IkarusValueSchema::to_json(IkarusValueSchema const & schema)
nlohmann::json & json, -> nlohmann::json {
IkarusValueSchema const & schema nlohmann::json json = nlohmann::json::object();
) -> void {
std::visit( std::visit(
cppbase::overloaded{ cppbase::overloaded{
[&json](IkarusValueSchemaPrimitive const & schema) { [&json](IkarusValueSchemaPrimitive const & schema) {
@ -97,18 +97,14 @@ auto IkarusValueSchema::to_json(
}, },
[&json](IkarusValueSchemaList const & schema) { [&json](IkarusValueSchemaList const & schema) {
json["type"] = IkarusValueSchemaType_List; json["type"] = IkarusValueSchemaType_List;
IkarusValueSchema::to_json(json["schema"], *schema.sub_schema); json["schema"] = IkarusValueSchema::to_json(*schema.sub_schema);
}, },
[&json](IkarusValueSchemaMap const & schema) { [&json](IkarusValueSchemaMap const & schema) {
json["type"] = IkarusValueSchemaType_Map; json["type"] = IkarusValueSchemaType_Map;
IkarusValueSchema::to_json( json["key_schema"] =
json["key_schema"], IkarusValueSchema::to_json(*schema.key_schema);
*schema.key_schema json["value_schema"] =
); IkarusValueSchema::to_json(*schema.value_schema);
IkarusValueSchema::to_json(
json["value_schema"],
*schema.value_schema
);
}, },
[&json](IkarusValueSchemaTuple const & schema) { [&json](IkarusValueSchemaTuple const & schema) {
json["type"] = IkarusValueSchemaType_Tuple; json["type"] = IkarusValueSchemaType_Tuple;
@ -117,9 +113,9 @@ auto IkarusValueSchema::to_json(
sub_schemas.reserve(schema.sub_schemas.size()); sub_schemas.reserve(schema.sub_schemas.size());
for (auto const & sub_schema : schema.sub_schemas) { for (auto const & sub_schema : schema.sub_schemas) {
nlohmann::json sub_schema_json{}; sub_schemas.push_back(
IkarusValueSchema::to_json(sub_schema_json, *sub_schema); IkarusValueSchema::to_json(*sub_schema)
sub_schemas.push_back(sub_schema_json); );
} }
json["schemas"] = sub_schemas; json["schemas"] = sub_schemas;
@ -127,6 +123,8 @@ auto IkarusValueSchema::to_json(
}, },
schema.variant schema.variant
); );
return json;
} }
auto IkarusValueSchema::validate(IkarusValueData const & data) const -> bool { auto IkarusValueSchema::validate(IkarusValueData const & data) const -> bool {

View file

@ -42,8 +42,7 @@ struct IkarusValueSchema {
static auto from_json(nlohmann::json const & json) static auto from_json(nlohmann::json const & json)
-> cppbase::Result<IkarusValueSchema, IkarusValueSchemaParseError>; -> cppbase::Result<IkarusValueSchema, IkarusValueSchemaParseError>;
static auto to_json(nlohmann::json & json, IkarusValueSchema const & value) static auto to_json(IkarusValueSchema const & value) -> nlohmann::json;
-> void;
auto validate(IkarusValueData const & data) const -> bool; auto validate(IkarusValueData const & data) const -> bool;

View file

@ -25,34 +25,11 @@ auto IkarusValue::from_json(nlohmann::json const & json)
return cppbase::ok(std::move(value)); return cppbase::ok(std::move(value));
} }
auto IkarusValue::to_json(nlohmann::json & json, IkarusValue const & value) auto IkarusValue::to_json(IkarusValue const & value) -> nlohmann::json {
-> void { nlohmann::json json = nlohmann::json::object();
IkarusValueSchema::to_json(json["schema"], value.schema);
IkarusValueData::to_json(json["data"], value.data); json["schema"] = IkarusValueSchema::to_json(value.schema);
} json["data"] = IkarusValueData::to_json(value.data);
auto IkarusValues::from_json(nlohmann::json const & json) return json;
-> cppbase::Result<IkarusValues, IkarusValuesParseError> {
IkarusValues values{};
for (auto const & json_entry : json) {
VTRY(auto name_json, get_key(json_entry, "name"));
if (!name_json->is_string()) {
return cppbase::err(IkarusJsonInvalidTypeError{});
}
VTRY(auto value_json, get_key(json_entry, "value"));
VTRY(auto value, IkarusValue::from_json(*value_json));
values.values.emplace_back(
std::make_pair(
std::move(name_json->get<std::string_view>()),
std::move(value)
)
);
}
return cppbase::ok(std::move(values));
} }

View file

@ -14,15 +14,5 @@ struct IkarusValue {
static auto from_json(nlohmann::json const & json) static auto from_json(nlohmann::json const & json)
-> cppbase::Result<IkarusValue, IkarusValueParseError>; -> cppbase::Result<IkarusValue, IkarusValueParseError>;
static auto to_json(nlohmann::json & json, IkarusValue const & value) static auto to_json(IkarusValue const & value) -> nlohmann::json;
-> void;
};
struct IkarusValues {
static auto from_json(nlohmann::json const & json)
-> cppbase::Result<IkarusValues, IkarusValuesParseError>;
static auto to_json(nlohmann::json & json, IkarusValues const & values)
-> void;
std::vector<std::pair<std::string, IkarusValue>> values;
}; };