implemented prepared statement for postgres and sqlite

This commit is contained in:
Sascha Kuehl 2023-12-09 11:08:03 +01:00
parent 8ba70cc79e
commit da423ea8bb
59 changed files with 1769 additions and 314 deletions

View File

@ -1,6 +1,6 @@
# query
# query_context
A fluent sql query builder
A fluent sql query_context builder
Object definition
```cpp

View File

@ -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"
)
target_link_libraries(${LIBRARY_TARGET} matador ${PostgreSQL_LIBRARIES})

View File

@ -14,6 +14,8 @@
#include "matador/sql/connection_impl.hpp"
#include <unordered_map>
#include <libpq-fe.h>
namespace matador::backends::postgres {
@ -27,7 +29,7 @@ public:
bool is_open() override;
std::unique_ptr<sql::query_result_impl> fetch(const std::string &stmt) override;
void prepare(const std::string &stmt) override;
std::unique_ptr<sql::statement_impl> 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<std::string, unsigned long>;
static string_to_int_map statement_name_map_;
};
}

View File

@ -0,0 +1,42 @@
#ifndef QUERY_POSTGRES_PARAMETER_BINDER_H
#define QUERY_POSTGRES_PARAMETER_BINDER_H
#include "matador/sql/parameter_binder.hpp"
#include <vector>
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<const char*>& params() const;
private:
std::vector<std::string> strings_;
std::vector<const char*> params_;
};
}
#endif //QUERY_POSTGRES_PARAMETER_BINDER_H

View File

@ -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 <libpq-fe.h>
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<sql::query_result_impl> 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

View File

@ -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 <iostream>
#include <sstream>
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<sql::query_result_impl> 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<sql::query_result_impl>(std::make_unique<postgres_result_reader>(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<sql::statement_impl> 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<int>(context.bind_vars.size()), nullptr);
throw_postgres_error(result, conn_, "postgres", context.sql);
return std::make_unique<postgres_statement>(conn_, result, statement_name, std::move(context));
}
size_t postgres_connection::execute(const std::string &stmt)

View File

@ -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;
}

View File

@ -0,0 +1,142 @@
#include "postgres_parameter_binder.h"
namespace matador::backends::postgres {
namespace detail {
template < class T >
void bind_value(std::vector<std::string> &strings, std::vector<const char*> &params, size_t index, T &x)
{
strings[index] = std::to_string(x);
params[index] = strings[index].c_str();
}
template <>
void bind_value(std::vector<std::string> &strings, std::vector<const char*> &params, size_t index, char &x)
{
strings[index] = std::to_string(x);
params[index] = strings[index].data();
}
template <>
void bind_value(std::vector<std::string> &strings, std::vector<const char*> &params, size_t index, unsigned char &x)
{
strings[index] = std::to_string(x);
params[index] = strings[index].data();
}
//template <>
//void bind_value(std::vector<std::string> &strings, std::vector<const char*> &params, 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<std::string> &strings, std::vector<const char*> &params, 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<const char *> &postgres_parameter_binder::params() const
{
return params_;
}
}

View File

@ -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<int>(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<sql::query_result_impl> postgres_statement::fetch()
{
PGresult *res = PQexecPrepared(db_, name_.c_str(), static_cast<int>(binder_.params().size()), binder_.params().data(), nullptr, nullptr, 0);
throw_postgres_error(res, db_, "postgres", query_.sql);
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<postgres_result_reader>(res), std::move(query_.prototype)));
}
void postgres_statement::reset() {}
sql::parameter_binder& postgres_statement::binder()
{
return binder_;
}
}

View File

@ -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"
)
target_link_libraries(${LIBRARY_TARGET} matador ${SQLite3_LIBRARIES})

View File

@ -29,7 +29,7 @@ public:
bool is_open() override;
std::unique_ptr<sql::query_result_impl> fetch(const std::string &stmt) override;
void prepare(const std::string &stmt) override;
std::unique_ptr<sql::statement_impl> 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_{};
};
}

View File

@ -0,0 +1,44 @@
#ifndef QUERY_SQLITE_PARAMETER_BINDER_H
#define QUERY_SQLITE_PARAMETER_BINDER_H
#include "matador/sql/parameter_binder.hpp"
#include <sqlite3.h>
#include <memory>
#include <vector>
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<std::shared_ptr<std::string> > host_strings_;
};
}
#endif //QUERY_SQLITE_PARAMETER_BINDER_H

View File

@ -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 <sqlite3.h>
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

View File

@ -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<char*>;

View File

@ -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<sql::query_result_impl> 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

View File

@ -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<sql::query_result_impl> sqlite_connection::fetch(const std::string &stmt)
@ -99,8 +100,13 @@ std::unique_ptr<sql::query_result_impl> sqlite_connection::fetch(const std::stri
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<sqlite_result_reader>(std::move(context.rows), context.prototype.size()), std::move(context.prototype)));
}
void sqlite_connection::prepare(const std::string &stmt)
std::unique_ptr<sql::statement_impl> sqlite_connection::prepare(sql::query_context query)
{
sqlite3_stmt *stmt{};
int ret = sqlite3_prepare_v2(db_, query.sql.c_str(), static_cast<int>(query.sql.size()), &stmt, nullptr);
throw_sqlite_error(ret, db_, "sqlite3_prepare_v2", query.sql);
return std::make_unique<sqlite_statement>(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;

View File

@ -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;
}

View File

@ -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());

View File

@ -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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++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<int>(++pos), str, static_cast<int>(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<int>(++pos), x, static_cast<int>(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<int>(++pos), str.c_str(), static_cast<int>(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<int>(++pos), x.data(), static_cast<int>(size), nullptr);
throw_sqlite_error(ret, db_, "sqlite3_bind_text");
}
}

View File

@ -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<const char*>(sqlite3_column_text(stmt_, static_cast<int>(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<char>(sqlite3_column_int(stmt_, static_cast<int>(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);
}
}

View File

@ -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<sql::query_result_impl> sqlite_statement::fetch()
{
int ret = sqlite3_reset(stmt_);
throw_sqlite_error(ret, db_, "sqlite3_reset");
auto reader = std::make_unique<sqlite_prepared_result_reader>(db_, stmt_);
return std::move(std::make_unique<sql::query_result_impl>(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_;
}
}

View File

@ -1,6 +1,8 @@
#ifndef QUERY_ANY_TYPE_HPP
#define QUERY_ANY_TYPE_HPP
#include "matador/sql/placeholder.hpp"
#include <string>
#include <variant>
@ -13,6 +15,7 @@ using any_type = std::variant<
bool,
const char*,
std::string,
placeholder,
nullptr_t>;
}

View File

@ -10,6 +10,8 @@
namespace matador::sql {
struct placeholder;
template < typename DestType, typename SourceType >
void convert(DestType &dest, SourceType source, typename std::enable_if<std::is_same<DestType, SourceType>::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{};
};

View File

@ -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<operand_t, std::string> operands;
};

View File

@ -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 <memory>
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 L, class R, class Enabled = void>
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<class T>
@ -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<column, query> : public basic_column_condition
class condition<column, query_context> : 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<column, std::initializer_list<V>> in(const column &col, std::initializ
* @param q The query to be executes as sub select
* @return The condition object
*/
condition<column, query> in(const column &col, query &&q);
condition<column, query_context> in(const column &col, query_context &&q);
/**
* @brief Creates a between condition.
@ -456,7 +451,7 @@ condition<column, T> operator==(const column &col, T val)
* @param q The query to compare with
* @return The condition object representing the equality operation
*/
condition<column, query> equals(const column &col, query &q);
condition<column, query_context> equals(const column &col, query_context &q);
/**
* @brief Condition inequality operator for a column and a value

View File

@ -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<query_result_impl> fetch(const std::string &sql) const;
[[nodiscard]] std::pair<size_t, std::string> execute(const std::string &sql) const;
statement prepare(query_context &&query) const;
private:
connection_info connection_info_;
std::unique_ptr<connection_impl> connection_;

View File

@ -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 <memory>
@ -22,7 +24,7 @@ public:
virtual size_t execute(const std::string &stmt) = 0;
virtual std::unique_ptr<query_result_impl> fetch(const std::string &stmt) = 0;
virtual void prepare(const std::string &stmt) = 0;
virtual std::unique_ptr<statement_impl> prepare(query_context context) = 0;
virtual record describe(const std::string &table) = 0;
virtual bool exists(const std::string &table_name) = 0;

View File

@ -4,6 +4,7 @@
#include "matador/sql/types.hpp"
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
@ -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<data_type_t, std::string>;
using sql_func_to_string_map = std::unordered_map<sql_function_t, std::string>;
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<std::string(size_t)>;
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<std::string> &bind_vars) const;
void add_host_var(const std::string &host_var);
void add_column(const std::string &column);
const std::vector<std::string>& host_vars() const;
const std::vector<std::string>& columns() const;
/**
* Returns the default schema name.
*
* @return The default schema name.
*/
[[nodiscard]] std::string default_schema_name() const;
private:
std::vector<std::string> host_vars_;
std::vector<std::string> 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"},

View File

@ -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

View File

@ -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 <string>
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<std::is_integral<V>::value && !std::is_same<bool, V>::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<typename Type>
void on_attribute(const char *id, Type &val, const utils::field_attributes &/*attr*/ = utils::null_attributes)
{
binder_.bind(index_++, val);
}
template<class Type, template < class ... > class Pointer>
void on_belongs_to(const char *id, Pointer<Type> &x, utils::cascade_type)
{
// binder_.bind(index_++, val);
}
template<class Type, template < class ... > class Pointer>
void on_has_one(const char *id, Pointer<Type> &x, utils::cascade_type)
{
// binder_.bind(index_++, val);
}
template<class ContainerType>
void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {}
template<class ContainerType>
void on_has_many(const char *, ContainerType &, utils::cascade_type) {}
private:
parameter_binder &binder_;
size_t index_{0};
};
}
#endif //QUERY_OBJECT_BINDER_HPP

View File

@ -0,0 +1,37 @@
#ifndef QUERY_PARAMETER_BINDER_HPP
#define QUERY_PARAMETER_BINDER_HPP
#include <string>
#include <cstring>
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

View File

@ -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

View File

@ -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 <vector>
namespace matador::sql {
class placeholder_generator
{
public:
template < class V >
void on_primary_key(const char *id, V &val, typename std::enable_if<std::is_integral<V>::value && !std::is_same<bool, V>::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<typename Type>
void on_attribute(const char *id, Type &val, const utils::field_attributes &/*attr*/ = utils::null_attributes)
{
placeholder_values.emplace_back(_);
}
template<class Type, template < class ... > class Pointer>
void on_belongs_to(const char *id, Pointer<Type> &x, utils::cascade_type)
{
placeholder_values.emplace_back(_);
}
template<class Type, template < class ... > class Pointer>
void on_has_one(const char *id, Pointer<Type> &x, utils::cascade_type)
{
placeholder_values.emplace_back(_);
}
template<class ContainerType>
void on_has_many(const char *, ContainerType &, const char *, const char *, utils::cascade_type) {}
template<class ContainerType>
void on_has_many(const char *, ContainerType &, utils::cascade_type) {}
std::vector<any_type> placeholder_values;
};
}
#endif //QUERY_PLACEHOLDER_GENERATOR_HPP

View File

@ -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 <string>
@ -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<typename Type>
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<any_type> 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<state_t>;
using query_state_transition_map = std::unordered_map<state_t, query_state_set>;

View File

@ -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<any_type> host_vars;
std::vector<std::string> bind_vars;
};
}
#endif //QUERY_QUERY_CONTEXT_HPP

View File

@ -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<size_t, std::string> execute();
statement prepare();
};
class query_select_finish : public query_intermediate
@ -57,6 +60,8 @@ public:
return fetch_one().at(0).as<Type>();
}
statement prepare();
private:
std::unique_ptr<query_result_impl> fetch();
};
@ -140,6 +145,15 @@ public:
query_from_intermediate from(const std::string &table, const std::string &as = "");
};
template < class Type >
std::vector<any_type> 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<any_type> values);
template<class Type>
query_execute_finish values()
{
Type obj;
return {session_, builder_.values(as_placeholder(obj))};
}
template<class Type>
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<key_value_pair> columns);
template<class Type>

View File

@ -1,14 +0,0 @@
#ifndef QUERY_RESULT_HPP
#define QUERY_RESULT_HPP
#include <string>
namespace matador::sql {
struct result
{
std::string sql;
};
}
#endif //QUERY_RESULT_HPP

View File

@ -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 <unordered_map>
@ -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<Type>(table_repository_)};
}
query_select_intermediate select();
query_select_intermediate select(std::initializer_list<column> columns);
query_insert_intermediate insert();
query_update_intermediate update(const std::string &table);
query_delete_intermediate remove();
[[nodiscard]] query_result<record> fetch(const query &q) const;
[[nodiscard]] query_result<record> fetch(const query_context &q) const;
// [[nodiscard]] query_result<record> fetch(const std::string &sql) const;
[[nodiscard]] std::pair<size_t, std::string> 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<std::string, record> prototypes_;
};
template<class Type>
query_select_intermediate session::select()
{
return query_select_intermediate{*this, column_generator::generate<Type>(table_repository_)};
}
}
#endif //QUERY_SESSION_HPP

View File

@ -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 <memory>
namespace matador::sql {
class statement
{
public:
explicit statement(std::unique_ptr<statement_impl> 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<class Type>
query_result<Type> fetch()
{
logger_.info(statement_->query_.sql);
return query_result<Type>(statement_->fetch());
}
query_result<record> fetch();
void reset();
private:
std::unique_ptr<statement_impl> statement_;
const utils::logger &logger_;
object_binder object_binder_;
};
}
#endif //QUERY_STATEMENT_HPP

View File

@ -0,0 +1,36 @@
#ifndef QUERY_STATEMENT_CACHE_HPP
#define QUERY_STATEMENT_CACHE_HPP
#include "matador/sql/statement.hpp"
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
namespace matador::sql {
class connection;
struct cache_info
{
std::unique_ptr<statement> 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<std::string> hash_;
using statement_map = std::unordered_map<size_t, cache_info>;
statement_map statement_map_;
};
}
#endif //QUERY_STATEMENT_CACHE_HPP

View File

@ -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 <memory>
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<query_result_impl> 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

View File

@ -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

View File

@ -3,21 +3,21 @@
namespace matador::sql {
condition<column, placeholder, std::enable_if<true>::type>::condition(const column &fld, basic_condition::operand_t op, const placeholder &val)
: basic_column_condition(fld, op)
, value(val)
{ }
std::string condition<column, placeholder, std::enable_if<true>::type>::evaluate(dialect &d) const {
d.add_host_var(field_.name());
return d.prepare_identifier(field_.name()) + " " + operand + " " + d.next_placeholder();
}
condition<column, query>::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<column, query>::evaluate(dialect &d) const {
std::string condition<column, placeholder, std::enable_if<true>::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<column, query_context>::condition(column col, basic_condition::operand_t op, query_context &q)
: basic_column_condition(std::move(col), op), query_(q)
{}
std::string condition<column, query_context>::evaluate(dialect &d, query_context &query) const
{
std::string result(d.prepare_identifier(field_.name()) + " " + operand + " (");
result += (")");
return result;

View File

@ -88,4 +88,9 @@ std::unique_ptr<query_result_impl> 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_);
}
}

View File

@ -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<std::string> &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<std::string> &dialect::host_vars() const
{
return host_vars_;
}
const std::vector<std::string> &dialect::columns() const
{
return columns_;
return default_schema_name_;
}
}

View File

@ -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_;
}
}

24
src/sql/object_binder.cpp Normal file
View File

@ -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);
}
}

View File

@ -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(_);
}
}

View File

@ -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<column> columns)
query_builder &query_builder::select(std::initializer_list<column> columns)
{
return select(std::vector<column>{columns});
}
@ -112,7 +123,7 @@ query_builder &query_builder::select(const std::vector<column> &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<column> &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<column> columns)
query_builder &query_builder::table(const std::string &table, std::initializer_list<column> columns)
{
return this->table(table, std::vector<column>{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<column> &columns) {
query_builder &query_builder::table(const std::string &table, const std::vector<column> &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<std::string> column_names)
query_builder &query_builder::into(const std::string &table, std::initializer_list<std::string> column_names)
{
return into(table, std::vector<std::string>{column_names});
}
@ -237,7 +253,7 @@ query_builder &query_builder::into(const std::string &table, const std::vector<s
std::string result{"("};
if (column_names.size() < 2) {
for (const auto &col : column_names) {
for (const auto &col: column_names) {
result.append(dialect_.prepare_identifier(col));
}
} else {
@ -254,19 +270,20 @@ query_builder &query_builder::into(const std::string &table, const std::vector<s
return *this;
}
query_builder& query_builder::values(std::initializer_list<any_type> values)
query_builder &query_builder::values(std::initializer_list<any_type> values)
{
return this->values(std::vector<any_type>{values});
}
query_builder& query_builder::values(const std::vector<any_type> &values) {
query_builder &query_builder::values(const std::vector<any_type> &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<any_type> &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_value_pair> key_valu
return set(std::vector<key_value_pair>{key_values});
}
query_builder& query_builder::set(const std::vector<key_value_pair> &key_values) {
query_builder &query_builder::set(const std::vector<key_value_pair> &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_value_pair> &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 &>(dialect_)));
query_parts_.emplace_back(cond.evaluate(const_cast<dialect &>(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);
}

View File

@ -18,6 +18,11 @@ std::unique_ptr<query_result_impl> 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<size_t, std::string> 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<any_type> 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<key_value_pair> columns)

View File

@ -40,7 +40,7 @@ query_delete_intermediate session::remove()
return query_delete_intermediate{*this};
}
query_result<record> session::fetch(const query &q) const
query_result<record> session::fetch(const query_context &q) const
{
auto c = pool_.acquire();
if (!c.valid()) {
@ -73,6 +73,15 @@ std::pair<size_t, std::string> 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();

34
src/sql/statement.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "matador/sql/statement.hpp"
namespace matador::sql {
statement::statement(std::unique_ptr<statement_impl> 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<record> statement::fetch()
{
logger_.info(statement_->query_.sql);
return query_result<record>(statement_->fetch());
}
void statement::reset()
{
statement_->reset();
}
}

View File

@ -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<std::mutex> 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) {
}
}

View File

@ -0,0 +1,9 @@
#include "matador/sql/statement_impl.hpp"
namespace matador::sql {
statement_impl::statement_impl(query_context query)
: query_(std::move(query))
{}
}

View File

@ -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

View File

@ -2,13 +2,13 @@
#include <matador/sql/column.hpp>
#include <matador/sql/condition.hpp>
#include <matador/sql/dialect.hpp>
#include <matador/sql/dialect_builder.hpp>
#include <matador/sql/query_builder.hpp>
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<unsigned long>("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")

View File

@ -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<int>();

View File

@ -0,0 +1,31 @@
#include <catch2/catch_test_macros.hpp>
#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<TestConnection> 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);
}

119
test/StatementTest.cpp Normal file
View File

@ -0,0 +1,119 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#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 Type>
class StatementTestFixture
{
public:
StatementTestFixture()
: pool_(Type::dns, 4), session_(pool_)
{
auto res = session_.create()
.table<airplane>("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<entity<airplane>> &planes()
{ return planes_; }
private:
matador::sql::connection_pool<matador::sql::connection> pool_;
matador::sql::session session_;
std::vector<entity<airplane>> planes_{
make_entity<airplane>(1, "Airbus", "A380"),
make_entity<airplane>(2, "Boeing", "707"),
make_entity<airplane>(3, "Boeing", "747")
};
};
TEMPLATE_TEST_CASE_METHOD(StatementTestFixture, "Create prepared statement", "[statement]", Sqlite, Postgres)
{
auto &s = StatementTestFixture<TestType>::session();
auto &planes = StatementTestFixture<TestType>::planes();
SECTION("Insert with prepared statement and placeholder") {
auto stmt = s.insert()
.template into<airplane>("airplane")
.template values<airplane>().prepare();
for (const auto &plane: planes) {
auto res = stmt.bind(*plane).execute();
REQUIRE(res == 1);
stmt.reset();
}
auto result = s.template select<airplane>().from("airplane").template fetch_all<airplane>();
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>("airplane").values(*plane).execute();
REQUIRE(res.first == 1);
}
auto stmt = s.template select<airplane>().from("airplane").where("brand"_col == _).prepare();
stmt.bind(0, "Airbus");
auto result = stmt.template fetch<airplane>();
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<airplane>();
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);
}
}
}