From 302674fc6849fd74884b5c0ab7765e1ab30b4e9f Mon Sep 17 00:00:00 2001 From: folling Date: Sat, 4 Jan 2025 15:25:10 +0100 Subject: [PATCH] add blueprint & property implementation Signed-off-by: Folling --- include/ikarus/objects/blueprint.h | 28 +- include/ikarus/objects/property.h | 109 +++++- src/ikarus/objects/blueprint.cpp | 329 +++++++++++++++++- src/ikarus/objects/entity.cpp | 36 +- src/ikarus/objects/property.cpp | 307 ++++++++++++++++ .../migrations/m0_initial_layout.sql | 6 +- 6 files changed, 772 insertions(+), 43 deletions(-) diff --git a/include/ikarus/objects/blueprint.h b/include/ikarus/objects/blueprint.h index 7df09a8..0631126 100644 --- a/include/ikarus/objects/blueprint.h +++ b/include/ikarus/objects/blueprint.h @@ -154,6 +154,22 @@ IKA_API void ikarus_blueprint_set_name( 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. /// \param blueprint The blueprint to get the properties of. /// \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 error_out \see errors.h /// \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, size_t * size_out, struct IkarusErrorData * error_out @@ -176,15 +192,15 @@ IKA_API struct IkarusBlueprint ** ikarus_blueprint_get_properties( IKA_API size_t ikarus_blueprint_get_properties_count(struct IkarusBlueprint * blueprint, struct IkarusErrorData * error_out); -/// \brief Gets all entities linked to a blueprint. -/// \param blueprint The blueprint to get the entities of. +/// \brief Gets the linked entities of a blueprint. +/// \param blueprint The blueprint to get the linked entities of. /// \pre \li Must not be null. /// \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. /// \remark Ignore if null. /// \param error_out \see errors.h -/// \return The entities linked to the blueprint or null if an error occurs. -IKA_API struct IkarusEntity ** ikarus_blueprint_get_entities( +/// \return The linked entities of the blueprint or null if an error occurs. +IKA_API struct IkarusEntity ** ikarus_blueprint_get_linked_entities( struct IkarusBlueprint * blueprint, size_t * size_out, struct IkarusErrorData * error_out @@ -197,7 +213,7 @@ IKA_API struct IkarusEntity ** ikarus_blueprint_get_entities( /// \param error_out \see errors.h /// \return The number of entities linked to the blueprint or 0 if an error occurs. 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 diff --git a/include/ikarus/objects/property.h b/include/ikarus/objects/property.h index 2124423..0a6c3d1 100644 --- a/include/ikarus/objects/property.h +++ b/include/ikarus/objects/property.h @@ -53,23 +53,47 @@ enum IkarusPropertyCreateFlags { /// \param project The project to create the property in. /// \pre \li Must not be null. /// \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. /// \pre \li Must not be null. /// \pre \li Must not be empty. /// \param schema The 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 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 error_out \see errors.h /// \return The created property or NULL if an error occurred. /// \remark Must only be deleted with #ikarus_property_delete. IKA_API IkarusProperty * ikarus_property_create( - struct IkarusProject * project, + struct IkarusBlueprint * blueprint, char const * name, - struct IkarusValueSchema * schema, + char const * schema, + char const * default_value, IkarusPropertyCreateFlags flags, 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. enum IkarusPropertyDeleteFlags { /// \brief No flags. @@ -101,15 +125,6 @@ IKA_API struct IkarusProject * ikarus_property_get_project(IkarusProperty * prop /// \remark Ownership remains with libikarus. 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. enum IkarusPropertySetNameFlags { /// \brief No flags. @@ -133,6 +148,78 @@ IKA_API void ikarus_property_set_name( 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 /// @} diff --git a/src/ikarus/objects/blueprint.cpp b/src/ikarus/objects/blueprint.cpp index ba9e7b1..e02e07f 100644 --- a/src/ikarus/objects/blueprint.cpp +++ b/src/ikarus/objects/blueprint.cpp @@ -3,12 +3,27 @@ #include #include #include +#include #include IkarusBlueprint::IkarusBlueprint(struct IkarusProject * project, int64_t id): project{project}, 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("SELECT EXISTS(SELECT 1 FROM `blueprints` WHERE `id` = ?)", blueprint->id) + ); + + return exists; +} + IkarusBlueprint * ikarus_blueprint_create( struct IkarusProject * project, char const * name, @@ -35,17 +50,14 @@ IkarusBlueprint * ikarus_blueprint_create_from_entity( IkarusBlueprintCreateFromEntityFlags flags, 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_ASCERTAIN( + nullptr, + "entity must not be null", + IkarusErrorInfo_Client_InvalidInput, + ikarus_entity_exists, + entity + ); IKARUS_VTRYRV_OR_FAIL( auto id, @@ -59,15 +71,308 @@ IkarusBlueprint * ikarus_blueprint_create_from_entity( CPPBASE_TRY(entity->project->db->execute( "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` = ?", id, entity->id )) - return cppbase::ok(entity->project->db->last_insert_rowid()); + return cppbase::ok(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 { + 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("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( + "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( + "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("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( + "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( + "SELECT COUNT(*) FROM `entity_blueprint_links` WHERE `blueprint` = ?", + blueprint->id + ) + ); + + return count; +} diff --git a/src/ikarus/objects/entity.cpp b/src/ikarus/objects/entity.cpp index aae7564..ad61368 100644 --- a/src/ikarus/objects/entity.cpp +++ b/src/ikarus/objects/entity.cpp @@ -80,6 +80,8 @@ IkarusEntity * ikarus_entity_copy(IkarusEntity * entity, IkarusEntityCopyFlags f entity->id )); + auto const id = entity->project->db->last_insert_rowid(); + CPPBASE_TRY(entity->project->db->execute( "INSERT INTO `entity_values`(`entity`, `name`, `value`) " "SELECT ?1, `name`, `value` FROM `entity_values` WHERE " @@ -101,7 +103,7 @@ IkarusEntity * ikarus_entity_copy(IkarusEntity * entity, IkarusEntityCopyFlags f 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 ** 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_VCALL(auto count, false, ikarus_entity_get_linked_blueprints_count, entity); + IKARUS_ASCERTAIN(nullptr, "entity doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_entity_exists, entity); + IKARUS_VCALL(auto count, nullptr, ikarus_entity_get_linked_blueprints_count, entity); std::int64_t ids[count]; @@ -495,7 +497,7 @@ bool ikarus_entity_has_property_value( IkarusEntityPropertyValue * 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( auto values_plain, @@ -503,9 +505,12 @@ ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, Ikar "failed to get property values for entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many( - "SELECT `e`.`property`, IFNULL(`e`.`value`, ikarus_default_value(`p`.`schema`)) FROM `entity_property_values` AS `e` " - "INNER JOIN `properties` AS `p` ON `p`.`id` = `e`.`property` " - " WHERE `e`.`entity` = ?", + "SELECT `p`.`id`, IFNULL(`v`.`value`, ikarus_default_value(`p`.`schema`)) " + "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` = ?", entity->id ) ); @@ -527,9 +532,9 @@ ikarus_entity_get_property_values(IkarusEntity * entity, size_t * size_out, Ikar char const * 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( - false, + nullptr, "property doesn't exist", IkarusErrorInfo_Client_NonExistent, ikarus_property_exists, @@ -543,6 +548,15 @@ ikarus_entity_get_property_value(IkarusEntity * entity, struct IkarusProperty * 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( auto value, nullptr, @@ -567,6 +581,8 @@ void ikarus_entity_set_property_value( IkarusEntitySetPropertyValueFlags flags, IkarusErrorData * error_out ) { + IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); + IKARUS_ASCERTAIN( IKARUS_VOID_RETURN, "entity doesn't exist", @@ -581,7 +597,6 @@ void ikarus_entity_set_property_value( ikarus_property_exists, property ); - IKARUS_FAIL_IF_NULL(value, IKARUS_VOID_RETURN); IKARUS_FAIL_IF( entity->project != property->project, @@ -650,6 +665,7 @@ void ikarus_entity_clear_property_value( "property does not belong to entity's project", IkarusErrorInfo_Client_NotLinked ); + IKARUS_ASCERTAIN( IKARUS_VOID_RETURN, "entity doesn't have property", diff --git a/src/ikarus/objects/property.cpp b/src/ikarus/objects/property.cpp index 43fac96..f6e8eb7 100644 --- a/src/ikarus/objects/property.cpp +++ b/src/ikarus/objects/property.cpp @@ -2,9 +2,316 @@ #include #include +#include #include #include IkarusProperty::IkarusProperty(struct IkarusProject * project, int64_t id): project{project}, 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("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("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("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 { + 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("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 + ) + ); +} diff --git a/src/ikarus/persistence/migrations/m0_initial_layout.sql b/src/ikarus/persistence/migrations/m0_initial_layout.sql index 50fb7f0..408eda1 100644 --- a/src/ikarus/persistence/migrations/m0_initial_layout.sql +++ b/src/ikarus/persistence/migrations/m0_initial_layout.sql @@ -10,8 +10,7 @@ CREATE TABLE `entity_values` `name` TEXT NOT NULL, `value` TEXT NOT NULL, - PRIMARY KEY (`id`), - UNIQUE (`entity`, `name`) + PRIMARY KEY (`entity`, `name`) ) STRICT; CREATE TABLE `blueprints` @@ -44,6 +43,5 @@ CREATE TABLE `entity_property_values` `property` INTEGER NOT NULL REFERENCES `properties` (`id`) ON DELETE CASCADE, `value` TEXT NOT NULL, - PRIMARY KEY (`id`), - UNIQUE (`entity`, `property`) + PRIMARY KEY (`entity`, `property`) ) STRICT;