add blueprint & property implementation

Signed-off-by: Folling <mail@folling.io>
This commit is contained in:
folling 2025-01-04 15:25:10 +01:00 committed by Folling
parent 6310335e41
commit 302674fc68
Signed by: folling
SSH key fingerprint: SHA256:S9qEx5WCFFLK49tE/LKnKuJYM5sw+++Dn6qJbbyxnCY
6 changed files with 772 additions and 43 deletions

View file

@ -154,6 +154,22 @@ IKA_API void ikarus_blueprint_set_name(
struct IkarusErrorData * error_out struct IkarusErrorData * error_out
); );
/// \brief Gets whether a blueprint has a property.
/// \param blueprint The blueprint to check the property of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param property The property to check the existence of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \pre \li Must be in the same project as the blueprint.
/// \param error_out \see errors.h
/// \return True if the blueprint has the property, false otherwise or if an error occurs.
IKA_API bool ikarus_blueprint_has_property(
struct IkarusBlueprint * blueprint,
struct IkarusProperty * property,
struct IkarusErrorData * error_out
);
/// \brief Gets the properties of a blueprint. /// \brief Gets the properties of a blueprint.
/// \param blueprint The blueprint to get the properties of. /// \param blueprint The blueprint to get the properties of.
/// \pre \li Must not be null. /// \pre \li Must not be null.
@ -161,7 +177,7 @@ IKA_API void ikarus_blueprint_set_name(
/// \param size_out An out parameter for the number of items in the returned array or undefined if an error occurs. /// \param size_out An out parameter for the number of items in the returned array or undefined if an error occurs.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The properties of the blueprint or null if an error occurs. /// \return The properties of the blueprint or null if an error occurs.
IKA_API struct IkarusBlueprint ** ikarus_blueprint_get_properties( IKA_API struct IkarusProperty ** ikarus_blueprint_get_properties(
struct IkarusBlueprint * blueprint, struct IkarusBlueprint * blueprint,
size_t * size_out, size_t * size_out,
struct IkarusErrorData * error_out struct IkarusErrorData * error_out
@ -176,15 +192,15 @@ IKA_API struct IkarusBlueprint ** ikarus_blueprint_get_properties(
IKA_API size_t IKA_API size_t
ikarus_blueprint_get_properties_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); ikarus_blueprint_get_properties_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out);
/// \brief Gets all entities linked to a blueprint. /// \brief Gets the linked entities of a blueprint.
/// \param blueprint The blueprint to get the entities of. /// \param blueprint The blueprint to get the linked entities 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 items in the returned array or undefined if an error occurs. /// \param size_out An out parameter for the number of items in the returned array or undefined if an error occurs.
/// \remark Ignore if null. /// \remark Ignore if null.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The entities linked to the blueprint or null if an error occurs. /// \return The linked entities of the blueprint or null if an error occurs.
IKA_API struct IkarusEntity ** ikarus_blueprint_get_entities( IKA_API struct IkarusEntity ** ikarus_blueprint_get_linked_entities(
struct IkarusBlueprint * blueprint, struct IkarusBlueprint * blueprint,
size_t * size_out, size_t * size_out,
struct IkarusErrorData * error_out struct IkarusErrorData * error_out
@ -197,7 +213,7 @@ IKA_API struct IkarusEntity ** ikarus_blueprint_get_entities(
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The number of entities linked to the blueprint or 0 if an error occurs. /// \return The number of entities linked to the blueprint or 0 if an error occurs.
IKA_API size_t IKA_API size_t
ikarus_blueprint_get_entities_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); ikarus_blueprint_get_linked_entities_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out);
IKARUS_END_HEADER IKARUS_END_HEADER

View file

@ -53,23 +53,47 @@ enum IkarusPropertyCreateFlags {
/// \param project The project to create the property in. /// \param project The project to create the property in.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must exist. /// \pre \li Must exist.
/// \param blueprint The blueprint to create the property for.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param name The name of the property. /// \param name The name of the property.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must not be empty. /// \pre \li Must not be empty.
/// \param schema The schema of the property. /// \param schema The schema of the property.
/// \pre \li Must not be null. /// \pre \li Must not be null.
/// \pre \li Must be a valid JSON buffer for a IkarusValueSchema. \see schema.h
/// \param default_value The default value of the property.
/// \pre \li Must not be null.
/// \pre \li Must be a valid JSON buffer for an IkarusValueData. \see data.h
/// \param flags Flags for creating the property. /// \param flags Flags for creating the property.
/// \param error_out \see errors.h /// \param error_out \see errors.h
/// \return The created property or NULL if an error occurred. /// \return The created property or NULL if an error occurred.
/// \remark Must only be deleted with #ikarus_property_delete. /// \remark Must only be deleted with #ikarus_property_delete.
IKA_API IkarusProperty * ikarus_property_create( IKA_API IkarusProperty * ikarus_property_create(
struct IkarusProject * project, struct IkarusBlueprint * blueprint,
char const * name, char const * name,
struct IkarusValueSchema * schema, char const * schema,
char const * default_value,
IkarusPropertyCreateFlags flags, IkarusPropertyCreateFlags flags,
IkarusErrorData * error_out IkarusErrorData * error_out
); );
/// \brief Flags for copying a property.
enum IkarusPropertyCopyFlags {
/// \brief No flags.
IkarusPropertyCopyFlags_None = 0,
};
/// \brief Copy a property.
/// \param property The property to copy.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param flags Flags for copying the property.
/// \param error_out \see errors.h
/// \return The copied property or NULL if an error occurred.
IKA_API IkarusProperty *
ikarus_property_copy(IkarusProperty * property, IkarusPropertyCopyFlags flags, IkarusErrorData * error_out);
/// \brief Flags for deleting a property. /// \brief Flags for deleting a property.
enum IkarusPropertyDeleteFlags { enum IkarusPropertyDeleteFlags {
/// \brief No flags. /// \brief No flags.
@ -101,15 +125,6 @@ IKA_API struct IkarusProject * ikarus_property_get_project(IkarusProperty * prop
/// \remark Ownership remains with libikarus. /// \remark Ownership remains with libikarus.
IKA_API char const * ikarus_property_get_name(IkarusProperty * property, IkarusErrorData * error_out); IKA_API char const * ikarus_property_get_name(IkarusProperty * property, IkarusErrorData * error_out);
/// \brief Get the schema of a property.
/// \param property The property to get the schema of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The schema of the property or null if an error occurred.
/// \remark Ownership remains with libikarus.
IKA_API struct IkarusValueSchema * ikarus_property_get_schema(IkarusProperty * property, IkarusErrorData * error_out);
/// \brief Flags for setting the name of a property. /// \brief Flags for setting the name of a property.
enum IkarusPropertySetNameFlags { enum IkarusPropertySetNameFlags {
/// \brief No flags. /// \brief No flags.
@ -133,6 +148,78 @@ IKA_API void ikarus_property_set_name(
IkarusErrorData * error_out IkarusErrorData * error_out
); );
/// \brief Get the schema of a property.
/// \param property The property to get the schema of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The schema of the property in JSON format or null if an error occurred. \see schema.h
IKA_API char const * ikarus_property_get_schema(IkarusProperty * property, IkarusErrorData * error_out);
/// \brief Flags for setting the schema of a property.
enum IkarusPropertySetSchemaFlags {
/// \brief No flags.
IkarusPropertySetSchemaFlags_None = 0,
};
/// \brief Set the schema of a property.
/// \details Setting the schema of a property will reset all existing values.
/// \param property The property to set the schema of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param schema The new schema of the property.
/// \pre \li Must not be null.
/// \pre \li Must be a valid JSON buffer for a IkarusValueSchema. \see schema.h
/// \param new_default_value The new default value of the property.
/// \pre \li Must not be null.
/// \pre \li Must be a valid JSON buffer for an IkarusValueData. \see data.h
/// \param flags Flags for setting the schema of the property.
/// \param error_out \see errors.h
IKA_API void ikarus_property_set_schema(
IkarusProperty * property,
char const * schema,
char const * new_default_value,
IkarusPropertySetSchemaFlags flags,
IkarusErrorData * error_out
);
/// \brief Get the default value of a property.
/// \param property The property to get the default value of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param error_out \see errors.h
/// \return The default value data of the property in JSON format or null if an error occurred. \see data.h
IKA_API char const * ikarus_property_get_default_value(IkarusProperty * property, IkarusErrorData * error_out);
/// \details The default value must match the schema of the property.
/// \brief Flags for setting the default value of a property.
enum IkarusPropertySetDefaultValueFlags {
/// \brief No flags.
IkarusPropertySetDefaultValueFlags_None = 0,
};
/// \brief Flags for setting the default value of a property.
/// \param property The property to set the default value of.
/// \pre \li Must not be null.
/// \pre \li Must exist.
/// \param value The new default value of the property.
/// \pre \li Must not be null.
/// \pre \li Must be valid JSON \see data.h
/// \pre \li Must be valid according to the property's schema.
/// \param flags Flags for setting the default value of the property.
/// \param error_out \see errors.h
IKA_API void ikarus_property_set_default_value(
IkarusProperty * property,
char const * value,
IkarusPropertySetDefaultValueFlags flags,
IkarusErrorData * error_out
);
/// \brief Gets all values for a property.
/// \param property The property to get the values of.
///
IKARUS_END_HEADER IKARUS_END_HEADER
/// @} /// @}

View file

@ -3,12 +3,27 @@
#include <ikarus/errors.hpp> #include <ikarus/errors.hpp>
#include <ikarus/objects/blueprint.hpp> #include <ikarus/objects/blueprint.hpp>
#include <ikarus/objects/entity.h> #include <ikarus/objects/entity.h>
#include <ikarus/objects/property.h>
#include <ikarus/persistence/project.hpp> #include <ikarus/persistence/project.hpp>
IkarusBlueprint::IkarusBlueprint(struct IkarusProject * project, int64_t id): IkarusBlueprint::IkarusBlueprint(struct IkarusProject * project, int64_t id):
project{project}, project{project},
id{id} {} id{id} {}
bool ikarus_blueprint_exists(IkarusBlueprint * blueprint, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(blueprint, false);
IKARUS_VTRYRV_OR_FAIL(
auto exists,
false,
"failed to check whether blueprint exists: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db
->query_one<bool>("SELECT EXISTS(SELECT 1 FROM `blueprints` WHERE `id` = ?)", blueprint->id)
);
return exists;
}
IkarusBlueprint * ikarus_blueprint_create( IkarusBlueprint * ikarus_blueprint_create(
struct IkarusProject * project, struct IkarusProject * project,
char const * name, char const * name,
@ -35,17 +50,14 @@ IkarusBlueprint * ikarus_blueprint_create_from_entity(
IkarusBlueprintCreateFromEntityFlags flags, IkarusBlueprintCreateFromEntityFlags flags,
struct IkarusErrorData * error_out struct IkarusErrorData * error_out
) { ) {
IKARUS_TRYRV_OR_FAIL(
nullptr,
"{}",
IkarusErrorInfo_Database_QueryFailed,
ikarus_libikarus_func_call_to_result(
error_out,
ikarus_must_return_true(ikarus_entity_exists, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent),
entity
)
);
IKARUS_FAIL_IF_NAME_INVALID(name, nullptr); IKARUS_FAIL_IF_NAME_INVALID(name, nullptr);
IKARUS_ASCERTAIN(
nullptr,
"entity must not be null",
IkarusErrorInfo_Client_InvalidInput,
ikarus_entity_exists,
entity
);
IKARUS_VTRYRV_OR_FAIL( IKARUS_VTRYRV_OR_FAIL(
auto id, auto id,
@ -59,15 +71,308 @@ IkarusBlueprint * ikarus_blueprint_create_from_entity(
CPPBASE_TRY(entity->project->db->execute( CPPBASE_TRY(entity->project->db->execute(
"INSERT INTO `properties`(`blueprint`, `name`, `schema`) " "INSERT INTO `properties`(`blueprint`, `name`, `schema`) "
"SELECT ?, `name`, json_extract(`value`, '$.schema') FROM `entity_values` " "SELECT ?, `name`, json_extract(`value`, '$.schema') AS `schema` FROM `entity_values` "
"WHERE `entity` = ?", "WHERE `entity` = ?",
id, id,
entity->id entity->id
)) ))
return cppbase::ok(entity->project->db->last_insert_rowid()); return cppbase::ok(id);
}) })
); );
return new IkarusBlueprint{entity->project, id}; return new IkarusBlueprint{entity->project, id};
} }
struct IkarusBlueprint * ikarus_blueprint_copy(
struct IkarusBlueprint * blueprint,
IkarusBlueprintCopyFlags flags,
struct IkarusErrorData * error_out
) {
IKARUS_ASCERTAIN(
nullptr,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VTRYRV_OR_FAIL(
auto id,
nullptr,
"failed to copy blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->transact(
[blueprint](auto * db) -> cppbase::Result<int64_t, sqlitecpp::TransactionError> {
CPPBASE_TRY(db->execute(
"INSERT INTO `blueprints`(`name`) "
"SELECT `name` FROM `blueprints` WHERE `id` = ?",
blueprint->id
));
auto const id = db->last_insert_rowid();
CPPBASE_TRY(blueprint->project->db->execute(
"INSERT INTO `properties`(`blueprint`, `name`, `schema`) "
"SELECT ?, `name`, `schema` FROM `properties` "
"WHERE `blueprint` = ?",
id,
blueprint->id
));
return cppbase::ok(id);
}
)
);
return new IkarusBlueprint{blueprint->project, id};
}
void ikarus_blueprint_delete(
struct IkarusBlueprint * blueprint,
IkarusBlueprintDeleteFlags flags,
IkarusErrorData * error_out
) {
IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_TRYRV_OR_FAIL(
IKARUS_VOID_RETURN,
"failed to delete blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->execute("DELETE FROM `blueprints` WHERE `id` = ?", blueprint->id)
);
delete blueprint;
}
struct IkarusProject *
ikarus_blueprint_get_project(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
return blueprint->project;
}
char const * ikarus_blueprint_get_name(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VTRYRV_OR_FAIL(
auto name,
nullptr,
"failed to get name for blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_one<char const *>("SELECT `name` FROM `blueprints` WHERE `id` = ?", blueprint->id)
);
return name;
}
void ikarus_blueprint_set_name(
struct IkarusBlueprint * blueprint,
char const * name,
IkarusBlueprintSetNameFlags flags,
struct IkarusErrorData * error_out
) {
IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_FAIL_IF_NAME_INVALID(name, IKARUS_VOID_RETURN);
IKARUS_TRYRV_OR_FAIL(
IKARUS_VOID_RETURN,
"failed to set name for blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->execute("UPDATE `blueprints` SET `name` = ? WHERE `id` = ?", name, blueprint->id)
);
}
bool ikarus_blueprint_has_property(
struct IkarusBlueprint * blueprint,
struct IkarusProperty * property,
struct IkarusErrorData * error_out
) {
IKARUS_ASCERTAIN(
false,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_ASCERTAIN(
false,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_FAIL_IF(
blueprint->project != property->project,
false,
"property does not belong to blueprint's project",
IkarusErrorInfo_Client_NotLinked
);
IKARUS_VTRYRV_OR_FAIL(
auto exists,
false,
"failed to check if blueprint has property: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_one<bool>(
"SELECT EXISTS(SELECT 1 FROM `properties` WHERE `blueprint` = ? AND `id` = ?)",
blueprint->id,
property->id
)
);
return exists;
}
struct IkarusProperty ** ikarus_blueprint_get_properties(
struct IkarusBlueprint * blueprint,
size_t * size_out,
struct IkarusErrorData * error_out
) {
IKARUS_ASCERTAIN(
nullptr,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VCALL(auto count, nullptr, ikarus_blueprint_get_properties_count, blueprint);
std::int64_t ids[count];
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to get properties for blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_many_buffered<std::int64_t>(
"SELECT `id` FROM `properties` WHERE `blueprint` = ?",
ids,
count,
blueprint->id
)
);
auto properties = new IkarusProperty *[count];
std::transform(ids, ids + count, properties, [project = blueprint->project](auto id) {
return new IkarusProperty{project, id};
});
if (size_out) {
*size_out = count;
}
return properties;
}
size_t ikarus_blueprint_get_properties_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
0,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VTRYRV_OR_FAIL(
auto count,
0,
"failed to get properties count for blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db
->query_one<std::int64_t>("SELECT COUNT(*) FROM `properties` WHERE `blueprint` = ?", blueprint->id)
);
return count;
}
struct IkarusEntity ** ikarus_blueprint_get_linked_entities(
struct IkarusBlueprint * blueprint,
size_t * size_out,
struct IkarusErrorData * error_out
) {
IKARUS_ASCERTAIN(
nullptr,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VCALL(auto count, nullptr, ikarus_blueprint_get_linked_entities_count, blueprint);
std::int64_t ids[count];
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to get entities for blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_many_buffered<std::int64_t>(
"SELECT `entity` FROM `entity_blueprint_links` WHERE `blueprint` = ?",
ids,
count,
blueprint->id
)
);
auto entities = new IkarusEntity *[count];
std::transform(ids, ids + count, entities, [project = blueprint->project](auto id) {
return new IkarusEntity{project, id};
});
if (size_out) {
*size_out = count;
}
return entities;
}
size_t
ikarus_blueprint_get_linked_entities_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
0,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VTRYRV_OR_FAIL(
auto count,
0,
"failed to get linked entities count for blueprint: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->query_one<std::int64_t>(
"SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?",
blueprint->id
)
);
return count;
}

View file

@ -80,6 +80,8 @@ IkarusEntity * ikarus_entity_copy(IkarusEntity * entity, IkarusEntityCopyFlags f
entity->id entity->id
)); ));
auto const id = entity->project->db->last_insert_rowid();
CPPBASE_TRY(entity->project->db->execute( CPPBASE_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 "
@ -101,7 +103,7 @@ IkarusEntity * ikarus_entity_copy(IkarusEntity * entity, IkarusEntityCopyFlags f
entity->id entity->id
)) ))
return cppbase::ok(entity->project->db->last_insert_rowid()); return cppbase::ok(id);
}) })
); );
@ -189,8 +191,8 @@ bool ikarus_entity_is_linked_to_blueprint(
struct IkarusBlueprint ** struct IkarusBlueprint **
ikarus_entity_get_linked_blueprints(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) { ikarus_entity_get_linked_blueprints(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity);
IKARUS_VCALL(auto count, false, ikarus_entity_get_linked_blueprints_count, entity); IKARUS_VCALL(auto count, nullptr, ikarus_entity_get_linked_blueprints_count, entity);
std::int64_t ids[count]; std::int64_t ids[count];
@ -495,7 +497,7 @@ bool ikarus_entity_has_property_value(
IkarusEntityPropertyValue * IkarusEntityPropertyValue *
ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) { ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity);
IKARUS_VTRYRV_OR_FAIL( IKARUS_VTRYRV_OR_FAIL(
auto values_plain, auto values_plain,
@ -503,8 +505,11 @@ ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, Ikar
"failed to get property values for entity: {}", "failed to get property values for entity: {}",
IkarusErrorInfo_Database_QueryFailed, IkarusErrorInfo_Database_QueryFailed,
entity->project->db->query_many<int64_t, char const *>( entity->project->db->query_many<int64_t, char const *>(
"SELECT `e`.`property`, IFNULL(`e`.`value`, ikarus_default_value(`p`.`schema`)) FROM `entity_property_values` AS `e` " "SELECT `p`.`id`, IFNULL(`v`.`value`, ikarus_default_value(`p`.`schema`)) "
"INNER JOIN `properties` AS `p` ON `p`.`id` = `e`.`property` " "FROM `entities` AS `e` "
"INNER JOIN `entity_blueprint_links` as `l` ON `l`.`entity` = `e`.`id` "
"INNER JOIN `properties` AS `p` ON `p`.`blueprint` = `l`.`blueprint` "
"LEFT JOIN `entity_property_values` AS `v` ON `e`.`entity` = `e`.`id` "
"WHERE `e`.`entity` = ?", "WHERE `e`.`entity` = ?",
entity->id entity->id
) )
@ -527,9 +532,9 @@ ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, Ikar
char const * char const *
ikarus_entity_get_property_value(IkarusEntity * entity, struct IkarusProperty * property, IkarusErrorData * error_out) { ikarus_entity_get_property_value(IkarusEntity * entity, struct IkarusProperty * property, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(false, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity);
IKARUS_ASCERTAIN( IKARUS_ASCERTAIN(
false, nullptr,
"property doesn't exist", "property doesn't exist",
IkarusErrorInfo_Client_NonExistent, IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists, ikarus_property_exists,
@ -543,6 +548,15 @@ ikarus_entity_get_property_value(IkarusEntity * entity, struct IkarusProperty *
IkarusErrorInfo_Client_NotLinked IkarusErrorInfo_Client_NotLinked
); );
IKARUS_ASCERTAIN(
nullptr,
"entity doesn't have property value",
IkarusErrorInfo_Client_NotLinked,
ikarus_entity_has_property_value,
entity,
property
);
IKARUS_VTRYRV_OR_FAIL( IKARUS_VTRYRV_OR_FAIL(
auto value, auto value,
nullptr, nullptr,
@ -567,6 +581,8 @@ void ikarus_entity_set_property_value(
IkarusEntitySetPropertyValueFlags flags, IkarusEntitySetPropertyValueFlags flags,
IkarusErrorData * error_out IkarusErrorData * error_out
) { ) {
IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN);
IKARUS_ASCERTAIN( IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN, IKARUS_VOID_RETURN,
"entity doesn't exist", "entity doesn't exist",
@ -581,7 +597,6 @@ void ikarus_entity_set_property_value(
ikarus_property_exists, ikarus_property_exists,
property property
); );
IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF( IKARUS_FAIL_IF(
entity->project != property->project, entity->project != property->project,
@ -650,6 +665,7 @@ void ikarus_entity_clear_property_value(
"property does not belong to entity's project", "property does not belong to entity's project",
IkarusErrorInfo_Client_NotLinked IkarusErrorInfo_Client_NotLinked
); );
IKARUS_ASCERTAIN( IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN, IKARUS_VOID_RETURN,
"entity doesn't have property", "entity doesn't have property",

View file

@ -2,9 +2,316 @@
#include <ikarus/errors.h> #include <ikarus/errors.h>
#include <ikarus/errors.hpp> #include <ikarus/errors.hpp>
#include <ikarus/objects/blueprint.h>
#include <ikarus/objects/property.h> #include <ikarus/objects/property.h>
#include <ikarus/persistence/project.hpp> #include <ikarus/persistence/project.hpp>
IkarusProperty::IkarusProperty(struct IkarusProject * project, int64_t id): IkarusProperty::IkarusProperty(struct IkarusProject * project, int64_t id):
project{project}, project{project},
id{id} {} id{id} {}
bool ikarus_property_exists(IkarusProperty * property, IkarusErrorData * error_out) {
IKARUS_FAIL_IF_NULL(property, false);
IKARUS_VTRYRV_OR_FAIL(
auto exists,
false,
"failed to check whether property exists: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->query_one<bool>("SELECT EXISTS(SELECT 1 FROM `properties` WHERE `id` = ?)", property->id)
);
return exists;
}
IkarusProperty * ikarus_property_create(
struct IkarusBlueprint * blueprint,
char const * name,
char const * schema,
char const * default_value,
IkarusPropertyCreateFlags flags,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(blueprint, nullptr);
IKARUS_FAIL_IF_NAME_INVALID(name, nullptr);
IKARUS_ASCERTAIN(
nullptr,
"blueprint doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_blueprint_exists,
blueprint
);
IKARUS_VTRYRV_OR_FAIL(
auto schema_parsed,
nullptr,
"cannot parse schema as JSON: {}",
IkarusErrorInfo_Client_InvalidInput,
IkarusValueSchema::from_json_str(schema)
);
IKARUS_VTRYRV_OR_FAIL(
auto default_value_parsed,
nullptr,
"cannot parse default value as JSON: {}",
IkarusErrorInfo_Client_InvalidInput,
IkarusValueData::from_json_str(default_value)
);
IKARUS_FAIL_IF(
!schema_parsed.validate(default_value_parsed),
nullptr,
"default value is invalid for schema",
IkarusErrorInfo_Client_InvalidInput
);
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to create property: {}",
IkarusErrorInfo_Database_QueryFailed,
blueprint->project->db->execute(
"INSERT INTO `properties`(`blueprint`, `name`, `schema`, `default_value`) VALUES(?, ?, ?, ?)",
blueprint->id,
name,
IkarusValueSchema::to_json(schema_parsed).dump(),
IkarusValueData::to_json(default_value_parsed).dump()
)
);
auto const id = blueprint->project->db->last_insert_rowid();
return new IkarusProperty{blueprint->project, id};
}
IkarusProperty *
ikarus_property_copy(IkarusProperty * property, IkarusPropertyCopyFlags flags, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_TRYRV_OR_FAIL(
nullptr,
"failed to copy property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->execute(
"INSERT INTO `properties`(`blueprint`, `name`, `schema`, `default_value`) "
"SELECT `blueprint`, `name`, `schema`, `default_value` FROM `properties` WHERE `id` = ?",
property->id
)
);
auto const id = property->project->db->last_insert_rowid();
return new IkarusProperty{property->project, id};
}
void ikarus_property_delete(IkarusProperty * property, IkarusPropertyDeleteFlags flags, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_TRYRV_OR_FAIL(
IKARUS_VOID_RETURN,
"failed to delete property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->execute("DELETE FROM `properties` WHERE `id` = ?", property->id)
);
delete property;
}
struct IkarusProject * ikarus_property_get_project(IkarusProperty * property, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
return property->project;
}
char const * ikarus_property_get_name(IkarusProperty * property, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_VTRYRV_OR_FAIL(
auto name,
nullptr,
"failed to get name for property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->query_one<char const *>("SELECT `name` FROM `properties` WHERE `id` = ?", property->id)
);
return name;
}
void ikarus_property_set_name(
IkarusProperty * property,
char const * name,
IkarusPropertySetNameFlags flags,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(name, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NAME_INVALID(name, IKARUS_VOID_RETURN);
IKARUS_TRYRV_OR_FAIL(
IKARUS_VOID_RETURN,
"failed to set name for property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->execute("UPDATE `properties` SET `name` = ? WHERE `id` = ?", name, property->id)
);
}
char const * ikarus_property_get_schema(IkarusProperty * property, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_VTRYRV_OR_FAIL(
auto schema,
nullptr,
"failed to get schema for property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->query_one<char const *>("SELECT `schema` FROM `properties` WHERE `id` = ?", property->id)
);
return schema;
}
void ikarus_property_set_schema(
IkarusProperty * property,
char const * schema,
char const * new_default_value,
IkarusPropertySetSchemaFlags flags,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(schema, IKARUS_VOID_RETURN);
IKARUS_FAIL_IF_NULL(new_default_value, IKARUS_VOID_RETURN);
IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_VTRYRV_OR_FAIL(
auto schema_parsed,
IKARUS_VOID_RETURN,
"cannot parse schema as JSON: {}",
IkarusErrorInfo_Client_InvalidInput,
IkarusValueSchema::from_json_str(schema)
);
IKARUS_VTRYRV_OR_FAIL(
auto default_value_parsed,
IKARUS_VOID_RETURN,
"cannot parse default value as JSON: {}",
IkarusErrorInfo_Client_InvalidInput,
IkarusValueData::from_json_str(new_default_value)
);
IKARUS_FAIL_IF(
!schema_parsed.validate(default_value_parsed),
IKARUS_VOID_RETURN,
"default value is invalid for schema",
IkarusErrorInfo_Client_InvalidInput
);
// transact updating the property and all of its associated values
IKARUS_TRYRV_OR_FAIL(
IKARUS_VOID_RETURN,
"failed to set schema for property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->transact([&](auto * db) -> cppbase::Result<void, sqlitecpp::TransactionError> {
CPPBASE_TRY(db->execute(
"UPDATE `properties` SET `schema` = ? WHERE `id` = ?",
IkarusValueSchema::to_json(schema_parsed).dump(),
property->id
));
CPPBASE_TRY(db->execute(
"UPDATE `entity_property_values` SET `value` = ? WHERE `property` = ?",
IkarusValueData::to_json(default_value_parsed).dump(),
property->id
));
return cppbase::ok();
})
);
}
char const * ikarus_property_get_default_value(IkarusProperty * property, IkarusErrorData * error_out) {
IKARUS_ASCERTAIN(
nullptr,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_VTRYRV_OR_FAIL(
auto default_value,
nullptr,
"failed to get default value for property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db
->query_one<char const *>("SELECT `default_value` FROM `properties` WHERE `id` = ?", property->id)
);
return default_value;
}
void ikarus_property_set_default_value(
IkarusProperty * property,
char const * value,
IkarusPropertySetDefaultValueFlags flags,
IkarusErrorData * error_out
) {
IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN);
IKARUS_ASCERTAIN(
IKARUS_VOID_RETURN,
"property doesn't exist",
IkarusErrorInfo_Client_NonExistent,
ikarus_property_exists,
property
);
IKARUS_VTRYRV_OR_FAIL(
auto value_parsed,
IKARUS_VOID_RETURN,
"cannot parse value as JSON: {}",
IkarusErrorInfo_Client_InvalidInput,
IkarusValueData::from_json_str(value)
);
IKARUS_TRYRV_OR_FAIL(
IKARUS_VOID_RETURN,
"failed to set default value for property: {}",
IkarusErrorInfo_Database_QueryFailed,
property->project->db->execute(
"UPDATE `properties` SET `default_value` = ? WHERE `id` = ?",
IkarusValueData::to_json(value_parsed).dump(),
property->id
)
);
}

View file

@ -10,8 +10,7 @@ CREATE TABLE `entity_values`
`name` TEXT NOT NULL, `name` TEXT NOT NULL,
`value` TEXT NOT NULL, `value` TEXT NOT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`entity`, `name`)
UNIQUE (`entity`, `name`)
) STRICT; ) STRICT;
CREATE TABLE `blueprints` CREATE TABLE `blueprints`
@ -44,6 +43,5 @@ CREATE TABLE `entity_property_values`
`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,
PRIMARY KEY (`id`), PRIMARY KEY (`entity`, `property`)
UNIQUE (`entity`, `property`)
) STRICT; ) STRICT;