diff --git a/include/matador/query/insert_query_builder.hpp b/include/matador/query/insert_query_builder.hpp index 4e351e1..14f5882 100644 --- a/include/matador/query/insert_query_builder.hpp +++ b/include/matador/query/insert_query_builder.hpp @@ -4,6 +4,7 @@ #include #include "matador/object/collection.hpp" +#include "matador/object/object_ptr.hpp" #include "matador/query/basic_schema.hpp" #include "matador/query/intermediates/executable_query.hpp" @@ -56,38 +57,117 @@ private: std::string join_column_; }; -template -class insert_query_builder { +template +static std::pair make_visit_key(const EntityType &ptr) { + return {std::type_index(typeid(EntityType)), static_cast(&ptr)}; +} + +struct visit_key_hash { + size_t operator()(const std::pair &p) const noexcept { + // combine hashes (simple + sufficient here) + const size_t h1 = p.first.hash_code(); + const size_t h2 = std::hash{}(p.second); + return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); + } +}; + +struct processing_many_to_many_key { + std::string id; + std::type_index local_type{typeid(void)}; + std::type_index foreign_type{typeid(void)}; + bool operator==(processing_many_to_many_key const &other) const { + return local_type == other.local_type && foreign_type == other.foreign_type && id == other.id; + } +}; + +template +static processing_many_to_many_key make_processing_many_to_many_key(const std::string &id) { + return {id, std::type_index(typeid(LocalType)), std::type_index(typeid(ForeignType))}; +} + +struct processing_many_to_many_key_hash { + size_t operator()(const processing_many_to_many_key &p) const noexcept { + size_t seed = std::hash{}(p.local_type); + + const size_t foreign_hash = std::hash{}(p.foreign_type); + seed ^= foreign_hash + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + + const size_t id_hash = std::hash{}(p.id); + seed ^= id_hash + 0x9e3779b97f4a7c15ULL + (seed << 6) + (seed >> 2); + + return seed; + } +}; + +struct insert_context { + const basic_schema &schema_; + const std::unordered_map &contexts_by_type_; + + std::vector> steps_; + std::vector> relation_steps_; + std::unordered_set, visit_key_hash> visited_; + std::unordered_set processing_many_to_many_relations_; + +}; + +template < typename ObjectType > +class insert_step_processor { public: - explicit insert_query_builder(const basic_schema &schema, const std::unordered_map &contexts_by_type) - : schema_(schema) - , contexts_by_type_{contexts_by_type} + explicit insert_step_processor(insert_context &ctx) + : ctx_{ctx} {} - utils::result>, utils::error> build(const object::object_ptr &ptr) { - if (const auto it = schema_.find(typeid(ObjectType)); it == schema_.end()) { - return utils::failure(utils::error{error_code::UnknownType, "Unknown type for insert query"}); + utils::result build(object::object_ptr ptr) { + if (!ptr) { + return utils::failure(utils::error{error_code::InvalidObject, "Object is null"}); } - - relation_steps_.clear(); - processing_many_to_many_relations_.clear(); - steps_.clear(); - visited_.clear(); - ptr_ = ptr; - const auto result = build_insert_steps(ptr, steps_); + + const auto key = make_visit_key(*ptr_); + if (ctx_.visited_.find(key) != ctx_.visited_.end()) { + return utils::ok(); + } + ctx_.visited_.insert(key); + + const auto it = ctx_.schema_.find(typeid(ObjectType)); + if (it == ctx_.schema_.end()) { + return utils::failure(utils::error{error_code::UnknownType, "Unknown type"}); + } + + // 1) Traverse relations first => dependencies will be inserted before this object + try { + access::process(*this, *ptr_); + + // relation inserts must run after all entity inserts were collected + for (auto &s : ctx_.relation_steps_) { + ctx_.steps_.push_back(std::move(s)); + } + ctx_.relation_steps_.clear(); + } catch (const query_builder_exception &ex) { + return utils::failure(ex.error()); + } + + // 2) Build INSERT for this object + const auto &info = it->second.node().info(); + if (!info.has_primary_key() || it->second.pk_generator().type() == utils::generator_type::None) { + return utils::failure(utils::error{error_code::MissingPrimaryKey, "Type " + info.name() + " has no primary key"}); + } + const auto cit = ctx_.contexts_by_type_.find(it->second.node().info().type_index()); + if (cit == ctx_.contexts_by_type_.end()) { + return utils::failure(utils::error{error_code::UnknownType, "Unknown type"}); + } + + if (it->second.pk_generator().type() == utils::generator_type::Manual) { + ctx_.steps_.push_back(std::make_unique>(cit->second.insert, ptr_)); + } else if (it->second.pk_generator().type() == utils::generator_type::Identity) { + ctx_.steps_.push_back(std::make_unique>(cit->second.insert, ptr_, info.primary_key_attribute()->name())); + } else { + ctx_.steps_.push_back(std::make_unique>(cit->second.insert, ptr_, it->second.pk_generator())); + } + ptr_.reset(); - if (!result) { - return utils::failure(result.err()); - } - // relation inserts must run after all entity inserts were collected - for (auto &s : relation_steps_) { - steps_.push_back(std::move(s)); - } - relation_steps_.clear(); - - return utils::ok(std::move(steps_)); + return utils::ok(); } template < class PrimaryKeyType > @@ -105,6 +185,7 @@ public: void on_has_one(const char * /*id*/, Pointer &obj, const char * /*join_column*/, const utils::foreign_attributes &attr) { on_foreign_object(obj, attr); } + template void on_has_many(const char * /*id*/, object::collection> &objects, @@ -118,12 +199,17 @@ public: } has_many_linker linker(ptr_, join_column); + insert_context ctx{ctx_.schema_, ctx_.contexts_by_type_}; + insert_step_processor processor{ctx}; for (auto &obj : objects) { - if (obj.is_transient()) { - const auto result = build_insert_steps(obj, relation_steps_); - if (!result) { - throw query_builder_exception(error_code::InvalidObject, "Invalid object"); - } + if (!obj.is_transient()) { + continue; + } + + const auto result = processor.build(obj); + if (!result) { + throw query_builder_exception(error_code::InvalidObject, "Invalid object"); + // return utils::failure(result.err()); } access::process(linker, *obj); @@ -142,8 +228,8 @@ public: return; } - const auto it = schema_.find(std::string{id}); - if (it == schema_.end()) { + const auto it = ctx_.schema_.find(std::string{id}); + if (it == ctx_.schema_.end()) { throw query_builder_exception(error_code::UnknownType, "Unknown type " + std::string{id}); } @@ -152,29 +238,40 @@ public: throw query_builder_exception(error_code::InvalidRelationType, "Invalid relation type"); } - const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); - if (cit == contexts_by_type_.end()) { + const auto cit = ctx_.contexts_by_type_.find(it->second.node().info().type_index()); + if (cit == ctx_.contexts_by_type_.end()) { throw query_builder_exception(error_code::UnknownType, "Unknown type" + std::string{id}); } for (auto &obj : objects) { auto rel = object::make_object(join_column, "value", ptr_, obj); - relation_steps_.push_back(std::make_unique>(cit->second.insert, rel)); + ctx_.relation_steps_.push_back(std::make_unique>(cit->second.insert, rel)); } } - template - void on_has_many_to_many(const char *id, object::collection> &objects, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr) { + template + void on_has_many_to_many(const char *id, + object::collection> &objects, + const char *join_column, + const char *inverse_join_column, + const utils::foreign_attributes &attr) { if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) { return; } using relation_value_type = object::many_to_many_relation; + const std::type_index foreign_type{typeid(ForeignType)}; + const std::type_index local_type{typeid(ObjectType)}; insert_many_to_many_relations( id, objects, attr, + [foreign_type, local_type](const char* relation_name) -> processing_many_to_many_key { + std::cout << "Processing many-to-many relation: " << local_type.name() << ":" << foreign_type.name() << ":" << relation_name << std::endl; + return {std::string{relation_name}, local_type, foreign_type}; + // return make_processing_many_to_many_key(relation_name); + }, [this, join_column, inverse_join_column](const auto &obj) { return object::make_object(join_column, inverse_join_column, ptr_, obj); }); @@ -193,90 +290,56 @@ public: } using relation_value_type = object::many_to_many_relation; + const std::type_index foreign_type{typeid(ForeignType)}; + const std::type_index local_type{typeid(ObjectType)}; insert_many_to_many_relations( id, objects, attr, + [foreign_type, local_type](const char* relation_name) -> processing_many_to_many_key { + std::cout << "Processing many-to-many relation: " << foreign_type.name() << ":" << local_type.name() << ":" << relation_name << std::endl; + return {std::string{relation_name}, foreign_type, local_type}; + return make_processing_many_to_many_key(relation_name); + }, [this, join_columns = std::move(join_columns)](const auto &obj) { return object::make_object(join_columns.inverse_join_column, join_columns.join_column, obj, ptr_); }); } private: - template - static std::pair make_visit_key(const EntityType &ptr) { - return {std::type_index(typeid(EntityType)), static_cast(&ptr)}; + template + void on_foreign_object(object::object_ptr &obj, const utils::foreign_attributes &attr) { + if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert) || !obj || obj.is_transient()) { + return; + } + + insert_context ctx{ctx_.schema_, ctx_.contexts_by_type_}; + insert_step_processor processor{ctx}; + + const auto result = processor.build(obj); + if (!result) { + throw query_builder_exception(error_code::InvalidObject, "Invalid object"); + // return utils::failure(result.err()); + } } - struct visit_key_hash { - size_t operator()(const std::pair &p) const noexcept { - // combine hashes (simple + sufficient here) - const size_t h1 = p.first.hash_code(); - const size_t h2 = std::hash{}(p.second); - return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); - } - }; - - template - utils::result build_insert_steps(const object::object_ptr &ptr, std::vector> &steps) { - if (!ptr) { - return utils::failure(utils::error{error_code::InvalidObject, "Object is null"}); - } - - const auto key = make_visit_key(*ptr); - if (visited_.find(key) != visited_.end()) { - return utils::ok(); - } - visited_.insert(key); - - const auto it = schema_.find(typeid(EntityType)); - if (it == schema_.end()) { - return utils::failure(utils::error{error_code::UnknownType, "Unknown type"}); - } - - // 1) Traverse relations first => dependencies will be inserted before this object - try { - access::process(*this, *ptr); - } catch (const query_builder_exception &ex) { - return utils::failure(ex.error()); - } - - // 2) Build INSERT for this object - const auto &info = it->second.node().info(); - if (!info.has_primary_key() || it->second.pk_generator().type() == utils::generator_type::None) { - return utils::failure(utils::error{error_code::MissingPrimaryKey, "Type " + info.name() + " has no primary key"}); - } - const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); - if (cit == contexts_by_type_.end()) { - return utils::failure(utils::error{error_code::UnknownType, "Unknown type"}); - } - - if (it->second.pk_generator().type() == utils::generator_type::Manual) { - steps.push_back(std::make_unique>(cit->second.insert, ptr)); - } else if (it->second.pk_generator().type() == utils::generator_type::Identity) { - steps.push_back(std::make_unique>(cit->second.insert, ptr, info.primary_key_attribute()->name())); - } else { - steps.push_back(std::make_unique>(cit->second.insert, ptr, it->second.pk_generator())); - } - - return utils::ok(); - } - - template - void insert_many_to_many_relations(const char *id, - object::collection> &objects, - const utils::foreign_attributes &attr, - RelationFactory make_relation) { + template + void insert_many_to_many_relations(const char *id, + object::collection> &objects, + const utils::foreign_attributes &attr, + RelationKeyFactory make_relation_key, + RelationFactory make_relation) { if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) { return; } - if (processing_many_to_many_relations_.find(id) != processing_many_to_many_relations_.end()) { + const auto key = make_relation_key(id); + if (ctx_.processing_many_to_many_relations_.find(key) != ctx_.processing_many_to_many_relations_.end()) { return; } - const auto it = schema_.find(std::string{id}); - if (it == schema_.end()) { + const auto it = ctx_.schema_.find(std::string{id}); + if (it == ctx_.schema_.end()) { throw query_builder_exception(error_code::UnknownType, "Unknown type"); } @@ -284,24 +347,23 @@ private: throw query_builder_exception(error_code::InvalidRelationType, "Invalid relation type"); } - const auto cit = contexts_by_type_.find(it->second.node().info().type_index()); - if (cit == contexts_by_type_.end()) { + const auto cit = ctx_.contexts_by_type_.find(it->second.node().info().type_index()); + if (cit == ctx_.contexts_by_type_.end()) { throw query_builder_exception(error_code::UnknownType, "Unknown type"); } - std::ignore = processing_many_to_many_relations_.insert(id); + std::ignore = ctx_.processing_many_to_many_relations_.insert(key); std::vector> insert_relation_steps; + insert_step_processor processor(ctx_); for (auto &obj : objects) { - if (!obj) { + if (!obj || !obj.is_transient()) { continue; } - // Ensure target exists as dependency (deps first) - if (obj.is_transient()) { - const auto result = build_insert_steps(obj, relation_steps_); - if (!result) { - throw query_builder_exception(error_code::InvalidObject, "Invalid object"); - } + const auto result = processor.build(obj); + if (!result) { + throw query_builder_exception(error_code::InvalidObject, "Invalid object"); + // return utils::failure(result.err()); } auto rel = make_relation(obj); @@ -310,43 +372,43 @@ private: insert_relation_steps.push_back(std::make_unique>(cit->second.insert, rel)); } - relation_steps_.insert( - relation_steps_.end(), + ctx_.relation_steps_.insert( + ctx_.relation_steps_.end(), std::make_move_iterator(insert_relation_steps.begin()), std::make_move_iterator(insert_relation_steps.end())); - processing_many_to_many_relations_.erase(id); - } - template - void on_foreign_object(Pointer &obj, const utils::foreign_attributes &attr) { - if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) { - return; - } - - if (!obj) { - return; - } - - if (obj.is_persistent()) { - return; - } - - using dep_t = std::remove_reference_t; - const auto result = build_insert_steps(obj, steps_); - if (!result) { - throw query_builder_exception(error_code::InvalidObject, "Invalid object"); - } + ctx_.processing_many_to_many_relations_.erase(key); } private: - const basic_schema &schema_; - const std::unordered_map &contexts_by_type_; - + insert_context& ctx_; object::object_ptr ptr_; +}; - std::vector> steps_; - std::vector> relation_steps_; - std::unordered_set, visit_key_hash> visited_; - std::unordered_set processing_many_to_many_relations_; +template +class insert_query_builder { +public: + explicit insert_query_builder(const basic_schema &schema, const std::unordered_map &contexts_by_type) + : context_{schema, contexts_by_type} + {} + + utils::result>, utils::error> build(const object::object_ptr &ptr) { + if (const auto it = context_.schema_.find(typeid(ObjectType)); it == context_.schema_.end()) { + return utils::failure(utils::error{error_code::UnknownType, "Unknown type for insert query"}); + } + + insert_context ctx{context_.schema_, context_.contexts_by_type_}; + insert_step_processor processor{ctx}; + + const auto result = processor.build(ptr); + if (!result) { + return utils::failure(result.err()); + } + + return utils::ok(std::move(ctx.steps_)); + } + +private: + insert_context context_; }; } #endif //MATADOR_INSERT_QUERY_BUILDER_HPP \ No newline at end of file