diff --git a/backends/postgres/include/postgres_result_reader.hpp b/backends/postgres/include/postgres_result_reader.hpp index 631d445..9d3675d 100644 --- a/backends/postgres/include/postgres_result_reader.hpp +++ b/backends/postgres/include/postgres_result_reader.hpp @@ -44,6 +44,7 @@ public: [[nodiscard]] const char *column(size_t index) const override; utils::result fetch() override; [[nodiscard]] size_t start_column_index() const override; + void unshift() override; void read_value(const char *id, size_t index, int8_t &value) override; void read_value(const char *id, size_t index, int16_t &value) override; diff --git a/backends/postgres/src/postgres_result_reader.cpp b/backends/postgres/src/postgres_result_reader.cpp index 5862903..f9f03dd 100644 --- a/backends/postgres/src/postgres_result_reader.cpp +++ b/backends/postgres/src/postgres_result_reader.cpp @@ -34,6 +34,12 @@ size_t postgres_result_reader::start_column_index() const { return 0; } +void postgres_result_reader::unshift() { + if (row_index_ > 0) { + --row_index_; + } +} + void postgres_result_reader::read_value(const char * /*id*/, const size_t index, int8_t &value) { if (auto res = utils::to(column(index)); res.is_ok()) { value = res.value(); diff --git a/include/matador/object/object_ptr.hpp b/include/matador/object/object_ptr.hpp index cead78c..4fad358 100644 --- a/include/matador/object/object_ptr.hpp +++ b/include/matador/object/object_ptr.hpp @@ -22,7 +22,7 @@ public: object_ptr &operator=(object_ptr &&other) = default; using value_type = Type; - Type* operator->() { return ptr_.get(); } + Type* operator->() const { return ptr_.get(); } Type& operator*() const { return *ptr_; } [[nodiscard]] bool empty() const { return ptr_ == nullptr; } diff --git a/include/matador/sql/field.hpp b/include/matador/sql/field.hpp index e95414b..0a7c82f 100644 --- a/include/matador/sql/field.hpp +++ b/include/matador/sql/field.hpp @@ -8,8 +8,17 @@ #include #include +namespace matador::utils { +enum class constraints : unsigned char; +} namespace matador::sql { +enum struct field_type { + Attribute, + PrimaryKey, + ForeignKey +}; + /** * */ @@ -17,11 +26,12 @@ class field { public: explicit field(std::string name); template - field(std::string name, Type value, const size_t size = 0, const int index = -1) + field(std::string name, Type value, const field_type type = field_type::Attribute, const size_t size = 0, const int index = -1) : name_(std::move(name)) + , type_(type) , index_(index) , value_(value, size) {} - field(std::string name, utils::basic_type dt, size_t size = 0, int index = -1); + field(std::string name, utils::basic_type dt, field_type type = field_type::Attribute, size_t size = 0, int index = -1); field(const field &x) = default; field& operator=(const field &x) = default; field(field &&x) noexcept; @@ -35,6 +45,7 @@ public: } [[nodiscard]] const std::string& name() const; + [[nodiscard]] field_type type() const; [[nodiscard]] size_t size() const; [[nodiscard]] int index() const; @@ -53,18 +64,25 @@ public: [[nodiscard]] bool is_blob() const; [[nodiscard]] bool is_null() const; + [[nodiscard]] bool is_primary_key() const; + [[nodiscard]] bool is_foreign_key() const; + [[nodiscard]] bool is_attribute() const; + friend std::ostream& operator<<(std::ostream &out, const field &col); private: + static utils::constraints determine_constraint(field_type type); + template void process(Operator &op) { - op.on_attribute(name_.c_str(), value_, value_.size()); + op.on_attribute(name_.c_str(), value_, { value_.size(), determine_constraint(type_) } ); } private: friend class record; std::string name_; + field_type type_{field_type::Attribute}; int index_{-1}; utils::value value_; diff --git a/include/matador/sql/interface/query_result_reader.hpp b/include/matador/sql/interface/query_result_reader.hpp index dceeafe..61bd982 100644 --- a/include/matador/sql/interface/query_result_reader.hpp +++ b/include/matador/sql/interface/query_result_reader.hpp @@ -16,6 +16,7 @@ public: [[nodiscard]] virtual const char* column(size_t index) const = 0; [[nodiscard]] virtual utils::result fetch() = 0; [[nodiscard]] virtual size_t start_column_index() const = 0; + virtual void unshift() = 0; template void bind(Type &obj) { diff --git a/include/matador/sql/internal/query_result_impl.hpp b/include/matador/sql/internal/query_result_impl.hpp index a609096..b0530b6 100644 --- a/include/matador/sql/internal/query_result_impl.hpp +++ b/include/matador/sql/internal/query_result_impl.hpp @@ -8,6 +8,8 @@ #include "matador/utils/identifier.hpp" #include "matador/sql/interface/query_result_reader.hpp" +#include "matador/sql/internal/query_result_pk_resolver.hpp" +#include "matador/sql/record.hpp" #include "matador/object/attribute_definition.hpp" @@ -40,19 +42,19 @@ public: template void on_primary_key(const char *id, ValueType &value, std::enable_if_t && !std::is_same_v>* = nullptr); void on_primary_key(const char *id, std::string &value, size_t size); - void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {} + void on_revision(const char * /*id*/, unsigned long long &/*rev*/) { ++column_index_; } template < class Type > - void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) { ++column_index_; } template < class Pointer > - void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) {} + void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &/*attr*/) { ++column_index_; } 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*/) { ++column_index_; } template - void on_has_many(const char * /*id*/, ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} + void on_has_many(const char * /*id*/, ContainerType &, const char * /*join_column*/, const utils::foreign_attributes &/*attr*/) {} template - 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*/) {} + 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 void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} @@ -86,6 +88,7 @@ public: { utils::data_type_traits::read_value(*reader_, id, column_index_++, x); } + void on_attribute(const char *id, char *value, const utils::field_attributes &attr = utils::null_attributes); void on_attribute(const char *id, std::string &value, const utils::field_attributes &attr = utils::null_attributes); void on_attribute(const char *id, utils::value &val, const utils::field_attributes &attr = utils::null_attributes); @@ -97,23 +100,25 @@ public: } if (attr.fetch() == utils::fetch_type::LAZY) { pk_reader_.read(*x, column_index_++); - } else if (const auto ti = std::type_index(typeid(*x)); processed_types_.count(ti) == 0) { - processed_types_.insert(ti); + } else { + const auto ti = std::type_index(typeid(*x)); type_stack_.push(ti); access::process(*this, *x); type_stack_.pop(); } } 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) { if (x.empty()) { x.reset(new typename Pointer::value_type); } if (attr.fetch() == utils::fetch_type::LAZY) { pk_reader_.read(*x, column_index_++); - } else if (const auto ti = std::type_index(typeid(*x)); processed_types_.count(ti) == 0) { - processed_types_.insert(ti); + } else { + const auto ti = std::type_index(typeid(*x)); + type_stack_.push(ti); + access::process(*this, *x); + type_stack_.pop(); } } @@ -125,9 +130,8 @@ public: void on_has_many(const char * /*id*/, ContainerType &cont, const char * /*join_column*/, const utils::foreign_attributes &attr) { if ( attr.fetch() == utils::fetch_type::LAZY ) { // pk_reader_.read(*id, column_index_++); - } else /*if (const auto ti = std::type_index(typeid(typename ContainerType::value_type::value_type)); processed_types_.count(ti) == 0)*/ { + } else { const auto ti = std::type_index(typeid(typename ContainerType::value_type::value_type)); - processed_types_.insert(ti); auto obj = std::make_unique(); type_stack_.push(ti); access::process(*this, *obj); @@ -148,37 +152,58 @@ public: reader_->bind(obj); } + [[nodiscard]] bool pk_has_changed() const { + return !last_pk_.is_null() && last_pk_ != current_pk_; + } + template bool fetch(Type &obj) { - bool result = false; + bool first = true; do { - column_index_ = reader_->start_column_index(); - auto fetched = reader_->fetch(); - if (!fetched.is_ok()) { - return false; + if (auto fetched = reader_->fetch(); !fetched.is_ok() || !*fetched) { + return !first; } - if (!*fetched) { - return false; + last_pk_ = current_pk_; + current_pk_ = discover_current_primary_key(obj); + if (pk_has_changed()) { + reader_->unshift(); + last_pk_.clear(); + current_pk_.clear(); + break; } - processed_types_.insert(typeid(Type)); + first = false; type_stack_.push(typeid(Type)); + column_index_ = reader_->start_column_index(); access::process(*this, obj); type_stack_.pop(); - std::cout << "last pk: " << last_pk_.str() << std::endl; - std::cout << "current pk: " << current_pk_.str() << std::endl; - std::cout << "last == current: " << std::boolalpha << (last_pk_ == current_pk_) << std::endl; } while (last_pk_ == current_pk_); return true; } + bool fetch(record &rec) { + if (auto fetched = reader_->fetch(); !fetched.is_ok() || !*fetched) { + return false; + } + + column_index_ = reader_->start_column_index(); + access::process(*this, rec); + return true; + } + [[nodiscard]] const std::vector& prototype() const; +private: + template + utils::identifier discover_current_primary_key(const Type &obj) { + internal::query_result_pk_resolver resolver(*reader_); + return resolver.discover(obj); + } + protected: size_t column_index_ = 0; std::vector prototype_; std::unique_ptr reader_; detail::pk_reader pk_reader_; - std::unordered_set processed_types_; std::stack type_stack_; utils::identifier current_pk_{}; utils::identifier last_pk_{}; diff --git a/include/matador/sql/internal/query_result_pk_resolver.hpp b/include/matador/sql/internal/query_result_pk_resolver.hpp new file mode 100644 index 0000000..0af5ffc --- /dev/null +++ b/include/matador/sql/internal/query_result_pk_resolver.hpp @@ -0,0 +1,78 @@ +#ifndef QUERY_RESULT_PK_RESOLVER_HPP +#define QUERY_RESULT_PK_RESOLVER_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/field_attributes.hpp" +#include "matador/utils/foreign_attributes.hpp" +#include "matador/utils/identifier.hpp" + +#include "matador/sql/interface/query_result_reader.hpp" + +#include +#include + +namespace matador::sql::internal { + +class query_result_pk_resolver final { +public: + explicit query_result_pk_resolver(query_result_reader &reader) : reader_(reader) {} + + template + utils::identifier discover(Type &obj) { + column_index_ = reader_.start_column_index(); + pk_.clear(); + access::process(*this, obj); + + return pk_; + } + + template + void on_primary_key(const char *id, ValueType &/*value*/, std::enable_if_t && !std::is_same_v>* = nullptr) { + if (!type_stack_.empty()) { + return; + } + ValueType value; + utils::data_type_traits::read_value(reader_, id, column_index_++, value); + pk_ = value; + } + void on_primary_key(const char *id, std::string &value, size_t size); + void on_revision(const char * /*id*/, unsigned long long &/*rev*/) { ++column_index_; } + + template < class Type > + void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) { ++column_index_; } + void on_attribute(const char *id, const utils::value &x, const utils::field_attributes &attr = utils::null_attributes); + + template < class Pointer > + void on_belongs_to(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &attr) { on_foreign_key(attr.fetch() ); } + template < class Pointer > + void on_has_one(const char * /*id*/, Pointer &/*x*/, const utils::foreign_attributes &attr) { on_foreign_key(attr.fetch() ); } + + template + void on_has_many(const char * /*id*/, ContainerType &, const char *, const utils::foreign_attributes &/*attr*/) {} + template + 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 + void on_has_many_to_many(const char *id, ContainerType &c, const utils::foreign_attributes &/*attr*/) {} + +private: + template + void on_foreign_key(const utils::fetch_type fetch) { + if (fetch == utils::fetch_type::LAZY) { + ++column_index_; + } else { + const Type obj; + type_stack_.push(typeid(Type)); + access::process(*this, obj); + type_stack_.pop(); + } + } +private: + size_t column_index_{}; + utils::identifier pk_; + query_result_reader &reader_; + std::stack type_stack_; +}; + +} + +#endif //QUERY_RESULT_PK_RESOLVER_HPP diff --git a/include/matador/sql/record.hpp b/include/matador/sql/record.hpp index 1dc3bdc..70775b5 100644 --- a/include/matador/sql/record.hpp +++ b/include/matador/sql/record.hpp @@ -1,8 +1,6 @@ #ifndef QUERY_RECORD_HPP #define QUERY_RECORD_HPP -#include "matador/utils/access.hpp" - #include "matador/sql/field.hpp" #include @@ -34,8 +32,7 @@ public: ~record() = default; template - void process(Operator &op) - { + void process(Operator &op) { for(auto &f : fields_) { f.get().process(op); } diff --git a/source/core/object/attribute_definition.cpp b/source/core/object/attribute_definition.cpp index 0f74f75..bb0569d 100644 --- a/source/core/object/attribute_definition.cpp +++ b/source/core/object/attribute_definition.cpp @@ -163,7 +163,7 @@ attribute_definition make_column(const std::string &name, utils::fi template<> attribute_definition make_pk_column(const std::string &name, size_t size) { - return make_column(name, {size, utils::constraints::FOREIGN_KEY}); + return make_column(name, {size, utils::constraints::PRIMARY_KEY}); } template<> diff --git a/source/orm/CMakeLists.txt b/source/orm/CMakeLists.txt index c432dd9..0ba764a 100644 --- a/source/orm/CMakeLists.txt +++ b/source/orm/CMakeLists.txt @@ -61,6 +61,7 @@ add_library(matador-orm STATIC ../../include/matador/sql/interface/statement_impl.hpp ../../include/matador/sql/internal/object_result_binder.hpp ../../include/matador/sql/internal/query_result_impl.hpp + ../../include/matador/sql/internal/query_result_pk_resolver.hpp ../../include/matador/sql/query_context.hpp ../../include/matador/sql/query_macro.hpp ../../include/matador/sql/query_result.hpp @@ -121,6 +122,7 @@ add_library(matador-orm STATIC sql/interface/query_result_reader.cpp sql/interface/statement_impl.cpp sql/internal/object_result_binder.cpp + sql/internal/query_result_pk_resolver.cpp sql/object_parameter_binder.cpp sql/query_result.cpp sql/record.cpp diff --git a/source/orm/sql/field.cpp b/source/orm/sql/field.cpp index 6b6b5a6..59ee145 100644 --- a/source/orm/sql/field.cpp +++ b/source/orm/sql/field.cpp @@ -1,5 +1,7 @@ #include "matador/sql/field.hpp" +#include "matador/utils/constraints.hpp" + #include namespace matador::sql { @@ -9,23 +11,24 @@ field::field(std::string name) , value_(nullptr) {} -field::field(std::string name, const utils::basic_type dt, const size_t size, const int index) - : name_(std::move(name)) - , index_(index) - , value_(dt, size) {} +field::field(std::string name, const utils::basic_type dt, const field_type type, const size_t size, const int index) +: name_(std::move(name)) +, type_(type) +, index_(index) +, value_(dt, size) {} field::field(field &&x) noexcept - : name_(std::move(x.name_)) - , index_(x.index_) - , value_(std::move(x.value_)) -{ +: name_(std::move(x.name_)) +, type_(x.type_) +, index_(x.index_) +, value_(std::move(x.value_)) { x.value_ = nullptr; x.index_ = -1; } -field &field::operator=(field &&x) noexcept -{ +field &field::operator=(field &&x) noexcept { name_ = std::move(x.name_); + type_ = x.type_; index_ = x.index_; value_ = std::move(x.value_); x.index_ = -1; @@ -39,6 +42,10 @@ const std::string &field::name() const return name_; } +field_type field::type() const { + return type_; +} + size_t field::size() const { return value_.size(); @@ -55,6 +62,19 @@ std::ostream &operator<<(std::ostream &out, const field &col) return out; } +utils::constraints field::determine_constraint(const field_type type) { + switch (type) { + case field_type::PrimaryKey: + return utils::constraints::PRIMARY_KEY; + case field_type::ForeignKey: + return utils::constraints::FOREIGN_KEY; + case field_type::Attribute: + default: + return utils::constraints::NONE; + } + +} + std::string field::str() const { return as().value_or(""); @@ -95,4 +115,17 @@ bool field::is_null() const return value_.is_null(); } +bool field::is_primary_key() const { + return type_ == field_type::PrimaryKey; +} + +bool field::is_foreign_key() const { + return type_ == field_type::ForeignKey; +} + +bool field::is_attribute() const { + return type_ == field_type::Attribute; +} + + } \ No newline at end of file diff --git a/source/orm/sql/internal/query_result_pk_resolver.cpp b/source/orm/sql/internal/query_result_pk_resolver.cpp new file mode 100644 index 0000000..e11be27 --- /dev/null +++ b/source/orm/sql/internal/query_result_pk_resolver.cpp @@ -0,0 +1,30 @@ +#include "matador/sql/internal/query_result_pk_resolver.hpp" + +#include "matador/utils/value.hpp" + +namespace matador::sql::internal { +void query_result_pk_resolver::on_primary_key(const char* id, std::string& /*value*/, const size_t size) { + if (!type_stack_.empty()) { + return; + } + std::string value; + utils::data_type_traits::read_value(reader_, id, column_index_++, value, size); + pk_ = value; +} + +void query_result_pk_resolver::on_attribute(const char *id, const utils::value &x, const utils::field_attributes &attr) { + if (is_constraint_set(attr.options(), utils::constraints::PRIMARY_KEY)) { + if (x.is_integer()) { + uint64_t val; + utils::data_type_traits::read_value(reader_, id, column_index_++, val); + pk_ = val; + } else if (x.is_varchar()) { + std::string value; + utils::data_type_traits::read_value(reader_, id, column_index_++, value, attr.size()); + pk_ = value; + } + } + ++column_index_; +} + +} \ No newline at end of file diff --git a/source/orm/sql/query_result.cpp b/source/orm/sql/query_result.cpp index 57bb02f..24624a8 100644 --- a/source/orm/sql/query_result.cpp +++ b/source/orm/sql/query_result.cpp @@ -5,12 +5,27 @@ namespace matador::sql::detail { +field_type determine_field_type(const utils::constraints c) { + if (is_constraint_set(c, utils::constraints::FOREIGN_KEY)) { + return field_type::ForeignKey; + } + if (is_constraint_set(c, utils::constraints::PRIMARY_KEY)) { + return field_type::PrimaryKey; + } + return field_type::Attribute; +} + template<> -record *create_prototype(const std::vector &prototype) -{ +record *create_prototype(const std::vector &prototype) { auto result = std::make_unique(); for (const auto &col: prototype) { - result->append({col.name(), col.type(), col.attributes().size(), col.index()}); + result->append({ + col.name(), + col.type(), + determine_field_type(col.attributes().options()), + col.attributes().size(), + col.index() + }); } return result.release(); } diff --git a/test/backends/SessionTest.cpp b/test/backends/SessionTest.cpp index 074f5da..4b15f11 100644 --- a/test/backends/SessionTest.cpp +++ b/test/backends/SessionTest.cpp @@ -197,6 +197,9 @@ TEST_CASE_METHOD(SessionFixture, "Use session to find all objects with one-to-ma all_departments = find_result.release(); for (auto it = all_departments.begin(); it != all_departments.end(); ++it) { - std::cout << "department: " << it->name << " (employees: " << it->employees.size() << ")\n"; + std::cout << "department: " << it->name << " (id: " << it->id << ", employees: " << it->employees.size() << ")\n"; + for (const auto& emp : it->employees) { + std::cout << "\temployee: " << emp->first_name << " " << emp->last_name << "( " << emp->id << ")\n"; + } } } \ No newline at end of file diff --git a/test/models/department.hpp b/test/models/department.hpp index 82e4a83..f84d875 100644 --- a/test/models/department.hpp +++ b/test/models/department.hpp @@ -38,7 +38,7 @@ struct employee { field::primary_key(op, "id", id); field::attribute(op, "first_name", first_name, 63); field::attribute(op, "last_name", last_name, 63); - field::belongs_to(op, "dep_id", dep, utils::fetch_type::EAGER); + field::belongs_to(op, "dep_id", dep, utils::fetch_type::LAZY); } }; diff --git a/test/models/flight.hpp b/test/models/flight.hpp index 87f62b9..1194eb6 100644 --- a/test/models/flight.hpp +++ b/test/models/flight.hpp @@ -28,7 +28,7 @@ struct flight { namespace field = matador::access; using namespace matador::utils; field::primary_key(op, "id", id); - field::has_one(op, "airplane_id", plane, {utils::cascade_type::ALL, utils::fetch_type::EAGER}); + field::has_one(op, "airplane_id", plane, {utils::cascade_type::ALL, fetch_type::EAGER}); field::attribute(op, "pilot_name", pilot_name, 255); } }; diff --git a/test/orm/backend/test_result_reader.cpp b/test/orm/backend/test_result_reader.cpp index 267c8ff..6ef8953 100644 --- a/test/orm/backend/test_result_reader.cpp +++ b/test/orm/backend/test_result_reader.cpp @@ -18,6 +18,8 @@ size_t test_result_reader::start_column_index() const { return 0; } +void test_result_reader::unshift() {} + void test_result_reader::read_value(const char *id, const size_t index, int8_t &value) { value = -8; } diff --git a/test/orm/backend/test_result_reader.hpp b/test/orm/backend/test_result_reader.hpp index c9e9b46..c52b30b 100644 --- a/test/orm/backend/test_result_reader.hpp +++ b/test/orm/backend/test_result_reader.hpp @@ -38,6 +38,7 @@ public: [[nodiscard]] const char *column(size_t index) const override; [[nodiscard]] utils::result fetch() override; [[nodiscard]] size_t start_column_index() const override; + void unshift() override; void read_value(const char *id, size_t index, int8_t &value) override; void read_value(const char *id, size_t index, int16_t &value) override; diff --git a/test/orm/sql/FieldTest.cpp b/test/orm/sql/FieldTest.cpp index 7e93fcb..cb5ba29 100644 --- a/test/orm/sql/FieldTest.cpp +++ b/test/orm/sql/FieldTest.cpp @@ -37,7 +37,7 @@ TEST_CASE("Test field", "[field]") { REQUIRE(bool_val.has_value()); REQUIRE(bool_val.value()); - f = sql::field("name", utils::blob{ 7,8,6,5,4,3 }, 0, 1); + f = sql::field("name", utils::blob{ 7,8,6,5,4,3 }, sql::field_type::Attribute, 0, 1); REQUIRE(f.index() == 1); REQUIRE(!f.is_null()); REQUIRE(!f.is_integer());