From da423ea8bb4ec9c37e789827de7f1b79acd7ad2c Mon Sep 17 00:00:00 2001 From: Sascha Kuehl Date: Sat, 9 Dec 2023 11:08:03 +0100 Subject: [PATCH] implemented prepared statement for postgres and sqlite --- README.md | 4 +- backends/postgres/CMakeLists.txt | 23 +- .../postgres/include/postgres_connection.hpp | 11 +- .../include/postgres_parameter_binder.h | 42 +++ .../postgres/include/postgres_statement.hpp | 34 +++ backends/postgres/src/postgres_connection.cpp | 33 ++- backends/postgres/src/postgres_dialect.cpp | 10 +- .../src/postgres_parameter_binder.cpp | 142 ++++++++++ backends/postgres/src/postgres_statement.cpp | 40 +++ backends/sqlite/CMakeLists.txt | 24 +- backends/sqlite/include/sqlite_connection.hpp | 4 +- .../sqlite/include/sqlite_parameter_binder.h | 44 +++ .../include/sqlite_prepared_result_reader.hpp | 42 +++ .../sqlite/include/sqlite_result_reader.hpp | 2 +- backends/sqlite/include/sqlite_statement.hpp | 31 ++ backends/sqlite/src/sqlite_connection.cpp | 32 ++- backends/sqlite/src/sqlite_dialect.cpp | 20 +- backends/sqlite/src/sqlite_error.cpp | 4 +- .../sqlite/src/sqlite_parameter_binder.cpp | 121 ++++++++ .../src/sqlite_prepared_result_reader.cpp | 114 ++++++++ backends/sqlite/src/sqlite_statement.cpp | 52 ++++ include/matador/sql/any_type.hpp | 3 + include/matador/sql/any_type_to_visitor.hpp | 3 + include/matador/sql/basic_condition.hpp | 3 +- include/matador/sql/condition.hpp | 53 ++-- include/matador/sql/connection.hpp | 6 +- include/matador/sql/connection_impl.hpp | 4 +- include/matador/sql/dialect.hpp | 43 +-- include/matador/sql/dialect_builder.hpp | 30 ++ include/matador/sql/object_binder.hpp | 55 ++++ include/matador/sql/parameter_binder.hpp | 37 +++ include/matador/sql/placeholder.hpp | 14 + include/matador/sql/placeholder_generator.hpp | 49 ++++ include/matador/sql/query_builder.hpp | 18 +- include/matador/sql/query_context.hpp | 20 ++ include/matador/sql/query_intermediates.hpp | 22 +- include/matador/sql/result.hpp | 14 - include/matador/sql/session.hpp | 15 +- include/matador/sql/statement.hpp | 55 ++++ include/matador/sql/statement_cache.hpp | 36 +++ include/matador/sql/statement_impl.hpp | 47 +++ src/CMakeLists.txt | 21 +- src/sql/condition.cpp | 26 +- src/sql/connection.cpp | 5 + src/sql/dialect.cpp | 42 +-- src/sql/dialect_builder.cpp | 52 ++++ src/sql/object_binder.cpp | 24 ++ src/sql/placeholder_generator.cpp | 15 + src/sql/query_builder.cpp | 267 ++++++++++-------- src/sql/query_intermediates.cpp | 14 +- src/sql/session.cpp | 11 +- src/sql/statement.cpp | 34 +++ src/sql/statement_cache.cpp | 17 ++ src/sql/statement_impl.cpp | 9 + test/CMakeLists.txt | 4 +- test/QueryBuilderTest.cpp | 34 ++- test/SessionRecordTest.cpp | 2 +- test/StatementCacheTest.cpp | 31 ++ test/StatementTest.cpp | 119 ++++++++ 59 files changed, 1769 insertions(+), 314 deletions(-) create mode 100644 backends/postgres/include/postgres_parameter_binder.h create mode 100644 backends/postgres/include/postgres_statement.hpp create mode 100644 backends/postgres/src/postgres_parameter_binder.cpp create mode 100644 backends/postgres/src/postgres_statement.cpp create mode 100644 backends/sqlite/include/sqlite_parameter_binder.h create mode 100644 backends/sqlite/include/sqlite_prepared_result_reader.hpp create mode 100644 backends/sqlite/include/sqlite_statement.hpp create mode 100644 backends/sqlite/src/sqlite_parameter_binder.cpp create mode 100644 backends/sqlite/src/sqlite_prepared_result_reader.cpp create mode 100644 backends/sqlite/src/sqlite_statement.cpp create mode 100644 include/matador/sql/dialect_builder.hpp create mode 100644 include/matador/sql/object_binder.hpp create mode 100644 include/matador/sql/parameter_binder.hpp create mode 100644 include/matador/sql/placeholder.hpp create mode 100644 include/matador/sql/placeholder_generator.hpp create mode 100644 include/matador/sql/query_context.hpp delete mode 100644 include/matador/sql/result.hpp create mode 100644 include/matador/sql/statement.hpp create mode 100644 include/matador/sql/statement_cache.hpp create mode 100644 include/matador/sql/statement_impl.hpp create mode 100644 src/sql/dialect_builder.cpp create mode 100644 src/sql/object_binder.cpp create mode 100644 src/sql/placeholder_generator.cpp create mode 100644 src/sql/statement.cpp create mode 100644 src/sql/statement_cache.cpp create mode 100644 src/sql/statement_impl.cpp create mode 100644 test/StatementCacheTest.cpp create mode 100644 test/StatementTest.cpp diff --git a/README.md b/README.md index f0732b9..d92e54e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# query +# query_context -A fluent sql query builder +A fluent sql query_context builder Object definition ```cpp diff --git a/backends/postgres/CMakeLists.txt b/backends/postgres/CMakeLists.txt index f12e53c..82932e3 100644 --- a/backends/postgres/CMakeLists.txt +++ b/backends/postgres/CMakeLists.txt @@ -3,6 +3,8 @@ set(HEADER include/postgres_error.hpp include/postgres_result_reader.hpp include/postgres_dialect.hpp + include/postgres_statement.hpp + include/postgres_parameter_binder.h ) set(SOURCES @@ -10,17 +12,22 @@ set(SOURCES src/postgres_error.cpp src/postgres_result_reader.cpp src/postgres_dialect.cpp + src/postgres_statement.cpp + src/postgres_parameter_binder.cpp ) -add_library(matador-postgres SHARED ${SOURCES} ${HEADER}) -target_include_directories(matador-postgres PRIVATE +set(LIBRARY_TARGET matador-postgres) + +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/postgres/include ${PostgreSQL_INCLUDE_DIRS}) -target_link_libraries(matador-postgres matador ${PostgreSQL_LIBRARIES}) - -set_target_properties(matador-postgres - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/backends" -) \ No newline at end of file +target_link_libraries(${LIBRARY_TARGET} matador ${PostgreSQL_LIBRARIES}) diff --git a/backends/postgres/include/postgres_connection.hpp b/backends/postgres/include/postgres_connection.hpp index fe273fd..629ded0 100644 --- a/backends/postgres/include/postgres_connection.hpp +++ b/backends/postgres/include/postgres_connection.hpp @@ -14,6 +14,8 @@ #include "matador/sql/connection_impl.hpp" +#include + #include namespace matador::backends::postgres { @@ -27,7 +29,7 @@ public: bool is_open() override; std::unique_ptr fetch(const std::string &stmt) override; - void prepare(const std::string &stmt) override; + std::unique_ptr prepare(sql::query_context context) override; size_t execute(const std::string &stmt) override; @@ -35,8 +37,15 @@ public: bool exists(const std::string &table_name) override; +private: + [[nodiscard]] static std::string generate_statement_name(const sql::query_context &query) ; + private: PGconn *conn_{nullptr}; + + using string_to_int_map = std::unordered_map; + + static string_to_int_map statement_name_map_; }; } diff --git a/backends/postgres/include/postgres_parameter_binder.h b/backends/postgres/include/postgres_parameter_binder.h new file mode 100644 index 0000000..441cb94 --- /dev/null +++ b/backends/postgres/include/postgres_parameter_binder.h @@ -0,0 +1,42 @@ +#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H +#define QUERY_POSTGRES_PARAMETER_BINDER_H + +#include "matador/sql/parameter_binder.hpp" + +#include + +namespace matador::backends::postgres { + +class postgres_parameter_binder final : public sql::parameter_binder +{ +public: + explicit postgres_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; + + const std::vector& params() const; + +private: + std::vector strings_; + std::vector params_; +}; + +} + +#endif //QUERY_POSTGRES_PARAMETER_BINDER_H diff --git a/backends/postgres/include/postgres_statement.hpp b/backends/postgres/include/postgres_statement.hpp new file mode 100644 index 0000000..7ed5eef --- /dev/null +++ b/backends/postgres/include/postgres_statement.hpp @@ -0,0 +1,34 @@ +#ifndef QUERY_POSTGRES_STATEMENT_HPP +#define QUERY_POSTGRES_STATEMENT_HPP + +#include "matador/sql/statement_impl.hpp" + +#include "postgres_parameter_binder.h" + +#include + +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); + + size_t execute() override; + std::unique_ptr fetch() override; + void reset() override; +protected: + sql::parameter_binder& binder() override; + +private: + PGconn *db_{nullptr}; + PGresult *result_{nullptr}; + + std::string name_; + + postgres_parameter_binder binder_; +}; + +} + +#endif //QUERY_POSTGRES_STATEMENT_HPP diff --git a/backends/postgres/src/postgres_connection.cpp b/backends/postgres/src/postgres_connection.cpp index eb50d23..ed7f39d 100644 --- a/backends/postgres/src/postgres_connection.cpp +++ b/backends/postgres/src/postgres_connection.cpp @@ -1,13 +1,17 @@ #include "postgres_connection.hpp" #include "postgres_error.hpp" #include "postgres_result_reader.hpp" +#include "postgres_statement.hpp" #include "matador/sql/record.hpp" #include +#include namespace matador::backends::postgres { +postgres_connection::string_to_int_map postgres_connection::statement_name_map_{}; + postgres_connection::postgres_connection(const sql::connection_info &info) : connection_impl(info) {} @@ -47,20 +51,41 @@ std::unique_ptr postgres_connection::fetch(const std::st throw_postgres_error(res, conn_, "postgres", stmt); sql::record prototype; - auto ncol = PQnfields(res); - for (int i = 0; i < ncol; ++i) { + auto num_col = PQnfields(res); + for (int i = 0; i < num_col; ++i) { const char *col_name = PQfname(res, i); auto type = PQftype(res, i); auto size = PQfmod(res, i); - std::cout << "column " << col_name << ", type " << type << " (size: " << size << ")\n"; +// std::cout << "column " << col_name << ", type " << type << " (size: " << size << ")\n"; prototype.append({col_name}); } return std::move(std::make_unique(std::make_unique(res), std::move(prototype))); } -void postgres_connection::prepare(const std::string &stmt) +std::string postgres_connection::generate_statement_name(const sql::query_context &query) { + std::stringstream name; + name << query.table_name << "_" << query.command_name; + auto result = postgres_connection::statement_name_map_.find(name.str()); + if (result == postgres_connection::statement_name_map_.end()) { + result = postgres_connection::statement_name_map_.insert(std::make_pair(name.str(), 0)).first; + } + + name << "_" << ++result->second; + + return name.str(); +} + +std::unique_ptr postgres_connection::prepare(sql::query_context context) +{ + auto statement_name = postgres_connection::generate_statement_name(context); + + PGresult *result = PQprepare(conn_, statement_name.c_str(), context.sql.c_str(), static_cast(context.bind_vars.size()), nullptr); + + throw_postgres_error(result, conn_, "postgres", context.sql); + + return std::make_unique(conn_, result, statement_name, std::move(context)); } size_t postgres_connection::execute(const std::string &stmt) diff --git a/backends/postgres/src/postgres_dialect.cpp b/backends/postgres/src/postgres_dialect.cpp index 9105a78..c854c0f 100644 --- a/backends/postgres/src/postgres_dialect.cpp +++ b/backends/postgres/src/postgres_dialect.cpp @@ -1,7 +1,15 @@ #include "postgres_dialect.hpp" +#include "matador/sql/dialect_builder.hpp" + [[maybe_unused]] const matador::sql::dialect* get_dialect() { using namespace matador::sql; - const static dialect d{}; + const static dialect d = dialect_builder::builder() + .create() + .with_placeholder_func([](size_t index) { + return "$" + std::to_string(index); + }) + .with_default_schema_name("public") + .build(); return &d; } diff --git a/backends/postgres/src/postgres_parameter_binder.cpp b/backends/postgres/src/postgres_parameter_binder.cpp new file mode 100644 index 0000000..9537204 --- /dev/null +++ b/backends/postgres/src/postgres_parameter_binder.cpp @@ -0,0 +1,142 @@ +#include "postgres_parameter_binder.h" + +namespace matador::backends::postgres { + +namespace detail { + +template < class T > +void bind_value(std::vector &strings, std::vector ¶ms, size_t index, T &x) +{ + strings[index] = std::to_string(x); + params[index] = strings[index].c_str(); +} + +template <> +void bind_value(std::vector &strings, std::vector ¶ms, size_t index, char &x) +{ + strings[index] = std::to_string(x); + params[index] = strings[index].data(); +} + +template <> +void bind_value(std::vector &strings, std::vector ¶ms, size_t index, unsigned char &x) +{ + strings[index] = std::to_string(x); + params[index] = strings[index].data(); +} + +//template <> +//void bind_value(std::vector &strings, std::vector ¶ms, size_t &index, const matador::date &x) +//{ +// strings[index] = matador::to_string(x, date_format::ISO8601); +// params[index] = strings[index].c_str(); +// ++index; +//} +// +//template <> +//void bind_value(std::vector &strings, std::vector ¶ms, size_t &index, const matador::time &x) +//{ +// strings[index] = matador::to_string(x, "%Y-%m-%d %T.%f"); +// params[index] = strings[index].c_str(); +// ++index; +//} + +} + +postgres_parameter_binder::postgres_parameter_binder(size_t size) +: strings_(size) +, params_(size) +{} + +void postgres_parameter_binder::bind(size_t pos, char i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, short i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, int i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, long i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, long long int i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, unsigned char i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, unsigned short i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, unsigned int i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, unsigned long i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, unsigned long long int i) +{ + detail::bind_value(strings_, params_, pos, i); +} + +void postgres_parameter_binder::bind(size_t pos, bool b) +{ + detail::bind_value(strings_, params_, pos, b); +} + +void postgres_parameter_binder::bind(size_t pos, float d) +{ + detail::bind_value(strings_, params_, pos, d); +} + +void postgres_parameter_binder::bind(size_t pos, double d) +{ + detail::bind_value(strings_, params_, pos, d); +} + +void postgres_parameter_binder::bind(size_t pos, const char *str) +{ + params_[pos] = str; +} + +void postgres_parameter_binder::bind(size_t pos, const char *str, size_t size) +{ + params_[pos] = str; +} + +void postgres_parameter_binder::bind(size_t pos, const std::string &str) +{ + strings_[pos] = str; + params_[pos] = strings_[pos].c_str(); +} + +void postgres_parameter_binder::bind(size_t pos, const std::string &str, size_t size) +{ + bind(pos, str); +} + +const std::vector &postgres_parameter_binder::params() const +{ + return params_; +} + +} \ No newline at end of file diff --git a/backends/postgres/src/postgres_statement.cpp b/backends/postgres/src/postgres_statement.cpp new file mode 100644 index 0000000..4f7e7db --- /dev/null +++ b/backends/postgres/src/postgres_statement.cpp @@ -0,0 +1,40 @@ +#include "postgres_statement.hpp" +#include "postgres_error.hpp" +#include "postgres_result_reader.hpp" + +namespace matador::backends::postgres { + +postgres_statement::postgres_statement(PGconn *db, PGresult *result, const std::string &name, const sql::query_context &query) +: statement_impl(query) +, db_(db) +, result_(result) +, name_(name) +, binder_(query_.bind_vars.size()) +{} + +size_t postgres_statement::execute() +{ + PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0); + + throw_postgres_error(res, db_, "postgres", query_.sql); + + return std::stoul(PQcmdTuples(res)); +} + +std::unique_ptr postgres_statement::fetch() +{ + PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0); + + throw_postgres_error(res, db_, "postgres", query_.sql); + + return std::move(std::make_unique(std::make_unique(res), std::move(query_.prototype))); +} + +void postgres_statement::reset() {} + +sql::parameter_binder& postgres_statement::binder() +{ + return binder_; +} + +} \ No newline at end of file diff --git a/backends/sqlite/CMakeLists.txt b/backends/sqlite/CMakeLists.txt index 8e68d79..a061a85 100644 --- a/backends/sqlite/CMakeLists.txt +++ b/backends/sqlite/CMakeLists.txt @@ -3,6 +3,9 @@ set(HEADER include/sqlite_error.hpp include/sqlite_dialect.hpp include/sqlite_result_reader.hpp + include/sqlite_statement.hpp + include/sqlite_parameter_binder.h + include/sqlite_prepared_result_reader.hpp ) set(SOURCES @@ -10,16 +13,23 @@ set(SOURCES src/sqlite_error.cpp src/sqlite_dialect.cpp src/sqlite_result_reader.cpp + src/sqlite_statement.cpp + src/sqlite_parameter_binder.cpp + src/sqlite_prepared_result_reader.cpp ) -add_library(matador-sqlite SHARED ${SOURCES} ${HEADER}) -target_include_directories(matador-sqlite PRIVATE +set(LIBRARY_TARGET matador-sqlite) + +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/sqlite/include ${SQLite3_INCLUDE_DIRS}) -target_link_libraries(matador-sqlite matador ${SQLite3_LIBRARIES}) -set_target_properties(matador-sqlite - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/backends" -) \ No newline at end of file +target_link_libraries(${LIBRARY_TARGET} matador ${SQLite3_LIBRARIES}) diff --git a/backends/sqlite/include/sqlite_connection.hpp b/backends/sqlite/include/sqlite_connection.hpp index d6ef698..9672a17 100644 --- a/backends/sqlite/include/sqlite_connection.hpp +++ b/backends/sqlite/include/sqlite_connection.hpp @@ -29,7 +29,7 @@ public: bool is_open() override; std::unique_ptr fetch(const std::string &stmt) override; - void prepare(const std::string &stmt) override; + std::unique_ptr prepare(sql::query_context query) override; size_t execute(const std::string &stmt) override; @@ -50,7 +50,7 @@ private: fetch_context fetch_internal(const std::string &stmt); private: - sqlite3 *sqlite_db_{}; + sqlite3 *db_{}; }; } diff --git a/backends/sqlite/include/sqlite_parameter_binder.h b/backends/sqlite/include/sqlite_parameter_binder.h new file mode 100644 index 0000000..0ace34a --- /dev/null +++ b/backends/sqlite/include/sqlite_parameter_binder.h @@ -0,0 +1,44 @@ +#ifndef QUERY_SQLITE_PARAMETER_BINDER_H +#define QUERY_SQLITE_PARAMETER_BINDER_H + +#include "matador/sql/parameter_binder.hpp" + +#include + +#include +#include + +namespace matador::backends::sqlite { + +class sqlite_parameter_binder final : public sql::parameter_binder +{ +public: + explicit sqlite_parameter_binder(sqlite3 *db, sqlite3_stmt *stmt); + + 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 *str, size_t size) override; + void bind(size_t pos, const std::string &str) override; + void bind(size_t pos, const std::string &str, size_t size) override; + +private: + sqlite3 *db_{nullptr}; + sqlite3_stmt *stmt_{nullptr}; + + std::vector > host_strings_; +}; +} + +#endif //QUERY_SQLITE_PARAMETER_BINDER_H diff --git a/backends/sqlite/include/sqlite_prepared_result_reader.hpp b/backends/sqlite/include/sqlite_prepared_result_reader.hpp new file mode 100644 index 0000000..9b55404 --- /dev/null +++ b/backends/sqlite/include/sqlite_prepared_result_reader.hpp @@ -0,0 +1,42 @@ +#ifndef QUERY_SQLITE_PREPARED_RESULT_READER_HPP +#define QUERY_SQLITE_PREPARED_RESULT_READER_HPP + +#include "matador/sql/query_result_reader.hpp" + +#include + +namespace matador::backends::sqlite { + +class sqlite_prepared_result_reader final : public sql::query_result_reader +{ +public: + sqlite_prepared_result_reader(sqlite3 *db, sqlite3_stmt *stmt); + + [[nodiscard]] size_t column_count() const override; + [[nodiscard]] const char *column(size_t index) const override; + bool fetch() 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; + void read_value(const char *id, size_t index, long &value) override; + void read_value(const char *id, size_t index, long long int &value) override; + void read_value(const char *id, size_t index, unsigned char &value) override; + void read_value(const char *id, size_t index, unsigned short &value) override; + void read_value(const char *id, size_t index, unsigned int &value) override; + void read_value(const char *id, size_t index, unsigned long &value) override; + void read_value(const char *id, size_t index, unsigned long long int &value) override; + void read_value(const char *id, size_t index, bool &value) override; + void read_value(const char *id, size_t index, float &value) override; + void read_value(const char *id, size_t index, double &value) override; + void read_value(const char *id, size_t index, char *value, size_t s) override; + void read_value(const char *id, size_t index, std::string &value) override; + void read_value(const char *id, size_t index, std::string &value, size_t s) override; + void read_value(const char *id, size_t index, sql::any_type &value, sql::data_type_t type, size_t size) override; + +private: + sqlite3 *db_{nullptr}; + sqlite3_stmt *stmt_{nullptr}; +}; +} +#endif //QUERY_SQLITE_PREPARED_RESULT_READER_HPP diff --git a/backends/sqlite/include/sqlite_result_reader.hpp b/backends/sqlite/include/sqlite_result_reader.hpp index 186e7d5..0e0d712 100644 --- a/backends/sqlite/include/sqlite_result_reader.hpp +++ b/backends/sqlite/include/sqlite_result_reader.hpp @@ -7,7 +7,7 @@ namespace matador::backends::sqlite { -class sqlite_result_reader : public sql::query_result_reader +class sqlite_result_reader final : public sql::query_result_reader { public: using columns = std::vector; diff --git a/backends/sqlite/include/sqlite_statement.hpp b/backends/sqlite/include/sqlite_statement.hpp new file mode 100644 index 0000000..fc60b87 --- /dev/null +++ b/backends/sqlite/include/sqlite_statement.hpp @@ -0,0 +1,31 @@ +#ifndef QUERY_SQLITE_STATEMENT_HPP +#define QUERY_SQLITE_STATEMENT_HPP + +#include "matador/sql/statement_impl.hpp" + +#include "sqlite_parameter_binder.h" + +namespace matador::backends::sqlite { + +class sqlite_statement final : public sql::statement_impl +{ +public: + sqlite_statement(sqlite3 *db, sqlite3_stmt *stmt, const sql::query_context &query); + ~sqlite_statement(); + + size_t execute() override; + std::unique_ptr fetch() override; + void reset() override; +protected: + sql::parameter_binder& binder() override; + +private: + sqlite3 *db_{nullptr}; + sqlite3_stmt *stmt_{nullptr}; + + sqlite_parameter_binder binder_; +}; + +} + +#endif //QUERY_SQLITE_STATEMENT_HPP diff --git a/backends/sqlite/src/sqlite_connection.cpp b/backends/sqlite/src/sqlite_connection.cpp index ad79dbc..7b02b46 100644 --- a/backends/sqlite/src/sqlite_connection.cpp +++ b/backends/sqlite/src/sqlite_connection.cpp @@ -1,6 +1,7 @@ #include "sqlite_connection.hpp" #include "sqlite_error.hpp" #include "sqlite_result_reader.hpp" +#include "sqlite_statement.hpp" #include "matador/sql/record.hpp" @@ -20,25 +21,25 @@ void sqlite_connection::open() return; } - const auto ret = sqlite3_open(info().database.c_str(), &sqlite_db_); + const auto ret = sqlite3_open(info().database.c_str(), &db_); if (ret != SQLITE_OK) { - throw_sqlite_error(ret, sqlite_db_, "open"); + throw_sqlite_error(ret, db_, "open"); } } void sqlite_connection::close() { - int ret = sqlite3_close(sqlite_db_); + int ret = sqlite3_close(db_); - throw_sqlite_error(ret, sqlite_db_, "close"); + throw_sqlite_error(ret, db_, "close"); - sqlite_db_ = nullptr; + db_ = nullptr; } bool sqlite_connection::is_open() { - return sqlite_db_ != nullptr; + return db_ != nullptr; } int sqlite_connection::parse_result(void* param, int column_count, char** values, char** columns) @@ -75,9 +76,9 @@ sqlite_connection::fetch_context sqlite_connection::fetch_internal(const std::st { fetch_context context; char *errmsg = nullptr; - const int ret = sqlite3_exec(sqlite_db_, stmt.c_str(), parse_result, &context, &errmsg); + const int ret = sqlite3_exec(db_, stmt.c_str(), parse_result, &context, &errmsg); - throw_sqlite_error(ret, sqlite_db_, "sqlite", stmt); + throw_sqlite_error(ret, db_, "sqlite", stmt); return context; } @@ -85,11 +86,11 @@ sqlite_connection::fetch_context sqlite_connection::fetch_internal(const std::st size_t sqlite_connection::execute(const std::string &stmt) { char *errmsg = nullptr; - int ret = sqlite3_exec(sqlite_db_, stmt.c_str(), nullptr, nullptr, &errmsg); + int ret = sqlite3_exec(db_, stmt.c_str(), nullptr, nullptr, &errmsg); - throw_sqlite_error(ret, sqlite_db_, "sqlite", stmt); + throw_sqlite_error(ret, db_, "sqlite", stmt); - return sqlite3_changes(sqlite_db_); + return sqlite3_changes(db_); } std::unique_ptr sqlite_connection::fetch(const std::string &stmt) @@ -99,8 +100,13 @@ std::unique_ptr sqlite_connection::fetch(const std::stri return std::move(std::make_unique(std::make_unique(std::move(context.rows), context.prototype.size()), std::move(context.prototype))); } -void sqlite_connection::prepare(const std::string &stmt) +std::unique_ptr sqlite_connection::prepare(sql::query_context query) { + sqlite3_stmt *stmt{}; + int ret = sqlite3_prepare_v2(db_, query.sql.c_str(), static_cast(query.sql.size()), &stmt, nullptr); + throw_sqlite_error(ret, db_, "sqlite3_prepare_v2", query.sql); + + return std::make_unique(db_, stmt, query); } sql::data_type_t string2type(const char *type) @@ -142,7 +148,7 @@ sql::record sqlite_connection::describe(const std::string& table) { const auto result = fetch_internal("PRAGMA table_info(" + table + ")"); - sqlite_result_reader reader(std::move(result.rows), result.prototype.size()); + sqlite_result_reader reader(result.rows, result.prototype.size()); sql::record prototype; while (reader.fetch()) { char *end = nullptr; diff --git a/backends/sqlite/src/sqlite_dialect.cpp b/backends/sqlite/src/sqlite_dialect.cpp index 49aaa62..74f421b 100644 --- a/backends/sqlite/src/sqlite_dialect.cpp +++ b/backends/sqlite/src/sqlite_dialect.cpp @@ -1,11 +1,19 @@ #include "sqlite_dialect.hpp" -[[maybe_unused]] const matador::sql::dialect* get_dialect() { +#include "matador/sql/dialect_builder.hpp" + +[[maybe_unused]] const matador::sql::dialect *get_dialect() +{ using namespace matador::sql; - const static dialect d{{ - { dialect::token_t::BEGIN, "BEGIN TRANSACTION"}, - { dialect::token_t::COMMIT, "COMMIT TRANSACTION"}, - { dialect::token_t::ROLLBACK, "ROLLBACK TRANSACTION"} - }}; + const static dialect d = dialect_builder::builder() + .create() + .with_token_replace_map({ + {dialect::token_t::BEGIN, "BEGIN TRANSACTION"}, + {dialect::token_t::COMMIT, "COMMIT TRANSACTION"}, + {dialect::token_t::ROLLBACK, "ROLLBACK TRANSACTION"} + }) + .with_default_schema_name("main") + .build(); + return &d; } diff --git a/backends/sqlite/src/sqlite_error.cpp b/backends/sqlite/src/sqlite_error.cpp index ead0493..b190f3a 100644 --- a/backends/sqlite/src/sqlite_error.cpp +++ b/backends/sqlite/src/sqlite_error.cpp @@ -9,7 +9,7 @@ namespace matador::backends::sqlite { void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source) { - if (ec != SQLITE_OK) { + if (ec != SQLITE_OK && ec != SQLITE_DONE) { std::stringstream msg; msg << "sqlite error (" << source << "): " << sqlite3_errmsg(db); throw std::logic_error(msg.str()); @@ -18,7 +18,7 @@ void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source) void throw_sqlite_error(int ec, sqlite3 *db, const std::string &source, const std::string &sql) { - if (ec != SQLITE_OK) { + if (ec != SQLITE_OK&& ec != SQLITE_DONE) { std::stringstream msg; msg << "sqlite error (" << source << ", sql: " << sql << "): " << sqlite3_errmsg(db); throw std::logic_error(msg.str()); diff --git a/backends/sqlite/src/sqlite_parameter_binder.cpp b/backends/sqlite/src/sqlite_parameter_binder.cpp new file mode 100644 index 0000000..ed26f03 --- /dev/null +++ b/backends/sqlite/src/sqlite_parameter_binder.cpp @@ -0,0 +1,121 @@ +#include "sqlite_parameter_binder.h" +#include "sqlite_error.hpp" + +namespace matador::backends::sqlite { + +sqlite_parameter_binder::sqlite_parameter_binder(sqlite3 *db, sqlite3_stmt *stmt) +: db_(db) +, stmt_(stmt) +{} + +void sqlite_parameter_binder::bind(size_t pos, char i) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, short i) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, int i) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, long i) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, long long int i) +{ + int ret = sqlite3_bind_int64(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, unsigned char i) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, unsigned short i) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, unsigned int i) +{ + int ret = sqlite3_bind_int64(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, unsigned long i) +{ + int ret = sqlite3_bind_int64(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, unsigned long long int i) +{ + int ret = sqlite3_bind_int64(stmt_, static_cast(++pos), i); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, bool b) +{ + int ret = sqlite3_bind_int(stmt_, static_cast(++pos), b); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, float d) +{ + int ret = sqlite3_bind_double(stmt_, static_cast(++pos), d); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, double d) +{ + int ret = sqlite3_bind_double(stmt_, static_cast(++pos), d); + throw_sqlite_error(ret, db_, "sqlite3_bind_int"); +} + +void sqlite_parameter_binder::bind(size_t pos, const char *str) +{ + int ret = sqlite3_bind_text(stmt_, static_cast(++pos), str, static_cast(strlen(str)), nullptr); + throw_sqlite_error(ret, db_, "sqlite3_bind_text"); +} + +void sqlite_parameter_binder::bind(size_t pos, const char *x, size_t size) +{ + auto len = strlen(x); + size = (len > size) ? size : len; + int ret = sqlite3_bind_text(stmt_, static_cast(++pos), x, static_cast(size), nullptr); + throw_sqlite_error(ret, db_, "sqlite3_bind_text"); +} + +void sqlite_parameter_binder::bind(size_t pos, const std::string &str) +{ + int ret = sqlite3_bind_text(stmt_, static_cast(++pos), str.c_str(), static_cast(str.size()), nullptr); + throw_sqlite_error(ret, db_, "sqlite3_bind_text"); +} + +void sqlite_parameter_binder::bind(size_t pos, const std::string &x, size_t size) +{ + auto len = x.size(); + if (size == 0) { + size = len; + } else { + size = (len > size) ? size : len; + } + int ret = sqlite3_bind_text(stmt_, static_cast(++pos), x.data(), static_cast(size), nullptr); + throw_sqlite_error(ret, db_, "sqlite3_bind_text"); +} + +} \ No newline at end of file diff --git a/backends/sqlite/src/sqlite_prepared_result_reader.cpp b/backends/sqlite/src/sqlite_prepared_result_reader.cpp new file mode 100644 index 0000000..3097d52 --- /dev/null +++ b/backends/sqlite/src/sqlite_prepared_result_reader.cpp @@ -0,0 +1,114 @@ +#include "sqlite_prepared_result_reader.hpp" +#include "sqlite_error.hpp" + +namespace matador::backends::sqlite { + +sqlite_prepared_result_reader::sqlite_prepared_result_reader(sqlite3 *db, sqlite3_stmt *stmt) +: db_(db) +, stmt_(stmt) +{} + +size_t sqlite_prepared_result_reader::column_count() const +{ + return sqlite3_column_count(stmt_); +} + +const char *sqlite_prepared_result_reader::column(size_t index) const +{ + return reinterpret_cast(sqlite3_column_text(stmt_, static_cast(index))); +} + +bool sqlite_prepared_result_reader::fetch() +{ + int ret = sqlite3_step(stmt_); + if (ret != SQLITE_ROW) { + throw_sqlite_error(ret, db_, "sqlite3_step"); + } + return ret != SQLITE_DONE; +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, char &value) +{ + value = static_cast(sqlite3_column_int(stmt_, static_cast(index))); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, short &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, int &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, long &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, long long int &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned char &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned short &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned int &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned long &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, unsigned long long int &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, bool &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, float &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, double &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, char *value, size_t s) +{ + query_result_reader::read_value(id, index, value, s); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, std::string &value) +{ + query_result_reader::read_value(id, index, value); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, std::string &value, size_t s) +{ + query_result_reader::read_value(id, index, value, s); +} + +void sqlite_prepared_result_reader::read_value(const char *id, size_t index, sql::any_type &value, sql::data_type_t type, size_t size) +{ + query_result_reader::read_value(id, index, value, type, size); +} +} \ No newline at end of file diff --git a/backends/sqlite/src/sqlite_statement.cpp b/backends/sqlite/src/sqlite_statement.cpp new file mode 100644 index 0000000..e73e91c --- /dev/null +++ b/backends/sqlite/src/sqlite_statement.cpp @@ -0,0 +1,52 @@ +#include "sqlite_statement.hpp" +#include "sqlite_prepared_result_reader.hpp" +#include "sqlite_error.hpp" + +namespace matador::backends::sqlite { +sqlite_statement::sqlite_statement(sqlite3 *db, sqlite3_stmt *stmt, const sql::query_context &query) +: statement_impl(query) +, db_(db) +, stmt_(stmt) +, binder_(db, stmt) +{} + +sqlite_statement::~sqlite_statement() +{ + sqlite3_finalize(stmt_); +} + +size_t sqlite_statement::execute() +{ + // get next row + int ret = sqlite3_reset(stmt_); + throw_sqlite_error(ret, db_, "sqlite3_reset"); + if (ret = sqlite3_step(stmt_); ret != SQLITE_DONE) { + throw_sqlite_error(ret, db_, "sqlite3_step"); + } + + return sqlite3_changes(db_); +} + +std::unique_ptr sqlite_statement::fetch() +{ + int ret = sqlite3_reset(stmt_); + throw_sqlite_error(ret, db_, "sqlite3_reset"); + + auto reader = std::make_unique(db_, stmt_); + return std::move(std::make_unique(std::move(reader), query_.prototype)); +} + +void sqlite_statement::reset() +{ + if (stmt_) { + sqlite3_reset(stmt_); + sqlite3_clear_bindings(stmt_); + } +} + +sql::parameter_binder& sqlite_statement::binder() +{ + return binder_; +} + +} \ No newline at end of file diff --git a/include/matador/sql/any_type.hpp b/include/matador/sql/any_type.hpp index 2bf6a83..d667149 100644 --- a/include/matador/sql/any_type.hpp +++ b/include/matador/sql/any_type.hpp @@ -1,6 +1,8 @@ #ifndef QUERY_ANY_TYPE_HPP #define QUERY_ANY_TYPE_HPP +#include "matador/sql/placeholder.hpp" + #include #include @@ -13,6 +15,7 @@ using any_type = std::variant< bool, const char*, std::string, + placeholder, nullptr_t>; } diff --git a/include/matador/sql/any_type_to_visitor.hpp b/include/matador/sql/any_type_to_visitor.hpp index 690b75d..c520026 100644 --- a/include/matador/sql/any_type_to_visitor.hpp +++ b/include/matador/sql/any_type_to_visitor.hpp @@ -10,6 +10,8 @@ namespace matador::sql { +struct placeholder; + template < typename DestType, typename SourceType > void convert(DestType &dest, SourceType source, typename std::enable_if::value>::type* = nullptr) { @@ -122,6 +124,7 @@ struct any_type_to_visitor void operator()(double &x) { convert(result, x); } void operator()(const char *x) { convert(result, x); } void operator()(std::string &x) { convert(result, x); } + void operator()(placeholder &/*x*/) {} Type result{}; }; diff --git a/include/matador/sql/basic_condition.hpp b/include/matador/sql/basic_condition.hpp index 1a13df1..0f9a135 100644 --- a/include/matador/sql/basic_condition.hpp +++ b/include/matador/sql/basic_condition.hpp @@ -10,6 +10,7 @@ namespace matador::sql { class dialect; +class query_context; class basic_condition { @@ -32,7 +33,7 @@ public: LIKE }; - virtual std::string evaluate(dialect &dialect) const = 0; + virtual std::string evaluate(dialect &dialect, query_context &query) const = 0; static std::unordered_map operands; }; diff --git a/include/matador/sql/condition.hpp b/include/matador/sql/condition.hpp index f82fa82..6b59759 100644 --- a/include/matador/sql/condition.hpp +++ b/include/matador/sql/condition.hpp @@ -3,15 +3,13 @@ #include "matador/sql/basic_condition.hpp" #include "matador/sql/dialect.hpp" +#include "matador/sql/placeholder.hpp" +#include "matador/sql/query_context.hpp" #include namespace matador::sql { -struct placeholder {}; - -const placeholder _; - /** * @class condition * @brief Represents a sql query condition @@ -27,8 +25,6 @@ const placeholder _; /// @cond MATADOR_DEV -class query; - template class condition; @@ -40,7 +36,7 @@ public: placeholder value; - std::string evaluate(dialect &d) const override; + std::string evaluate(dialect &d, query_context &query) const override; }; template @@ -57,9 +53,9 @@ public: T value; - std::string evaluate(dialect &d) const override + std::string evaluate(dialect &d, query_context &query) const override { - d.add_host_var(field_.name()); + query.bind_vars.emplace_back(field_.name()); return d.prepare_identifier(field_.name()) + " " + operand + " " + std::to_string(value); } }; @@ -77,9 +73,9 @@ public: T value; - std::string evaluate(dialect &d) const override + std::string evaluate(dialect &d, query_context &query) const override { - d.add_host_var(field_.name()); + query.bind_vars.emplace_back(field_.name()); return d.prepare_identifier(field_.name()) + " " + operand + " '" + value + "'"; } }; @@ -98,7 +94,7 @@ public: T value; - std::string evaluate(dialect &d) const override + std::string evaluate(dialect &d, query_context &query) const override { return std::to_string(value) + " " + operand + " " + d.prepare_identifier(field_.name()); } @@ -117,7 +113,7 @@ public: T value; - std::string evaluate(dialect &d) const override + std::string evaluate(dialect &d, query_context &query) const override { return "'" + std::to_string(value) + "' " + operand + " " + d.prepare_identifier(field_.name()); } @@ -159,13 +155,12 @@ public: * @param d The d used to evaluate * @return A condition IN part of the query */ - std::string evaluate(dialect &d) const override { + std::string evaluate(dialect &d, query_context &query) const override { auto count = size(); for (size_t i = 0; i < count; ++i) { - d.add_host_var(field_.name()); + query.bind_vars.emplace_back(field_.name()); } - std::string result = d.prepare_identifier(field_.name()) + " IN ("; if (args_.size() < 2) { for (const auto &val : args_) { @@ -205,7 +200,7 @@ private: * @endcode */ template <> -class condition : public basic_column_condition +class condition : public basic_column_condition { public: /** @@ -219,7 +214,7 @@ public: * @param op Operand of the condition * @param q The query to be evaluated to the IN arguments */ - condition(column col, basic_condition::operand_t op, query &q); + condition(column col, basic_condition::operand_t op, query_context &q); /** * @brief Evaluates the condition @@ -230,10 +225,10 @@ public: * @param d The d used to evaluate * @return A condition IN part of the query */ - std::string evaluate(dialect &d) const override; + std::string evaluate(dialect &d, query_context &query) const override; private: - query &query_; + query_context &query_; }; /** @@ -265,9 +260,9 @@ public: * @param d The d used to evaluate * @return A condition BETWEEN part of the query */ - std::string evaluate(dialect &d) const override { - d.add_host_var(field_.name()); - d.add_host_var(field_.name()); + std::string evaluate(dialect &d, query_context &query) const override { + query.bind_vars.emplace_back(field_.name()); + query.bind_vars.emplace_back(field_.name()); return d.prepare_identifier(field_.name()) + " BETWEEN " + std::to_string(range_.first) + " AND " + std::to_string(range_.second); } @@ -307,11 +302,11 @@ public: * @param d The d used to evaluate * @return The evaluated string based on the compile type */ - std::string evaluate(dialect &d) const override + std::string evaluate(dialect &d, query_context &query) const override { // ensure the numbering order for host vars - auto cl = left.evaluate(d); - auto cr = right.evaluate(d); + auto cl = left.evaluate(d, query); + auto cr = right.evaluate(d, query); if (operand == basic_condition::operand_t::AND) { return "(" + cl + " " + basic_condition::operands[operand] + " " + cr + ")"; } else { @@ -350,7 +345,7 @@ public: * @param d The d used to evaluate * @return The evaluated string based on the compile type */ - std::string evaluate(dialect &d) const override + std::string evaluate(dialect &d, query_context &query) const override { return operand + " (" + cond.evaluate(d) + ")"; } @@ -397,7 +392,7 @@ condition> in(const column &col, std::initializ * @param q The query to be executes as sub select * @return The condition object */ -condition in(const column &col, query &&q); +condition in(const column &col, query_context &&q); /** * @brief Creates a between condition. @@ -456,7 +451,7 @@ condition operator==(const column &col, T val) * @param q The query to compare with * @return The condition object representing the equality operation */ -condition equals(const column &col, query &q); +condition equals(const column &col, query_context &q); /** * @brief Condition inequality operator for a column and a value diff --git a/include/matador/sql/connection.hpp b/include/matador/sql/connection.hpp index 7c2c35f..7572a73 100644 --- a/include/matador/sql/connection.hpp +++ b/include/matador/sql/connection.hpp @@ -4,8 +4,10 @@ #include "matador/sql/connection_info.hpp" #include "matador/sql/connection_impl.hpp" #include "matador/sql/dialect.hpp" +#include "matador/sql/query_context.hpp" #include "matador/sql/query_result.hpp" #include "matador/sql/record.hpp" +#include "matador/sql/statement.hpp" #include "matador/utils/logger.hpp" @@ -13,8 +15,6 @@ namespace matador::sql { -class connection_impl; - class connection { public: @@ -37,6 +37,8 @@ public: [[nodiscard]] std::unique_ptr fetch(const std::string &sql) const; [[nodiscard]] std::pair execute(const std::string &sql) const; + statement prepare(query_context &&query) const; + private: connection_info connection_info_; std::unique_ptr connection_; diff --git a/include/matador/sql/connection_impl.hpp b/include/matador/sql/connection_impl.hpp index 07c3170..5bca38e 100644 --- a/include/matador/sql/connection_impl.hpp +++ b/include/matador/sql/connection_impl.hpp @@ -3,7 +3,9 @@ #include "matador/sql/connection_info.hpp" #include "matador/sql/query_result_impl.hpp" +#include "matador/sql/query_context.hpp" #include "matador/sql/record.hpp" +#include "matador/sql/statement_impl.hpp" #include @@ -22,7 +24,7 @@ public: virtual size_t execute(const std::string &stmt) = 0; virtual std::unique_ptr fetch(const std::string &stmt) = 0; - virtual void prepare(const std::string &stmt) = 0; + 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; diff --git a/include/matador/sql/dialect.hpp b/include/matador/sql/dialect.hpp index 8ee22fc..37d91e1 100644 --- a/include/matador/sql/dialect.hpp +++ b/include/matador/sql/dialect.hpp @@ -4,6 +4,7 @@ #include "matador/sql/types.hpp" #include +#include #include #include #include @@ -12,7 +13,7 @@ namespace matador::sql { class column; -class dialect +class dialect final { public: enum class token_t : uint8_t @@ -65,14 +66,11 @@ public: using data_type_to_string_map = std::unordered_map; using sql_func_to_string_map = std::unordered_map; -public: - dialect() = default; - dialect(const token_to_string_map &token_replace_map, const data_type_to_string_map &data_type_replace_map); - explicit dialect(const data_type_to_string_map &data_type_replace_map); - explicit dialect(const token_to_string_map &token_replace_map); + using next_placeholder_func = std::function; - const std::string& token_at(token_t token) const; - const std::string& data_type_at(data_type_t type) const; +public: + [[nodiscard]] const std::string& token_at(token_t token) const; + [[nodiscard]] const std::string& data_type_at(data_type_t type) const; /** * Prepare sql dialect identifier for execution @@ -82,14 +80,14 @@ public: * @param str The identifier string to be prepared * @return The prepared string */ - std::string prepare_identifier(const column &col) const; + [[nodiscard]] std::string prepare_identifier(const column &col) const; /** * Prepare string literal * * @param str String literal to be prepared */ - std::string prepare_literal(const std::string &str) const; + [[nodiscard]] std::string prepare_literal(const std::string &str) const; /** * Wrap identifier quotes around a sql identifier keyword @@ -118,7 +116,7 @@ public: * * @return How the identifier quotes should be escaped */ - virtual escape_identifier_t identifier_escape_type() const; + [[nodiscard]] virtual escape_identifier_t identifier_escape_type() const; /** * Generates a next placeholder string. default is @@ -126,17 +124,24 @@ public: * * @return Placeholder string */ - virtual std::string next_placeholder() const; + [[nodiscard]] std::string next_placeholder(const std::vector &bind_vars) const; - void add_host_var(const std::string &host_var); - void add_column(const std::string &column); - - const std::vector& host_vars() const; - const std::vector& columns() const; + /** + * Returns the default schema name. + * + * @return The default schema name. + */ + [[nodiscard]] std::string default_schema_name() const; private: - std::vector host_vars_; - std::vector columns_; + dialect() = default; + +private: + friend class dialect_builder; + + next_placeholder_func placeholder_func_ = [](size_t) { return "?"; }; + + std::string default_schema_name_; token_to_string_map tokens_ { {token_t::CREATE, "CREATE"}, diff --git a/include/matador/sql/dialect_builder.hpp b/include/matador/sql/dialect_builder.hpp new file mode 100644 index 0000000..a065df7 --- /dev/null +++ b/include/matador/sql/dialect_builder.hpp @@ -0,0 +1,30 @@ +#ifndef QUERY_DIALECT_BUILDER_HPP +#define QUERY_DIALECT_BUILDER_HPP + +#include "matador/sql/dialect.hpp" + +namespace matador::sql { + +class dialect_builder +{ +public: + static dialect_builder& builder(); + + dialect_builder& create(); + + dialect_builder& with_token_replace_map(const dialect::token_to_string_map &token_replace_map); + dialect_builder& with_data_type_replace_map(const dialect::data_type_to_string_map &data_type_replace_map); + dialect_builder& with_placeholder_func(const dialect::next_placeholder_func &func); + dialect_builder& with_default_schema_name(const std::string &schema_name); + + dialect build(); + +private: + dialect_builder() = default; + +private: + dialect dialect_; +}; + +} +#endif //QUERY_DIALECT_BUILDER_HPP diff --git a/include/matador/sql/object_binder.hpp b/include/matador/sql/object_binder.hpp new file mode 100644 index 0000000..db3bd51 --- /dev/null +++ b/include/matador/sql/object_binder.hpp @@ -0,0 +1,55 @@ +#ifndef QUERY_OBJECT_BINDER_HPP +#define QUERY_OBJECT_BINDER_HPP + +#include "matador/utils/cascade_type.hpp" +#include "matador/utils/field_attributes.hpp" + +#include + +namespace matador::sql { + +class parameter_binder; + +class object_binder +{ +public: + explicit object_binder(parameter_binder &binder); + + void reset(); + + template < class V > + void on_primary_key(const char *id, V &val, typename std::enable_if::value && !std::is_same::value>::type* = 0) + { + binder_.bind(index_++, val); + } + void on_primary_key(const char *id, std::string &, size_t); + void on_revision(const char *id, unsigned long long &/*rev*/); + + template + void on_attribute(const char *id, Type &val, const utils::field_attributes &/*attr*/ = utils::null_attributes) + { + binder_.bind(index_++, val); + } + + template class Pointer> + void on_belongs_to(const char *id, Pointer &x, utils::cascade_type) + { +// binder_.bind(index_++, val); + } + template class Pointer> + void on_has_one(const char *id, Pointer &x, utils::cascade_type) + { +// binder_.bind(index_++, val); + } + 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: + parameter_binder &binder_; + size_t index_{0}; +}; + +} +#endif //QUERY_OBJECT_BINDER_HPP diff --git a/include/matador/sql/parameter_binder.hpp b/include/matador/sql/parameter_binder.hpp new file mode 100644 index 0000000..ff387a0 --- /dev/null +++ b/include/matador/sql/parameter_binder.hpp @@ -0,0 +1,37 @@ +#ifndef QUERY_PARAMETER_BINDER_HPP +#define QUERY_PARAMETER_BINDER_HPP + +#include +#include + +namespace matador::sql { + +class parameter_binder +{ +public: + virtual ~parameter_binder() = default; + + virtual void bind(size_t pos, char) = 0; + virtual void bind(size_t pos, short) = 0; + virtual void bind(size_t pos, int) = 0; + virtual void bind(size_t pos, long) = 0; + virtual void bind(size_t pos, long long) = 0; + virtual void bind(size_t pos, unsigned char) = 0; + virtual void bind(size_t pos, unsigned short) = 0; + virtual void bind(size_t pos, unsigned int) = 0; + virtual void bind(size_t pos, unsigned long) = 0; + virtual void bind(size_t pos, unsigned long long) = 0; + virtual void bind(size_t pos, bool) = 0; + virtual void bind(size_t pos, float) = 0; + virtual void bind(size_t pos, double) = 0; + virtual void bind(size_t pos, const char *) = 0; + virtual void bind(size_t pos, const char *, size_t size) = 0; + virtual void bind(size_t pos, const std::string&) = 0; + virtual void bind(size_t pos, const std::string &x, size_t size) = 0; +// virtual void bind(size_t pos, const matador::time&) = 0; +// virtual void bind(size_t pos, const matador::date&) = 0; +}; + +} + +#endif //QUERY_PARAMETER_BINDER_HPP diff --git a/include/matador/sql/placeholder.hpp b/include/matador/sql/placeholder.hpp new file mode 100644 index 0000000..ab9dce8 --- /dev/null +++ b/include/matador/sql/placeholder.hpp @@ -0,0 +1,14 @@ +#ifndef QUERY_PLACEHOLDER_HPP +#define QUERY_PLACEHOLDER_HPP + +namespace matador::sql { + +struct placeholder {}; + +inline constexpr bool operator==(const placeholder&, const placeholder&) { return true; } + +const placeholder _; + +} + +#endif //QUERY_PLACEHOLDER_HPP diff --git a/include/matador/sql/placeholder_generator.hpp b/include/matador/sql/placeholder_generator.hpp new file mode 100644 index 0000000..e658520 --- /dev/null +++ b/include/matador/sql/placeholder_generator.hpp @@ -0,0 +1,49 @@ +#ifndef QUERY_PLACEHOLDER_GENERATOR_HPP +#define QUERY_PLACEHOLDER_GENERATOR_HPP + +#include "matador/sql/any_type.hpp" + +#include "matador/utils/cascade_type.hpp" +#include "matador/utils/field_attributes.hpp" + +#include + +namespace matador::sql { + +class placeholder_generator +{ +public: + template < class V > + void on_primary_key(const char *id, V &val, typename std::enable_if::value && !std::is_same::value>::type* = 0) + { + placeholder_values.emplace_back(_); + } + void on_primary_key(const char *id, std::string &, size_t); + void on_revision(const char *id, unsigned long long &/*rev*/); + + template + void on_attribute(const char *id, Type &val, const utils::field_attributes &/*attr*/ = utils::null_attributes) + { + placeholder_values.emplace_back(_); + } + + template class Pointer> + void on_belongs_to(const char *id, Pointer &x, utils::cascade_type) + { + placeholder_values.emplace_back(_); + } + template class Pointer> + void on_has_one(const char *id, Pointer &x, utils::cascade_type) + { + placeholder_values.emplace_back(_); + } + 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) {} + + std::vector placeholder_values; +}; + +} +#endif //QUERY_PLACEHOLDER_GENERATOR_HPP diff --git a/include/matador/sql/query_builder.hpp b/include/matador/sql/query_builder.hpp index acf90f2..e7f585b 100644 --- a/include/matador/sql/query_builder.hpp +++ b/include/matador/sql/query_builder.hpp @@ -4,6 +4,7 @@ #include "matador/sql/basic_condition.hpp" #include "matador/sql/column.hpp" #include "matador/sql/key_value_pair.hpp" +#include "matador/sql/query_context.hpp" #include "matador/sql/record.hpp" #include @@ -18,7 +19,7 @@ class dialect; namespace detail { struct any_type_to_string_visitor { - explicit any_type_to_string_visitor(const dialect &d) : d(d) {} + explicit any_type_to_string_visitor(const dialect &d, query_context &query); void operator()(char &x) { to_string(x); } void operator()(short &x) { to_string(x); } @@ -35,6 +36,7 @@ struct any_type_to_string_visitor void operator()(double &x) { to_string(x); } void operator()(const char *x) { to_string(x); } void operator()(std::string &x) { to_string(x); } + void operator()(placeholder &x) { to_string(x); } template void to_string(Type &val) @@ -43,8 +45,10 @@ struct any_type_to_string_visitor } void to_string(const char *val); void to_string(std::string &val); + void to_string(placeholder &val); const dialect &d; + query_context &query; std::string result; }; @@ -59,14 +63,6 @@ enum class join_type_t { INNER, OUTER, LEFT, RIGHT }; -struct query -{ - std::string sql; - std::string table_name; - record prototype; - std::vector host_vars; -}; - class query_builder { private: @@ -134,7 +130,7 @@ public: query_builder& offset(size_t count); query_builder& limit(size_t count); - query compile(); + query_context compile(); private: void transition_to(state_t next); @@ -150,7 +146,7 @@ private: detail::any_type_to_string_visitor value_to_string_; - query query_; + query_context query_; using query_state_set = std::unordered_set; using query_state_transition_map = std::unordered_map; diff --git a/include/matador/sql/query_context.hpp b/include/matador/sql/query_context.hpp new file mode 100644 index 0000000..4bcee70 --- /dev/null +++ b/include/matador/sql/query_context.hpp @@ -0,0 +1,20 @@ +#ifndef QUERY_QUERY_CONTEXT_HPP +#define QUERY_QUERY_CONTEXT_HPP + +#include "matador/sql/record.hpp" + +namespace matador::sql { + +struct query_context +{ + std::string sql; + std::string command_name; + std::string table_name; + record prototype; + std::vector host_vars; + std::vector bind_vars; +}; + +} + +#endif //QUERY_QUERY_CONTEXT_HPP diff --git a/include/matador/sql/query_intermediates.hpp b/include/matador/sql/query_intermediates.hpp index 50871f8..618fcc2 100644 --- a/include/matador/sql/query_intermediates.hpp +++ b/include/matador/sql/query_intermediates.hpp @@ -6,9 +6,11 @@ #include "matador/sql/column_name_generator.hpp" #include "matador/sql/key_value_generator.hpp" #include "matador/sql/key_value_pair.hpp" +#include "matador/sql/placeholder_generator.hpp" #include "matador/sql/query_builder.hpp" #include "matador/sql/query_result.hpp" #include "matador/sql/record.hpp" +#include "matador/sql/statement.hpp" #include "matador/sql/table_repository.hpp" #include "matador/sql/value_extractor.hpp" @@ -35,6 +37,7 @@ public: using query_intermediate::query_intermediate; std::pair execute(); + statement prepare(); }; class query_select_finish : public query_intermediate @@ -57,6 +60,8 @@ public: return fetch_one().at(0).as(); } + statement prepare(); + private: std::unique_ptr fetch(); }; @@ -140,6 +145,15 @@ public: query_from_intermediate from(const std::string &table, const std::string &as = ""); }; +template < class Type > +std::vector as_placeholder(const Type &obj) +{ + placeholder_generator generator; + matador::utils::access::process(generator, obj); + + return generator.placeholder_values; +} + class query_into_intermediate : public query_intermediate { public: @@ -147,6 +161,12 @@ public: query_execute_finish values(std::initializer_list values); template + query_execute_finish values() + { + Type obj; + return {session_, builder_.values(as_placeholder(obj))}; + } + template query_execute_finish values(const Type &obj) { return {session_, builder_.values(value_extractor::extract(obj))}; @@ -216,7 +236,7 @@ public: class query_update_intermediate : public query_start_intermediate { public: - query_update_intermediate(session &s, std::string table_name); + query_update_intermediate(session &s, const std::string& table_name); query_set_intermediate set(std::initializer_list columns); template diff --git a/include/matador/sql/result.hpp b/include/matador/sql/result.hpp deleted file mode 100644 index 241e78c..0000000 --- a/include/matador/sql/result.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef QUERY_RESULT_HPP -#define QUERY_RESULT_HPP - -#include - -namespace matador::sql { - -struct result -{ - std::string sql; -}; - -} -#endif //QUERY_RESULT_HPP diff --git a/include/matador/sql/session.hpp b/include/matador/sql/session.hpp index c70e5c6..d9b5f6a 100644 --- a/include/matador/sql/session.hpp +++ b/include/matador/sql/session.hpp @@ -6,6 +6,7 @@ #include "matador/sql/connection_pool.hpp" #include "matador/sql/query_builder.hpp" #include "matador/sql/query_intermediates.hpp" +#include "matador/sql/statement.hpp" #include "matador/sql/table_repository.hpp" #include @@ -22,18 +23,16 @@ public: query_create_intermediate create(); query_drop_intermediate drop(); template < class Type > - query_select_intermediate select() - { - return query_select_intermediate{*this, column_generator::generate(table_repository_)}; - } + query_select_intermediate select(); query_select_intermediate select(std::initializer_list columns); query_insert_intermediate insert(); query_update_intermediate update(const std::string &table); query_delete_intermediate remove(); - [[nodiscard]] query_result fetch(const query &q) const; + [[nodiscard]] query_result fetch(const query_context &q) const; // [[nodiscard]] query_result fetch(const std::string &sql) const; [[nodiscard]] std::pair execute(const std::string &sql) const; + statement prepare(query_context q) const; record describe_table(const std::string &table_name) const; bool table_exists(const std::string &table_name) const; @@ -61,5 +60,11 @@ private: mutable std::unordered_map prototypes_; }; +template +query_select_intermediate session::select() +{ + return query_select_intermediate{*this, column_generator::generate(table_repository_)}; +} + } #endif //QUERY_SESSION_HPP diff --git a/include/matador/sql/statement.hpp b/include/matador/sql/statement.hpp new file mode 100644 index 0000000..0a388ba --- /dev/null +++ b/include/matador/sql/statement.hpp @@ -0,0 +1,55 @@ +#ifndef QUERY_STATEMENT_HPP +#define QUERY_STATEMENT_HPP + +#include "matador/sql/object_binder.hpp" +#include "matador/sql/query_result.hpp" +#include "matador/sql/statement_impl.hpp" + +#include "matador/utils/logger.hpp" + +#include + +namespace matador::sql { + +class statement +{ +public: + explicit statement(std::unique_ptr impl, const utils::logger &logger); + + template < typename Type > + statement& bind(size_t pos, const Type &value) + { + statement_->bind(pos, value); + return *this; + } + + statement& bind(size_t pos, std::string &val, size_t size); + + template < class Type > + statement& bind(const Type &obj) + { + object_binder_.reset(); + matador::utils::access::process(object_binder_, obj); + return *this; + } + + size_t execute(); + + template + query_result fetch() + { + logger_.info(statement_->query_.sql); + return query_result(statement_->fetch()); + } + query_result fetch(); + + void reset(); + +private: + std::unique_ptr statement_; + const utils::logger &logger_; + object_binder object_binder_; +}; +} + +#endif //QUERY_STATEMENT_HPP diff --git a/include/matador/sql/statement_cache.hpp b/include/matador/sql/statement_cache.hpp new file mode 100644 index 0000000..f5f45a2 --- /dev/null +++ b/include/matador/sql/statement_cache.hpp @@ -0,0 +1,36 @@ +#ifndef QUERY_STATEMENT_CACHE_HPP +#define QUERY_STATEMENT_CACHE_HPP + +#include "matador/sql/statement.hpp" + +#include +#include +#include +#include + +namespace matador::sql { + +class connection; + +struct cache_info +{ + std::unique_ptr statement_; + size_t connection_id_; +}; + +class statement_cache +{ +public: + statement& acquire(const std::string &stmt, const connection &conn); + void release(const statement &stmt); + +private: + mutable std::mutex mutex_; + size_t max_cache_size_{256}; + std::hash hash_; + using statement_map = std::unordered_map; + statement_map statement_map_; +}; + +} +#endif //QUERY_STATEMENT_CACHE_HPP diff --git a/include/matador/sql/statement_impl.hpp b/include/matador/sql/statement_impl.hpp new file mode 100644 index 0000000..3cb2a19 --- /dev/null +++ b/include/matador/sql/statement_impl.hpp @@ -0,0 +1,47 @@ +#ifndef QUERY_STATEMENT_IMPL_HPP +#define QUERY_STATEMENT_IMPL_HPP + +#include "matador/sql/query_context.hpp" +#include "matador/sql/query_result_impl.hpp" +#include "matador/sql/parameter_binder.hpp" + +#include + +namespace matador::sql { + +class statement_impl +{ +protected: + explicit statement_impl(query_context query); + +public: + virtual ~statement_impl() = default; + + virtual size_t execute() = 0; + virtual std::unique_ptr fetch() = 0; + + template < class V > + void bind(size_t pos, V &val) + { + binder().bind(pos, val); + } + + void bind(size_t pos, std::string &val, size_t size) + { + binder().bind(pos, val, size); + } + + virtual void reset() = 0; + +protected: + virtual parameter_binder& binder() = 0; + +protected: + friend class statement; + + query_context query_; +}; + +} + +#endif //QUERY_STATEMENT_IMPL_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7105699..dac3f4f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,7 +19,13 @@ set(SQL_SOURCES sql/table_repository.cpp sql/any_type_to_visitor.cpp sql/query_result.cpp - sql/query_result_reader.cpp) + sql/query_result_reader.cpp + sql/statement_cache.cpp + sql/statement_impl.cpp + sql/dialect_builder.cpp + sql/object_binder.cpp + sql/placeholder_generator.cpp +) set(SQL_HEADER ../include/matador/sql/dialect.hpp @@ -31,7 +37,6 @@ set(SQL_HEADER ../include/matador/sql/condition.hpp ../include/matador/sql/connection.hpp ../include/matador/sql/query_intermediates.hpp - ../include/matador/sql/result.hpp ../include/matador/sql/record.hpp ../include/matador/sql/query_result.hpp ../include/matador/sql/connection_impl.hpp @@ -50,7 +55,17 @@ set(SQL_HEADER ../include/matador/sql/table_repository.hpp ../include/matador/sql/any_type_to_visitor.hpp ../include/matador/sql/query_result_reader.hpp - ../include/matador/sql/to_value.hpp) + ../include/matador/sql/to_value.hpp + ../include/matador/sql/statement_cache.hpp + ../include/matador/sql/statement.hpp + ../include/matador/sql/statement_impl.hpp + ../include/matador/sql/query_context.hpp + sql/statement.cpp + ../include/matador/sql/parameter_binder.hpp + ../include/matador/sql/dialect_builder.hpp + ../include/matador/sql/object_binder.hpp + ../include/matador/sql/placeholder_generator.hpp +) set(UTILS_HEADER ../include/matador/utils/field_attributes.hpp diff --git a/src/sql/condition.cpp b/src/sql/condition.cpp index 125e508..628824a 100644 --- a/src/sql/condition.cpp +++ b/src/sql/condition.cpp @@ -3,21 +3,21 @@ namespace matador::sql { condition::type>::condition(const column &fld, basic_condition::operand_t op, const placeholder &val) -: basic_column_condition(fld, op) -, value(val) -{ } - -std::string condition::type>::evaluate(dialect &d) const { - d.add_host_var(field_.name()); - return d.prepare_identifier(field_.name()) + " " + operand + " " + d.next_placeholder(); -} - -condition::condition(column col, basic_condition::operand_t op, query &q) -: basic_column_condition(std::move(col), op) -, query_(q) +: basic_column_condition(fld, op), value(val) {} -std::string condition::evaluate(dialect &d) const { +std::string condition::type>::evaluate(dialect &d, query_context &query) const +{ + query.bind_vars.emplace_back(field_.name()); + return d.prepare_identifier(field_.name()) + " " + operand + " " + d.next_placeholder(query.bind_vars); +} + +condition::condition(column col, basic_condition::operand_t op, query_context &q) +: basic_column_condition(std::move(col), op), query_(q) +{} + +std::string condition::evaluate(dialect &d, query_context &query) const +{ std::string result(d.prepare_identifier(field_.name()) + " " + operand + " ("); result += (")"); return result; diff --git a/src/sql/connection.cpp b/src/sql/connection.cpp index d55691d..2246ab8 100644 --- a/src/sql/connection.cpp +++ b/src/sql/connection.cpp @@ -88,4 +88,9 @@ std::unique_ptr connection::fetch(const std::string &sql) con return connection_->fetch(sql); } +statement connection::prepare(query_context &&query) const +{ + return statement(connection_->prepare(std::move(query)), logger_); +} + } diff --git a/src/sql/dialect.cpp b/src/sql/dialect.cpp index 2f48ab0..cecab65 100644 --- a/src/sql/dialect.cpp +++ b/src/sql/dialect.cpp @@ -5,25 +5,6 @@ namespace matador::sql { -dialect::dialect(const dialect::token_to_string_map &token_replace_map, const dialect::data_type_to_string_map &data_type_replace_map) -{ - for (const auto &token : token_replace_map) { - tokens_[token.first] = token.second; - } - - for (const auto &data_type : data_type_replace_map) { - data_types_[data_type.first] = data_type.second; - } -} - -dialect::dialect(const dialect::data_type_to_string_map &data_type_replace_map) -: dialect({}, data_type_replace_map) -{} - -dialect::dialect(const dialect::token_to_string_map &token_replace_map) -: dialect(token_replace_map, {}) -{} - const std::string& dialect::token_at(dialect::token_t token) const { return tokens_.at(token); @@ -85,29 +66,14 @@ dialect::escape_identifier_t dialect::identifier_escape_type() const return dialect::escape_identifier_t::ESCAPE_BOTH_SAME; } -std::string dialect::next_placeholder() const +std::string dialect::next_placeholder(const std::vector &bind_vars) const { - return "?"; + return placeholder_func_(bind_vars.size()); } -void dialect::add_host_var(const std::string &host_var) +std::string dialect::default_schema_name() const { - host_vars_.emplace_back(host_var); -} - -void dialect::add_column(const std::string &column) -{ - columns_.emplace_back(column); -} - -const std::vector &dialect::host_vars() const -{ - return host_vars_; -} - -const std::vector &dialect::columns() const -{ - return columns_; + return default_schema_name_; } } \ No newline at end of file diff --git a/src/sql/dialect_builder.cpp b/src/sql/dialect_builder.cpp new file mode 100644 index 0000000..ea2197b --- /dev/null +++ b/src/sql/dialect_builder.cpp @@ -0,0 +1,52 @@ +#include "matador/sql/dialect_builder.hpp" + +namespace matador::sql { +dialect_builder &dialect_builder::builder() +{ + static dialect_builder instance; + return instance; +} + +dialect_builder &dialect_builder::create() +{ + dialect_ = {}; + return *this; +} + +dialect_builder& dialect_builder::with_token_replace_map(const dialect::token_to_string_map &token_replace_map) +{ + for (const auto &token : token_replace_map) { + dialect_.tokens_[token.first] = token.second; + } + + return *this; +} + +dialect_builder& dialect_builder::with_data_type_replace_map(const dialect::data_type_to_string_map &data_type_replace_map) +{ + for (const auto &data_type : data_type_replace_map) { + dialect_.data_types_[data_type.first] = data_type.second; + } + + return *this; +} + +dialect_builder& dialect_builder::with_placeholder_func(const dialect::next_placeholder_func &func) +{ + dialect_.placeholder_func_ = func; + + return *this; +} + +dialect_builder &dialect_builder::with_default_schema_name(const std::string &schema_name) +{ + dialect_.default_schema_name_ = schema_name; + + return *this; +} + +dialect dialect_builder::build() +{ + return dialect_; +} +} \ No newline at end of file diff --git a/src/sql/object_binder.cpp b/src/sql/object_binder.cpp new file mode 100644 index 0000000..30aece5 --- /dev/null +++ b/src/sql/object_binder.cpp @@ -0,0 +1,24 @@ +#include "matador/sql/object_binder.hpp" +#include "matador/sql/parameter_binder.hpp" + +namespace matador::sql { + +object_binder::object_binder(parameter_binder &binder) +: binder_(binder) {} + +void object_binder::reset() +{ + index_ = 0; +} + +void object_binder::on_primary_key(const char *id, std::string &val, size_t) +{ + binder_.bind(index_++, val); +} + +void object_binder::on_revision(const char *id, unsigned long long int &rev) +{ + binder_.bind(index_++, rev); +} + +} \ No newline at end of file diff --git a/src/sql/placeholder_generator.cpp b/src/sql/placeholder_generator.cpp new file mode 100644 index 0000000..cc1b5df --- /dev/null +++ b/src/sql/placeholder_generator.cpp @@ -0,0 +1,15 @@ +#include "matador/sql/placeholder_generator.hpp" + +namespace matador::sql { + +void placeholder_generator::on_primary_key(const char *id, std::string &, size_t) +{ + placeholder_values.emplace_back(_); +} + +void placeholder_generator::on_revision(const char *id, unsigned long long int &) +{ + placeholder_values.emplace_back(_); +} + +} \ No newline at end of file diff --git a/src/sql/query_builder.cpp b/src/sql/query_builder.cpp index e232010..8c072f8 100644 --- a/src/sql/query_builder.cpp +++ b/src/sql/query_builder.cpp @@ -9,6 +9,9 @@ namespace matador::sql { namespace detail { +any_type_to_string_visitor::any_type_to_string_visitor(const dialect &d, query_context &query) +: d(d), query(query) {} + void any_type_to_string_visitor::to_string(const char *val) { result = "'" + d.prepare_literal(val) + "'"; @@ -19,69 +22,76 @@ void any_type_to_string_visitor::to_string(std::string &val) result = "'" + d.prepare_literal(val) + "'"; } +void any_type_to_string_visitor::to_string(placeholder &/*val*/) +{ + query.bind_vars.emplace_back("unknown"); + result = d.next_placeholder(query.bind_vars); +} + } // poor mens state machine // but does the job for query query_builder::query_state_transition_map query_builder::transitions_{ - {state_t::QUERY_INIT, {state_t::QUERY_CREATE, state_t::QUERY_DROP, state_t::QUERY_SELECT, state_t::QUERY_INSERT, state_t::QUERY_UPDATE, state_t::QUERY_DELETE}}, - {state_t::QUERY_CREATE, {state_t::QUERY_TABLE_CREATE}}, - {state_t::QUERY_DROP, {state_t::QUERY_TABLE_DROP}}, - {state_t::QUERY_SELECT, {state_t::QUERY_FROM}}, - {state_t::QUERY_INSERT, {state_t::QUERY_INTO}}, - {state_t::QUERY_UPDATE, {state_t::QUERY_SET}}, - {state_t::QUERY_DELETE, {state_t::QUERY_FROM}}, - {state_t::QUERY_TABLE_CREATE, {state_t::QUERY_FINISH}}, - {state_t::QUERY_TABLE_DROP, {state_t::QUERY_FINISH}}, - {state_t::QUERY_FROM, {state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_WHERE, state_t::QUERY_ORDER_BY, state_t::QUERY_GROUP_BY, state_t::QUERY_FINISH}}, - {state_t::QUERY_SET, {state_t::QUERY_WHERE, state_t::QUERY_FINISH}}, - {state_t::QUERY_INTO, {state_t::QUERY_VALUES}}, - {state_t::QUERY_WHERE, {state_t::QUERY_ORDER_BY, state_t::QUERY_GROUP_BY, state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_FINISH}}, - {state_t::QUERY_ORDER_BY, {state_t::QUERY_ORDER_DIRECTION}}, - {state_t::QUERY_ORDER_DIRECTION, {state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_FINISH}}, - {state_t::QUERY_GROUP_BY, {state_t::QUERY_ORDER_BY, state_t::QUERY_FINISH}}, - {state_t::QUERY_OFFSET, {state_t::QUERY_LIMIT}}, - {state_t::QUERY_LIMIT, {state_t::QUERY_FINISH}}, - {state_t::QUERY_VALUES, {state_t::QUERY_FINISH}}, - {state_t::QUERY_FINISH, {}}, +{state_t::QUERY_INIT, {state_t::QUERY_CREATE, state_t::QUERY_DROP, state_t::QUERY_SELECT, state_t::QUERY_INSERT, state_t::QUERY_UPDATE, state_t::QUERY_DELETE}}, +{state_t::QUERY_CREATE, {state_t::QUERY_TABLE_CREATE}}, +{state_t::QUERY_DROP, {state_t::QUERY_TABLE_DROP}}, +{state_t::QUERY_SELECT, {state_t::QUERY_FROM}}, +{state_t::QUERY_INSERT, {state_t::QUERY_INTO}}, +{state_t::QUERY_UPDATE, {state_t::QUERY_SET}}, +{state_t::QUERY_DELETE, {state_t::QUERY_FROM}}, +{state_t::QUERY_TABLE_CREATE, {state_t::QUERY_FINISH}}, +{state_t::QUERY_TABLE_DROP, {state_t::QUERY_FINISH}}, +{state_t::QUERY_FROM, {state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_WHERE, state_t::QUERY_ORDER_BY, state_t::QUERY_GROUP_BY, state_t::QUERY_FINISH}}, +{state_t::QUERY_SET, {state_t::QUERY_WHERE, state_t::QUERY_FINISH}}, +{state_t::QUERY_INTO, {state_t::QUERY_VALUES}}, +{state_t::QUERY_WHERE, {state_t::QUERY_ORDER_BY, state_t::QUERY_GROUP_BY, state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_FINISH}}, +{state_t::QUERY_ORDER_BY, {state_t::QUERY_ORDER_DIRECTION}}, +{state_t::QUERY_ORDER_DIRECTION, {state_t::QUERY_OFFSET, state_t::QUERY_LIMIT, state_t::QUERY_FINISH}}, +{state_t::QUERY_GROUP_BY, {state_t::QUERY_ORDER_BY, state_t::QUERY_FINISH}}, +{state_t::QUERY_OFFSET, {state_t::QUERY_LIMIT}}, +{state_t::QUERY_LIMIT, {state_t::QUERY_FINISH}}, +{state_t::QUERY_VALUES, {state_t::QUERY_FINISH}}, +{state_t::QUERY_FINISH, {}}, }; -query_builder::query_state_to_string_map query_builder::state_strings_ { - { state_t::QUERY_INIT, "init" }, - { state_t::QUERY_CREATE, "create" }, - { state_t::QUERY_DROP, "drop" }, - { state_t::QUERY_SELECT, "select" }, - { state_t::QUERY_INSERT, "insert" }, - { state_t::QUERY_UPDATE, "update" }, - { state_t::QUERY_DELETE, "delete" }, - { state_t::QUERY_TABLE_CREATE, "table" }, - { state_t::QUERY_TABLE_DROP, "table" }, - { state_t::QUERY_FROM, "from" }, - { state_t::QUERY_SET, "set" }, - { state_t::QUERY_INTO, "into" }, - { state_t::QUERY_WHERE, "where" }, - { state_t::QUERY_ORDER_BY, "order_by" }, - { state_t::QUERY_GROUP_BY, "group_by" }, - { state_t::QUERY_OFFSET, "offset" }, - { state_t::QUERY_LIMIT, "limit" }, - { state_t::QUERY_FINISH, "finish" }, +query_builder::query_state_to_string_map query_builder::state_strings_{ +{state_t::QUERY_INIT, "init"}, +{state_t::QUERY_CREATE, "create"}, +{state_t::QUERY_DROP, "drop"}, +{state_t::QUERY_SELECT, "select"}, +{state_t::QUERY_INSERT, "insert"}, +{state_t::QUERY_UPDATE, "update"}, +{state_t::QUERY_DELETE, "delete"}, +{state_t::QUERY_TABLE_CREATE, "table"}, +{state_t::QUERY_TABLE_DROP, "table"}, +{state_t::QUERY_FROM, "from"}, +{state_t::QUERY_SET, "set"}, +{state_t::QUERY_INTO, "into"}, +{state_t::QUERY_WHERE, "where"}, +{state_t::QUERY_ORDER_BY, "order_by"}, +{state_t::QUERY_GROUP_BY, "group_by"}, +{state_t::QUERY_OFFSET, "offset"}, +{state_t::QUERY_LIMIT, "limit"}, +{state_t::QUERY_FINISH, "finish"}, }; -query_builder::query_command_to_string_map query_builder::command_strings_ { - { command_t::UNKNOWN, "unknown" }, - { command_t::CREATE, "create" }, - { command_t::DROP, "drop" }, - { command_t::SELECT, "select" }, - { command_t::INSERT, "insert" }, - { command_t::UPDATE, "update" }, - { command_t::REMOVE, "remove" }, +query_builder::query_command_to_string_map query_builder::command_strings_{ +{command_t::UNKNOWN, "unknown"}, +{command_t::CREATE, "create"}, +{command_t::DROP, "drop"}, +{command_t::SELECT, "select"}, +{command_t::INSERT, "insert"}, +{command_t::UPDATE, "update"}, +{command_t::REMOVE, "remove"}, }; query_builder::query_builder(const dialect &d) -: dialect_(d) -, value_to_string_(d) {} +: dialect_(d), value_to_string_(d, query_) +{} -query_builder& query_builder::create() { +query_builder &query_builder::create() +{ initialize(command_t::CREATE, state_t::QUERY_CREATE); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::CREATE)); @@ -89,7 +99,8 @@ query_builder& query_builder::create() { return *this; } -query_builder& query_builder::drop() { +query_builder &query_builder::drop() +{ initialize(command_t::DROP, state_t::QUERY_DROP); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::DROP)); @@ -97,7 +108,7 @@ query_builder& query_builder::drop() { return *this; } -query_builder& query_builder::select(std::initializer_list columns) +query_builder &query_builder::select(std::initializer_list columns) { return select(std::vector{columns}); } @@ -112,7 +123,7 @@ query_builder &query_builder::select(const std::vector &columns) std::string result; if (columns.size() < 2) { - for (const auto &col : columns) { + for (const auto &col: columns) { result.append(dialect_.prepare_identifier(col)); query_.prototype.append(col); } @@ -131,7 +142,8 @@ query_builder &query_builder::select(const std::vector &columns) return *this; } -query_builder& query_builder::insert() { +query_builder &query_builder::insert() +{ initialize(command_t::INSERT, state_t::QUERY_INSERT); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::INSERT)); @@ -139,7 +151,8 @@ query_builder& query_builder::insert() { return *this; } -query_builder& query_builder::update(const std::string &table) { +query_builder &query_builder::update(const std::string &table) +{ initialize(command_t::UPDATE, state_t::QUERY_UPDATE); query_.table_name = table; @@ -148,7 +161,8 @@ query_builder& query_builder::update(const std::string &table) { return *this; } -query_builder& query_builder::remove() { +query_builder &query_builder::remove() +{ initialize(command_t::REMOVE, state_t::QUERY_DELETE); query_parts_.emplace_back(dialect_.token_at(dialect::token_t::REMOVE)); @@ -156,7 +170,7 @@ query_builder& query_builder::remove() { return *this; } -query_builder& query_builder::table(const std::string &table, std::initializer_list columns) +query_builder &query_builder::table(const std::string &table, std::initializer_list columns) { return this->table(table, std::vector{columns}); } @@ -176,7 +190,8 @@ struct column_context std::string build_create_column(const column &col, const dialect &d, column_context &context); -query_builder &query_builder::table(const std::string &table, const std::vector &columns) { +query_builder &query_builder::table(const std::string &table, const std::vector &columns) +{ transition_to(state_t::QUERY_TABLE_CREATE); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table) + " "); @@ -187,7 +202,7 @@ query_builder &query_builder::table(const std::string &table, const std::vector< column_context context; if (columns.size() < 2) { - for (const auto &col : columns) { + for (const auto &col: columns) { result.append(build_create_column(col, dialect_, context)); } } else { @@ -202,7 +217,7 @@ query_builder &query_builder::table(const std::string &table, const std::vector< if (!context.primary_keys.empty()) { result.append(", CONSTRAINT PK_" + table + " PRIMARY KEY (" + utils::join(context.primary_keys, ", ") + ")"); } - for (const auto &fk : context.foreign_contexts) { + for (const auto &fk: context.foreign_contexts) { result += ", CONSTRAINT FK_" + table; result += "_" + fk.column; result += " FOREIGN KEY (" + fk.column + ")"; @@ -214,7 +229,8 @@ query_builder &query_builder::table(const std::string &table, const std::vector< return *this; } -query_builder& query_builder::table(const std::string &table) { +query_builder &query_builder::table(const std::string &table) +{ transition_to(state_t::QUERY_TABLE_DROP); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::TABLE) + " " + dialect_.prepare_identifier(table)); @@ -223,7 +239,7 @@ query_builder& query_builder::table(const std::string &table) { return *this; } -query_builder& query_builder::into(const std::string &table, std::initializer_list column_names) +query_builder &query_builder::into(const std::string &table, std::initializer_list column_names) { return into(table, std::vector{column_names}); } @@ -237,7 +253,7 @@ query_builder &query_builder::into(const std::string &table, const std::vector values) +query_builder &query_builder::values(std::initializer_list values) { return this->values(std::vector{values}); } -query_builder& query_builder::values(const std::vector &values) { +query_builder &query_builder::values(const std::vector &values) +{ transition_to(state_t::QUERY_VALUES); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::VALUES) + " "); std::string result{"("}; if (values.size() < 2) { - for (auto val : values) { + for (auto val: values) { query_.host_vars.push_back(val); std::visit(value_to_string_, val); result.append(value_to_string_.result); @@ -291,10 +308,20 @@ query_builder& query_builder::values(const std::vector &values) { return *this; } -query_builder& query_builder::from(const std::string &table, const std::string &as) { +query_builder &query_builder::from(const std::string &table, const std::string &as) +{ transition_to(state_t::QUERY_FROM); - query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::FROM) + " " + dialect_.prepare_identifier(table) + (as.empty() ? "" : " " + as)); + if (dialect_.default_schema_name().empty()) { + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::FROM) + + " " + dialect_.prepare_identifier(table) + + (as.empty() ? "" : " " + as)); + } else { + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::FROM) + + " " + dialect_.prepare_identifier(dialect_.default_schema_name()) + + "." + dialect_.prepare_identifier(table) + + (as.empty() ? "" : " " + as)); + } query_.table_name = table; return *this; @@ -315,14 +342,15 @@ query_builder &query_builder::set(std::initializer_list key_valu return set(std::vector{key_values}); } -query_builder& query_builder::set(const std::vector &key_values) { +query_builder &query_builder::set(const std::vector &key_values) +{ transition_to(state_t::QUERY_SET); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::SET) + " "); std::string result; if (key_values.size() < 2) { - for (const auto &col : key_values) { + for (const auto &col: key_values) { result.append(dialect_.prepare_identifier(col.name()) + "="); auto var = col.value(); std::visit(value_to_string_, var); @@ -347,67 +375,76 @@ query_builder& query_builder::set(const std::vector &key_values) return *this; } -query_builder& query_builder::where(const basic_condition &cond) { +query_builder &query_builder::where(const basic_condition &cond) +{ transition_to(state_t::QUERY_WHERE); query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::WHERE) + " "); - query_parts_.emplace_back(cond.evaluate(const_cast(dialect_))); + query_parts_.emplace_back(cond.evaluate(const_cast(dialect_), query_)); return *this; } -query_builder& query_builder::order_by(const std::string& column) { - transition_to(state_t::QUERY_ORDER_BY); +query_builder &query_builder::order_by(const std::string &column) +{ + transition_to(state_t::QUERY_ORDER_BY); - query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::ORDER_BY) + " " + dialect_.prepare_identifier(column)); + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::ORDER_BY) + " " + dialect_.prepare_identifier(column)); - return *this; + return *this; } -query_builder& query_builder::group_by(const std::string& column) { - transition_to(state_t::QUERY_GROUP_BY); +query_builder &query_builder::group_by(const std::string &column) +{ + transition_to(state_t::QUERY_GROUP_BY); - query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::GROUP_BY) + " " + dialect_.prepare_identifier(column)); + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::GROUP_BY) + " " + dialect_.prepare_identifier(column)); - return *this; + return *this; } -query_builder& query_builder::asc() { - transition_to(state_t::QUERY_ORDER_DIRECTION); +query_builder &query_builder::asc() +{ + transition_to(state_t::QUERY_ORDER_DIRECTION); - query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::ASC)); + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::ASC)); - return *this; + return *this; } -query_builder& query_builder::desc() { - transition_to(state_t::QUERY_ORDER_DIRECTION); +query_builder &query_builder::desc() +{ + transition_to(state_t::QUERY_ORDER_DIRECTION); - query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::DESC)); + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::DESC)); - return *this; + return *this; } -query_builder& query_builder::offset( size_t count ) { - transition_to(state_t::QUERY_OFFSET); +query_builder &query_builder::offset(size_t count) +{ + transition_to(state_t::QUERY_OFFSET); - query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::OFFSET) + " " + std::to_string(count)); + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::OFFSET) + " " + std::to_string(count)); - return *this; + return *this; } -query_builder& query_builder::limit( size_t count ) { - transition_to(state_t::QUERY_LIMIT); +query_builder &query_builder::limit(size_t count) +{ + transition_to(state_t::QUERY_LIMIT); - query_parts_.emplace_back( " " + dialect_.token_at(dialect::token_t::LIMIT) + " " + std::to_string( count)); + query_parts_.emplace_back(" " + dialect_.token_at(dialect::token_t::LIMIT) + " " + std::to_string(count)); - return *this; + return *this; } -query query_builder::compile() { - for (const auto &part : query_parts_) { +query_context query_builder::compile() +{ + for (const auto &part: query_parts_) { query_.sql.append(part); } + query_.command_name = command_strings_[command_]; return query_; } @@ -427,22 +464,23 @@ void query_builder::initialize(query_builder::command_t cmd, query_builder::stat query_parts_.clear(); } -std::string build_create_column(const column &col, const dialect &d, column_context &context) { - std::string result = d.prepare_identifier(col.name()) + " " + d.data_type_at(col.type()); - if (col.attributes().size() > 0) { - result.append("(" + std::to_string(col.attributes().size()) +")"); - } - if (is_constraint_set(col.attributes().options(), utils::constraints::NOT_NULL)) { - result.append(" NOT NULL"); - } - if (is_constraint_set(col.attributes().options(), utils::constraints::PRIMARY_KEY)) { - context.primary_keys.emplace_back(col.name()); - } - if (is_constraint_set(col.attributes().options(), utils::constraints::FOREIGN_KEY)) { - context.foreign_contexts.push_back({col.name(), col.ref_table(), col.ref_column()}); - } +std::string build_create_column(const column &col, const dialect &d, column_context &context) +{ + std::string result = d.prepare_identifier(col.name()) + " " + d.data_type_at(col.type()); + if (col.attributes().size() > 0) { + result.append("(" + std::to_string(col.attributes().size()) + ")"); + } + if (is_constraint_set(col.attributes().options(), utils::constraints::NOT_NULL)) { + result.append(" NOT NULL"); + } + if (is_constraint_set(col.attributes().options(), utils::constraints::PRIMARY_KEY)) { + context.primary_keys.emplace_back(col.name()); + } + if (is_constraint_set(col.attributes().options(), utils::constraints::FOREIGN_KEY)) { + context.foreign_contexts.push_back({col.name(), col.ref_table(), col.ref_column()}); + } - return result; + return result; } column alias(const std::string &column, const std::string &as) @@ -450,7 +488,8 @@ column alias(const std::string &column, const std::string &as) return {column, as}; } -column alias(column &&col, const std::string &as) { +column alias(column &&col, const std::string &as) +{ col.alias(as); return std::move(col); } diff --git a/src/sql/query_intermediates.cpp b/src/sql/query_intermediates.cpp index 8a5b545..a964221 100644 --- a/src/sql/query_intermediates.cpp +++ b/src/sql/query_intermediates.cpp @@ -18,6 +18,11 @@ std::unique_ptr query_select_finish::fetch() return session_.fetch(builder_.compile().sql); } +statement query_select_finish::prepare() +{ + return session_.prepare(builder_.compile()); +} + query_intermediate::query_intermediate(session &db, query_builder &query) : session_(db), builder_(query) {} @@ -103,6 +108,11 @@ std::pair query_execute_finish::execute() return session_.execute(builder_.compile().sql); } +statement query_execute_finish::prepare() +{ + return session_.prepare(builder_.compile()); +} + query_execute_finish query_into_intermediate::values(std::initializer_list values) { return {session_, builder_.values(values)}; @@ -140,10 +150,10 @@ query_execute_where_intermediate query_set_intermediate::where(const basic_condi return {session_, builder_.where(cond)}; } -query_update_intermediate::query_update_intermediate(session &s, std::string table_name) +query_update_intermediate::query_update_intermediate(session &s, const std::string& table_name) : query_start_intermediate(s) { - builder_.update(std::move(table_name)); + builder_.update(table_name); } query_set_intermediate query_update_intermediate::set(std::initializer_list columns) diff --git a/src/sql/session.cpp b/src/sql/session.cpp index 832f121..9a9ff6d 100644 --- a/src/sql/session.cpp +++ b/src/sql/session.cpp @@ -40,7 +40,7 @@ query_delete_intermediate session::remove() return query_delete_intermediate{*this}; } -query_result session::fetch(const query &q) const +query_result session::fetch(const query_context &q) const { auto c = pool_.acquire(); if (!c.valid()) { @@ -73,6 +73,15 @@ std::pair session::execute(const std::string &sql) const { return c->execute(sql); } +statement session::prepare(query_context q) const +{ + auto c = pool_.acquire(); + if (!c.valid()) { + throw std::logic_error("no database connection available"); + } + return c->prepare(std::move(q)); +} + record session::describe_table(const std::string &table_name) const { auto c = pool_.acquire(); diff --git a/src/sql/statement.cpp b/src/sql/statement.cpp new file mode 100644 index 0000000..546b59d --- /dev/null +++ b/src/sql/statement.cpp @@ -0,0 +1,34 @@ +#include "matador/sql/statement.hpp" + +namespace matador::sql { + +statement::statement(std::unique_ptr impl, const utils::logger &logger) +: statement_(std::move(impl)) +, logger_(logger) +, object_binder_(statement_->binder()) +{} + +statement &statement::bind(size_t pos, std::string &val, size_t size) +{ + statement_->bind(pos, val, size); + return *this; +} + +size_t statement::execute() +{ + logger_.info(statement_->query_.sql); + return statement_->execute(); +} + +query_result statement::fetch() +{ + logger_.info(statement_->query_.sql); + return query_result(statement_->fetch()); +} + +void statement::reset() +{ + statement_->reset(); +} + +} \ No newline at end of file diff --git a/src/sql/statement_cache.cpp b/src/sql/statement_cache.cpp new file mode 100644 index 0000000..1a191e5 --- /dev/null +++ b/src/sql/statement_cache.cpp @@ -0,0 +1,17 @@ +#include "matador/sql/statement_cache.hpp" + +namespace matador::sql { +statement &statement_cache::acquire(const std::string &stmt, const connection &conn) { + std::lock_guard guard(mutex_); + auto key = hash_(stmt); + auto it = statement_map_.find(key); + if (it == statement_map_.end()) { + + } + return *it->second.statement_; +} + +void statement_cache::release(const statement &stmt) { + +} +} \ No newline at end of file diff --git a/src/sql/statement_impl.cpp b/src/sql/statement_impl.cpp new file mode 100644 index 0000000..b23c496 --- /dev/null +++ b/src/sql/statement_impl.cpp @@ -0,0 +1,9 @@ +#include "matador/sql/statement_impl.hpp" + +namespace matador::sql { + +statement_impl::statement_impl(query_context query) +: query_(std::move(query)) +{} + +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 582a676..e1b6c60 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,9 @@ add_executable(tests QueryBuilderTest.cpp models/person.hpp AnyTypeToVisitorTest.cpp ColumnTest.cpp - SessionRecordTest.cpp) + SessionRecordTest.cpp + StatementCacheTest.cpp + StatementTest.cpp) target_link_libraries(tests PRIVATE Catch2::Catch2WithMain matador diff --git a/test/QueryBuilderTest.cpp b/test/QueryBuilderTest.cpp index bfa77e1..6c59c9a 100644 --- a/test/QueryBuilderTest.cpp +++ b/test/QueryBuilderTest.cpp @@ -2,13 +2,13 @@ #include #include -#include +#include #include using namespace matador::sql; TEST_CASE("Create table sql statement string", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); auto q = query.create().table("person", { make_pk_column("id"), @@ -31,7 +31,7 @@ TEST_CASE("Create table sql statement string", "[query]") { } TEST_CASE("Drop table sql statement string", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.drop().table("person").compile(); @@ -40,7 +40,7 @@ TEST_CASE("Drop table sql statement string", "[query]") { } TEST_CASE("Select sql statement string", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.select({"id", "name", "age"}).from("person").compile(); @@ -49,7 +49,7 @@ TEST_CASE("Select sql statement string", "[query]") { } TEST_CASE("Insert sql statement string", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.insert().into("person", { "id", "name", "age" @@ -60,7 +60,7 @@ TEST_CASE("Insert sql statement string", "[query]") { } TEST_CASE("Update sql statement string", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.update("person").set({ {"id", 7UL}, @@ -73,7 +73,7 @@ TEST_CASE("Update sql statement string", "[query]") { } TEST_CASE("Delete sql statement string", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.remove().from("person").compile(); @@ -82,7 +82,7 @@ TEST_CASE("Delete sql statement string", "[query]") { } TEST_CASE("Select sql statement string with where clause", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); auto q = query.select({"id", "name", "age"}) .from("person") @@ -101,8 +101,20 @@ TEST_CASE("Select sql statement string with where clause", "[query]") { REQUIRE(q.table_name == "person"); } +TEST_CASE("Insert sql statement with placeholder", "[query]") { + dialect d = dialect_builder::builder().create().build(); + query_builder query(d); + const auto q = query.insert().into("person", { + "id", "name", "age" + }).values({_, _, _}).compile(); + + REQUIRE(q.sql == R"(INSERT INTO "person" ("id", "name", "age") VALUES (?, ?, ?))"); + REQUIRE(q.table_name == "person"); + REQUIRE(q.bind_vars.size() == 3); +} + TEST_CASE("Select sql statement string with order by", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.select({"id", "name", "age"}) .from("person") @@ -114,7 +126,7 @@ TEST_CASE("Select sql statement string with order by", "[query]") { } TEST_CASE("Select sql statement string with group by", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.select({"id", "name", "age"}) .from("person") @@ -126,7 +138,7 @@ TEST_CASE("Select sql statement string with group by", "[query]") { } TEST_CASE("Select sql statement string with offset and limit", "[query]") { - dialect d; + dialect d = dialect_builder::builder().create().build(); query_builder query(d); const auto q = query.select({"id", "name", "age"}) .from("person") diff --git a/test/SessionRecordTest.cpp b/test/SessionRecordTest.cpp index e84a57a..137cbe4 100644 --- a/test/SessionRecordTest.cpp +++ b/test/SessionRecordTest.cpp @@ -365,7 +365,7 @@ TEST_CASE("Execute delete statement", "[session record]") { .where("id"_col == 1) .execute(); - REQUIRE(res.second == R"(DELETE FROM "person" WHERE "id" = 1)"); + REQUIRE(res.second == R"(DELETE FROM "main"."person" WHERE "id" = 1)"); REQUIRE(res.first == 1); count = s.select({count_all()}).from("person").fetch_value(); diff --git a/test/StatementCacheTest.cpp b/test/StatementCacheTest.cpp new file mode 100644 index 0000000..3c3cd4e --- /dev/null +++ b/test/StatementCacheTest.cpp @@ -0,0 +1,31 @@ +#include + +#include "matador/sql/connection_info.hpp" +#include "matador/sql/connection_pool.hpp" +#include "matador/sql/session.hpp" +#include "matador/sql/statement_cache.hpp" + +using namespace matador; + +class TestConnection +{ +public: + explicit TestConnection(sql::connection_info info) + : info_(std::move(info)) {} + void open() {} + +private: + sql::connection_info info_; +}; + +TEST_CASE("Acquire prepared statement", "[statement cache]") { + sql::statement_cache cache; + sql::connection_pool pool("sqlite://sqlite.db", 4); + +// sql::session s(pool); +// auto conn = pool.acquire(); + + std::string sql = R"(SELECT * FROM person WHERE name = 'george')"; +// auto stmt = cache.acquire(sql, conn); + +} \ No newline at end of file diff --git a/test/StatementTest.cpp b/test/StatementTest.cpp new file mode 100644 index 0000000..34c06ab --- /dev/null +++ b/test/StatementTest.cpp @@ -0,0 +1,119 @@ +#include +#include + +#include "matador/sql/column.hpp" +#include "matador/sql/condition.hpp" +#include "matador/sql/connection_info.hpp" +#include "matador/sql/connection_pool.hpp" +#include "matador/sql/session.hpp" + +#include "models/airplane.hpp" + +using namespace matador::sql; +using namespace matador::test; + +struct Postgres +{ +// constexpr static const char *dns{"postgres://test:test123@127.0.0.1:15432/test"}; + constexpr static const char *dns{"postgres://test:test123@127.0.0.1:5432/matador_test"}; +}; + +struct Sqlite +{ + constexpr static const char *dns{"sqlite://sqlite.db"}; +}; + +template +class StatementTestFixture +{ +public: + StatementTestFixture() + : pool_(Type::dns, 4), session_(pool_) + { + auto res = session_.create() + .table("airplane") + .execute(); + REQUIRE(res.first == 0); + REQUIRE(res.second == R"(CREATE TABLE "airplane" ("id" BIGINT, "brand" VARCHAR(255), "model" VARCHAR(255), CONSTRAINT PK_airplane PRIMARY KEY (id)))"); + } + + ~StatementTestFixture() + { + session_.drop().table("airplane").execute(); + } + + matador::sql::session &session() + { return session_; } + + std::vector> &planes() + { return planes_; } + +private: + matador::sql::connection_pool pool_; + matador::sql::session session_; + + std::vector> planes_{ + make_entity(1, "Airbus", "A380"), + make_entity(2, "Boeing", "707"), + make_entity(3, "Boeing", "747") + }; +}; + +TEMPLATE_TEST_CASE_METHOD(StatementTestFixture, "Create prepared statement", "[statement]", Sqlite, Postgres) +{ + auto &s = StatementTestFixture::session(); + auto &planes = StatementTestFixture::planes(); + + SECTION("Insert with prepared statement and placeholder") { + auto stmt = s.insert() + .template into("airplane") + .template values().prepare(); + + for (const auto &plane: planes) { + auto res = stmt.bind(*plane).execute(); + REQUIRE(res == 1); + stmt.reset(); + } + + auto result = s.template select().from("airplane").template fetch_all(); + + size_t index{0}; + for (const auto &i: result) { + REQUIRE(i.id == planes[index]->id); + REQUIRE(i.brand == planes[index]->brand); + REQUIRE(i.model == planes[index++]->model); + } + } + + SECTION("Select with prepared statement") { + for (const auto &plane: planes) { + auto res = s.insert().template into("airplane").values(*plane).execute(); + REQUIRE(res.first == 1); + } + + auto stmt = s.template select().from("airplane").where("brand"_col == _).prepare(); + + stmt.bind(0, "Airbus"); + + auto result = stmt.template fetch(); + + for (const auto &i: result) { + REQUIRE(i.id == planes[0]->id); + REQUIRE(i.brand == planes[0]->brand); + REQUIRE(i.model == planes[0]->model); + } + + stmt.reset(); + + stmt.bind(0, "Boeing"); + + result = stmt.template fetch(); + + size_t index{1}; + for (const auto &i: result) { + REQUIRE(i.id == planes[index]->id); + REQUIRE(i.brand == planes[index]->brand); + REQUIRE(i.model == planes[index++]->model); + } + } +} \ No newline at end of file