#include #include #include #include #include using namespace ::cpphibernate; using namespace ::cpphibernate::mariadb; static std::string build_create_update_query( const table_t& table, const filter_t * filter, const field_t * owner); static std::string execute_create_update( const table_t& table, const create_update_context& context, ::cppmariadb::statement * statement); /* table_t */ std::string table_t::create_update(const create_update_context& context) const { return create_update_exec(context); } std::string table_t::create_update_exec(const create_update_context& context) const { auto * statement = context.is_update() ? get_statement_update(*context.filter, context.owner_field) : get_statement_insert_into(); return execute_create_update(*this, context, statement); } ::cppmariadb::statement* table_t::get_statement_insert_into() const { if (!_statement_insert_into) { auto query = build_create_update_query(*this, nullptr, nullptr); _statement_insert_into.reset(new ::cppmariadb::statement(query)); } return _statement_insert_into->empty() ? nullptr : _statement_insert_into.get(); } ::cppmariadb::statement* table_t::get_statement_update(const filter_t& filter, const field_t * owner) const { auto key = std::make_tuple(filter.cache_id, owner); auto it = _statement_update.find(key); if (it == _statement_update.end()) { auto query = build_create_update_query(*this, &filter, owner); it = _statement_update.emplace(key, ::cppmariadb::statement(query)).first; } return it->second.empty() ? nullptr : &it->second; } std::string build_create_update_query(const table_t& table, const filter_t* filter, const field_t* owner) { std::ostringstream os; size_t index = 0; bool is_update = static_cast(filter); bool is_create = !is_update; /* INSER INTO / UPDATE */ os << (is_update ? "UPDATE" : "INSERT INTO") << " `" << table.name << "`"; /* primary key */ if (is_create) { assert(table.primary_key_field); auto& key_info = *table.primary_key_field; if (!key_info.value_is_auto_incremented) { if (index++) os << ", "; else os << " SET "; os << "`" << key_info.name << "`=" << key_info.convert_to_open << "?" << key_info.name << "?" << key_info.convert_to_close; } } /* base table key fields */ if ( static_cast(table.base_table) && ( is_create || !filter->is_excluded(*table.base_table))) { if (index++) os << ", "; else os << " SET "; auto& base_table_info = *table.base_table; assert(base_table_info.primary_key_field); auto& key_info = *base_table_info.primary_key_field; os << "`" << key_info.name << "`=" << key_info.convert_to_open << "?" << key_info.name << "?" << key_info.convert_to_close; } /* foreign table one fields */ for (auto& ptr : table.foreign_table_one_fields) { assert(static_cast(ptr)); auto& field_info = *ptr; if (is_update && filter->is_excluded(field_info)) continue; assert(field_info.referenced_table); assert(field_info.referenced_table->primary_key_field); if (field_info.referenced_table->is_used_in_container) continue; if (index++) os << ", "; else os << " SET "; auto& key_info = *field_info.referenced_table->primary_key_field; os << "`" << key_info.table.name << "_id_" << field_info.name << "`=" << key_info.convert_to_open << "?" << key_info.table.name << "_id_" << field_info.name << "?" << key_info.convert_to_close; } /* foreign fields */ for (auto& ptr : table.foreign_key_fields) { assert(static_cast(ptr)); if (is_update && ptr != owner) continue; if (index++) os << ", "; else os << " SET "; auto& field_info = *ptr; assert(field_info.table.primary_key_field); auto& key_info = *field_info.table.primary_key_field; os << "`" << field_info.table.name << "_id_" << field_info.name << "`=" << key_info.convert_to_open << "?" << field_info.table.name << "_id_" << field_info.name << "?" << key_info.convert_to_close; if (field_info.value_is_ordered) { if (index++) os << ", "; else os << " SET "; os << "`" << field_info.table.name << "_index_" << field_info.name << "`=?\?"; } } /* data fields */ for (auto& ptr : table.data_fields) { assert(ptr); auto& field_info = *ptr; if (is_update && filter->is_excluded(field_info)) continue; if (index++) os << ", "; else os << " SET "; os << "`" << field_info.name << "`=" << field_info.convert_to_open << "?" << field_info.name << "?" << field_info.convert_to_close; } /* type field for derived tables */ if ( !table.derived_tables.empty() && !table.base_table && is_create) { if (index++) os << ", "; else os << " SET "; os << "`__type`=?__type?"; } /* where primary key (for update) */ if (is_update) { assert(table.primary_key_field); auto& key_info = *table.primary_key_field; os << " WHERE `" << key_info.name << "`=" << key_info.convert_to_open << "?" << key_info.name << "?" << key_info.convert_to_close; } return index == 0 && !(table.primary_key_field->value_is_auto_incremented && is_create) ? std::string() : os.str(); } std::string execute_create_update( const table_t& table, const create_update_context& context, ::cppmariadb::statement * statement) { auto& connection = context.connection; auto* filter = context.filter; size_t index = 0; bool is_update = context.is_update(); bool is_create = context.is_create(); std::string primary_key; if (statement) statement->clear(); /* primary key */ assert(table.primary_key_field); if (is_update) { primary_key = table.get_primary_key(context); } else if (!table.primary_key_field->value_is_auto_incremented) { primary_key = table.primary_key_field->generate_value(context.connection); if (statement) statement->set(index, primary_key); ++index; } /* base_key */ if ( table.base_table && ( is_create || !filter->is_excluded(*table.base_table))) { auto new_context = context; if (!new_context.derived_table) new_context.derived_table = &table; std::string key = table.base_table->create_update_exec(new_context); if (statement) statement->set(index, std::move(key)); ++index; } if (is_update && filter->is_excluded(table)) return primary_key; /* foreign table one fields */ for (auto& ptr : table.foreign_table_one_fields) { assert(ptr); assert(ptr->referenced_table); auto& field = *ptr; if (is_update && filter->is_excluded(field)) continue; if (field.referenced_table->is_used_in_container) continue; /* insert/update dataset */ value_t key = field.foreign_create_update(context); if (key.has_value()) { if (statement) statement->set(index, *key); } else if (field.value_is_nullable) { if (statement) statement->set_null(index); } else { throw exception("Received null key for non nullable foreign dataset!"); } ++index; /* cleanup old dataset (if new one was created) */ if (context.is_update()) { field.foreign_one_delete(context, primary_key, key); } } /* foreign key fields */ for (auto& ptr : table.foreign_key_fields) { assert(ptr); if (is_update && ptr != context.owner_field) continue; auto& field_info = *ptr; bool set_value = context.owner_field && ptr == context.owner_field; if (set_value) { assert(!context.owner_key.empty()); if (statement) statement->set(index, context.owner_key); } else { if (statement) statement->set_null(index); } ++index; if (field_info.value_is_ordered) { if (set_value) { if (statement) statement->set(index, context.index); } else { if (statement) statement->set(index, 0); } ++index; } } /* data fields */ for (auto& ptr : table.data_fields) { assert(ptr); if (is_update && filter->is_excluded(*ptr)) continue; auto& field_info = *ptr; auto value = field_info.get(context); if (value.has_value()) { if (statement) statement->set(index, *value); } else { if (statement) statement->set_null(index); } ++index; } /* type field for derived tables */ if ( !table.derived_tables.empty() && !table.base_table && is_create) { if (statement) statement->set(index, context.derived_table ? context.derived_table->id : table.id); ++index; } /* where primary key (for update) */ if (is_update) { assert(table.primary_key_field); if (statement) statement->set(index, *table.primary_key_field->get(context)); ++index; } /* execute */ if (statement) { if (is_create) { cpphibernate_log_debug("execute INSERT query: " << std::endl << statement->query(connection) << std::endl); } else { cpphibernate_log_debug("execute UPDATE query: " << std::endl << statement->query(connection) << std::endl); } if ( table.primary_key_field->value_is_auto_incremented && is_create) { auto id = connection.execute_id(*statement); primary_key = cppcore::to_string(id); } else { auto count = connection.execute_rows(*statement); if (count > 1) throw exception("Expected one/ row to be inserted/updated!"); cpphibernate_log_debug(count << " rows inserted/updated" << std::endl); } table.primary_key_field->set(context, primary_key); } /* foreign table many fields */ for (auto& ptr : table.foreign_table_fields) { assert(ptr); assert(ptr->referenced_table); auto& field = *ptr; auto& ref_table = *field.referenced_table; if (!ref_table.is_used_in_container) continue; if ( is_update && ( filter->is_excluded(field) || filter->is_excluded(ref_table))) continue; /* set foreign keys of existing elements to null */ if (context.is_update()) { field.foreign_many_update(context, primary_key); } /* update elements */ auto next_context = context; next_context.owner_field = ptr; next_context.owner_key = primary_key; next_context.derived_table = nullptr; field.foreign_create_update(next_context); /* delete non referenced elements */ if (context.is_update()) { table_t::table_set processed; ref_table.cleanup(context, processed, true, true); } } return primary_key; }