diff --git a/backends/sqlite/include/sqlite_query_result.hpp b/backends/sqlite/include/sqlite_query_result.hpp index 071e4e6..b609727 100644 --- a/backends/sqlite/include/sqlite_query_result.hpp +++ b/backends/sqlite/include/sqlite_query_result.hpp @@ -2,6 +2,7 @@ #define QUERY_SQLITE_QUERY_RESULT_HPP #include "matador/sql/query_result_impl.hpp" +#include "matador/sql/record.hpp" #include @@ -9,8 +10,15 @@ namespace matador::backends::sqlite { class sqlite_query_result : public sql::query_result_impl { public: + using columns = std::vector; + using rows = std::vector; + +public: + sqlite_query_result(sql::record prototype, rows result); ~sqlite_query_result() override; + size_t column_count() const override; + void read_value(const char *id, size_t index, char &value) override; void read_value(const char *id, size_t index, short &value) override; void read_value(const char *id, size_t index, int &value) override; @@ -39,9 +47,6 @@ private: void push_back(char **row_values, int column_count); private: - using columns = std::vector; - using rows = std::vector; - rows result_; long long row_index_ = -1; }; diff --git a/backends/sqlite/src/sqlite_connection.cpp b/backends/sqlite/src/sqlite_connection.cpp index 9f39d85..f854ba7 100644 --- a/backends/sqlite/src/sqlite_connection.cpp +++ b/backends/sqlite/src/sqlite_connection.cpp @@ -37,14 +37,37 @@ bool sqlite_connection::is_open() return sqlite_db_ != nullptr; } +struct fetch_context +{ + sql::record prototype; + sqlite_query_result::rows rows; +}; + int sqlite_connection::parse_result(void* param, int column_count, char** values, char** columns) { - auto *result = static_cast(param); - result->push_back(values, column_count); + auto *context = static_cast(param); - sql::record prototype; + sqlite_query_result::columns column; for(int i = 0; i < column_count; ++i) { - prototype.append(sql::column{columns[i]}); + // copy and store column data; + if (values[i] == nullptr) { + auto val = new char[1]; + val[0] = '\0'; + column.push_back(val); + } else { + size_t size = strlen(values[i]); + auto val = new char[size + 1]; + std::memcpy(val, values[i], size); + val[size] = '\0'; + column.push_back(val); + } + } + context->rows.emplace_back(column); + + if (context->prototype.empty()) { + for(int i = 0; i < column_count; ++i) { + context->prototype.append(sql::column{columns[i]}); + } } return 0; @@ -62,13 +85,13 @@ size_t sqlite_connection::execute(const std::string &stmt) std::unique_ptr sqlite_connection::fetch(const std::string &stmt) { - auto result = std::make_unique(); + fetch_context context; char *errmsg = nullptr; - const int ret = sqlite3_exec(sqlite_db_, stmt.c_str(), parse_result, result.get(), &errmsg); + const int ret = sqlite3_exec(sqlite_db_, stmt.c_str(), parse_result, &context, &errmsg); throw_sqlite_error(ret, sqlite_db_, "sqlite", stmt); - return std::move(result); + return std::move(std::make_unique(std::move(context.prototype), std::move(context.rows))); } void sqlite_connection::prepare(const std::string &stmt) diff --git a/backends/sqlite/src/sqlite_query_result.cpp b/backends/sqlite/src/sqlite_query_result.cpp index 9ee758d..331336d 100644 --- a/backends/sqlite/src/sqlite_query_result.cpp +++ b/backends/sqlite/src/sqlite_query_result.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace matador::backends::sqlite { template < class Type > @@ -48,6 +50,11 @@ void read(Type &x, const char *val, typename std::enable_if query_result fetch(const std::string &sql) { - return query_result(connection_->execute(sql)); + return query_result(connection_->fetch(sql)); } - query_result fetch(const std::string &sql); - std::pair execute(const std::string &sql); + [[nodiscard]] query_result fetch(const std::string &sql) const; + [[nodiscard]] std::pair execute(const std::string &sql) const; + +private: + friend class session; + + [[nodiscard]] std::unique_ptr call_fetch(const std::string &sql) const; private: connection_info connection_info_; - bool is_open_{false}; std::unique_ptr connection_; }; diff --git a/include/matador/sql/query_builder.hpp b/include/matador/sql/query_builder.hpp index c5fed9d..80564d6 100644 --- a/include/matador/sql/query_builder.hpp +++ b/include/matador/sql/query_builder.hpp @@ -54,6 +54,13 @@ enum class join_type_t { INNER, OUTER, LEFT, RIGHT }; +struct query +{ + std::string sql; + record prototype; + std::vector host_vars; +}; + class query_builder { private: @@ -123,8 +130,6 @@ public: std::string compile(); - [[nodiscard]] const record& prototype() const; - private: void transition_to(state_t next); void initialize(command_t cmd, state_t state); @@ -139,7 +144,7 @@ private: detail::any_type_to_string_visitor value_to_string_; - record prototype_; + query query_; using query_state_set = std::unordered_set; using query_state_transition_map = std::unordered_map; diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 0b3c3d5..52bb441 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -47,6 +47,12 @@ protected: using query_intermediate::query_intermediate; public: + template < class Type > + query_result fetch_all() + { + return query_result(fetch()); + } + query_result fetch_all(); record fetch_one(); template @@ -55,6 +61,9 @@ public: auto result = fetch_all(); return {}; } + +private: + std::unique_ptr fetch(); }; class query_limit_intermediate : public query_select_finish @@ -124,7 +133,6 @@ public: using query_intermediate::query_intermediate; query_from_intermediate from(const std::string &table, const std::string &as = ""); - }; class query_into_intermediate : public query_intermediate @@ -149,7 +157,7 @@ public: template query_execute_finish table(const std::string &table_name) { - const auto &info = repository_.attach(table_name, record{column_generator::generate(repository_)}); + const auto &info = repository_.attach(table_name/*, record{column_generator::generate(repository_)}*/); return {db(), query().table(table_name, info.prototype.columns())}; } diff --git a/include/matador/sql/query_result.hpp b/include/matador/sql/query_result.hpp index 847122b..831774b 100644 --- a/include/matador/sql/query_result.hpp +++ b/include/matador/sql/query_result.hpp @@ -115,6 +115,7 @@ public: public: explicit query_result(std::unique_ptr impl) : impl_(std::move(impl)) {} + query_result(std::unique_ptr impl, creator_func creator) : creator_(creator) , impl_(std::move(impl)) {} diff --git a/include/matador/sql/query_result_impl.hpp b/include/matador/sql/query_result_impl.hpp index 137a796..edc71af 100644 --- a/include/matador/sql/query_result_impl.hpp +++ b/include/matador/sql/query_result_impl.hpp @@ -5,6 +5,7 @@ #include "matador/utils/field_attributes.hpp" #include "matador/sql/any_type.hpp" +#include "matador/sql/record.hpp" #include "matador/sql/types.hpp" #include @@ -16,6 +17,8 @@ class query_result_impl public: virtual ~query_result_impl() = default; + virtual size_t column_count() const = 0; + virtual void read_value(const char *id, size_t index, char &value) = 0; virtual void read_value(const char *id, size_t index, short &value) = 0; virtual void read_value(const char *id, size_t index, int &value) = 0; @@ -79,8 +82,14 @@ public: [[nodiscard]] virtual const char* column(size_t index) const = 0; [[nodiscard]] virtual bool fetch() = 0; + const record& prototype() const; + +protected: + explicit query_result_impl(record prototype); + protected: size_t column_index_ = 0; + record prototype_; }; } diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index b82b3e1..172ccdb 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -29,8 +29,8 @@ public: query_update_intermediate update(const std::string &table); query_delete_intermediate remove(); - query_result fetch(const std::string &sql); - std::pair execute(const std::string &sql); + [[nodiscard]] query_result fetch(const std::string &sql) const; + [[nodiscard]] std::pair execute(const std::string &sql) const; template void attach(const std::string &table_name) @@ -40,6 +40,11 @@ public: [[nodiscard]] const table_repository& tables() const; +private: + friend class query_select_finish; + + [[nodiscard]] std::unique_ptr call_fetch(const std::string &sql) const; + private: connection_pool &pool_; query_builder query_; diff --git a/include/matador/sql/table_repository.hpp b/include/matador/sql/table_repository.hpp index 5bff18d..32d8771 100644 --- a/include/matador/sql/table_repository.hpp +++ b/include/matador/sql/table_repository.hpp @@ -1,6 +1,7 @@ #ifndef QUERY_TABLE_REPOSITORY_HPP #define QUERY_TABLE_REPOSITORY_HPP +#include "matador/sql/column_generator.hpp" #include "matador/sql/record.hpp" #include @@ -20,12 +21,11 @@ class table_repository { public: template - const table_info& attach(const std::string &table_name, const record &proto) + const table_info& attach(const std::string &table_name) { - return attach(std::type_index(typeid(Type)), table_name, proto); + return attach(std::type_index(typeid(Type)), table_info{table_name, record{column_generator::generate(*this)}}); } - const table_info& attach(std::type_index ti, const std::string &table_name, const record &proto); const table_info& attach(std::type_index ti, const table_info& table); template diff --git a/src/sql/column_generator.cpp b/src/sql/column_generator.cpp index 3846408..56afb2d 100644 --- a/src/sql/column_generator.cpp +++ b/src/sql/column_generator.cpp @@ -1,4 +1,5 @@ #include "matador/sql/column_generator.hpp" +#include "matador/sql/table_repository.hpp" namespace matador::sql { diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index bcda76d..0e1ccec 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -63,14 +63,25 @@ const connection_info &connection::info() const return connection_info_; } -query_result connection::fetch(const std::string &sql) +record connection::describe(const std::string &table_name) const +{ + return std::move(connection_->describe(table_name)); +} + +query_result connection::fetch(const std::string &sql) const { auto rec = connection_->describe("person"); return {connection_->fetch(sql), [rec](){ return new record(rec); }}; } -std::pair connection::execute(const std::string &sql) +std::pair connection::execute(const std::string &sql) const { return {connection_->execute(sql), sql}; } + +std::unique_ptr connection::call_fetch(const std::string &sql) const +{ + return connection_->fetch(sql); +} + } diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index 8b2dd9e..00ba5af 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -108,13 +108,13 @@ query_builder& query_builder::select(const std::vector &column_name query_parts_.emplace_back(dialect_.token_at(dialect::token_t::SELECT) + " "); - prototype_.clear(); + query_.prototype.clear(); std::string result; if (column_names.size() < 2) { for (const auto &col : column_names) { result.append(dialect_.prepare_identifier(col)); - prototype_.append(column{col}); + query_.prototype.append(column{col}); } } else { auto it = column_names.begin(); @@ -122,7 +122,7 @@ query_builder& query_builder::select(const std::vector &column_name for (; it != column_names.end(); ++it) { result.append(", "); result.append(dialect_.prepare_identifier(*it)); - prototype_.append(column{*it}); + query_.prototype.append(column{*it}); } } @@ -262,17 +262,20 @@ query_builder& query_builder::values(const std::vector &values) { std::string result{"("}; if (values.size() < 2) { for (auto val : values) { + query_.host_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); } } else { auto it = values.begin(); auto val = *it++; + query_.host_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); for (; it != values.end(); ++it) { result.append(", "); val = *it; + query_.host_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); } @@ -403,11 +406,6 @@ std::string query_builder::compile() { return result; } -const record& query_builder::prototype() const -{ - return prototype_; -} - void query_builder::transition_to(query_builder::state_t next) { if (transitions_[state_].count(next) == 0) { @@ -419,6 +417,7 @@ void query_builder::transition_to(query_builder::state_t next) void query_builder::initialize(query_builder::command_t cmd, query_builder::state_t state) { command_ = cmd; + query_ = {}; state_ = state; query_parts_.clear(); } @@ -432,11 +431,9 @@ std::string build_create_column(const column &col, const dialect &d, column_cont result.append(" NOT NULL"); } if (is_constraint_set(col.attributes().options(), utils::constraints::PRIMARY_KEY)) { -// result.append(" PRIMARY KEY"); context.primary_keys.emplace_back(col.name()); } if (is_constraint_set(col.attributes().options(), utils::constraints::FOREIGN_KEY)) { -// result.append(" FOREIGN KEY"); context.foreign_contexts.push_back({col.name(), col.ref_table(), col.ref_column()}); } diff --git a/src/sql/query_intermediates.cpp b/src/sql/query_intermediates.cpp index 847c27f..3ae8866 100644 --- a/src/sql/query_intermediates.cpp +++ b/src/sql/query_intermediates.cpp @@ -22,6 +22,11 @@ record query_select_finish::fetch_one() return *db().fetch(query().compile()).begin().get(); } +std::unique_ptr query_select_finish::fetch() +{ + return db().call_fetch(query().compile()); +} + query_intermediate::query_intermediate(session &db, query_builder &query) : db_(db), query_(query) {} diff --git a/src/sql/query_result_impl.cpp b/src/sql/query_result_impl.cpp index f0f199c..e67ba1b 100644 --- a/src/sql/query_result_impl.cpp +++ b/src/sql/query_result_impl.cpp @@ -28,4 +28,13 @@ query_result_impl::on_attribute(const char *id, any_type &value, data_type_t typ read_value(id, column_index_++, value, type, attr.size()); } +const record& query_result_impl::prototype() const +{ + return prototype_; +} + +query_result_impl::query_result_impl(record prototype) +: prototype_(std::move(prototype)) +{} + } \ No newline at end of file diff --git a/src/sql/record.cpp b/src/sql/record.cpp index c023cce..10666d0 100644 --- a/src/sql/record.cpp +++ b/src/sql/record.cpp @@ -16,6 +16,11 @@ record::record(const std::vector &columns) init(); } +const std::vector &record::columns() const +{ + return columns_; +} + bool record::has_primary_key() const { return pk_index_ > -1; @@ -30,11 +35,6 @@ const column &record::primary_key() const return columns_[pk_index_]; } -const std::vector &record::columns() const -{ - return columns_; -} - const column &record::at(const std::string &name) const { return columns_by_name_.at(name).first; @@ -113,7 +113,6 @@ void record::init() size_t index{0}; for(auto &col : columns_) { add_to_map(col, index++); -// columns_by_name_.emplace(col.name(), column_index_pair {std::ref(col), index++}); } } diff --git a/src/sql/session.cpp b/src/sql/session.cpp index caf8b8a..b176358 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -22,6 +22,7 @@ query_drop_intermediate session::drop() query_select_intermediate session::select(std::initializer_list column_names) { + return query_select_intermediate{*this, query_.select(column_names)}; } @@ -40,15 +41,14 @@ query_delete_intermediate session::remove() return query_delete_intermediate{*this, query_.remove()}; } -query_result session::fetch(const std::string &sql) { - auto c = pool_.acquire(); - if (!c.valid()) { - throw std::logic_error("no database connection available"); - } - return c->fetch(sql); +query_result session::fetch(const std::string &sql) const +{ + auto res = call_fetch(sql); + auto proto = res->prototype(); + return query_result{std::move(res), [proto]() { return new record(proto); }}; } -std::pair session::execute(const std::string &sql) { +std::pair session::execute(const std::string &sql) const { auto c = pool_.acquire(); if (!c.valid()) { throw std::logic_error("no database connection available"); @@ -60,4 +60,14 @@ const table_repository& session::tables() const { return table_repository_; } + +std::unique_ptr session::call_fetch(const std::string &sql) const +{ + auto c = pool_.acquire(); + if (!c.valid()) { + throw std::logic_error("no database connection available"); + } + return c->call_fetch(sql); +} + } \ No newline at end of file diff --git a/src/sql/table_repository.cpp b/src/sql/table_repository.cpp index b6697e1..af6e4c1 100644 --- a/src/sql/table_repository.cpp +++ b/src/sql/table_repository.cpp @@ -4,11 +4,6 @@ namespace matador::sql { -const table_info &table_repository::attach(std::type_index ti, const std::string &table_name, const record &proto) -{ - return attach(ti, table_info{table_name, proto}); -} - const table_info& table_repository::attach(const std::type_index ti, const table_info& table) { return repository_.try_emplace(ti, table).first->second; diff --git a/test/session.cpp b/test/session.cpp index 76204b6..78fb06a 100644 --- a/test/session.cpp +++ b/test/session.cpp @@ -161,12 +161,13 @@ TEST_CASE("Execute select statement with where clause", "[session]") { .execute(); REQUIRE(res.first == 1); - auto result = s.select() - .from("person") - .where("id"_col == 7) - .fetch_all(); + // fetch person as record + auto result_record = s.select() + .from("person") + .where("id"_col == 7) + .fetch_all(); - for (const auto& i : result) { + for (const auto& i : result_record) { REQUIRE(i.size() == 3); REQUIRE(i.at(0).name() == "id"); REQUIRE(i.at(0).type() == data_type_t::type_long_long); @@ -179,10 +180,19 @@ TEST_CASE("Execute select statement with where clause", "[session]") { REQUIRE(i.at(2).value() == george.age); } -// REQUIRE(res.str() == R"(SELECT "id", "name", "color" FROM "person" WHERE "id" = 8)"); + // fetch person as person + auto result_person = s.select() + .from("person") + .where("id"_col == 7) + .fetch_all(); + + for (const auto& i : result_person) { + REQUIRE(i.id == 7); + REQUIRE(i.name == "george"); + REQUIRE(i.age == 45); + } s.drop().table("person").execute(); - } TEST_CASE("Execute select statement with order by", "[session]") {