insert query builder has many to many progress
This commit is contained in:
parent
f144b26dbb
commit
59e1533cca
|
|
@ -41,6 +41,7 @@ set(TEST_SOURCES
|
||||||
../../../test/backends/TableSequenceFixture.cpp
|
../../../test/backends/TableSequenceFixture.cpp
|
||||||
../../../test/backends/TableSequenceTest.cpp
|
../../../test/backends/TableSequenceTest.cpp
|
||||||
../../../test/backends/SessionInsertHasMany.cpp
|
../../../test/backends/SessionInsertHasMany.cpp
|
||||||
|
../../../test/backends/SessionInsertHasManyToManyTest.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIBRARY_TEST_TARGET PostgresTests)
|
set(LIBRARY_TEST_TARGET PostgresTests)
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ namespace matador::object {
|
||||||
template < class Type >
|
template < class Type >
|
||||||
class collection {
|
class collection {
|
||||||
public:
|
public:
|
||||||
using value_type = Type;
|
using value_type = typename collection_proxy<Type>::value_type;
|
||||||
using iterator = typename std::vector<value_type>::iterator;
|
using iterator = typename collection_proxy<Type>::iterator;
|
||||||
using const_iterator = typename std::vector<value_type>::const_iterator;
|
using const_iterator = typename collection_proxy<Type>::const_iterator;
|
||||||
|
|
||||||
collection()
|
collection()
|
||||||
: proxy_(std::make_shared<collection_proxy<Type>>()) {}
|
: proxy_(std::make_shared<collection_proxy<Type>>()) {}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ namespace matador::object {
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
class collection_proxy final {
|
class collection_proxy final {
|
||||||
public:
|
public:
|
||||||
|
using value_type = Type;
|
||||||
|
using iterator = typename std::vector<value_type>::iterator;
|
||||||
|
using const_iterator = typename std::vector<value_type>::const_iterator;
|
||||||
|
|
||||||
collection_proxy() = default;
|
collection_proxy() = default;
|
||||||
|
|
||||||
// Lazy
|
// Lazy
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@ public:
|
||||||
many_to_many_relation(std::string local_name, std::string remote_name)
|
many_to_many_relation(std::string local_name, std::string remote_name)
|
||||||
: local_name_(std::move(local_name))
|
: local_name_(std::move(local_name))
|
||||||
, remote_name_(std::move(remote_name)) {}
|
, remote_name_(std::move(remote_name)) {}
|
||||||
|
many_to_many_relation(std::string local_name, std::string remote_name, const object_ptr<LocalType>& local, const object_ptr<ForeignType>& remote)
|
||||||
|
: local_name_(std::move(local_name))
|
||||||
|
, remote_name_(std::move(remote_name))
|
||||||
|
, local_(local)
|
||||||
|
, remote_(remote)
|
||||||
|
{}
|
||||||
|
|
||||||
template<class Operator>
|
template<class Operator>
|
||||||
void process(Operator &op) {
|
void process(Operator &op) {
|
||||||
|
|
|
||||||
|
|
@ -128,14 +128,14 @@ public:
|
||||||
}
|
}
|
||||||
template<class CollectionType>
|
template<class CollectionType>
|
||||||
static void on_has_many(const char * /*id*/, object::collection<CollectionType> &/*con*/, const char *, const utils::foreign_attributes &/*attr*/) {}
|
static void on_has_many(const char * /*id*/, object::collection<CollectionType> &/*con*/, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
template<class Collection>
|
|
||||||
void on_has_many_to_many(const char *id, Collection &container, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes & ) {
|
template<class ForeignType>
|
||||||
|
void on_has_many_to_many(const char *id, object::collection<object::object_ptr<ForeignType>> &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) {
|
if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (current_entity_pk_ == 0ULL) {
|
|
||||||
// Without a local PK we cannot create relation rows
|
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
|
||||||
// (PK may be assigned later by Identity; that case needs a different mechanism than requested here)
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,38 +143,92 @@ public:
|
||||||
if (it == schema_.end()) {
|
if (it == schema_.end()) {
|
||||||
throw query_builder_exception(query_build_error::UnknownType);
|
throw query_builder_exception(query_build_error::UnknownType);
|
||||||
}
|
}
|
||||||
const table &relation_table = it->second.table();
|
|
||||||
|
|
||||||
for (auto &obj : container) {
|
using relation_value_type = object::many_to_many_relation<ObjectType, ForeignType>;
|
||||||
|
|
||||||
|
if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) {
|
||||||
|
throw query_builder_exception(query_build_error::InvalidRelationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &obj : objects) {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure target exists as dependency (deps first)
|
if (obj.is_persistent()) {
|
||||||
if (!obj.is_persistent()) {
|
|
||||||
using dep_t = std::remove_reference_t<decltype(*obj)>;
|
|
||||||
build_for<dep_t>(obj, steps_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract FK value from the foreign object
|
|
||||||
insert_step rel_step{};
|
|
||||||
const auto pk = rel_step.pk_accessor.get(*obj);
|
|
||||||
if (pk.is_valid()) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build INSERT into relation table with the 2 FK columns
|
// Ensure target exists as dependency (deps first)
|
||||||
// rel_step.query = executable_query{
|
if (obj.is_transient()) {
|
||||||
// insert()
|
build_for(obj, relation_steps_);
|
||||||
// .into(relation_table, {table_column{&relation_table, join_column}, table_column{&relation_table, inverse_join_column}})
|
}
|
||||||
// .values({utils::database_type{current_entity_pk_}, utils::database_type{fk.value}})
|
|
||||||
// };
|
auto rel = object::make_object<relation_value_type>(join_column, inverse_join_column, ptr_, obj);
|
||||||
|
|
||||||
|
// Extract FK value from the foreign object
|
||||||
|
insert_step rel_step{};
|
||||||
|
// const auto pk = rel_step.pk_accessor.get(*obj);
|
||||||
|
// if (pk.is_valid()) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
relation_steps_.push_back(std::move(rel_step));
|
relation_steps_.push_back(std::move(rel_step));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template<class C>
|
template<class ForeignType>
|
||||||
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
|
void on_has_many_to_many(const char *id, object::collection<object::object_ptr<ForeignType>> &objects, const utils::foreign_attributes &attr) {
|
||||||
|
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
object::join_columns_collector collector;
|
||||||
|
auto join_columns = collector.collect<ForeignType>();
|
||||||
|
|
||||||
|
const auto it = schema_.find(std::string{id});
|
||||||
|
if (it == schema_.end()) {
|
||||||
|
throw query_builder_exception(query_build_error::UnknownType);
|
||||||
|
}
|
||||||
|
|
||||||
|
using relation_value_type = object::many_to_many_relation<ForeignType, ObjectType>;
|
||||||
|
|
||||||
|
if (std::type_index(typeid(relation_value_type)) != it->second.node().info().type_index()) {
|
||||||
|
throw query_builder_exception(query_build_error::InvalidRelationType);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &obj : objects) {
|
||||||
|
if (!obj) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj.is_persistent()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure target exists as dependency (deps first)
|
||||||
|
if (obj.is_transient()) {
|
||||||
|
build_for(obj, relation_steps_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rel = object::make_object<relation_value_type>(join_columns.inverse_join_column, join_columns.join_column, obj, ptr_);
|
||||||
|
|
||||||
|
// Extract FK value from the foreign object
|
||||||
|
insert_step rel_step{};
|
||||||
|
// const auto pk = rel_step.pk_accessor.get(*obj);
|
||||||
|
// if (pk.is_valid()) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
relation_steps_.push_back(std::move(rel_step));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class CollectionType>
|
||||||
|
void process_has_many_to_many(const char *id, CollectionType &objects, const char *join_column, const char *inverse_join_column) {
|
||||||
|
if (id == nullptr || join_column == nullptr || inverse_join_column == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
template<class EntityType>
|
template<class EntityType>
|
||||||
static std::pair<std::type_index, const void *> make_visit_key(const object::object_ptr<EntityType> &ptr) {
|
static std::pair<std::type_index, const void *> make_visit_key(const object::object_ptr<EntityType> &ptr) {
|
||||||
|
|
@ -264,9 +318,6 @@ private:
|
||||||
std::vector<insert_step> steps_;
|
std::vector<insert_step> steps_;
|
||||||
std::vector<insert_step> relation_steps_;
|
std::vector<insert_step> relation_steps_;
|
||||||
std::unordered_set<std::pair<std::type_index, const void *>, visit_key_hash> visited_;
|
std::unordered_set<std::pair<std::type_index, const void *>, visit_key_hash> visited_;
|
||||||
|
|
||||||
|
|
||||||
std::uint64_t current_entity_pk_{0};
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP
|
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP
|
||||||
|
|
@ -13,7 +13,8 @@ enum class query_build_error : std::uint8_t {
|
||||||
MissingPrimaryKey,
|
MissingPrimaryKey,
|
||||||
MissingObject,
|
MissingObject,
|
||||||
UnexpectedError,
|
UnexpectedError,
|
||||||
QueryError
|
QueryError,
|
||||||
|
InvalidRelationType
|
||||||
};
|
};
|
||||||
|
|
||||||
class query_builder_exception final : public std::exception {
|
class query_builder_exception final : public std::exception {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#include "catch2/catch_test_macros.hpp"
|
||||||
|
|
||||||
|
#include "SessionFixture.hpp"
|
||||||
|
|
||||||
|
#include "connection.hpp"
|
||||||
|
|
||||||
|
#include "models/recipe.hpp"
|
||||||
|
|
||||||
|
using namespace matador;
|
||||||
|
using namespace matador::object;
|
||||||
|
using namespace matador::test;
|
||||||
|
|
||||||
|
TEST_CASE_METHOD(SessionFixture, "Test insert object with has many to many relation", "[session][insert][has_many_to_many]") {
|
||||||
|
auto result = schema.attach<recipe>("recipes")
|
||||||
|
.and_then( [this] { return schema.attach<ingredient>("ingredients"); } )
|
||||||
|
.and_then([this] { return schema.create(db); } );
|
||||||
|
|
||||||
|
orm::session ses({bus, connection::dns, 4}, schema);
|
||||||
|
|
||||||
|
std::vector ingredients {
|
||||||
|
make_object<ingredient>(1, "Apple"),
|
||||||
|
make_object<ingredient>(2, "Strawberry"),
|
||||||
|
make_object<ingredient>(3, "Pineapple"),
|
||||||
|
make_object<ingredient>(4, "Sugar"),
|
||||||
|
make_object<ingredient>(5, "Flour"),
|
||||||
|
make_object<ingredient>(6, "Butter"),
|
||||||
|
make_object<ingredient>(7, "Beans")
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector recipes {
|
||||||
|
make_object<recipe>(1, "Apple Pie", std::vector{ingredients[0], ingredients[3], ingredients[4]}),
|
||||||
|
make_object<recipe>(2, "Strawberry Cake", std::vector{ingredients[5], ingredients[6]}),
|
||||||
|
make_object<recipe>(3, "Pineapple Pie", std::vector{ingredients[0], ingredients[1], ingredients[2]})
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto &r: recipes) {
|
||||||
|
auto res = ses.insert(r);
|
||||||
|
REQUIRE(res.is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue