Compare commits
4 Commits
7216f07c9f
...
ddf19bbf07
| Author | SHA1 | Date |
|---|---|---|
|
|
ddf19bbf07 | |
|
|
194e139e8b | |
|
|
6629a71f6e | |
|
|
72019bc1e7 |
|
|
@ -6,7 +6,10 @@
|
|||
|
||||
namespace matador::backends::postgres {
|
||||
|
||||
utils::error make_error(const sql::error_code ec, const PGresult *res, const PGconn *db, const std::string &msg,
|
||||
utils::error make_error(const sql::error_code ec,
|
||||
const PGresult *res,
|
||||
const PGconn *db,
|
||||
const std::string &msg,
|
||||
const std::string &sql) {
|
||||
utils::error err(ec, msg);
|
||||
err.add_error_info("dbms", "postgres");
|
||||
|
|
@ -30,22 +33,19 @@ bool is_result_error(const PGresult *res) {
|
|||
return status != PGRES_TUPLES_OK && status != PGRES_COMMAND_OK;
|
||||
}
|
||||
|
||||
void throw_postgres_error(const char *what, const std::string &source)
|
||||
{
|
||||
void throw_postgres_error(const char *what, const std::string &source) {
|
||||
std::stringstream msg;
|
||||
msg << "postgres error (" << source << "): " << what;
|
||||
throw std::logic_error(msg.str());
|
||||
}
|
||||
|
||||
void throw_postgres_error(PGconn *db, const std::string &source)
|
||||
{
|
||||
void throw_postgres_error(const PGconn *db, const std::string &source) {
|
||||
if (PQstatus(db) == CONNECTION_BAD) {
|
||||
throw_postgres_error(PQerrorMessage(db), source);
|
||||
}
|
||||
}
|
||||
|
||||
void throw_postgres_error(PGresult *res, PGconn *db, const std::string &source, const std::string &sql)
|
||||
{
|
||||
void throw_postgres_error(const PGresult *res, const PGconn *db, const std::string &source, const std::string &sql) {
|
||||
if (res == nullptr) {
|
||||
std::stringstream msg;
|
||||
msg << "postgres error (" << source << ", " << PQerrorMessage(db) << ": " << sql;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,6 @@ static std::pair<std::type_index, const void *> make_visit_key(const EntityType
|
|||
|
||||
struct visit_key_hash {
|
||||
size_t operator()(const std::pair<std::type_index, const void *> &p) const noexcept {
|
||||
// combine hashes (simple + sufficient here)
|
||||
const size_t h1 = p.first.hash_code();
|
||||
const size_t h2 = std::hash<const void *>{}(p.second);
|
||||
return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2));
|
||||
|
|
@ -117,7 +116,7 @@ public:
|
|||
: ctx_{ctx}
|
||||
{}
|
||||
|
||||
utils::result<void, utils::error> build(object::object_ptr<ObjectType> ptr) {
|
||||
utils::result<void, utils::error> build(object::object_ptr<ObjectType> ptr, const bool as_relation_step = false) {
|
||||
if (!ptr) {
|
||||
return utils::failure(utils::error{error_code::InvalidObject, "Object is null"});
|
||||
}
|
||||
|
|
@ -137,12 +136,6 @@ public:
|
|||
// 1) Traverse relations first => dependencies will be inserted before this object
|
||||
try {
|
||||
access::process(*this, *ptr_);
|
||||
|
||||
// relation inserts must run after all entity inserts were collected
|
||||
for (auto &s : ctx_.relation_steps_) {
|
||||
ctx_.steps_.push_back(std::move(s));
|
||||
}
|
||||
ctx_.relation_steps_.clear();
|
||||
} catch (const query_builder_exception &ex) {
|
||||
return utils::failure(ex.error());
|
||||
}
|
||||
|
|
@ -157,12 +150,11 @@ public:
|
|||
return utils::failure(utils::error{error_code::UnknownType, "Unknown type"});
|
||||
}
|
||||
|
||||
if (it->second.pk_generator().type() == utils::generator_type::Manual) {
|
||||
ctx_.steps_.push_back(std::make_unique<insert_step_pk_manual<ObjectType>>(cit->second.insert, ptr_));
|
||||
} else if (it->second.pk_generator().type() == utils::generator_type::Identity) {
|
||||
ctx_.steps_.push_back(std::make_unique<insert_step_pk_identity<ObjectType>>(cit->second.insert, ptr_, info.primary_key_attribute()->name()));
|
||||
auto step = create_insert_step(cit->second.insert, it->second);
|
||||
if (as_relation_step) {
|
||||
ctx_.relation_steps_.push_back(std::move(step));
|
||||
} else {
|
||||
ctx_.steps_.push_back(std::make_unique<insert_step_pk_generated<ObjectType>>(cit->second.insert, ptr_, it->second.pk_generator()));
|
||||
ctx_.steps_.push_back(std::move(step));
|
||||
}
|
||||
|
||||
ptr_.reset();
|
||||
|
|
@ -199,17 +191,17 @@ public:
|
|||
}
|
||||
|
||||
has_many_linker<ObjectType> linker(ptr_, join_column);
|
||||
insert_context ctx{ctx_.schema_, ctx_.contexts_by_type_};
|
||||
insert_step_processor<CollectionType> processor{ctx};
|
||||
insert_step_processor<CollectionType> processor{ctx_};
|
||||
for (auto &obj : objects) {
|
||||
if (!obj.is_transient()) {
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto result = processor.build(obj);
|
||||
if (obj.is_transient()) {
|
||||
auto result = processor.build(obj, true);
|
||||
if (!result) {
|
||||
throw query_builder_exception(error_code::InvalidObject, "Invalid object");
|
||||
// return utils::failure(result.err());
|
||||
throw query_builder_exception(result.release_error());
|
||||
}
|
||||
}
|
||||
|
||||
access::process(linker, *obj);
|
||||
|
|
@ -268,7 +260,6 @@ public:
|
|||
objects,
|
||||
attr,
|
||||
[foreign_type, local_type](const char* relation_name) -> processing_many_to_many_key {
|
||||
std::cout << "Processing many-to-many relation: " << local_type.name() << ":" << foreign_type.name() << ":" << relation_name << std::endl;
|
||||
return {std::string{relation_name}, local_type, foreign_type};
|
||||
// return make_processing_many_to_many_key<ObjectType, ForeignType>(relation_name);
|
||||
},
|
||||
|
|
@ -297,7 +288,6 @@ public:
|
|||
objects,
|
||||
attr,
|
||||
[foreign_type, local_type](const char* relation_name) -> processing_many_to_many_key {
|
||||
std::cout << "Processing many-to-many relation: " << foreign_type.name() << ":" << local_type.name() << ":" << relation_name << std::endl;
|
||||
return {std::string{relation_name}, foreign_type, local_type};
|
||||
return make_processing_many_to_many_key<ForeignType, ObjectType>(relation_name);
|
||||
},
|
||||
|
|
@ -309,17 +299,15 @@ public:
|
|||
private:
|
||||
template<class PointerType>
|
||||
void on_foreign_object(object::object_ptr<PointerType> &obj, const utils::foreign_attributes &attr) {
|
||||
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert) || !obj || obj.is_transient()) {
|
||||
if (!utils::is_cascade_type_set(attr.cascade(), utils::cascade_type::Insert) || !obj || !obj.is_transient()) {
|
||||
return;
|
||||
}
|
||||
|
||||
insert_context ctx{ctx_.schema_, ctx_.contexts_by_type_};
|
||||
insert_step_processor<PointerType> processor{ctx};
|
||||
insert_step_processor<PointerType> processor{ctx_};
|
||||
|
||||
const auto result = processor.build(obj);
|
||||
auto result = processor.build(obj);
|
||||
if (!result) {
|
||||
throw query_builder_exception(error_code::InvalidObject, "Invalid object");
|
||||
// return utils::failure(result.err());
|
||||
throw query_builder_exception(result.release_error());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -340,30 +328,31 @@ private:
|
|||
|
||||
const auto it = ctx_.schema_.find(std::string{id});
|
||||
if (it == ctx_.schema_.end()) {
|
||||
throw query_builder_exception(error_code::UnknownType, "Unknown type");
|
||||
throw query_builder_exception(error_code::UnknownType, "Unknown type for relation " + std::string{id});
|
||||
}
|
||||
|
||||
if (std::type_index(typeid(LocalType)) != it->second.node().info().type_index()) {
|
||||
throw query_builder_exception(error_code::InvalidRelationType, "Invalid relation type");
|
||||
throw query_builder_exception(error_code::InvalidRelationType, "Invalid relation type for " + std::string{id});
|
||||
}
|
||||
|
||||
const auto cit = ctx_.contexts_by_type_.find(it->second.node().info().type_index());
|
||||
if (cit == ctx_.contexts_by_type_.end()) {
|
||||
throw query_builder_exception(error_code::UnknownType, "Unknown type");
|
||||
throw query_builder_exception(error_code::UnknownType, "No query contexts for type " + it->second.node().name());
|
||||
}
|
||||
|
||||
std::ignore = ctx_.processing_many_to_many_relations_.insert(key);
|
||||
std::vector<std::unique_ptr<insert_step>> insert_relation_steps;
|
||||
insert_step_processor<ForeignType> processor(ctx_);
|
||||
for (auto &obj : objects) {
|
||||
if (!obj || !obj.is_transient()) {
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto result = processor.build(obj);
|
||||
if (obj.is_transient()) {
|
||||
auto result = processor.build(obj, true);
|
||||
if (!result) {
|
||||
throw query_builder_exception(error_code::InvalidObject, "Invalid object");
|
||||
// return utils::failure(result.err());
|
||||
throw query_builder_exception(result.release_error());
|
||||
};
|
||||
}
|
||||
|
||||
auto rel = make_relation(obj);
|
||||
|
|
@ -379,6 +368,16 @@ private:
|
|||
ctx_.processing_many_to_many_relations_.erase(key);
|
||||
}
|
||||
|
||||
std::unique_ptr<insert_step> create_insert_step(const sql::query_context& query_ctx, const schema_node& node) {
|
||||
if (node.pk_generator().type() == utils::generator_type::Manual) {
|
||||
return std::make_unique<insert_step_pk_manual<ObjectType>>(query_ctx, ptr_);
|
||||
}
|
||||
if (node.pk_generator().type() == utils::generator_type::Identity) {
|
||||
return std::make_unique<insert_step_pk_identity<ObjectType>>(query_ctx, ptr_, node.node().info().primary_key_attribute()->name());
|
||||
}
|
||||
return std::make_unique<insert_step_pk_generated<ObjectType>>(query_ctx, ptr_, node.pk_generator());
|
||||
}
|
||||
|
||||
private:
|
||||
insert_context& ctx_;
|
||||
object::object_ptr<ObjectType> ptr_;
|
||||
|
|
@ -388,15 +387,16 @@ template<class ObjectType>
|
|||
class insert_query_builder {
|
||||
public:
|
||||
explicit insert_query_builder(const basic_schema &schema, const std::unordered_map<std::type_index, query_contexts> &contexts_by_type)
|
||||
: context_{schema, contexts_by_type}
|
||||
: schema_(schema)
|
||||
, contexts_by_type_(contexts_by_type)
|
||||
{}
|
||||
|
||||
utils::result<std::vector<std::unique_ptr<insert_step>>, utils::error> build(const object::object_ptr<ObjectType> &ptr) {
|
||||
if (const auto it = context_.schema_.find(typeid(ObjectType)); it == context_.schema_.end()) {
|
||||
if (const auto it = schema_.find(typeid(ObjectType)); it == schema_.end()) {
|
||||
return utils::failure(utils::error{error_code::UnknownType, "Unknown type for insert query"});
|
||||
}
|
||||
|
||||
insert_context ctx{context_.schema_, context_.contexts_by_type_};
|
||||
insert_context ctx{schema_, contexts_by_type_};
|
||||
insert_step_processor<ObjectType> processor{ctx};
|
||||
|
||||
const auto result = processor.build(ptr);
|
||||
|
|
@ -404,11 +404,18 @@ public:
|
|||
return utils::failure(result.err());
|
||||
}
|
||||
|
||||
// relation inserts must run after all entity inserts were collected
|
||||
for (auto &s : ctx.relation_steps_) {
|
||||
ctx.steps_.push_back(std::move(s));
|
||||
}
|
||||
ctx.relation_steps_.clear();
|
||||
|
||||
return utils::ok(std::move(ctx.steps_));
|
||||
}
|
||||
|
||||
private:
|
||||
insert_context context_;
|
||||
const basic_schema &schema_;
|
||||
const std::unordered_map<std::type_index, query_contexts> &contexts_by_type_;
|
||||
};
|
||||
}
|
||||
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP
|
||||
|
|
@ -11,7 +11,8 @@ namespace matador::query {
|
|||
|
||||
class query_builder_exception final : public std::exception {
|
||||
public:
|
||||
explicit query_builder_exception(error_code error, std::string &&msg);
|
||||
explicit query_builder_exception(utils::error &&err);
|
||||
query_builder_exception(error_code error, std::string &&msg);
|
||||
|
||||
[[nodiscard]] const utils::error &error() const;
|
||||
|
||||
|
|
|
|||
|
|
@ -119,10 +119,10 @@ public:
|
|||
}
|
||||
|
||||
template<typename Func,
|
||||
typename SecondErrorType = typename std::invoke_result_t<Func, ErrorType >::value_type>
|
||||
typename SecondErrorType = typename std::invoke_result_t<Func, ErrorType&& >::value_type>
|
||||
result<ValueType, SecondErrorType> or_else(Func &&f) {
|
||||
if (is_error()) {
|
||||
return f(err());
|
||||
return f(release_error());
|
||||
}
|
||||
|
||||
return result<ValueType, SecondErrorType>(ok(release()));
|
||||
|
|
@ -175,10 +175,10 @@ public:
|
|||
return result(failure(release_error()));
|
||||
}
|
||||
|
||||
template<typename Func, typename SecondErrorType = typename std::invoke_result_t<Func, ErrorType >::value_type>
|
||||
template<typename Func, typename SecondErrorType = typename std::invoke_result_t<Func, ErrorType&& >::value_type>
|
||||
result<void, SecondErrorType> or_else(Func &&f) {
|
||||
if (is_error()) {
|
||||
return f(err());
|
||||
return f(release_error());
|
||||
}
|
||||
|
||||
return result<void, SecondErrorType>(ok<void>());
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ public:
|
|||
* @param args Passed arguments
|
||||
*/
|
||||
template <typename F, typename... Args>
|
||||
auto schedule(F&& func, Args&&... args) -> result_fut<std::result_of_t<F(cancel_token&, Args...)>> {
|
||||
using return_type = std::result_of_t<F(cancel_token&, Args...)>;
|
||||
auto schedule(F&& func, Args&&... args) -> result_fut<std::invoke_result_t<F, cancel_token&, Args...>> {
|
||||
using return_type = std::invoke_result_t<F, cancel_token&, Args...>;
|
||||
const auto token = std::make_shared<cancel_token>();
|
||||
|
||||
auto task_ptr = std::make_shared<std::packaged_task<return_type()>>(
|
||||
|
|
@ -73,7 +73,7 @@ public:
|
|||
if (!running_) {
|
||||
return failure(std::string("Thread pool is shut down, cannot schedule new tasks."));
|
||||
}
|
||||
tasks_.emplace_back([task_ptr, token] {
|
||||
tasks_.emplace_back([task_ptr] {
|
||||
try { (*task_ptr)(); }
|
||||
catch (...) { /* Prevent exception escape */ }
|
||||
}, token);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include "matador/query/query_builder_exception.hpp"
|
||||
|
||||
namespace matador::query {
|
||||
query_builder_exception::query_builder_exception(utils::error &&err)
|
||||
: error_(std::move(err)) {}
|
||||
|
||||
query_builder_exception::query_builder_exception(const error_code error, std::string&& msg)
|
||||
: error_(error, msg)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ TEST_CASE_METHOD(SessionFixture, "Test insert object with belongs to relation wi
|
|||
const auto carrie = make_object<book_identity>("Carrie", s_king, 1974);
|
||||
|
||||
REQUIRE(carrie.is_transient());
|
||||
REQUIRE(ses.insert(carrie).is_ok());
|
||||
const auto res = ses.insert(carrie);
|
||||
REQUIRE(res.is_ok());
|
||||
REQUIRE(carrie.is_persistent());
|
||||
|
||||
auto found_author = ses.find<author_identity>(s_king->id);
|
||||
|
|
|
|||
|
|
@ -61,6 +61,16 @@ void validate_author_state(const object_ptr<AuthorType>& ptr, object_state expec
|
|||
}
|
||||
}
|
||||
|
||||
namespace matador::utils {
|
||||
template < typename ValueType >
|
||||
std::ostream& operator<<(std::ostream& os, const result<ValueType, error>& value) {
|
||||
if (value) {
|
||||
return os;
|
||||
}
|
||||
return os << "Error: " << value.err();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(SessionFixture, "Test insert object with has many relation", "[session][insert][has_many]") {
|
||||
const auto result = schema.attach<book>("books")
|
||||
.and_then( [this] { return schema.attach<author>("authors"); } )
|
||||
|
|
@ -79,7 +89,7 @@ TEST_CASE_METHOD(SessionFixture, "Test insert object with has many relation", "[
|
|||
|
||||
validate_author_state(s_king, object_state::Transient);
|
||||
auto res = ses.insert(s_king);
|
||||
REQUIRE(res.is_ok());
|
||||
REQUIRE(res);
|
||||
validate_author_state(s_king, object_state::Persistent);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue