insert_query_builder progress
This commit is contained in:
parent
5668f1060b
commit
38dc42ade8
|
|
@ -96,6 +96,52 @@ private:
|
||||||
const std::unordered_map<std::string, sql::statement> &statements_per_column_;
|
const std::unordered_map<std::string, sql::statement> &statements_per_column_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
template<typename Type>
|
||||||
|
utils::result<object::object_ptr<Type>, utils::error> session::insert(object::object_ptr<Type> obj) {
|
||||||
|
const auto it = schema_.find(typeid(Type));
|
||||||
|
if (it == schema_.end()) {
|
||||||
|
return utils::failure(make_error(error_code::UnknownType, "Failed to determine requested type."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build dependency-ordered insert steps (deps first, root last)
|
||||||
|
query::insert_query_builder iqb(schema_);
|
||||||
|
auto steps = iqb.build(obj);
|
||||||
|
if (!steps.is_ok()) {
|
||||||
|
return utils::failure(make_error(error_code::FailedToBuildQuery, "Failed to build insert dependency queries."));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute all steps; for Identity steps read RETURNING and write pk back into the object
|
||||||
|
for (auto &step : *steps) {
|
||||||
|
if (!step.has_returning) {
|
||||||
|
const auto exec_res = step.query.execute(*this);
|
||||||
|
if (!exec_res.is_ok()) {
|
||||||
|
return utils::failure(exec_res.err());
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stmt_res = step.query.prepare(*this);
|
||||||
|
if (!stmt_res.is_ok()) {
|
||||||
|
return utils::failure(stmt_res.err());
|
||||||
|
}
|
||||||
|
|
||||||
|
// RETURNING produces a result set; fetch the first row (single-row insert)
|
||||||
|
auto rec_res = stmt_res->fetch_one(); // const overload => std::optional<sql::record>
|
||||||
|
if (!rec_res.is_ok()) {
|
||||||
|
return utils::failure(rec_res.err());
|
||||||
|
}
|
||||||
|
if (!rec_res.value().has_value()) {
|
||||||
|
return utils::failure(make_error(error_code::FailedToFindObject, "INSERT ... RETURNING did not return a row."));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.apply_returning) {
|
||||||
|
step.apply_returning(*rec_res.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils::ok(obj);
|
||||||
|
} */
|
||||||
class session final : public sql::executor {
|
class session final : public sql::executor {
|
||||||
public:
|
public:
|
||||||
session(session_context &&ctx, const query::schema &scm);
|
session(session_context &&ctx, const query::schema &scm);
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,56 @@
|
||||||
#include "matador/query/query_builder_exception.hpp"
|
#include "matador/query/query_builder_exception.hpp"
|
||||||
|
|
||||||
namespace matador::query {
|
namespace matador::query {
|
||||||
|
|
||||||
|
struct pk_setter {
|
||||||
|
const std::string &name;
|
||||||
|
std::uint64_t value;
|
||||||
|
|
||||||
|
template<class V>
|
||||||
|
void on_primary_key(const char *id, V &pk, const utils::primary_key_attribute & = utils::default_pk_attributes) {
|
||||||
|
if (id != nullptr && name == id) {
|
||||||
|
pk = static_cast<V>(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void on_revision(const char * /*id*/, uint64_t & /*rev*/) {}
|
||||||
|
template<typename T>
|
||||||
|
static void on_attribute(const char * /*id*/, T &, const utils::field_attributes & = utils::null_attributes) {}
|
||||||
|
template<class P>
|
||||||
|
static void on_belongs_to(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
||||||
|
template<class P>
|
||||||
|
static void on_has_one(const char * /*id*/, P &, const utils::foreign_attributes & ) {}
|
||||||
|
template<class C>
|
||||||
|
static void on_has_many(const char * /*id*/, C &, const char * /*join_column*/, const utils::foreign_attributes & ) {}
|
||||||
|
template<class C>
|
||||||
|
static void on_has_many_to_many(const char * /*id*/, C &, const char * /*join_column*/, const char * /*inverse_join_column*/, const utils::foreign_attributes & ) {}
|
||||||
|
template<class C>
|
||||||
|
static void on_has_many_to_many(const char * /*id*/, C &, const utils::foreign_attributes & ) {}
|
||||||
|
};
|
||||||
|
|
||||||
class insert_query_builder {
|
class insert_query_builder {
|
||||||
|
public:
|
||||||
|
struct insert_step {
|
||||||
|
executable_query query;
|
||||||
|
bool has_returning{false};
|
||||||
|
std::function<void(const sql::record &)> apply_returning{};
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit insert_query_builder(const basic_schema &schema);
|
explicit insert_query_builder(const basic_schema &schema);
|
||||||
|
|
||||||
template<class EntityType>
|
template<class EntityType>
|
||||||
utils::result<std::vector<executable_query>, query_build_error> build(const object::object_ptr<EntityType> &ptr) {
|
utils::result<std::vector<insert_step>, query_build_error> build(const object::object_ptr<EntityType> &ptr) {
|
||||||
const auto it = schema_.find(typeid(EntityType));
|
auto it = schema_.find(typeid(EntityType));
|
||||||
if (it == schema_.end()) {
|
if (it == schema_.end()) {
|
||||||
return utils::failure(query_build_error::UnknownType);
|
return utils::failure(query_build_error::UnknownType);
|
||||||
}
|
}
|
||||||
|
|
||||||
executable_query q = query::query::insert().into(it->second.table()).values(*ptr);
|
steps_.clear();
|
||||||
return utils::ok(std::vector{q});
|
visited_.clear();
|
||||||
|
|
||||||
|
build_for(ptr);
|
||||||
|
|
||||||
|
return utils::ok(steps_);
|
||||||
}
|
}
|
||||||
|
|
||||||
template < class V >
|
template < class V >
|
||||||
|
|
@ -39,8 +76,93 @@ public:
|
||||||
on_foreign_object(id, obj, attr);
|
on_foreign_object(id, obj, attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<class EntityType>
|
||||||
|
static std::pair<std::type_index, const void *> make_visit_key(const object::object_ptr<EntityType> &ptr) {
|
||||||
|
return {std::type_index(typeid(EntityType)), static_cast<const void *>(&(*ptr))};
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class EntityType>
|
||||||
|
void build_for(const object::object_ptr<EntityType> &ptr) {
|
||||||
|
if (!ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto key = make_visit_key<EntityType>(ptr);
|
||||||
|
if (visited_.find(key) != visited_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visited_.insert(key);
|
||||||
|
|
||||||
|
const auto it = schema_.find(typeid(EntityType));
|
||||||
|
if (it == schema_.end()) {
|
||||||
|
throw query_builder_exception(query_build_error::UnknownType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Traverse relations first => dependencies will be inserted before this object
|
||||||
|
access::process(*this, *ptr);
|
||||||
|
|
||||||
|
// 2) Build INSERT for this object
|
||||||
|
// For Identity PK strategy: append RETURNING(pk_column)
|
||||||
|
const auto &info = it->second.node().info();
|
||||||
|
if (info.has_primary_key() && info.primary_key_attribute()->generator() == utils::generator_type::Identity) {
|
||||||
|
const std::string pk_name = info.primary_key_attribute()->name();
|
||||||
|
const table_column pk_col(&it->second.table(), pk_name);
|
||||||
|
insert_step step {
|
||||||
|
query::query::insert()
|
||||||
|
.into(it->second.table())
|
||||||
|
.values(*ptr)
|
||||||
|
.returning(pk_col),
|
||||||
|
true,
|
||||||
|
[ptr, pk_name](const sql::record &rec) {
|
||||||
|
// record.at<T>(name) returns optional<T>
|
||||||
|
// We assume integer-like PKs for now (as in your examples).
|
||||||
|
if (auto v = rec.at<std::uint64_t>(pk_name); v.has_value()) {
|
||||||
|
pk_setter setter{pk_name, *v};
|
||||||
|
access::process(setter, *ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
steps_.push_back(std::move(step));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_step step{
|
||||||
|
query::query::insert().into(it->second.table()).values(*ptr),
|
||||||
|
false,
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
steps_.push_back(std::move(step));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Pointer>
|
||||||
|
void on_foreign_object(Pointer &obj, const utils::foreign_attributes & /*attr*/) {
|
||||||
|
if (!obj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dependency only matters if the referenced object must be inserted
|
||||||
|
if (obj.is_persistent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using dep_t = std::remove_reference_t<decltype(*obj)>;
|
||||||
|
build_for<dep_t>(obj);
|
||||||
|
}
|
||||||
private:
|
private:
|
||||||
const basic_schema &schema_;
|
const basic_schema &schema_;
|
||||||
|
|
||||||
|
std::vector<insert_step> steps_;
|
||||||
|
std::unordered_set<std::pair<std::type_index, const void *>, visit_key_hash> visited_;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP
|
#endif //MATADOR_INSERT_QUERY_BUILDER_HPP
|
||||||
|
|
@ -11,6 +11,7 @@ enum class query_build_error : std::uint8_t {
|
||||||
Ok = 0,
|
Ok = 0,
|
||||||
UnknownType,
|
UnknownType,
|
||||||
MissingPrimaryKey,
|
MissingPrimaryKey,
|
||||||
|
MissingObject,
|
||||||
UnexpectedError,
|
UnexpectedError,
|
||||||
QueryError
|
QueryError
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue