#include #include #include using namespace ::cpphibernate; using namespace ::cpphibernate::mariadb; struct foreign_many_tuple_t { const field_t& field; read_context_ptr_u context; std::string owner; }; struct foreign_many_list_t : public std::list { }; struct data_extractor_t { const table_t& _table; const read_context& _context; const ::cppmariadb::row& _row; foreign_many_list_t& _foreign_many_list; const filter_t& _filter; mutable size_t _index; data_extractor_t( const table_t& p_table, const read_context& p_context, const ::cppmariadb::row& p_row, foreign_many_list_t& p_foreign_many_list) : _table (p_table) , _context (p_context) , _row (p_row) , _foreign_many_list(p_foreign_many_list) , _filter (p_context.filter) , _index (0) { } inline bool has_value() const { return !_row.at(_index).is_null(); } inline value_t get_value() const { value_t ret; auto f = _row.at(_index); if (!f.is_null()) ret = f.get(); return ret; } inline void next_field() const { ++_index; } inline void read_field(const field_t& field, const read_context& context, bool skip = false) const { auto value = get_value(); ++_index; if (!skip) field.set(context, value); } inline bool read_table(const table_t& table, const read_context& context, bool read_base, bool read_derived, bool skip = false) const { /* read the base table */ if (read_base && table.base_table) { skip = read_table(*table.base_table, context, true, false, skip); } /* create a dynamic dataset depending on the derived table */ else if ( read_base && context.is_dynamic && !table.derived_tables.empty()) { auto value = get_value(); next_field(); if (static_cast(value) && !skip) { auto type = cppcore::from_string(*value); auto derived = _table.get_derived_by_table_id(type); if (!derived) throw exception(std::string("unable to find dereived table for id ") + std::to_string(type)); derived->emplace(context); } else { skip = true; } } /* create a static dataset */ else if (has_value() && !skip) { if (read_base) { context.emplace(); } } /* no data -> skip */ else { skip = true; } if (_context.filter.is_excluded(table)) return skip; /* primary key */ assert(table.primary_key_field); read_field(*table.primary_key_field, context, skip); /* data fields */ for (auto& ptr : table.data_fields) { assert(ptr); auto& field = *ptr; if (!_context.filter.is_excluded(field)) read_field(field, context, skip); } /* foreign table one */ for (auto& ptr : table.foreign_table_one_fields) { assert(ptr); assert(ptr->referenced_table); auto& field = *ptr; auto& ref_table = *field.referenced_table; if ( _filter.is_excluded(field) || _filter.is_excluded(ref_table)) continue; auto next_context = field.foreign_read(context, skip); assert(static_cast(next_context)); read_table(ref_table, *next_context, true, true, skip); next_context->finish(); } /* foreign table many */ if (!skip) { for (auto& ptr : table.foreign_table_many_fields) { assert(ptr); assert(ptr->referenced_table); auto& field = *ptr; auto& ref_table = *field.referenced_table; if ( _filter.is_excluded(field) || _filter.is_excluded(ref_table)) continue; _foreign_many_list.emplace_back( foreign_many_tuple_t { field, field.foreign_read(context, false), *table.primary_key_field->get(context), }); } } /* derived tables */ if (read_derived && context.is_dynamic) { for (auto& ptr : table.derived_tables) { assert(ptr); auto& derived_table = *ptr; read_table(derived_table, context, false, true, skip); } } return skip; } inline void operator()() const { _index = 0; read_table(_table, _context, true, true, false); if (_index != _row.size()) throw exception("result was not completely read!"); } }; /* select_query_builder_t */ struct select_query_builder_t { struct local_context { const table_t& table; std::string alias; bool add_base; bool add_derived; bool is_dynamic; }; const table_t& _table; const filter_t& _filter; bool _is_dynamic; size_t alias_id { 0 }; size_t index { 0 }; std::ostringstream os; std::list joins; select_query_builder_t( const table_t& p_table, const filter_t& p_filter, bool p_is_dynamic) : _table (p_table) , _filter (p_filter) , _is_dynamic(p_is_dynamic) { } inline std::string make_alias() { return std::string("T") + std::to_string(alias_id++); } inline void add_field(const field_t& field, const std::string& alias) { if (index++) os << ", "; os << field.convert_from_open << "`" << alias << "`.`" << field.name << "`" << field.convert_from_close; } inline bool add_table(const local_context& ctx) { bool ret = false; auto has_alias = !ctx.alias.empty(); auto real_alias = has_alias ? ctx.alias : ctx.table.name; if (ctx.table.base_table && ctx.add_base) { assert(ctx.table.base_table->primary_key_field); auto& base_table = *ctx.table.base_table; auto& base_key = *base_table.primary_key_field; auto base_alias = has_alias ? make_alias() : std::string(); auto real_base_alias = has_alias ? base_alias : base_table.name; std::ostringstream ss; ss << " JOIN `" << base_table.name; if (has_alias) { ss << "` AS `" << base_alias; } ss << "` ON `" << real_alias << "`.`" << base_key.name << "`=`" << real_base_alias << "`.`" << base_key.name << "`"; auto it = joins.insert(joins.end(), ss.str()); if (add_table({ base_table, base_alias, true, false, ctx.is_dynamic })) { ret = true; } else { joins.erase(it); } } /* __type */ if ( ctx.is_dynamic && !ctx.table.base_table && !ctx.table.derived_tables.empty()) { if (index++) os << ", "; os << "`" << ctx.table.name << "`.`__type` AS `__type`"; ret = true; } if (_filter.is_excluded(ctx.table)) return ret; /* primary key */ assert(ctx.table.primary_key_field); add_field(*ctx.table.primary_key_field, real_alias); ret = true; /* data fields */ for (auto& ptr : ctx.table.data_fields) { assert(ptr); auto& field = *ptr; if (!_filter.is_excluded(field)) { add_field(field, real_alias); } } /* foreign table one */ for (auto& ptr : ctx.table.foreign_table_one_fields) { assert(ptr); assert(ptr->table.primary_key_field); assert(ptr->referenced_table); assert(ptr->referenced_table->primary_key_field); auto& field = *ptr; auto& table = field.table; auto& own_key = *table.primary_key_field; auto& ref_table = *field.referenced_table; auto& ref_key = *ref_table.primary_key_field; if ( _filter.is_excluded(field) || _filter.is_excluded(ref_table)) continue; auto new_alias = make_alias(); std::ostringstream ss; if (ref_table.is_used_in_container) { ss << " LEFT JOIN `" << ref_table.name << "` AS `" << new_alias << "` ON `" << real_alias << "`.`" << own_key.name << "`=`" << new_alias << "`.`" << table.name << "_id_" << field.name << "`"; } else { ss << " LEFT JOIN `" << ref_table.name << "` AS `" << new_alias << "` ON `" << real_alias << "`.`" << ref_key.table.name << "_id_" << field.name << "`=`" << new_alias << "`.`" << ref_key.name << "`"; } auto it = joins.insert(joins.end(), ss.str()); if (!add_table({ ref_table, new_alias, true, true, field.value_is_pointer })) { joins.erase(it); } } /* derived tables */ if (ctx.add_derived && ctx.is_dynamic) { for (auto& ptr : ctx.table.derived_tables) { assert(ptr); assert(ptr->primary_key_field); auto& derived_table = *ptr; auto& primary_key = *ctx.table.primary_key_field; auto derived_alias = has_alias ? make_alias() : std::string(); auto real_derived_alias = has_alias ? derived_alias : derived_table.name; std::ostringstream ss; ss << " LEFT JOIN `" << derived_table.name; if (has_alias) { ss << "` AS `" << derived_alias; } ss << "` ON `" << real_alias << "`.`" << primary_key.name << "`=`" << real_derived_alias << "`.`" << primary_key.name << "`"; auto it = joins.insert(joins.end(), ss.str()); if (!add_table({ derived_table, derived_alias, false, true, ctx.is_dynamic, })) { joins.erase(it); } } } return ret; } inline std::string operator()() { os << "SELECT "; add_table({ _table, "", true, true, _is_dynamic, }); os << " FROM `" << _table.name << "`"; for (auto& join : joins) os << join; os << " ?where! ?order! ?limit!"; return os.str(); } }; void table_t::read(const read_context& context) const { auto& statement = get_statement_select(context.filter, context.is_dynamic); auto& connection = context.connection; statement.set(0, context.where); statement.set(1, context.order_by); statement.set(2, context.limit); cpphibernate_log_debug("execute SELECT query: " << std::endl << statement.query(connection) << std::endl); auto result = connection.execute_used(statement); if (!result) throw exception("Unable to fetching data from database!"); ::cppmariadb::row * row; foreign_many_list_t foreign_many_list; while ((row = result->next())) { data_extractor_t(*this, context, *row, foreign_many_list)(); } context.finish(); for (auto& tuple : foreign_many_list) { auto& field = tuple.field; auto& next_context = *tuple.context; assert(field.referenced_table); assert(field.referenced_table->primary_key_field); auto& ref_table = *field.referenced_table; auto& ref_field = *ref_table.primary_key_field; { std::ostringstream ss; ss << "WHERE (`" << ref_table.name << "`.`" << field.table.name << "_id_" << field.name << "`=" << ref_field.convert_to_open << "'" << context.connection.escape(tuple.owner) << "'" << ref_field.convert_to_close << ")"; next_context.where = ss.str(); } { std::ostringstream ss; ss << "ORDER BY `" << ref_table.name << "`.`" << field.table.name << "_index_" << field.name << "` ASC"; next_context.order_by = ss.str(); } ref_table.read(next_context); } } void table_t::emplace(const read_context& context) const { throw exception(std::string("'") + name + "' does not implement the emplace() method!"); } ::cppmariadb::statement& table_t::get_statement_select(const filter_t& filter, bool dynamic) const { auto& map = dynamic ? _statement_select_dynamic : _statement_select_static; auto key = std::make_tuple(filter.cache_id, static_cast(nullptr)); auto it = map.find(key); if (it == map.end()) { auto query = select_query_builder_t(*this, filter, dynamic)(); it = map.emplace(key, ::cppmariadb::statement(query)).first; } return it->second; }