Compare commits
3 Commits
31e9c7e9ac
...
fa7ff832c9
| Author | SHA1 | Date |
|---|---|---|
|
|
fa7ff832c9 | |
|
|
30e2e0c221 | |
|
|
85f87367c0 |
43
Todo.md
43
Todo.md
|
|
@ -3,4 +3,45 @@
|
||||||
- Add is_valid() method to connection & connection_impl
|
- Add is_valid() method to connection & connection_impl
|
||||||
- Read in entity fields
|
- Read in entity fields
|
||||||
- Add special handling for update in backends
|
- Add special handling for update in backends
|
||||||
- Add ODBC/SQL Server backend
|
- Add ODBC/SQL Server backend
|
||||||
|
|
||||||
|
|
||||||
|
Fetch eager strategies
|
||||||
|
======================
|
||||||
|
|
||||||
|
ONE TO ONE/MANY
|
||||||
|
*person* *address*
|
||||||
|
- has one address - belongs to person
|
||||||
|
|
||||||
|
=> join "address" on "person.id" == "address.person_id"
|
||||||
|
|
||||||
|
*address* *person*
|
||||||
|
- belongs to person - has one address
|
||||||
|
|
||||||
|
=> join "person" on "address.person_id" == "person.id"
|
||||||
|
|
||||||
|
*book* *author*
|
||||||
|
- belongs to author - has many books
|
||||||
|
|
||||||
|
- => join "author" on "book.author_id" == "author.id"
|
||||||
|
|
||||||
|
HAS MANY TO ONE (WITHOUT RELATION TABLE)
|
||||||
|
|
||||||
|
*author* *book*
|
||||||
|
- has many books - belongs to author
|
||||||
|
|
||||||
|
- => join "book" on "author.id" == "book.author_id"
|
||||||
|
|
||||||
|
if "has many" type has primary key & field "author_id"
|
||||||
|
if table name belongs to entity template type?
|
||||||
|
|
||||||
|
HAS MANY TO MANY (WITHOUT RELATION TABLE)
|
||||||
|
|
||||||
|
*student* *student_course* *course*
|
||||||
|
- has many courses - belongs to student
|
||||||
|
- belongs to course - has many students
|
||||||
|
|
||||||
|
=> join "student_course" on "student.id" == "student_course.student_id"
|
||||||
|
join "student_course" on "course.id" == "student_course.course_id"
|
||||||
|
|
||||||
|
if has many type hasn't primary key (is relation table)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ public:
|
||||||
LIKE
|
LIKE
|
||||||
};
|
};
|
||||||
|
|
||||||
virtual std::string evaluate(dialect &dialect, query_context &query) const = 0;
|
virtual std::string evaluate(const dialect &dialect, query_context &query) const = 0;
|
||||||
|
|
||||||
static std::unordered_map<operand_t, std::string> operands;
|
static std::unordered_map<operand_t, std::string> operands;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ struct column
|
||||||
column(const char *name); // NOLINT(*-explicit-constructor)
|
column(const char *name); // NOLINT(*-explicit-constructor)
|
||||||
column(std::string name); // NOLINT(*-explicit-constructor)
|
column(std::string name); // NOLINT(*-explicit-constructor)
|
||||||
column(sql_function_t func, std::string name); // NOLINT(*-explicit-constructor)
|
column(sql_function_t func, std::string name); // NOLINT(*-explicit-constructor)
|
||||||
column(std::string table_name, std::string name, std::string as);
|
column(std::string table_name, std::string name, std::string as = "");
|
||||||
column(std::string table_name, const char* name, std::string as);
|
column(std::string table_name, const char* name, std::string as = "");
|
||||||
column(struct table &t, const char* name, std::string as);
|
column(struct table &t, const char* name, std::string as = "");
|
||||||
|
|
||||||
[[nodiscard]] bool equals(const column &x) const;
|
[[nodiscard]] bool equals(const column &x) const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,11 @@ public:
|
||||||
template<class Pointer>
|
template<class Pointer>
|
||||||
void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
data_type_t type_{};
|
data_type_t type_{};
|
||||||
|
|
@ -92,9 +94,11 @@ public:
|
||||||
columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column));
|
columns_.push_back(fk_column_generator_.generate(id, *x, ref_table, ref_column));
|
||||||
}
|
}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::pair<std::string, std::string> determine_foreign_ref(const std::type_index &ti);
|
std::pair<std::string, std::string> determine_foreign_ref(const std::type_index &ti);
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ public:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr)
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &attr)
|
||||||
{
|
{
|
||||||
if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -101,10 +101,15 @@ public:
|
||||||
matador::utils::access::process(*this, obj);
|
matador::utils::access::process(*this, obj);
|
||||||
table_name_stack_.pop();
|
table_name_stack_.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
|
||||||
{
|
{
|
||||||
on_has_many(id, c, "", "", attr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ public:
|
||||||
|
|
||||||
placeholder value;
|
placeholder value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d, query_context &query) const override;
|
std::string evaluate(const dialect &d, query_context &query) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|
@ -54,7 +54,7 @@ public:
|
||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
query.bind_vars.emplace_back(field_.name);
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_) + " " + operand + " " + std::to_string(value);
|
return d.prepare_identifier(field_) + " " + operand + " " + std::to_string(value);
|
||||||
|
|
@ -74,7 +74,7 @@ public:
|
||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
query.bind_vars.emplace_back(field_.name);
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_) + " " + operand + " '" + value + "'";
|
return d.prepare_identifier(field_) + " " + operand + " '" + value + "'";
|
||||||
|
|
@ -95,7 +95,7 @@ public:
|
||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return std::to_string(value) + " " + operand + " " + d.prepare_identifier(field_);
|
return std::to_string(value) + " " + operand + " " + d.prepare_identifier(field_);
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +114,7 @@ public:
|
||||||
|
|
||||||
T value;
|
T value;
|
||||||
|
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return "'" + std::to_string(value) + "' " + operand + " " + d.prepare_identifier(field_);
|
return "'" + std::to_string(value) + "' " + operand + " " + d.prepare_identifier(field_);
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ public:
|
||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return A condition IN part of the query
|
* @return A condition IN part of the query
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d, query_context &query) const override {
|
std::string evaluate(const dialect &d, query_context &query) const override {
|
||||||
auto count = size();
|
auto count = size();
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
query.bind_vars.emplace_back(field_.name);
|
query.bind_vars.emplace_back(field_.name);
|
||||||
|
|
@ -226,7 +226,7 @@ public:
|
||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return A condition IN part of the query
|
* @return A condition IN part of the query
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d, query_context &query) const override;
|
std::string evaluate(const dialect &d, query_context &query) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
query_context &query_;
|
query_context &query_;
|
||||||
|
|
@ -261,7 +261,7 @@ public:
|
||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return A condition BETWEEN part of the query
|
* @return A condition BETWEEN part of the query
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d, query_context &query) const override {
|
std::string evaluate(const dialect &d, query_context &query) const override {
|
||||||
query.bind_vars.emplace_back(field_.name);
|
query.bind_vars.emplace_back(field_.name);
|
||||||
query.bind_vars.emplace_back(field_.name);
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_) + " BETWEEN " + std::to_string(range_.first) + " AND " + std::to_string(range_.second);
|
return d.prepare_identifier(field_) + " BETWEEN " + std::to_string(range_.first) + " AND " + std::to_string(range_.second);
|
||||||
|
|
@ -303,7 +303,7 @@ public:
|
||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return The evaluated string based on the compile type
|
* @return The evaluated string based on the compile type
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
// ensure the numbering order for host vars
|
// ensure the numbering order for host vars
|
||||||
auto cl = left.evaluate(d, query);
|
auto cl = left.evaluate(d, query);
|
||||||
|
|
@ -346,7 +346,7 @@ public:
|
||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return The evaluated string based on the compile type
|
* @return The evaluated string based on the compile type
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return operand + " (" + cond.evaluate(d) + ")";
|
return operand + " (" + cond.evaluate(d) + ")";
|
||||||
}
|
}
|
||||||
|
|
@ -369,7 +369,7 @@ public:
|
||||||
* @param d The d used to evaluate
|
* @param d The d used to evaluate
|
||||||
* @return The evaluated string based on the compile type
|
* @return The evaluated string based on the compile type
|
||||||
*/
|
*/
|
||||||
std::string evaluate(dialect &d, query_context &query) const override
|
std::string evaluate(const dialect &d, query_context &query) const override
|
||||||
{
|
{
|
||||||
return d.prepare_identifier(field_) + " " + operand + " " + d.prepare_identifier(other_column_);
|
return d.prepare_identifier(field_) + " " + operand + " " + d.prepare_identifier(other_column_);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
#define QUERY_ENTITY_QUERY_BUILDER_HPP
|
#define QUERY_ENTITY_QUERY_BUILDER_HPP
|
||||||
|
|
||||||
#include "matador/sql/connection.hpp"
|
#include "matador/sql/connection.hpp"
|
||||||
|
#include "matador/sql/condition.hpp"
|
||||||
#include "matador/sql/query_context.hpp"
|
#include "matador/sql/query_context.hpp"
|
||||||
#include "matador/sql/query.hpp"
|
#include "matador/sql/query.hpp"
|
||||||
#include "matador/sql/query_intermediates.hpp"
|
#include "matador/sql/query_intermediates.hpp"
|
||||||
|
|
@ -10,6 +11,19 @@
|
||||||
|
|
||||||
namespace matador::sql {
|
namespace matador::sql {
|
||||||
|
|
||||||
|
struct join_data
|
||||||
|
{
|
||||||
|
table join_table;
|
||||||
|
std::unique_ptr<basic_condition> condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct entity_query_data {
|
||||||
|
std::string root_table_name;
|
||||||
|
std::vector<column> columns;
|
||||||
|
std::vector<join_data> joins;
|
||||||
|
std::unique_ptr<basic_condition> where_clause;
|
||||||
|
};
|
||||||
|
|
||||||
template < typename PrimaryKeyType >
|
template < typename PrimaryKeyType >
|
||||||
class entity_query_builder
|
class entity_query_builder
|
||||||
{
|
{
|
||||||
|
|
@ -18,95 +32,172 @@ public:
|
||||||
: pk_(pk)
|
: pk_(pk)
|
||||||
, schema_(scm) {}
|
, schema_(scm) {}
|
||||||
|
|
||||||
// determine pk
|
|
||||||
// collect eager relations for joins
|
|
||||||
template<class EntityType>
|
template<class EntityType>
|
||||||
std::optional<query_context> build(connection &db) {
|
std::optional<entity_query_data> build() {
|
||||||
EntityType obj;
|
|
||||||
matador::utils::access::process(*this, obj);
|
|
||||||
|
|
||||||
const auto info = schema_.info<EntityType>();
|
const auto info = schema_.info<EntityType>();
|
||||||
if (!info) {
|
if (!info) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
columns_.clear();
|
table_info_stack_.push(info.value());
|
||||||
table_name_ = info.value().name;
|
entity_query_data_ = { info->name };
|
||||||
query q(db, schema_);
|
EntityType obj;
|
||||||
return q.select(column_generator::generate<EntityType>(schema_)).from({table_name_}).build();
|
matador::utils::access::process(*this, obj);
|
||||||
// auto from_intermediate = q.select<EntityType>().from({"t"});
|
|
||||||
// return {};
|
return std::move(entity_query_data_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < class V >
|
template < class V >
|
||||||
void on_primary_key(const char *id, V &, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
|
void on_primary_key(const char *id, V &, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::value>::type* = 0)
|
||||||
{
|
{
|
||||||
auto b = std::is_integral_v<PrimaryKeyType>;
|
push(id);
|
||||||
std::cout << "is matching primary key: " << std::boolalpha << b << "\n";
|
if (is_root_entity() && std::is_integral_v<PrimaryKeyType>) {
|
||||||
columns_.emplace_back(table_name_, id, "");
|
entity_query_data_.where_clause = make_condition(column{table_info_stack_.top().name, id, ""} == pk_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_primary_key(const char *id, std::string &, size_t)
|
void on_primary_key(const char *id, std::string &, size_t)
|
||||||
{
|
{
|
||||||
auto b = std::is_same_v<PrimaryKeyType, std::string>;
|
push(id);
|
||||||
std::cout << "is matching primary key: " << std::boolalpha << b << "\n";
|
if (!is_root_entity()) {
|
||||||
columns_.emplace_back(table_name_, id, "");
|
auto b = std::is_same_v<PrimaryKeyType, std::string>;
|
||||||
|
std::cout << "is matching primary key: " << std::boolalpha << b << "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_revision(const char *id, unsigned long long &/*rev*/)
|
void on_revision(const char *id, unsigned long long &/*rev*/)
|
||||||
{
|
{
|
||||||
columns_.emplace_back(table_name_, id, "");
|
push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes)
|
||||||
{
|
{
|
||||||
columns_.emplace_back(table_name_, id, "");
|
push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Pointer>
|
template<class Pointer>
|
||||||
void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
{
|
{
|
||||||
if (attr.fetch() == utils::fetch_type::EAGER) {
|
if (attr.fetch() == utils::fetch_type::EAGER) {
|
||||||
column col{id};
|
const auto info = schema_.info<typename Pointer::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename Pointer::value_type obj;
|
||||||
|
matador::utils::access::process(*this , obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
// collect join information (determine primary key of foreign entity)
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
// error, prototype doesn't has a pk
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
append_join({table_info_stack_.top().name, id}, {info->name, pk->name()});
|
||||||
|
} else {
|
||||||
|
push(id);
|
||||||
}
|
}
|
||||||
columns_.emplace_back(table_name_, id, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class Pointer>
|
template<class Pointer>
|
||||||
void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
{
|
{
|
||||||
if (attr.fetch() == utils::fetch_type::EAGER) {
|
if (attr.fetch() == utils::fetch_type::EAGER) {
|
||||||
auto info = schema_.info<typename Pointer::value_type>();
|
const auto info = schema_.info<typename Pointer::value_type>();
|
||||||
if (info) {
|
if (!info) {
|
||||||
column lcol(table_name_, id, "");
|
return;
|
||||||
// column rcol(info.value())
|
|
||||||
}
|
}
|
||||||
// collect join information
|
table_info_stack_.push(info.value());
|
||||||
|
typename Pointer::value_type obj;
|
||||||
|
matador::utils::access::process(*this, obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
// error, prototype doesn't has a pk
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
append_join({table_info_stack_.top().name, id}, {info->name, pk->name()});
|
||||||
|
} else {
|
||||||
|
push(id);
|
||||||
}
|
}
|
||||||
columns_.emplace_back(table_name_, id, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr)
|
void on_has_many(ContainerType &, const char *join_column, const utils::foreign_attributes &attr)
|
||||||
{
|
{
|
||||||
if (attr.fetch() == utils::fetch_type::EAGER) {
|
if (attr.fetch() == utils::fetch_type::EAGER) {
|
||||||
// collect join information
|
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename ContainerType::value_type::value_type obj;
|
||||||
|
matador::utils::access::process(*this , obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
// error, prototype doesn't has a pk
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {info->name, join_column});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &attr)
|
||||||
{
|
{
|
||||||
on_has_many(id, c, "", "", attr);
|
if (attr.fetch() == utils::fetch_type::EAGER) {
|
||||||
|
const auto info = schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
if (!info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
table_info_stack_.push(info.value());
|
||||||
|
typename ContainerType::value_type::value_type obj;
|
||||||
|
matador::utils::access::process(*this , obj);
|
||||||
|
table_info_stack_.pop();
|
||||||
|
|
||||||
|
auto pk = info->prototype.primary_key();
|
||||||
|
if (!pk) {
|
||||||
|
// error, prototype doesn't has a pk
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
append_join({table_info_stack_.top().name, table_info_stack_.top().prototype.primary_key()->name()}, {id, join_column});
|
||||||
|
append_join({info->name, pk->name()}, {id, inverse_join_column});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void push(const std::string &column_name)
|
||||||
|
{
|
||||||
|
char str[4];
|
||||||
|
snprintf(str, 4, "c%02d", ++column_index);
|
||||||
|
entity_query_data_.columns.emplace_back(table_info_stack_.top().name, column_name, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_root_entity() const {
|
||||||
|
return table_info_stack_.size() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void append_join(const column &left, const column &right)
|
||||||
|
{
|
||||||
|
entity_query_data_.joins.push_back({
|
||||||
|
{ left.table },
|
||||||
|
make_condition(left == right)
|
||||||
|
});
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
PrimaryKeyType pk_;
|
PrimaryKeyType pk_;
|
||||||
std::vector<column> columns_;
|
std::stack<table_info> table_info_stack_;
|
||||||
const schema &schema_;
|
const schema &schema_;
|
||||||
std::string table_name_;
|
entity_query_data entity_query_data_;
|
||||||
|
int column_index{0};
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef QUERY_HAS_MANY_TO_MANY_RELATION_HPP
|
||||||
|
#define QUERY_HAS_MANY_TO_MANY_RELATION_HPP
|
||||||
|
|
||||||
|
#include "matador/sql/entity.hpp"
|
||||||
|
|
||||||
|
#include "matador/utils/access.hpp"
|
||||||
|
#include "matador/utils/foreign_attributes.hpp"
|
||||||
|
|
||||||
|
namespace matador::sql {
|
||||||
|
|
||||||
|
template < class LocalType, class ForeignType >
|
||||||
|
class has_many_to_many_relation
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
has_many_to_many_relation() = default;
|
||||||
|
has_many_to_many_relation(std::string local_name, std::string remote_name)
|
||||||
|
: local_name_(std::move(local_name))
|
||||||
|
, remote_name_(std::move(remote_name)) {}
|
||||||
|
|
||||||
|
template<class Operator>
|
||||||
|
void process(Operator &op) {
|
||||||
|
namespace field = matador::utils::access;
|
||||||
|
field::belongs_to(op, local_name_.c_str(), local_, utils::default_foreign_attributes);
|
||||||
|
field::belongs_to(op, remote_name_.c_str(), remote_, utils::default_foreign_attributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity<LocalType> local() const { return local_; }
|
||||||
|
entity<LocalType> remote() const { return remote_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string local_name_;
|
||||||
|
std::string remote_name_;
|
||||||
|
sql::entity<LocalType> local_;
|
||||||
|
sql::entity<ForeignType> remote_;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //QUERY_HAS_MANY_TO_MANY_RELATION_HPP
|
||||||
|
|
@ -161,6 +161,10 @@ public:
|
||||||
{
|
{
|
||||||
return where_clause(std::make_unique<Condition>(std::move(cond)));
|
return where_clause(std::make_unique<Condition>(std::move(cond)));
|
||||||
}
|
}
|
||||||
|
query_where_intermediate where(std::unique_ptr<basic_condition> &&cond)
|
||||||
|
{
|
||||||
|
return where_clause(std::move(cond));
|
||||||
|
}
|
||||||
query_group_by_intermediate group_by(const column &col);
|
query_group_by_intermediate group_by(const column &col);
|
||||||
query_order_by_intermediate order_by(const column &col);
|
query_order_by_intermediate order_by(const column &col);
|
||||||
|
|
||||||
|
|
@ -178,6 +182,10 @@ public:
|
||||||
{
|
{
|
||||||
return on_clause(std::make_unique<Condition>(std::move(cond)));
|
return on_clause(std::make_unique<Condition>(std::move(cond)));
|
||||||
}
|
}
|
||||||
|
query_on_intermediate on(std::unique_ptr<basic_condition> &&cond)
|
||||||
|
{
|
||||||
|
return on_clause(std::move(cond));
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
query_on_intermediate on_clause(std::unique_ptr<basic_condition> &&cond);
|
query_on_intermediate on_clause(std::unique_ptr<basic_condition> &&cond);
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,11 @@ public:
|
||||||
void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t column_index_{};
|
size_t column_index_{};
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class schema
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using repository = std::unordered_map<std::type_index, table_info>;
|
using repository = std::unordered_map<std::type_index, table_info>;
|
||||||
|
using repository_by_name = std::unordered_map<std::string, std::reference_wrapper<table_info>>;
|
||||||
using iterator = repository::iterator;
|
using iterator = repository::iterator;
|
||||||
using const_iterator = repository::const_iterator;
|
using const_iterator = repository::const_iterator;
|
||||||
|
|
||||||
|
|
@ -50,6 +51,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::optional<table_info> info(std::type_index ti) const;
|
[[nodiscard]] std::optional<table_info> info(std::type_index ti) const;
|
||||||
|
[[nodiscard]] std::optional<table_info> info(const std::string &name) const;
|
||||||
|
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
[[nodiscard]] std::pair<std::string, std::string> reference() const
|
[[nodiscard]] std::pair<std::string, std::string> reference() const
|
||||||
|
|
@ -77,6 +79,7 @@ public:
|
||||||
private:
|
private:
|
||||||
std::string name_;
|
std::string name_;
|
||||||
repository repository_;
|
repository repository_;
|
||||||
|
repository_by_name repository_by_name_;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,100 @@ namespace matador::sql {
|
||||||
|
|
||||||
class dialect;
|
class dialect;
|
||||||
|
|
||||||
|
struct join_data
|
||||||
|
{
|
||||||
|
table join_table;
|
||||||
|
std::unique_ptr<basic_condition> condition;
|
||||||
|
};
|
||||||
|
|
||||||
|
//class join_collector
|
||||||
|
//{
|
||||||
|
//private:
|
||||||
|
// explicit join_collector(const sql::schema &ts, std::vector<join_data> &joins)
|
||||||
|
// : table_schema_(ts)
|
||||||
|
// , joins_(joins) {}
|
||||||
|
//
|
||||||
|
//public:
|
||||||
|
// template < class Type >
|
||||||
|
// static std::vector<join_data> collect(const sql::schema &ts)
|
||||||
|
// {
|
||||||
|
// const auto info = ts.info<Type>();
|
||||||
|
// if (!info) {
|
||||||
|
// return {};
|
||||||
|
// }
|
||||||
|
// std::vector<join_data> joins;
|
||||||
|
// join_collector collector(ts, joins);
|
||||||
|
// Type obj;
|
||||||
|
// matador::utils::access::process(collector, obj);
|
||||||
|
// return joins;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// template < class PrimaryKeyType >
|
||||||
|
// void on_primary_key(const char *id, PrimaryKeyType &, typename std::enable_if<std::is_integral<PrimaryKeyType>::value && !std::is_same<bool, PrimaryKeyType>::value>::type* = 0) {}
|
||||||
|
// void on_primary_key(const char *id, std::string &, size_t) {}
|
||||||
|
// void on_revision(const char *id, unsigned long long &/*rev*/) {}
|
||||||
|
// template<typename Type>
|
||||||
|
// void on_attribute(const char *id, Type &, const utils::field_attributes &/*attr*/ = utils::null_attributes) {}
|
||||||
|
//
|
||||||
|
// template<class Pointer>
|
||||||
|
// void on_belongs_to(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
|
// {
|
||||||
|
// if (attr.fetch() == utils::fetch_type::LAZY) {
|
||||||
|
// push(id);
|
||||||
|
// } else {
|
||||||
|
// const auto info = table_schema_.info<typename Pointer::value_type>();
|
||||||
|
// if (!info) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// table_name_stack_.push(info.value().name);
|
||||||
|
// typename Pointer::value_type obj;
|
||||||
|
// matador::utils::access::process(*this, obj);
|
||||||
|
// table_name_stack_.pop();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// template<class Pointer>
|
||||||
|
// void on_has_one(const char *id, Pointer &, const utils::foreign_attributes &attr)
|
||||||
|
// {
|
||||||
|
// if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
||||||
|
// push(id);
|
||||||
|
// } else {
|
||||||
|
// const auto info = table_schema_.info<typename Pointer::value_type>();
|
||||||
|
// if (!info) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// table_name_stack_.push(info.value().name);
|
||||||
|
// typename Pointer::value_type obj;
|
||||||
|
// matador::utils::access::process(*this, obj);
|
||||||
|
// table_name_stack_.pop();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// template<class ContainerType>
|
||||||
|
// void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &attr)
|
||||||
|
// {
|
||||||
|
// if (attr.fetch() == utils::fetch_type::LAZY || force_lazy_) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const auto info = table_schema_.info<typename ContainerType::value_type::value_type>();
|
||||||
|
// if (!info) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// table_name_stack_.push(info.value().name);
|
||||||
|
// typename ContainerType::value_type::value_type obj;
|
||||||
|
// matador::utils::access::process(*this, obj);
|
||||||
|
// table_name_stack_.pop();
|
||||||
|
// }
|
||||||
|
// template<class ContainerType>
|
||||||
|
// void on_has_many(const char *id, ContainerType &c, const utils::foreign_attributes &attr)
|
||||||
|
// {
|
||||||
|
// on_has_many(id, c, "", "", attr);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//private:
|
||||||
|
// const sql::schema &table_schema_;
|
||||||
|
// std::vector<join_data> &joins_;
|
||||||
|
//};
|
||||||
|
|
||||||
class session
|
class session
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -44,6 +138,9 @@ public:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto columns = sql::column_generator::generate<Type>(*schema_);
|
||||||
|
|
||||||
|
// auto joins = sql
|
||||||
c->query(*schema_).select({}).from(info->name);
|
c->query(*schema_).select({}).from(info->name);
|
||||||
// build pk where condition
|
// build pk where condition
|
||||||
// - check if type has pk
|
// - check if type has pk
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,11 @@ public:
|
||||||
values_.emplace_back(fk_value_extractor_.extract(*x));
|
values_.emplace_back(fk_value_extractor_.extract(*x));
|
||||||
}
|
}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const char *, const char *, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many(ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {}
|
||||||
template<class ContainerType>
|
template<class ContainerType>
|
||||||
void on_has_many(const char *, ContainerType &, const utils::foreign_attributes &/*attr*/) {}
|
void on_has_many_to_many(const char *id, ContainerType &c, const char *join_column, const char *inverse_join_column, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
template<class ContainerType>
|
||||||
|
void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename Type>
|
template<typename Type>
|
||||||
|
|
|
||||||
|
|
@ -64,24 +64,19 @@ void belongs_to(Operator &op, const char *id, Type &value, const foreign_attribu
|
||||||
op.on_belongs_to(id, value, attr);
|
op.on_belongs_to(id, value, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//template<class Operator, class Type, template<class ...> class ContainerType>
|
|
||||||
//void has_many(Operator &op, const char *id, container<Type, ContainerType> &container, const foreign_attributes &attr) {
|
|
||||||
// op.on_has_many(id, container, attr);
|
|
||||||
//}
|
|
||||||
|
|
||||||
template<class Operator, class Type, template<class ...> class ContainerType>
|
template<class Operator, class Type, template<class ...> class ContainerType>
|
||||||
void has_many(Operator &op, const char *id, ContainerType<Type> &container, const foreign_attributes &attr) {
|
void has_many(Operator &op, ContainerType<Type> &container, const char *join_column, const foreign_attributes &attr) {
|
||||||
op.on_has_many(id, container, attr);
|
op.on_has_many(container, join_column, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//template<class Operator, class Type, template<class ...> class ContainerType>
|
template<class Operator, class Type, template<class ...> class ContainerType>
|
||||||
//void has_many(Operator &op, const char *id, container<Type, ContainerType> &container, const char *left_column, const char *right_column, const foreign_attributes &attr) {
|
void has_many_to_many(Operator &op, const char *id, ContainerType<Type> &container, const char *join_column, const char *inverse_join_column, const foreign_attributes &attr) {
|
||||||
// op.on_has_many(id, container, left_column, right_column, attr);
|
op.on_has_many_to_many(id, container, join_column, inverse_join_column, attr);
|
||||||
//}
|
}
|
||||||
|
|
||||||
template<class Operator, class Type, template<class ...> class ContainerType>
|
template<class Operator, class Type, template<class ...> class ContainerType>
|
||||||
void has_many(Operator &op, const char *id, ContainerType<Type> &container, const char *left_column, const char *right_column, const foreign_attributes &attr) {
|
void has_many_to_many(Operator &op, const char *id, ContainerType<Type> &container, const foreign_attributes &attr) {
|
||||||
op.on_has_many(id, container, left_column, right_column, attr);
|
op.on_has_many_to_many(id, container, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,7 @@ set(SQL_HEADER
|
||||||
../include/matador/sql/field.hpp
|
../include/matador/sql/field.hpp
|
||||||
../include/matador/sql/entity_query_builder.hpp
|
../include/matador/sql/entity_query_builder.hpp
|
||||||
../include/matador/sql/table_definition.hpp
|
../include/matador/sql/table_definition.hpp
|
||||||
|
../include/matador/sql/has_many_to_many_relation.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(QUERY_SOURCES
|
set(QUERY_SOURCES
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ condition<column, placeholder, std::enable_if<true>::type>::condition(const colu
|
||||||
: basic_column_condition(fld, op), value(val)
|
: basic_column_condition(fld, op), value(val)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
std::string condition<column, placeholder, std::enable_if<true>::type>::evaluate(dialect &d, query_context &query) const
|
std::string condition<column, placeholder, std::enable_if<true>::type>::evaluate(const dialect &d, query_context &query) const
|
||||||
{
|
{
|
||||||
query.bind_vars.emplace_back(field_.name);
|
query.bind_vars.emplace_back(field_.name);
|
||||||
return d.prepare_identifier(field_) + " " + operand + " " + d.next_placeholder(query.bind_vars);
|
return d.prepare_identifier(field_) + " " + operand + " " + d.next_placeholder(query.bind_vars);
|
||||||
|
|
@ -16,7 +16,7 @@ condition<column, query_context>::condition(column col, basic_condition::operand
|
||||||
: basic_column_condition(std::move(col), op), query_(q)
|
: basic_column_condition(std::move(col), op), query_(q)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
std::string condition<column, query_context>::evaluate(dialect &d, query_context &query) const
|
std::string condition<column, query_context>::evaluate(const dialect &d, query_context &query) const
|
||||||
{
|
{
|
||||||
std::string result(d.prepare_identifier(field_) + " " + operand + " (");
|
std::string result(d.prepare_identifier(field_) + " " + operand + " (");
|
||||||
result += (")");
|
result += (")");
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ std::string dialect::prepare_identifier_string(const std::string &col) const
|
||||||
|
|
||||||
for (auto &part : parts) {
|
for (auto &part : parts) {
|
||||||
escape_quotes_in_identifier(part);
|
escape_quotes_in_identifier(part);
|
||||||
quote_identifier(part);
|
// quote_identifier(part);
|
||||||
}
|
}
|
||||||
return utils::join(parts, ".");
|
return utils::join(parts, ".");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ std::string schema::name() const
|
||||||
|
|
||||||
const table_info& schema::attach(const std::type_index ti, const table_info& table)
|
const table_info& schema::attach(const std::type_index ti, const table_info& table)
|
||||||
{
|
{
|
||||||
return repository_.try_emplace(ti, table).first->second;
|
|
||||||
|
auto &ref = repository_.try_emplace(ti, table).first->second;
|
||||||
|
repository_by_name_.try_emplace(ref.name, std::ref(ref));
|
||||||
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<table_info> schema::info(std::type_index ti) const
|
std::optional<table_info> schema::info(std::type_index ti) const
|
||||||
|
|
@ -27,6 +30,15 @@ std::optional<table_info> schema::info(std::type_index ti) const
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<table_info> schema::info(const std::string &name) const
|
||||||
|
{
|
||||||
|
const auto it = repository_by_name_.find(name);
|
||||||
|
if (it == repository_by_name_.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
std::pair<std::string, std::string> schema::reference(const std::type_index &ti) const
|
std::pair<std::string, std::string> schema::reference(const std::type_index &ti) const
|
||||||
{
|
{
|
||||||
const auto it = repository_.find(ti);
|
const auto it = repository_.find(ti);
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,123 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
#include <matador/sql/connection.hpp>
|
#include <matador/sql/connection.hpp>
|
||||||
|
#include <matador/sql/dialect.hpp>
|
||||||
#include <matador/sql/entity_query_builder.hpp>
|
#include <matador/sql/entity_query_builder.hpp>
|
||||||
|
|
||||||
#include "models/author.hpp"
|
#include "models/author.hpp"
|
||||||
#include "models/book.hpp"
|
#include "models/book.hpp"
|
||||||
|
#include "models/recipe.hpp"
|
||||||
struct schiff {
|
#include "models/order.hpp"
|
||||||
std::string id;
|
|
||||||
|
|
||||||
template<typename Operator>
|
|
||||||
void process(Operator &op)
|
|
||||||
{
|
|
||||||
namespace field = matador::utils::access;
|
|
||||||
field::primary_key(op, "id", id, 255);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using namespace matador::sql;
|
using namespace matador::sql;
|
||||||
|
|
||||||
TEST_CASE("Create sql query for entity", "[query][entity][builder]") {
|
TEST_CASE("Create sql query data for entity with eager belongs to", "[query][entity][builder]") {
|
||||||
connection noop("noop://noop.db");
|
using namespace matador::test;
|
||||||
|
connection db("noop://noop.db");
|
||||||
schema scm("noop");
|
schema scm("noop");
|
||||||
scm.attach<matador::test::author>("authors");
|
scm.attach<author>("authors");
|
||||||
scm.attach<matador::test::book>("books");
|
scm.attach<book>("books");
|
||||||
|
|
||||||
entity_query_builder eqb(17, scm);
|
entity_query_builder eqb(17, scm);
|
||||||
|
|
||||||
auto context = eqb.build<matador::test::book>(noop);
|
auto data = eqb.build<book>();
|
||||||
std::cout << "SQL: " << context.value().sql << "\n";
|
|
||||||
context = eqb.build<schiff>(noop);
|
REQUIRE(data.has_value());
|
||||||
|
REQUIRE(data->root_table_name == "books");
|
||||||
|
REQUIRE(data->joins.size() == 1);
|
||||||
|
REQUIRE(data->columns.size() == 9);
|
||||||
|
REQUIRE(data->where_clause);
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> expected_join_data {
|
||||||
|
{ "books", "books.author_id = authors.id"}
|
||||||
|
};
|
||||||
|
|
||||||
|
query_context qc;
|
||||||
|
size_t index{0};
|
||||||
|
for (const auto &jd : data->joins) {
|
||||||
|
REQUIRE(jd.join_table.name == expected_join_data[index].first);
|
||||||
|
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cond = data->where_clause->evaluate(db.dialect(), qc);
|
||||||
|
REQUIRE(cond == "books.id = 17");
|
||||||
|
|
||||||
|
auto &jd = data->joins.front();
|
||||||
|
|
||||||
|
auto context = db.query(scm)
|
||||||
|
.select(data->columns)
|
||||||
|
.from(data->root_table_name)
|
||||||
|
.join_left(jd.join_table).on(std::move(jd.condition))
|
||||||
|
.where(std::move(data->where_clause))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create sql query data for entity with eager has many belongs to", "[query][entity][builder]") {
|
||||||
|
using namespace matador::test;
|
||||||
|
connection db("noop://noop.db");
|
||||||
|
schema scm("noop");
|
||||||
|
scm.attach<product>("products");
|
||||||
|
scm.attach<order_details>("order_details");
|
||||||
|
scm.attach<order>("orders");
|
||||||
|
|
||||||
|
entity_query_builder eqb(17, scm);
|
||||||
|
|
||||||
|
auto data = eqb.build<order>();
|
||||||
|
|
||||||
|
REQUIRE(data.has_value());
|
||||||
|
REQUIRE(data->root_table_name == "orders");
|
||||||
|
REQUIRE(data->joins.size() == 1);
|
||||||
|
REQUIRE(data->columns.size() == 15);
|
||||||
|
REQUIRE(data->where_clause);
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> expected_join_data {
|
||||||
|
{ "orders", "orders.order_id = order_details.order_id"}
|
||||||
|
};
|
||||||
|
|
||||||
|
query_context qc;
|
||||||
|
size_t index{0};
|
||||||
|
for (const auto &jd : data->joins) {
|
||||||
|
REQUIRE(jd.join_table.name == expected_join_data[index].first);
|
||||||
|
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cond = data->where_clause->evaluate(db.dialect(), qc);
|
||||||
|
REQUIRE(cond == "orders.order_id = 17");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create sql query data for entity with eager many to many", "[query][entity][builder]") {
|
||||||
|
using namespace matador::test;
|
||||||
|
connection db("noop://noop.db");
|
||||||
|
schema scm("noop");
|
||||||
|
scm.attach<recipe>("recipes");
|
||||||
|
scm.attach<ingredient>("ingredients");
|
||||||
|
scm.attach<recipe_ingredient>("recipe_ingredients");
|
||||||
|
|
||||||
|
entity_query_builder eqb(17, scm);
|
||||||
|
|
||||||
|
auto data = eqb.build<ingredient>();
|
||||||
|
|
||||||
|
REQUIRE(data.has_value());
|
||||||
|
REQUIRE(data->root_table_name == "ingredients");
|
||||||
|
REQUIRE(data->joins.size() == 2);
|
||||||
|
REQUIRE(data->columns.size() == 4);
|
||||||
|
REQUIRE(data->where_clause);
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>> expected_join_data {
|
||||||
|
{ "ingredients", "ingredients.id = recipe_ingredients.ingredient_id"},
|
||||||
|
{ "recipes", "recipes.id = recipe_ingredients.recipe_id"}
|
||||||
|
};
|
||||||
|
|
||||||
|
query_context qc;
|
||||||
|
size_t index{0};
|
||||||
|
for (const auto &jd : data->joins) {
|
||||||
|
REQUIRE(jd.join_table.name == expected_join_data[index].first);
|
||||||
|
REQUIRE(jd.condition->evaluate(db.dialect(), qc) == expected_join_data[index].second);
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cond = data->where_clause->evaluate(db.dialect(), qc);
|
||||||
|
REQUIRE(cond == "ingredients.id = 17");
|
||||||
}
|
}
|
||||||
|
|
@ -3,10 +3,14 @@
|
||||||
|
|
||||||
#include "matador/utils/access.hpp"
|
#include "matador/utils/access.hpp"
|
||||||
|
|
||||||
|
#include "matador/sql/entity.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace matador::test {
|
namespace matador::test {
|
||||||
|
|
||||||
|
struct book;
|
||||||
|
|
||||||
struct author
|
struct author
|
||||||
{
|
{
|
||||||
unsigned long id{};
|
unsigned long id{};
|
||||||
|
|
@ -27,7 +31,7 @@ struct author
|
||||||
field::attribute(op, "date_of_birth", date_of_birth, 31);
|
field::attribute(op, "date_of_birth", date_of_birth, 31);
|
||||||
field::attribute(op, "year_of_birth", year_of_birth);
|
field::attribute(op, "year_of_birth", year_of_birth);
|
||||||
field::attribute(op, "distinguished", distinguished);
|
field::attribute(op, "distinguished", distinguished);
|
||||||
field::has_many(op, "books", books, "author_id", "id", utils::fetch_type::LAZY);
|
field::has_many(op, books, "author_id", utils::fetch_type::LAZY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ struct order
|
||||||
field::attribute(op, "ship_region", ship_region, 255);
|
field::attribute(op, "ship_region", ship_region, 255);
|
||||||
field::attribute(op, "ship_postal_code", ship_postal_code, 255);
|
field::attribute(op, "ship_postal_code", ship_postal_code, 255);
|
||||||
field::attribute(op, "ship_country", ship_country, 255);
|
field::attribute(op, "ship_country", ship_country, 255);
|
||||||
field::has_many(op, "order_details", order_details_, utils::fetch_type::EAGER);
|
field::has_many(op, order_details_, "order_id", utils::fetch_type::EAGER);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "matador/utils/foreign_attributes.hpp"
|
#include "matador/utils/foreign_attributes.hpp"
|
||||||
|
|
||||||
#include "matador/sql/entity.hpp"
|
#include "matador/sql/entity.hpp"
|
||||||
|
#include "matador/sql/has_many_to_many_relation.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ struct ingredient
|
||||||
namespace field = matador::utils::access;
|
namespace field = matador::utils::access;
|
||||||
field::primary_key(op, "id", id);
|
field::primary_key(op, "id", id);
|
||||||
field::attribute(op, "name", name, 255);
|
field::attribute(op, "name", name, 255);
|
||||||
field::has_many(op, "recipes", recipes, "ingredient_id", "recipe_id", utils::fetch_type::EAGER);
|
field::has_many_to_many(op, "recipe_ingredients", recipes, "ingredient_id", "recipe_id", utils::fetch_type::EAGER);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -38,21 +39,15 @@ struct recipe
|
||||||
namespace field = matador::utils::access;
|
namespace field = matador::utils::access;
|
||||||
field::primary_key(op, "id", id);
|
field::primary_key(op, "id", id);
|
||||||
field::attribute(op, "name", name, 255);
|
field::attribute(op, "name", name, 255);
|
||||||
field::has_many(op, "ingredients", ingredients, "recipe_id", "ingredient_id", utils::fetch_type::EAGER);
|
field::has_many_to_many(op, "recipe_ingredients", ingredients, utils::fetch_type::LAZY);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct recipe_ingredient
|
class recipe_ingredient : public sql::has_many_to_many_relation<recipe, ingredient>
|
||||||
{
|
{
|
||||||
sql::entity<recipe> recipe_;
|
public:
|
||||||
sql::entity<ingredient> ingredient_;
|
recipe_ingredient()
|
||||||
|
: has_many_to_many_relation("recipe_id", "ingredient_id") {}
|
||||||
template<class Operator>
|
|
||||||
void process(Operator &op) {
|
|
||||||
namespace field = matador::utils::access;
|
|
||||||
field::belongs_to(op, "recipe_id", recipe_, utils::default_foreign_attributes);
|
|
||||||
field::belongs_to(op, "ingredient_id", ingredient_, utils::default_foreign_attributes);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue