diff --git a/backends/CMakeLists.txt b/backends/CMakeLists.txt index d89581e..cfdc8d1 100644 --- a/backends/CMakeLists.txt +++ b/backends/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(sqlite) -add_subdirectory(postgres) \ No newline at end of file +add_subdirectory(postgres) +add_subdirectory(mysql) \ No newline at end of file diff --git a/backends/mysql/CMakeLists.txt b/backends/mysql/CMakeLists.txt new file mode 100644 index 0000000..10d63ba --- /dev/null +++ b/backends/mysql/CMakeLists.txt @@ -0,0 +1,35 @@ +set(HEADER + include/mysql_connection.hpp + include/mysql_error.hpp + include/mysql_result_reader.hpp + include/mysql_dialect.hpp + include/mysql_statement.hpp + include/mysql_parameter_binder.hpp + include/mysql_prepared_result_reader.hpp +) + +set(SOURCES + src/mysql_connection.cpp + src/mysql_error.cpp + src/mysql_result_reader.cpp + src/mysql_dialect.cpp + src/mysql_statement.cpp + src/mysql_parameter_binder.cpp + src/mysql_prepared_result_reader.cpp +) + +set(LIBRARY_TARGET matador-mysql) + +add_library(${LIBRARY_TARGET} MODULE ${SOURCES} ${HEADER}) + +set_target_properties(${LIBRARY_TARGET} + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/backends" +) + +target_include_directories(${LIBRARY_TARGET} PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/backends/mysql/include + ${MYSQL_INCLUDE_DIR}) + +target_link_libraries(${LIBRARY_TARGET} matador ${MYSQL_LIBRARY}) diff --git a/backends/mysql/include/mysql_connection.hpp b/backends/mysql/include/mysql_connection.hpp new file mode 100644 index 0000000..9382c8c --- /dev/null +++ b/backends/mysql/include/mysql_connection.hpp @@ -0,0 +1,61 @@ +#ifndef QUERY_POSTGRES_CONNECTION_HPP +#define QUERY_POSTGRES_CONNECTION_HPP + +#ifdef _MSC_VER +#ifdef matador_mysql_EXPORTS +#define MATADOR_MYSQL_API __declspec(dllexport) +#else +#define MATADOR_MYSQL_API __declspec(dllimport) +#endif +#pragma warning(disable: 4355) +#else +#define MATADOR_MYSQL_API +#endif + +#include "matador/sql/connection_impl.hpp" + +#include + +#ifdef _MSC_VER +#include +#else +#include +#endif + +namespace matador::backends::mysql { + +class mysql_connection : public matador::sql::connection_impl +{ +public: + explicit mysql_connection(const sql::connection_info &info); + void open() override; + void close() override; + bool is_open() override; + + std::unique_ptr fetch(const std::string &stmt) override; + std::unique_ptr prepare(sql::query_context context) override; + + size_t execute(const std::string &stmt) override; + + sql::record describe(const std::string& table) override; + + bool exists(const std::string &schema_name, const std::string &table_name) override; + +private: + mutable std::unique_ptr mysql_; + + using string_to_int_map = std::unordered_map; + + static string_to_int_map statement_name_map_; +}; + +} + +extern "C" +{ +MATADOR_MYSQL_API matador::sql::connection_impl* create_database(const matador::sql::connection_info &info); + +MATADOR_MYSQL_API void destroy_database(matador::sql::connection_impl *db); +} + +#endif //QUERY_POSTGRES_CONNECTION_HPP diff --git a/backends/mysql/include/mysql_dialect.hpp b/backends/mysql/include/mysql_dialect.hpp new file mode 100644 index 0000000..e2e3747 --- /dev/null +++ b/backends/mysql/include/mysql_dialect.hpp @@ -0,0 +1,19 @@ +#ifndef QUERY_POSTGRES_DIALECT_HPP +#define QUERY_POSTGRES_DIALECT_HPP + +#ifdef _MSC_VER +#ifdef matador_mysql_EXPORTS +#define MATADOR_MYSQL_API __declspec(dllexport) +#else +#define MATADOR_MYSQL_API __declspec(dllimport) +#endif +#pragma warning(disable: 4355) +#else +#define MATADOR_MYSQL_API +#endif + +#include "matador/sql/dialect.hpp" + +extern "C" [[maybe_unused]] MATADOR_MYSQL_API const matador::sql::dialect* get_dialect(); + +#endif //QUERY_POSTGRES_DIALECT_HPP diff --git a/backends/mysql/include/mysql_error.hpp b/backends/mysql/include/mysql_error.hpp new file mode 100644 index 0000000..3f0ad4e --- /dev/null +++ b/backends/mysql/include/mysql_error.hpp @@ -0,0 +1,20 @@ +#ifndef QUERY_POSTGRES_ERROR_HPP +#define QUERY_POSTGRES_ERROR_HPP + +#ifdef _MSC_VER +#include +#else +#include +#endif + +#include + +namespace matador::backends::mysql { + +void throw_mysql_error(const char *what, const std::string &source); +void throw_mysql_error(MYSQL *db, const std::string &source); +void throw_mysql_error(MYSQL_STMT *stmt, const std::string &source, const std::string &sql); + +} + +#endif //QUERY_POSTGRES_ERROR_HPP diff --git a/backends/mysql/include/mysql_parameter_binder.hpp b/backends/mysql/include/mysql_parameter_binder.hpp new file mode 100644 index 0000000..ef37467 --- /dev/null +++ b/backends/mysql/include/mysql_parameter_binder.hpp @@ -0,0 +1,72 @@ +#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H +#define QUERY_POSTGRES_PARAMETER_BINDER_H + +#include "matador/sql/parameter_binder.hpp" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +#include + +namespace matador::backends::mysql { + +struct mysql_result_info +{ + unsigned long length = 0; + my_bool is_null = false; + my_bool error = false; +// std::unique_ptr buffer; + char *buffer = nullptr; + unsigned long buffer_length = 0; + bool is_allocated = false; + + ~mysql_result_info() + { + if (is_allocated) { + delete [] buffer; + } + } +}; + +class mysql_parameter_binder final : public sql::parameter_binder +{ +public: + explicit mysql_parameter_binder(size_t size); + + void bind(size_t pos, char i) override; + void bind(size_t pos, short i) override; + void bind(size_t pos, int i) override; + void bind(size_t pos, long i) override; + void bind(size_t pos, long long int i) override; + void bind(size_t pos, unsigned char i) override; + void bind(size_t pos, unsigned short i) override; + void bind(size_t pos, unsigned int i) override; + void bind(size_t pos, unsigned long i) override; + void bind(size_t pos, unsigned long long int i) override; + void bind(size_t pos, bool b) override; + void bind(size_t pos, float d) override; + void bind(size_t pos, double d) override; + void bind(size_t pos, const char *string) override; + void bind(size_t pos, const char *string, size_t size) override; + void bind(size_t pos, const std::string &string) override; + void bind(size_t pos, const std::string &x, size_t size) override; + + [[nodiscard]] std::vector& bind_params(); + +private: + struct is_null_t + { + my_bool is_null = false; + }; + + std::vector bind_params_; + std::vector is_null_vector; + std::vector info_; +}; + +} + +#endif //QUERY_POSTGRES_PARAMETER_BINDER_H diff --git a/backends/mysql/include/mysql_prepared_result_reader.hpp b/backends/mysql/include/mysql_prepared_result_reader.hpp new file mode 100644 index 0000000..7135308 --- /dev/null +++ b/backends/mysql/include/mysql_prepared_result_reader.hpp @@ -0,0 +1,36 @@ +#ifndef QUERY_MYSQL_PREPARED_RESULT_READER_HPP +#define QUERY_MYSQL_PREPARED_RESULT_READER_HPP + +#include "matador/sql/query_result_reader.hpp" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +namespace matador::backends::mysql { + +class mysql_prepared_result_reader : public sql::query_result_reader +{ +public: + explicit mysql_prepared_result_reader(MYSQL_STMT *stmt); + ~mysql_prepared_result_reader() override; + + [[nodiscard]] size_t column_count() const override; + [[nodiscard]] const char *column(size_t index) const override; + bool fetch() override; + +private: + MYSQL_STMT *stmt_{}; + + MYSQL_ROW current_row_{}; + + size_t row_count_{}; + size_t column_count_{}; + int row_index_{-1}; +}; + +} + +#endif //QUERY_MYSQL_PREPARED_RESULT_READER_HPP diff --git a/backends/mysql/include/mysql_result_reader.hpp b/backends/mysql/include/mysql_result_reader.hpp new file mode 100644 index 0000000..f1fb756 --- /dev/null +++ b/backends/mysql/include/mysql_result_reader.hpp @@ -0,0 +1,36 @@ +#ifndef QUERY_POSTGRES_RESULT_READER_HPP +#define QUERY_POSTGRES_RESULT_READER_HPP + +#include "matador/sql/query_result_reader.hpp" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +namespace matador::backends::mysql { + +class mysql_result_reader : public sql::query_result_reader +{ +public: + explicit mysql_result_reader(MYSQL_RES *result, unsigned int column_count); + ~mysql_result_reader() override; + + [[nodiscard]] size_t column_count() const override; + [[nodiscard]] const char *column(size_t index) const override; + bool fetch() override; + +private: + MYSQL_RES *result_{}; + + MYSQL_ROW current_row_{}; + + size_t row_count_{}; + size_t column_count_{}; + int row_index_{-1}; +}; + +} + +#endif //QUERY_POSTGRES_RESULT_READER_HPP diff --git a/backends/mysql/include/mysql_statement.hpp b/backends/mysql/include/mysql_statement.hpp new file mode 100644 index 0000000..5b936d9 --- /dev/null +++ b/backends/mysql/include/mysql_statement.hpp @@ -0,0 +1,37 @@ +#ifndef QUERY_POSTGRES_STATEMENT_HPP +#define QUERY_POSTGRES_STATEMENT_HPP + +#include "matador/sql/statement_impl.hpp" + +#include "mysql_parameter_binder.hpp" + +#ifdef _MSC_VER +#include +#else +#include +#endif + +namespace matador::backends::mysql { + +class mysql_statement final : public sql::statement_impl +{ +public: + mysql_statement(MYSQL_STMT *stmt, const sql::query_context &query); + + size_t execute() override; + std::unique_ptr fetch() override; + void reset() override; +protected: + sql::parameter_binder& binder() override; + +private: + MYSQL_STMT *stmt_{nullptr}; + + std::string name_; + + mysql_parameter_binder binder_; +}; + +} + +#endif //QUERY_POSTGRES_STATEMENT_HPP diff --git a/backends/mysql/src/mysql_connection.cpp b/backends/mysql/src/mysql_connection.cpp new file mode 100644 index 0000000..0ae8d17 --- /dev/null +++ b/backends/mysql/src/mysql_connection.cpp @@ -0,0 +1,248 @@ +#include "mysql_connection.hpp" +#include "mysql_error.hpp" +#include "mysql_result_reader.hpp" +#include "mysql_statement.hpp" + +#include "matador/sql/record.hpp" + +#include +#include + +namespace matador::backends::mysql { + +mysql_connection::string_to_int_map mysql_connection::statement_name_map_{}; + +mysql_connection::mysql_connection(const sql::connection_info &info) +: connection_impl(info) {} + +void mysql_connection::open() +{ + if (is_open()) { + return; + } + + mysql_ = std::make_unique(); + + if (!mysql_init(mysql_.get())) { + throw_mysql_error(mysql_.get(), "mysql_init"); + } + + if (!mysql_real_connect(mysql_.get(), + info().hostname.c_str(), + info().user.c_str(), + !info().password.empty() ? info().password.c_str() : nullptr, + info().database.c_str(), + info().port, + nullptr, + 0)) { + // disconnect all handles + const std::string error_message = mysql_error(mysql_.get()); + mysql_close(mysql_.get()); + + mysql_.reset(); + // throw exception + throw_mysql_error(error_message.c_str(), "mysql_real_connect"); + } +} + +void mysql_connection::close() +{ + if (mysql_) { + mysql_close(mysql_.get()); + mysql_.reset(); + } +} + +bool mysql_connection::is_open() +{ + return mysql_ != nullptr; +} + +sql::data_type_t to_type(enum_field_types type, unsigned int flags) +{ + switch (type) { + case MYSQL_TYPE_TINY: + return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_char : sql::data_type_t::type_char; + case MYSQL_TYPE_SHORT: + return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_short : sql::data_type_t::type_short; + case MYSQL_TYPE_LONG: + return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_int : sql::data_type_t::type_int; + case MYSQL_TYPE_LONGLONG: + return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_long_long : sql::data_type_t::type_long_long; + case MYSQL_TYPE_FLOAT: + return sql::data_type_t::type_float; + case MYSQL_TYPE_DOUBLE: + return sql::data_type_t::type_double; + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + return sql::data_type_t::type_varchar; + case MYSQL_TYPE_BLOB: + return sql::data_type_t::type_blob; + case MYSQL_TYPE_STRING: + return sql::data_type_t::type_text; + case MYSQL_TYPE_DATE: + return sql::data_type_t::type_date; + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + return sql::data_type_t::type_time; + default: + return sql::data_type_t::type_unknown; + } +} + +utils::constraints to_options(unsigned int flags) +{ + utils::constraints options{utils::constraints::NONE}; + if (flags & NOT_NULL_FLAG) { + options |= utils::constraints::NOT_NULL; + } + if (flags & PRI_KEY_FLAG) { + options |= utils::constraints::PRIMARY_KEY; + } + if (flags & UNIQUE_KEY_FLAG) { + options |= utils::constraints::UNIQUE; + } + + return options; +} + +sql::data_type_t string2type(const std::string &type_string) +{ + // if (strcmp(type_string.c_str(), "int") + return sql::data_type_t::type_unknown; +} + +struct type_info +{ + sql::data_type_t type{sql::data_type_t::type_unknown}; + size_t size{}; +}; + +type_info determine_type_info(const std::string &type_string) +{ + static const std::regex TYPE_REGEX(R"(^(\w+)(\((\d+)(,(\d+))?\))?$)"); + std::smatch matcher; + + type_info result; + if (std::regex_match(type_string, matcher, TYPE_REGEX)) { + result.type = string2type(matcher[1].str()); + if (matcher[3].matched) { + result.size = std::stoi(matcher[3].str()); + } + } + return result; +} + +std::unique_ptr mysql_connection::fetch(const std::string &stmt) +{ + if (mysql_query(mysql_.get(), stmt.c_str())) { + throw_mysql_error(mysql_.get(), stmt); + } + + auto result = mysql_store_result(mysql_.get()); + if (result == nullptr) { + throw_mysql_error(mysql_.get(), stmt); + } + + auto field_count = mysql_num_fields(result); + auto fields = mysql_fetch_fields(result); + sql::record prototype; + for (unsigned i = 0; i < field_count; ++i) { + auto type = to_type(fields[i].type, fields[i].flags); + auto options = to_options(fields[i].flags); + + prototype.append({fields[i].name, type, options}); + } + + return std::move(std::make_unique(std::make_unique(result, field_count), std::move(prototype))); +} + +std::unique_ptr mysql_connection::prepare(sql::query_context context) +{ + MYSQL_STMT *stmt = mysql_stmt_init(mysql_.get()); + if (stmt == nullptr) { + throw_mysql_error(mysql_.get(), "mysql_stmt_init"); + } + + if (mysql_stmt_prepare(stmt, context.sql.c_str(), static_cast(context.sql.size())) != 0) { + throw_mysql_error(stmt, "mysql_stmt_prepare", context.sql); + } + + return std::make_unique(stmt, std::move(context)); +} + +size_t mysql_connection::execute(const std::string &stmt) +{ + if (mysql_query(mysql_.get(), stmt.c_str())) { + throw_mysql_error(mysql_.get(), stmt); + } + + return mysql_affected_rows(mysql_.get()); +} + +sql::record mysql_connection::describe(const std::string &table) +{ + std::string stmt("SHOW COLUMNS FROM " + table); + + if (mysql_query(mysql_.get(), stmt.c_str())) { + throw_mysql_error(mysql_.get(), stmt); + } + + auto result = mysql_store_result(mysql_.get()); + if (result == nullptr) { + throw_mysql_error(mysql_.get(), stmt); + } + + mysql_result_reader reader(result, mysql_num_fields(result)); + sql::record prototype; + while (reader.fetch()) { + + char *end = nullptr; + // Todo: Handle error + auto index = strtoul(reader.column(0), &end, 10); + std::string name = reader.column(1); + + // Todo: extract size + auto typeinfo = determine_type_info(reader.column(2)); + end = nullptr; + utils::constraints options{}; + if (strtoul(reader.column(4), &end, 10) == 0) { + options = utils::constraints::NOT_NULL; + } + // f.default_value(res->column(4)); + prototype.append({name, typeinfo.type, {typeinfo.size, options}}); + } + + return prototype; +} + +bool mysql_connection::exists(const std::string &/*schema_name*/, const std::string &table_name) +{ + std::string stmt("SELECT 1 FROM information_schema.tables WHERE table_schema = '" + info().database + "' AND table_name = '" + table_name + "'"); + + if (mysql_query(mysql_.get(), stmt.c_str())) { + throw_mysql_error(mysql_.get(), stmt); + } + + auto result = mysql_store_result(mysql_.get()); + if (result == nullptr) { + throw_mysql_error(mysql_.get(), stmt); + } + + return result->row_count == 1; +} + +} + +extern "C" +{ +MATADOR_MYSQL_API matador::sql::connection_impl *create_database(const matador::sql::connection_info &info) +{ + return new matador::backends::mysql::mysql_connection(info); +} + +MATADOR_MYSQL_API void destroy_database(matador::sql::connection_impl *db) +{ + delete db; +} +} diff --git a/backends/mysql/src/mysql_dialect.cpp b/backends/mysql/src/mysql_dialect.cpp new file mode 100644 index 0000000..c9ff5fb --- /dev/null +++ b/backends/mysql/src/mysql_dialect.cpp @@ -0,0 +1,20 @@ +#include "mysql_dialect.hpp" + +#include "matador/sql/dialect_builder.hpp" + +[[maybe_unused]] const matador::sql::dialect *get_dialect() +{ + using namespace matador::sql; + const static dialect d = dialect_builder::builder() + .create() + .with_placeholder_func([](size_t index) { + return "$" + std::to_string(index); + }) + .with_token_replace_map({ + {dialect::token_t::START_QUOTE, "`"}, + {dialect::token_t::END_QUOTE, "`"}, + }) + .with_default_schema_name("public") + .build(); + return &d; +} diff --git a/backends/mysql/src/mysql_error.cpp b/backends/mysql/src/mysql_error.cpp new file mode 100644 index 0000000..ea8d503 --- /dev/null +++ b/backends/mysql/src/mysql_error.cpp @@ -0,0 +1,30 @@ +#include "mysql_error.hpp" + +#include + +namespace matador::backends::mysql { + +void throw_mysql_error(const char *what, const std::string &source) +{ + std::stringstream msg; + msg << "mysql error (" << source << "): " << what; + throw std::logic_error(msg.str()); +} + +void throw_mysql_error(MYSQL *db, const std::string &source) +{ + if (mysql_errno(db) != 0) { + throw_mysql_error(mysql_error(db), source); + } +} + +void throw_mysql_error(MYSQL_STMT *stmt, const std::string &source, const std::string &sql) +{ + if (mysql_stmt_errno(stmt) != 0) { + std::stringstream msg; + msg << "mysql error (" << source << ") " << mysql_stmt_error(stmt) << ": " << sql; + throw std::logic_error(msg.str()); + } +} + +} \ No newline at end of file diff --git a/backends/mysql/src/mysql_parameter_binder.cpp b/backends/mysql/src/mysql_parameter_binder.cpp new file mode 100644 index 0000000..ab1242d --- /dev/null +++ b/backends/mysql/src/mysql_parameter_binder.cpp @@ -0,0 +1,186 @@ +#include "mysql_parameter_binder.hpp" + +namespace matador::backends::mysql { + +namespace detail { + +template < class T > +void bind_value(enum_field_types type, T value, MYSQL_BIND &bind, my_bool &is_null) +{ + if (bind.buffer == nullptr) { + // allocating memory + bind.buffer = new char[sizeof(T)]; + bind.buffer_type = type; + bind.buffer_length = sizeof(T); + bind.is_null = &is_null; + bind.is_unsigned = std::is_unsigned::value; + } + *static_cast(bind.buffer) = value; + is_null = false; +} + +void bind_value(enum_field_types type, const char *value, size_t, MYSQL_BIND &bind, my_bool &is_null) +{ + std::size_t len(strlen(value) + 1); + if (bind.buffer_length < len) { + // reallocate memory + delete [] static_cast(bind.buffer); + bind.buffer = nullptr; + bind.buffer_length = 0; + bind.buffer_type = type; + bind.is_null = &is_null; + } + if (bind.buffer == nullptr) { + // allocating memory + bind.buffer = new char[len]; + memset(bind.buffer, 0, len); + } + bind.buffer_length = (unsigned long)(len - 1); +#ifdef _MSC_VER + strncpy_s(static_cast(bind.buffer), len, value, _TRUNCATE); +#else + strncpy(static_cast(bind.buffer), value, len); +#endif + is_null = false; +} + +//void bind_value(enum_field_types type, const matador::date &x, MYSQL_BIND &bind, my_bool &is_null) +//{ +// if (bind.buffer == nullptr) { +// size_t s = sizeof(MYSQL_TIME); +// bind.buffer = new char[s]; +// bind.buffer_length = (unsigned long)s; +// bind.is_null = &is_null; +// bind.buffer_type = type; +// bind.length = nullptr; +// } +// memset(bind.buffer, 0, sizeof(MYSQL_TIME)); +// is_null = false; +// auto *mt = static_cast(bind.buffer); +// mt->day = (unsigned int)x.day(); +// mt->month = (unsigned int)x.month(); +// mt->year = (unsigned int)x.year(); +// mt->time_type = MYSQL_TIMESTAMP_DATE; +//} +// +//void bind_value(enum_field_types type, const matador::time &x, MYSQL_BIND &bind, my_bool &is_null) +//{ +// if (bind.buffer == nullptr) { +// size_t s = sizeof(MYSQL_TIME); +// bind.buffer = new char[s]; +// bind.buffer_length = (unsigned long)s; +// bind.buffer_type = type; +// bind.length = nullptr; +// bind.is_null = &is_null; +// } +// memset(bind.buffer, 0, sizeof(MYSQL_TIME)); +// is_null = false; +// auto *mt = static_cast(bind.buffer); +// mt->day = (unsigned int)x.day(); +// mt->month = (unsigned int)x.month(); +// mt->year = (unsigned int)x.year(); +// mt->hour = (unsigned int)x.hour(); +// mt->minute = (unsigned int)x.minute(); +// mt->second = (unsigned int)x.second(); +// mt->second_part = (unsigned long)x.milli_second() * 1000; +// mt->time_type = MYSQL_TIMESTAMP_DATETIME; +//} + +} + +mysql_parameter_binder::mysql_parameter_binder(size_t size) +: bind_params_(size) +, info_(size) +{} + +void mysql_parameter_binder::bind(size_t pos, char i) +{ + detail::bind_value(MYSQL_TYPE_TINY, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, short i) +{ + detail::bind_value(MYSQL_TYPE_SHORT, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, int i) +{ + detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, long i) +{ + detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, long long int i) +{ + detail::bind_value(MYSQL_TYPE_LONGLONG, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, unsigned char i) +{ + detail::bind_value(MYSQL_TYPE_TINY, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, unsigned short i) +{ + detail::bind_value(MYSQL_TYPE_SHORT, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, unsigned int i) +{ + detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, unsigned long i) +{ + detail::bind_value(MYSQL_TYPE_LONG, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, unsigned long long int i) +{ + detail::bind_value(MYSQL_TYPE_LONGLONG, i, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, bool b) +{ + detail::bind_value(MYSQL_TYPE_TINY, b, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, float d) +{ + detail::bind_value(MYSQL_TYPE_FLOAT, d, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, double d) +{ + detail::bind_value(MYSQL_TYPE_DOUBLE, d, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, const char *str) +{ + detail::bind_value(MYSQL_TYPE_STRING, str, strlen(str), bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, const char *str, size_t size) +{ + detail::bind_value(MYSQL_TYPE_VAR_STRING, str, size, bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, const std::string &str) +{ + detail::bind_value(MYSQL_TYPE_STRING, str.data(), str.size(), bind_params_[pos], is_null_vector[pos].is_null); +} + +void mysql_parameter_binder::bind(size_t pos, const std::string &str, size_t size) +{ + detail::bind_value(MYSQL_TYPE_VAR_STRING, str.data(), size, bind_params_[pos], is_null_vector[pos].is_null); +} + +std::vector &mysql_parameter_binder::bind_params() +{ + return bind_params_; +} + +} \ No newline at end of file diff --git a/backends/mysql/src/mysql_prepared_result_reader.cpp b/backends/mysql/src/mysql_prepared_result_reader.cpp new file mode 100644 index 0000000..e524248 --- /dev/null +++ b/backends/mysql/src/mysql_prepared_result_reader.cpp @@ -0,0 +1,28 @@ +#include "mysql_prepared_result_reader.hpp" + +namespace matador::backends::mysql { + +mysql_prepared_result_reader::mysql_prepared_result_reader(MYSQL_STMT *stmt) +: stmt_(stmt) +{} + +mysql_prepared_result_reader::~mysql_prepared_result_reader() +{ + +} + +size_t mysql_prepared_result_reader::column_count() const +{ + return 0; +} + +const char *mysql_prepared_result_reader::column(size_t index) const +{ + return nullptr; +} + +bool mysql_prepared_result_reader::fetch() +{ + return false; +} +} \ No newline at end of file diff --git a/backends/mysql/src/mysql_result_reader.cpp b/backends/mysql/src/mysql_result_reader.cpp new file mode 100644 index 0000000..828ebf7 --- /dev/null +++ b/backends/mysql/src/mysql_result_reader.cpp @@ -0,0 +1,35 @@ +#include "mysql_result_reader.hpp" + +namespace matador::backends::mysql { + +mysql_result_reader::mysql_result_reader(MYSQL_RES *result, unsigned int column_count) +: result_(result) +, row_count_(mysql_num_rows(result_)) +, column_count_(column_count) +{} + +mysql_result_reader::~mysql_result_reader() +{ + if (result_) { + mysql_free_result(result_); + } +} + +size_t mysql_result_reader::column_count() const +{ + return column_count_; +} + +const char *mysql_result_reader::column(size_t index) const +{ + return current_row_[index]; +} + +bool mysql_result_reader::fetch() +{ + current_row_ = mysql_fetch_row(result_); + + return current_row_ != nullptr; +} + +} \ No newline at end of file diff --git a/backends/mysql/src/mysql_statement.cpp b/backends/mysql/src/mysql_statement.cpp new file mode 100644 index 0000000..f8c2761 --- /dev/null +++ b/backends/mysql/src/mysql_statement.cpp @@ -0,0 +1,53 @@ +#include "mysql_statement.hpp" +#include "mysql_error.hpp" +#include "mysql_prepared_result_reader.hpp" + +namespace matador::backends::mysql { + +mysql_statement::mysql_statement(MYSQL_STMT *stmt, const sql::query_context &query) +: statement_impl(query) +, stmt_(stmt) +, binder_(query_.bind_vars.size()) +{} + +size_t mysql_statement::execute() +{ + if (!binder_.bind_params().empty()) { + if (mysql_stmt_bind_param(stmt_, binder_.bind_params().data()) != 0) { + throw_mysql_error(stmt_, "mysql", query_.sql); + } + } + + if (mysql_stmt_execute(stmt_) != 0) { + throw_mysql_error(stmt_, "mysql", query_.sql); + } + + return mysql_stmt_affected_rows(stmt_); +} + +std::unique_ptr mysql_statement::fetch() +{ + if (!binder_.bind_params().empty()) { + if (mysql_stmt_bind_param(stmt_, binder_.bind_params().data()) != 0) { + throw_mysql_error(stmt_, "mysql", query_.sql); + } + } + + if (mysql_stmt_execute(stmt_) != 0) { + throw_mysql_error(stmt_, "mysql", query_.sql); + } + if (mysql_stmt_store_result(stmt_) != 0) { + throw_mysql_error(stmt_, "mysql", query_.sql); + } + + return std::move(std::make_unique(std::make_unique(stmt_), std::move(query_.prototype))); +} + +void mysql_statement::reset() {} + +sql::parameter_binder& mysql_statement::binder() +{ + return binder_; +} + +} \ No newline at end of file diff --git a/backends/postgres/include/postgres_connection.hpp b/backends/postgres/include/postgres_connection.hpp index 629ded0..2f2dade 100644 --- a/backends/postgres/include/postgres_connection.hpp +++ b/backends/postgres/include/postgres_connection.hpp @@ -35,7 +35,7 @@ public: sql::record describe(const std::string& table) override; - bool exists(const std::string &table_name) override; + bool exists(const std::string &schema_name, const std::string &table_name) override; private: [[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ; diff --git a/backends/postgres/include/postgres_statement.hpp b/backends/postgres/include/postgres_statement.hpp index 7ed5eef..7c88762 100644 --- a/backends/postgres/include/postgres_statement.hpp +++ b/backends/postgres/include/postgres_statement.hpp @@ -12,7 +12,7 @@ namespace matador::backends::postgres { class postgres_statement final : public sql::statement_impl { public: - postgres_statement(PGconn *db, PGresult *result, const std::string &name, const sql::query_context &query); + postgres_statement(PGconn *db, PGresult *result, std::string name, const sql::query_context &query); size_t execute() override; std::unique_ptr fetch() override; diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index 777832f..a07bffd 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -161,9 +161,9 @@ sql::record postgres_connection::describe(const std::string &table) return std::move(prototype); } -bool postgres_connection::exists(const std::string &table_name) +bool postgres_connection::exists(const std::string &schema_name, const std::string &table_name) { - std::string stmt("SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = '" + table_name + "'"); + std::string stmt("SELECT 1 FROM information_schema.tables WHERE table_schema = '" + schema_name + "' AND table_name = '" + table_name + "'"); PGresult *res = PQexec(conn_, stmt.c_str()); diff --git a/backends/postgres/src/postgres_statement.cpp b/backends/postgres/src/postgres_statement.cpp index 4f7e7db..d329f47 100644 --- a/backends/postgres/src/postgres_statement.cpp +++ b/backends/postgres/src/postgres_statement.cpp @@ -4,11 +4,11 @@ namespace matador::backends::postgres { -postgres_statement::postgres_statement(PGconn *db, PGresult *result, const std::string &name, const sql::query_context &query) +postgres_statement::postgres_statement(PGconn *db, PGresult *result, std::string name, const sql::query_context &query) : statement_impl(query) , db_(db) , result_(result) -, name_(name) +, name_(std::move(name)) , binder_(query_.bind_vars.size()) {} diff --git a/backends/sqlite/include/sqlite_connection.hpp b/backends/sqlite/include/sqlite_connection.hpp index 9672a17..38493fc 100644 --- a/backends/sqlite/include/sqlite_connection.hpp +++ b/backends/sqlite/include/sqlite_connection.hpp @@ -35,7 +35,7 @@ public: sql::record describe(const std::string& table) override; - bool exists(const std::string &table_name) override; + bool exists(const std::string &schema_name, const std::string &table_name) override; private: struct fetch_context diff --git a/backends/sqlite/src/sqlite_connection.cpp b/backends/sqlite/src/sqlite_connection.cpp index eb5ecc9..70b9789 100644 --- a/backends/sqlite/src/sqlite_connection.cpp +++ b/backends/sqlite/src/sqlite_connection.cpp @@ -156,7 +156,6 @@ sql::record sqlite_connection::describe(const std::string& table) auto index = strtoul(reader.column(0), &end, 10); std::string name = reader.column(1); - // Todo: extract size auto type = (string2type(reader.column(2))); end = nullptr; utils::constraints options{}; @@ -170,7 +169,7 @@ sql::record sqlite_connection::describe(const std::string& table) return std::move(prototype); } -bool sqlite_connection::exists(const std::string &table_name) +bool sqlite_connection::exists(const std::string &schema_name, const std::string &table_name) { const auto result = fetch_internal("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND tbl_name='" + table_name + "' LIMIT 1"); sqlite_result_reader reader(result.rows, result.prototype.size()); diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index 8fd5a68..4ca909a 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -32,7 +32,7 @@ public: [[nodiscard]] const connection_info& info() const; [[nodiscard]] record describe(const std::string &table_name) const; - [[nodiscard]] bool exists(const std::string &table_name) const; + [[nodiscard]] bool exists(const std::string &schema_name, const std::string &table_name) const; [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; [[nodiscard]] size_t execute(const std::string &sql) const; diff --git a/include/matador/sql/connection_impl.hpp b/include/matador/sql/connection_impl.hpp index 5bca38e..6a2d1aa 100644 --- a/include/matador/sql/connection_impl.hpp +++ b/include/matador/sql/connection_impl.hpp @@ -27,7 +27,7 @@ public: virtual std::unique_ptr prepare(query_context context) = 0; virtual record describe(const std::string &table) = 0; - virtual bool exists(const std::string &table_name) = 0; + virtual bool exists(const std::string &schema_name, const std::string &table_name) = 0; protected: explicit connection_impl(const connection_info &info); diff --git a/include/matador/sql/object_binder.hpp b/include/matador/sql/object_parameter_binder.hpp similarity index 93% rename from include/matador/sql/object_binder.hpp rename to include/matador/sql/object_parameter_binder.hpp index 6a11ea1..d7b9879 100644 --- a/include/matador/sql/object_binder.hpp +++ b/include/matador/sql/object_parameter_binder.hpp @@ -1,5 +1,5 @@ -#ifndef QUERY_OBJECT_BINDER_HPP -#define QUERY_OBJECT_BINDER_HPP +#ifndef QUERY_OBJECT_PARAMETER_BINDER_HPP +#define QUERY_OBJECT_PARAMETER_BINDER_HPP #include "matador/sql/parameter_binder.hpp" #include "matador/sql/types.hpp" @@ -49,10 +49,10 @@ private: } -class object_binder +class object_parameter_binder { public: - explicit object_binder(parameter_binder &binder); + explicit object_parameter_binder(parameter_binder &binder); void reset(); @@ -101,4 +101,4 @@ void fk_binder::on_primary_key(const char *id, ValueType &value, typename std::e } } -#endif //QUERY_OBJECT_BINDER_HPP +#endif //QUERY_OBJECT_PARAMETER_BINDER_HPP diff --git a/include/matador/sql/query_context.hpp b/include/matador/sql/query_context.hpp index 4bcee70..861791f 100644 --- a/include/matador/sql/query_context.hpp +++ b/include/matador/sql/query_context.hpp @@ -11,7 +11,7 @@ struct query_context std::string command_name; std::string table_name; record prototype; - std::vector host_vars; + std::vector result_vars; std::vector bind_vars; }; diff --git a/include/matador/sql/result_parameter_binder.hpp b/include/matador/sql/result_parameter_binder.hpp new file mode 100644 index 0000000..998654f --- /dev/null +++ b/include/matador/sql/result_parameter_binder.hpp @@ -0,0 +1,133 @@ +#ifndef QUERY_RESULT_PARAMETER_BINDER_HPP +#define QUERY_RESULT_PARAMETER_BINDER_HPP + +#include "matador/utils/access.hpp" +#include "matador/utils/cascade_type.hpp" +#include "matador/utils/field_attributes.hpp" + +#include "matador/sql/any_type.hpp" +#include "matador/sql/types.hpp" + +#include + +namespace matador::sql { + +class result_parameter_binder; + +namespace detail { +class fk_result_binder +{ +public: + explicit fk_result_binder(result_parameter_binder &result_binder); + + template + void bind_result(Type &obj, size_t column_index) + { + column_index_ = column_index; + utils::access::process(*this, obj); + } + + template + void on_primary_key(const char *id, ValueType &value, typename std::enable_if::value && !std::is_same::value>::type* = 0); + void on_primary_key(const char *id, std::string &value, size_t size); + void on_revision(const char * /*id*/, unsigned long long &/*rev*/) {} + + template < class Type > + void on_attribute(const char * /*id*/, Type &/*x*/, const utils::field_attributes &/*attr*/ = utils::null_attributes) {} + template < class Pointer > + void on_belongs_to(const char * /*id*/, Pointer &/*x*/, utils::cascade_type) {} + template < class Pointer > + void on_has_one(const char * /*id*/, Pointer &/*x*/, utils::cascade_type) {} + + template + void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {} + template + void on_has_many(const char *, ContainerType &, utils::cascade_type) {} + +private: + size_t column_index_{}; + result_parameter_binder &result_binder_; +}; + +} + +class result_parameter_binder +{ +public: + template + void on_primary_key(const char * /*id*/, ValueType &value, typename std::enable_if::value && !std::is_same::value>::type* = 0) + { + data_type_traits::bind_result_value(*this, column_index_++, value); + } + void on_primary_key(const char *id, std::string &value, size_t size); + void on_revision(const char *id, unsigned long long &rev); + + template < class Type > + void on_attribute(const char * /*id*/, Type &x, const utils::field_attributes &/*attr*/ = utils::null_attributes) + { + data_type_traits::bind_result_value(*this, 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, any_type &value, data_type_t type, const utils::field_attributes &attr = utils::null_attributes); + + template < class Pointer > + void on_belongs_to(const char * /*id*/, Pointer &x, utils::cascade_type) + { + if (!x.get()) { + x.reset(new typename Pointer::value_type); + } + fk_result_binder_.bind_result(*x, column_index_++); + } + template < class Pointer > + void on_has_one(const char * /*id*/, Pointer &x, utils::cascade_type) + { + if (!x.get()) { + x.reset(new typename Pointer::value_type); + } + fk_result_binder_.bind_result(*x, column_index_++); + } + + template + void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {} + template + void on_has_many(const char *, ContainerType &, utils::cascade_type) {} + + virtual void bind_result_value(size_t index, char &value) {} + virtual void bind_result_value(size_t index, short &value) {} + virtual void bind_result_value(size_t index, int &value) {} + virtual void bind_result_value(size_t index, long &value) {} + virtual void bind_result_value(size_t index, long long &value) {} + virtual void bind_result_value(size_t index, unsigned char &value) {} + virtual void bind_result_value(size_t index, unsigned short &value) {} + virtual void bind_result_value(size_t index, unsigned int &value) {} + virtual void bind_result_value(size_t index, unsigned long &value) {} + virtual void bind_result_value(size_t index, unsigned long long &value) {} + virtual void bind_result_value(size_t index, bool &value) {} + virtual void bind_result_value(size_t index, float &value) {} + virtual void bind_result_value(size_t index, double &value) {} +// virtual void bind_result_value(size_t index, matador::time &value) {} +// virtual void bind_result_value(size_t index, matador::date &value) {} + virtual void bind_result_value(size_t index, char *value, size_t s) {} + virtual void bind_result_value(size_t index, std::string &value) {} + virtual void bind_result_value(size_t index, std::string &value, size_t s) {} + virtual void bind_result_value(size_t index, any_type &value, data_type_t type, size_t size) {} + +private: + size_t column_index_{}; + detail::fk_result_binder fk_result_binder_; +}; + +namespace detail { + +template +void fk_result_binder::on_primary_key(const char * /*id*/, ValueType &value, typename std::enable_if::value && !std::is_same::value>::type *) +{ + data_type_traits::bind_result_value(result_binder_, column_index_++, value); +} + + +} +} + +#endif //QUERY_RESULT_PARAMETER_BINDER_HPP diff --git a/include/matador/sql/statement.hpp b/include/matador/sql/statement.hpp index 5ad2a42..4c0f641 100644 --- a/include/matador/sql/statement.hpp +++ b/include/matador/sql/statement.hpp @@ -1,7 +1,7 @@ #ifndef QUERY_STATEMENT_HPP #define QUERY_STATEMENT_HPP -#include "matador/sql/object_binder.hpp" +#include "matador/sql/object_parameter_binder.hpp" #include "matador/sql/query_result.hpp" #include "matador/sql/statement_impl.hpp" @@ -50,7 +50,7 @@ public: private: std::unique_ptr statement_; const utils::logger &logger_; - object_binder object_binder_; + object_parameter_binder object_binder_; }; } diff --git a/include/matador/sql/types.hpp b/include/matador/sql/types.hpp index 57924a4..56d5873 100644 --- a/include/matador/sql/types.hpp +++ b/include/matador/sql/types.hpp @@ -10,6 +10,7 @@ namespace matador::sql { class query_result_reader; class parameter_binder; +class result_parameter_binder; /** * @brief Enumeration type of all supported builtin data types @@ -65,6 +66,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_char; } static void read_value(query_result_reader &reader, const char *id, size_t index, char &value); static void bind_value(parameter_binder &binder, size_t index, char &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, char &value); inline static any_type create_value(char &value) { return value; } }; @@ -73,6 +75,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_short; } static void read_value(query_result_reader &reader, const char *id, size_t index, short &value); static void bind_value(parameter_binder &binder, size_t index, short &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, short &value); inline static any_type create_value(short &value) { return value; } }; @@ -81,6 +84,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_int; } static void read_value(query_result_reader &reader, const char *id, size_t index, int &value); static void bind_value(parameter_binder &binder, size_t index, int &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, int &value); inline static any_type create_value(int &value) { return value; } }; @@ -89,6 +93,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_long; } static void read_value(query_result_reader &reader, const char *id, size_t index, long &value); static void bind_value(parameter_binder &binder, size_t index, long &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, long &value); inline static any_type create_value(long &value) { return value; } }; @@ -97,6 +102,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_long_long; } static void read_value(query_result_reader &reader, const char *id, size_t index, long long &value); static void bind_value(parameter_binder &binder, size_t index, long long &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, long long &value); inline static any_type create_value(long long &value) { return value; } }; @@ -105,6 +111,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_char; } static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned char &value); static void bind_value(parameter_binder &binder, size_t index, unsigned char &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned char &value); inline static any_type create_value(unsigned char &value) { return value; } }; @@ -113,6 +120,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_short; } static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned short &value); static void bind_value(parameter_binder &binder, size_t index, unsigned short &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned short &value); inline static any_type create_value(unsigned short &value) { return value; } }; @@ -121,6 +129,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_int; } static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned int &value); static void bind_value(parameter_binder &binder, size_t index, unsigned int &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned int &value); inline static any_type create_value(unsigned int &value) { return value; } }; @@ -129,6 +138,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/ = 0) { return data_type_t::type_unsigned_long; } static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned long &value); static void bind_value(parameter_binder &binder, size_t index, unsigned long &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned long &value); inline static any_type create_value(unsigned long &value) { return value; } }; @@ -137,6 +147,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_unsigned_long_long; } static void read_value(query_result_reader &reader, const char *id, size_t index, unsigned long long &value); static void bind_value(parameter_binder &binder, size_t index, unsigned long long &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, unsigned long long &value); inline static any_type create_value(unsigned long long &value) { return value; } }; @@ -145,6 +156,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_bool; } static void read_value(query_result_reader &reader, const char *id, size_t index, bool &value); static void bind_value(parameter_binder &binder, size_t index, bool &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, bool &value); inline static any_type create_value(bool &value) { return value; } }; @@ -152,6 +164,7 @@ template <> struct data_type_traits { inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_float; } static void read_value(query_result_reader &reader, const char *id, size_t index, float &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, float &value); static void bind_value(parameter_binder &binder, size_t index, float &value); inline static any_type create_value(float &value) { return value; } }; @@ -160,6 +173,7 @@ template <> struct data_type_traits { inline static data_type_t builtin_type(std::size_t /*size*/) { return data_type_t::type_double; } static void read_value(query_result_reader &reader, const char *id, size_t index, double &value); + static void bind_result_value(result_parameter_binder &binder, size_t index, double &value); static void bind_value(parameter_binder &binder, size_t index, double &value); inline static any_type create_value(double &value) { return value; } }; @@ -169,6 +183,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_char_pointer; } static void read_value(query_result_reader &reader, const char *id, size_t index, const char* value, size_t size); static void bind_value(parameter_binder &binder, size_t index, const char *value, size_t size = 0); + static void bind_result_value(result_parameter_binder &binder, size_t index, const char *value, size_t size = 0); inline static any_type create_value(const char *value) { return value; } }; @@ -177,6 +192,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_varchar; } static void read_value(query_result_reader &reader, const char *id, size_t index, char *value, size_t size); static void bind_value(parameter_binder &binder, size_t index, char *value, size_t size = 0); + static void bind_result_value(result_parameter_binder &binder, size_t index, char *value, size_t size = 0); inline static any_type create_value(const char *value) { return value; } }; @@ -185,6 +201,7 @@ template <> struct data_type_traits inline static data_type_t builtin_type(std::size_t size) { return size == 0 ? data_type_t::type_text : data_type_t::type_varchar; } static void read_value(query_result_reader &reader, const char *id, size_t index, std::string &value, size_t size); static void bind_value(parameter_binder &binder, size_t index, std::string &value, size_t size = 0); + static void bind_result_value(result_parameter_binder &binder, size_t index, std::string &value, size_t size = 0); inline static any_type create_value(std::string &value) { return value; } }; @@ -216,6 +233,10 @@ struct data_type_traits { data_type_traits::bind_value(binder, index, (int&)value); } + static void bind_result_value(result_parameter_binder &binder, size_t index, EnumType &value) + { + data_type_traits::bind_result_value(binder, index, (int&)value); + } inline static any_type create_value(EnumType &value) { return (int)value; } }; /// @endcond diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7feb6f7..00bc96f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,9 +23,10 @@ set(SQL_SOURCES sql/statement_cache.cpp sql/statement_impl.cpp sql/dialect_builder.cpp - sql/object_binder.cpp + sql/object_parameter_binder.cpp sql/placeholder_generator.cpp sql/types.cpp + sql/result_parameter_binder.cpp ) set(SQL_HEADER @@ -64,8 +65,9 @@ set(SQL_HEADER sql/statement.cpp ../include/matador/sql/parameter_binder.hpp ../include/matador/sql/dialect_builder.hpp - ../include/matador/sql/object_binder.hpp + ../include/matador/sql/object_parameter_binder.hpp ../include/matador/sql/placeholder_generator.hpp + ../include/matador/sql/result_parameter_binder.hpp ) set(UTILS_HEADER diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index 94c8ec6..60b3c93 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -71,9 +71,9 @@ record connection::describe(const std::string &table_name) const return std::move(connection_->describe(table_name)); } -bool connection::exists(const std::string &table_name) const +bool connection::exists(const std::string &schema_name, const std::string &table_name) const { - return connection_->exists(table_name); + return connection_->exists(schema_name, table_name); } size_t connection::execute(const std::string &sql) const diff --git a/src/sql/object_binder.cpp b/src/sql/object_parameter_binder.cpp similarity index 61% rename from src/sql/object_binder.cpp rename to src/sql/object_parameter_binder.cpp index b1549db..933fe1b 100644 --- a/src/sql/object_binder.cpp +++ b/src/sql/object_parameter_binder.cpp @@ -1,4 +1,4 @@ -#include "matador/sql/object_binder.hpp" +#include "matador/sql/object_parameter_binder.hpp" #include "matador/sql/parameter_binder.hpp" namespace matador::sql { @@ -15,21 +15,21 @@ void fk_binder::on_primary_key(const char *id, std::string &value, size_t size) } -object_binder::object_binder(parameter_binder &binder) +object_parameter_binder::object_parameter_binder(parameter_binder &binder) : binder_(binder) , fk_binder_(binder) {} -void object_binder::reset() +void object_parameter_binder::reset() { index_ = 0; } -void object_binder::on_primary_key(const char *id, std::string &val, size_t size) +void object_parameter_binder::on_primary_key(const char *id, std::string &val, size_t size) { data_type_traits::bind_value(binder_, index_++, val, size); } -void object_binder::on_revision(const char *id, unsigned long long int &rev) +void object_parameter_binder::on_revision(const char *id, unsigned long long int &rev) { data_type_traits::bind_value(binder_, index_++, rev); } diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index 8c072f8..b2e4911 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -125,15 +125,18 @@ query_builder &query_builder::select(const std::vector &columns) if (columns.size() < 2) { for (const auto &col: columns) { result.append(dialect_.prepare_identifier(col)); + query_.result_vars.emplace_back(col.name()); query_.prototype.append(col); } } else { auto it = columns.begin(); result.append(dialect_.prepare_identifier(*it)); + query_.result_vars.emplace_back(it->name()); query_.prototype.append(column{*it++}); for (; it != columns.end(); ++it) { result.append(", "); result.append(dialect_.prepare_identifier(*it)); + query_.result_vars.emplace_back(it->name()); query_.prototype.append(column{*it}); } } @@ -284,20 +287,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); +// query_.result_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); +// query_.result_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); +// query_.result_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); } diff --git a/src/sql/result_parameter_binder.cpp b/src/sql/result_parameter_binder.cpp new file mode 100644 index 0000000..adb90f6 --- /dev/null +++ b/src/sql/result_parameter_binder.cpp @@ -0,0 +1,42 @@ +#include "matador/sql/result_parameter_binder.hpp" + +namespace matador::sql { + +namespace detail { +fk_result_binder::fk_result_binder(result_parameter_binder &result_binder) +: result_binder_(result_binder) +{} + +void fk_result_binder::on_primary_key(const char * /*id*/, std::string &value, size_t size) +{ + data_type_traits::bind_result_value(result_binder_, column_index_++, value, size); +} + +} + +void result_parameter_binder::on_primary_key(const char * /*id*/, std::string &value, size_t size) +{ + data_type_traits::bind_result_value(*this, column_index_++, value, size); +} + +void result_parameter_binder::on_revision(const char * /*id*/, unsigned long long &rev) +{ + data_type_traits::bind_result_value(*this, column_index_++, rev); +} + +void result_parameter_binder::on_attribute(const char * /*id*/, char *value, const utils::field_attributes &attr) +{ + data_type_traits::bind_result_value(*this, column_index_++, value, attr.size()); +} + +void result_parameter_binder::on_attribute(const char * /*id*/, std::string &value, const utils::field_attributes &attr) +{ + data_type_traits::bind_result_value(*this, column_index_++, value, attr.size()); +} + +void result_parameter_binder::on_attribute(const char * /*id*/, any_type &value, data_type_t type, const utils::field_attributes &attr) +{ + +} + +} \ No newline at end of file diff --git a/src/sql/session.cpp b/src/sql/session.cpp index 7d299f8..37f4edb 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -97,7 +97,7 @@ bool session::table_exists(const std::string &table_name) const if (!c.valid()) { throw std::logic_error("no database connection available"); } - return c->exists(table_name); + return c->exists(dialect_.default_schema_name(), table_name); } const table_repository& session::tables() const diff --git a/src/sql/types.cpp b/src/sql/types.cpp index 34d436c..4449864 100644 --- a/src/sql/types.cpp +++ b/src/sql/types.cpp @@ -2,6 +2,7 @@ #include "matador/sql/parameter_binder.hpp" #include "matador/sql/query_result_reader.hpp" +#include "matador/sql/result_parameter_binder.hpp" namespace matador::sql { @@ -15,6 +16,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index, binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, char &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, short &value) { reader.read_value(id, index, value); @@ -25,6 +31,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index, binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, short &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, int &value) { reader.read_value(id, index, value); @@ -35,6 +46,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index, i binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, int &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, long &value) { reader.read_value(id, index, value); @@ -45,6 +61,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index, binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, long &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, long long &value) { reader.read_value(id, index, value); @@ -55,6 +76,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_ binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, long long int &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, unsigned char &value) { reader.read_value(id, index, value); @@ -65,6 +91,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_ binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, unsigned char &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, unsigned short &value) { reader.read_value(id, index, value); @@ -75,6 +106,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, unsigned short &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, unsigned int &value) { reader.read_value(id, index, value); @@ -85,6 +121,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, unsigned int &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, unsigned long &value) { reader.read_value(id, index, value); @@ -95,16 +136,26 @@ void data_type_traits::bind_value(parameter_binder &binder, size_ binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, unsigned long &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, unsigned long long &value) { reader.read_value(id, index, value); } -void data_type_traits::bind_value(parameter_binder &binder, size_t index, unsigned long long int &value) +void data_type_traits::bind_value(parameter_binder &binder, size_t index, unsigned long long &value) { binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, unsigned long long &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, bool &value) { reader.read_value(id, index, value); @@ -115,6 +166,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index, binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, bool &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, float &value) { reader.read_value(id, index, value); @@ -125,6 +181,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index, binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, float &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, double &value) { reader.read_value(id, index, value); @@ -135,6 +196,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index binder.bind(index, value); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, double &value) +{ + binder.bind_result_value(index, value); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, const char* value, size_t size) { reader.read_value(id, index, const_cast(value), size); @@ -145,6 +211,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t binder.bind(index, value, size); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, const char *value, size_t size) +{ + binder.bind_result_value(index, const_cast(value), size); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, char* value, size_t size) { reader.read_value(id, index, value, size); @@ -155,6 +226,11 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t index binder.bind(index, value, size); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, char *value, size_t size) +{ + binder.bind_result_value(index, value, size); +} + void data_type_traits::read_value(query_result_reader &reader, const char *id, size_t index, std::string &value, size_t size) { reader.read_value(id, index, value, size); @@ -165,4 +241,8 @@ void data_type_traits::bind_value(parameter_binder &binder, size_t binder.bind(index, value, size); } +void data_type_traits::bind_result_value(result_parameter_binder &binder, size_t index, std::string &value, size_t size) +{ + binder.bind_result_value(index, value, size); +} } \ No newline at end of file diff --git a/test/ConnectionTest.cpp b/test/ConnectionTest.cpp index 3e2a034..68deb5c 100644 --- a/test/ConnectionTest.cpp +++ b/test/ConnectionTest.cpp @@ -1,16 +1,25 @@ #include -#include +#include #include "matador/sql/connection.hpp" +#include "Databases.hpp" + using namespace matador::sql; -TEST_CASE("Create connection", "[connection]") { - auto dns = GENERATE(as{}, - "sqlite://sqlite.db", - "postgres://test:test123@127.0.0.1:5432/matador_test" ); +template +class ConnectionTestFixture +{ +public: + ConnectionTestFixture() = default; + ~ConnectionTestFixture() = default; - connection c(dns); + std::string dns() { return Type::dns; } +}; + +TEMPLATE_TEST_CASE_METHOD(ConnectionTestFixture, "Create connection", "[connection]", Sqlite, Postgres, MySql) { + + connection c(ConnectionTestFixture::dns()); REQUIRE(!c.is_open()); c.open(); diff --git a/test/Databases.hpp b/test/Databases.hpp index 0f47626..c87a4e7 100644 --- a/test/Databases.hpp +++ b/test/Databases.hpp @@ -12,4 +12,9 @@ struct Sqlite constexpr static const char *dns{"sqlite://sqlite.db"}; }; +struct MySql +{ + constexpr static const char *dns{"mysql://test:test123!@127.0.0.1:3306/matador_test"}; +}; + #endif //QUERY_DATABASES_HPP diff --git a/test/SessionRecordTest.cpp b/test/SessionRecordTest.cpp index 5e68760..44ca9fe 100644 --- a/test/SessionRecordTest.cpp +++ b/test/SessionRecordTest.cpp @@ -1,20 +1,35 @@ #include #include +#include #include #include +#include "Databases.hpp" + #include using namespace matador::sql; -TEST_CASE("Create and drop table statement", "[session record]") +template +class SessionRecordTestFixture { - auto dns = GENERATE(as < std::string > {}, - "sqlite://sqlite.db", - "postgres://test:test123@127.0.0.1:5432/matador_test"); - connection_pool pool(dns, 4); - session s(pool); +public: + SessionRecordTestFixture() + : pool_(Type::dns, 4), session_(pool_) + {} + + matador::sql::session &session() + { return session_; } + +private: + matador::sql::connection_pool pool_; + matador::sql::session session_; +}; + +TEMPLATE_TEST_CASE_METHOD(SessionRecordTestFixture, "Create and drop table statement", "[session record]", Sqlite, Postgres, MySql) +{ + auto &s = SessionRecordTestFixture::session(); REQUIRE(!s.table_exists("person")); s.create()