collection has-many-to-many resolve progress

This commit is contained in:
Sascha Kühl 2026-02-03 15:36:58 +01:00
parent 73abd61eba
commit 4d886f0343
7 changed files with 135 additions and 45 deletions

View File

@ -121,8 +121,10 @@ int main() {
using namespace matador::query::meta;
using namespace matador::utils;
const auto payloads = PAYLOAD.as( "payloads" );
const auto tasks = TASK.as( "tasks" );
const auto stmt1 = query:: query::select( { tasks.payload, payloads.id } )
.from( tasks, payloads )
.where ( payloads.id == tasks.payload )

View File

@ -96,18 +96,42 @@ public:
}
template<class CollectionType>
void on_has_many_to_many(const char *, CollectionType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &/*attr*/) {
const auto it = schema_.find(typeid(typename CollectionType::value_type::value_type));
void on_has_many_to_many(const char *id, CollectionType &, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {
const auto it = schema_.find(id);
if (it == schema_.end()) {
throw query_builder_exception{query_build_error::UnknownType};
}
if (!it->second.node().info().has_primary_key()) {
throw query_builder_exception{query_build_error::MissingPrimaryKey};
auto producer = std::make_unique<query_collection_resolver_producer<typename CollectionType::value_type>>(
schema_,
it->second.table(),
inverse_join_column,
root_type_,
join_column);
const object::collection_composite_key key{root_type_, typeid(typename CollectionType::value_type), inverse_join_column};
schema_.collection_resolver_producers_[key] = std::move(producer);
}
template<class CollectionType>
void on_has_many_to_many(const char *id, CollectionType &, const utils::foreign_attributes &/*attr*/) {
const auto it = schema_.find(id);
if (it == schema_.end()) {
throw query_builder_exception{query_build_error::UnknownType};
}
object::join_columns_collector collector;
const auto jc = collector.collect<typename CollectionType::value_type::value_type>();
auto producer = std::make_unique<query_collection_resolver_producer<typename CollectionType::value_type>>(
schema_,
it->second.table(),
jc.join_column,
root_type_,
jc.inverse_join_column);
const object::collection_composite_key key{root_type_, typeid(typename CollectionType::value_type), jc.join_column};
schema_.collection_resolver_producers_[key] = std::move(producer);
}
template<class ContainerType>
static void on_has_many_to_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
private:
basic_schema& schema_;

View File

@ -18,6 +18,7 @@
#include "matador/object/object_proxy.hpp"
#include "matador/object/object_ptr.hpp"
#include "matador/object/collection_proxy.hpp"
#include "matador/object/join_columns_collector.hpp"
#include <memory>
#include <stack>
@ -112,26 +113,15 @@ public:
on_foreign_key(x, attr);
}
template<class ContainerType>
void on_has_many_to_many(const char *, ContainerType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &attr) {
if (attr.fetch() == utils::fetch_type::Lazy) {
} else {}
}
template<class ContainerType>
void on_has_many_to_many(const char *, ContainerType &, const utils::foreign_attributes &attr) {
if (attr.fetch() == utils::fetch_type::Lazy) {
} else {}
}
template<class CollectionType>
void on_has_many(const char * /*id*/, CollectionType &cont, const char *join_column, const utils::foreign_attributes &attr, std::enable_if_t<object::is_object_ptr<typename CollectionType::value_type>::value> * = nullptr);
template<class CollectionType>
void on_has_many(const char * /*id*/, CollectionType &, const char * /*join_column*/, const utils::foreign_attributes &/*attr*/, std::enable_if_t<!object::is_object_ptr<typename CollectionType::value_type>::value> * = nullptr) {
}
template<class CollectionType>
void on_has_many_to_many(const char *id, CollectionType &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes &attr);
template<class CollectionType>
void on_has_many_to_many(const char *, CollectionType &, const utils::foreign_attributes &attr);
template<class Type>
void bind(const Type &obj) {
@ -186,7 +176,7 @@ protected:
std::unordered_set<object::collection_composite_key, object::collection_composite_key_hash> initialized_collections_;
};
template<class CollectionType>
template <class CollectionType>
void query_result_impl::on_has_many(const char *, CollectionType &cont, const char *join_column, const utils::foreign_attributes &attr, std::enable_if_t<object::is_object_ptr<typename CollectionType::value_type>::value> *) {
using value_type = typename CollectionType::value_type::value_type;
auto object_resolver = resolver_->object_resolver<value_type>();
@ -212,6 +202,59 @@ void query_result_impl::on_has_many(const char *, CollectionType &cont, const ch
}
}
template <class CollectionType>
void query_result_impl::on_has_many_to_many(const char *id, CollectionType &cont, const char *join_column, const char *, const utils::foreign_attributes &attr) {
using value_type = typename CollectionType::value_type::value_type;
auto object_resolver = resolver_->object_resolver<value_type>();
auto resolver = resolver_->collection_resolver<typename CollectionType::value_type>(result_type_, join_column);
if (attr.fetch() == utils::fetch_type::Lazy) {
cont.reset(std::make_shared<object::collection_proxy<typename CollectionType::value_type>>(resolver, current_pk_));
} else {
if (initialized_collections_.insert({result_type_, typeid(typename CollectionType::value_type), std::string{id}}).second) {
cont.reset(std::make_shared<object::collection_proxy<typename CollectionType::value_type>>(resolver, std::vector<typename CollectionType::value_type>()));
}
const auto ti = std::type_index(typeid(value_type));
type_stack_.push(ti);
auto obj = std::make_shared<typename CollectionType::value_type::value_type>();
access::process(*this, *obj);
type_stack_.pop();
auto ptr = typename CollectionType::value_type(std::make_shared<object::object_proxy<value_type>>(object_resolver, obj));
const auto pk = ptr.primary_key();
if (ptr.primary_key().is_valid()) {
cont.push_back(ptr);
}
}
}
template <class CollectionType>
void query_result_impl::on_has_many_to_many(const char *id, CollectionType &cont, const utils::foreign_attributes &attr) {
using value_type = typename CollectionType::value_type::value_type;
object::join_columns_collector collector;
const auto jc = collector.collect<typename CollectionType::value_type::value_type>();
auto object_resolver = resolver_->object_resolver<value_type>();
auto resolver = resolver_->collection_resolver<typename CollectionType::value_type>(result_type_, jc.inverse_join_column);
if (attr.fetch() == utils::fetch_type::Lazy) {
cont.reset(std::make_shared<object::collection_proxy<typename CollectionType::value_type>>(resolver, current_pk_));
} else {
if (initialized_collections_.insert({result_type_, typeid(typename CollectionType::value_type), std::string{id}}).second) {
cont.reset(std::make_shared<object::collection_proxy<typename CollectionType::value_type>>(resolver, std::vector<typename CollectionType::value_type>()));
}
const auto ti = std::type_index(typeid(value_type));
type_stack_.push(ti);
auto obj = std::make_shared<typename CollectionType::value_type::value_type>();
access::process(*this, *obj);
type_stack_.pop();
auto ptr = typename CollectionType::value_type(std::make_shared<object::object_proxy<value_type>>(object_resolver, obj));
const auto pk = ptr.primary_key();
if (ptr.primary_key().is_valid()) {
cont.push_back(ptr);
}
}
}
template<class Type>
bool query_result_impl::fetch(Type &obj) {
bool first = true;

View File

@ -187,10 +187,7 @@ utils::result<object::object_ptr<Type>, utils::error> statement::fetch_one() {
});
auto first = records.begin();
if (first == records.end()) {
return utils::failure(utils::error{
error_code::FETCH_FAILED,
"Failed to find entity."
});
return utils::failure(utils::error{error_code::FETCH_FAILED,"Failed to find entity."});
}
return utils::ok(first.optr());
@ -198,6 +195,7 @@ utils::result<object::object_ptr<Type>, utils::error> statement::fetch_one() {
template<class Type>
utils::result<std::shared_ptr<Type>, utils::error> statement::fetch_one_raw() {
std::cout << statement_proxy_->sql() << std::endl;
auto result = statement_proxy_->fetch(*bindings_);
if (!result.is_ok()) {
return utils::failure(result.err());

View File

@ -47,7 +47,7 @@ connection::connection(const std::string& dns, const logger_ptr &sql_logger)
connection::connection(const std::string &dns,
const std::shared_ptr<resolver_service> &resolver,
const logger_ptr &sql_logger)
: connection(connection_info::parse(dns), std::move(resolver), sql_logger)
: connection(connection_info::parse(dns), resolver, sql_logger)
{}
connection::connection(const connection &x) {
@ -165,6 +165,7 @@ utils::result<bool, utils::error> connection::exists(const std::string &table_na
utils::result<size_t, utils::error> connection::execute(const std::string &sql) const {
logger_->on_execute(sql);
std::cout << sql << std::endl;
return connection_->execute(sql);
}
@ -203,6 +204,7 @@ bool has_unknown_columns(const std::vector<object::attribute> &columns) {
utils::result<std::unique_ptr<query_result_impl>, utils::error> connection::fetch(const query_context &ctx) const {
logger_->on_fetch(ctx.sql);
std::cout << ctx.sql << std::endl;
return connection_->fetch(ctx);
}

View File

@ -821,6 +821,10 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel
.and_then( [this] { return repo.attach<recipe>("recipes"); } )
.and_then([this] {return repo.create(db); });
REQUIRE(result.is_ok());
repo.initialize_executor(db);
REQUIRE(db.exists(RECIPE.table_name()));
REQUIRE(db.exists(INGREDIENT.table_name()));
REQUIRE(db.exists(RECIPE_INGREDIENT.table_name()));
@ -845,9 +849,9 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel
}
std::vector<recipe> recipes{
{7, "Apple Crumble"},
{8, "Beans Chili"},
{9, "Fruit Salad"}
{8, "Apple Crumble"},
{9, "Beans Chili"},
{10, "Fruit Salad"}
};
for (const auto &r: recipes) {
@ -860,14 +864,14 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel
}
std::vector<std::pair<int, int>> recipe_ingredients {
{ 7, 1 },
{ 7, 4 },
{ 7, 5 },
{ 8, 6 },
{ 8, 7 },
{ 9, 1 },
{ 9, 2 },
{ 9, 3 }
{ 8, 1 },
{ 8, 4 },
{ 8, 5 },
{ 9, 6 },
{ 9, 7 },
{ 10, 1 },
{ 10, 2 },
{ 10, 3 }
};
for (const auto & [recipe_id, ingredient_id]: recipe_ingredients) {
@ -883,15 +887,31 @@ TEST_CASE_METHOD(QueryFixture, "Test load entity with eager has many to many rel
const auto ri= RECIPE_INGREDIENT.as("ri");
const auto i = INGREDIENT.as("i");
auto ingredients_result = query::select({r.id, r.name, ri.ingredient_id, i.name})
auto recipes_result = query::select({r.id, r.name })
.from(r)
.join_left(ri).on(r.id == ri.recipe_id)
.join_left(i).on(ri.ingredient_id == i.id)
.order_by({r.id}).asc()
.fetch_all<recipe>(db);
REQUIRE(recipes_result.is_ok());
for (const auto &reps : *recipes_result) {
REQUIRE(!reps->ingredients.empty());
for (const auto &ingr : reps->ingredients) {
std::cout << ingr->name << " (" << reps->name << ")" << std::endl;
}
}
auto ingredients_result = query::select({i.id, i.name, ri.recipe_id, r.name})
.from(i)
.join_left(ri).on(i.id == ri.ingredient_id)
.join_left(r).on(ri.recipe_id == r.id)
.order_by({i.id, r.id}).asc()
.fetch_all<ingredient>(db);
REQUIRE(ingredients_result.is_ok());
for (const auto &ing : *ingredients_result) {
REQUIRE(!ing->recipes.empty());
for (const auto &ingr : *ingredients_result) {
REQUIRE(!ingr->recipes.empty());
for (const auto &recipe : ingr->recipes) {
std::cout << recipe->name << " (" << ingr->name << ")" << std::endl;
}
}
}

View File

@ -5,6 +5,7 @@
#include "matador/utils/foreign_attributes.hpp"
#include "matador/object/object_ptr.hpp"
#include "matador/object/collection.hpp"
#include "matador/object/many_to_many_relation.hpp"
#include <string>
@ -15,7 +16,7 @@ struct recipe;
struct ingredient {
unsigned int id{};
std::string name;
std::vector<object::object_ptr<recipe> > recipes{};
object::collection<object::object_ptr<recipe> > recipes{};
ingredient() = default;
@ -35,7 +36,7 @@ struct ingredient {
struct recipe {
unsigned int id{};
std::string name;
std::vector<object::object_ptr<ingredient> > ingredients{};
object::collection<object::object_ptr<ingredient> > ingredients{};
recipe() = default;