TeaSpeakLibrary/src/sql/SqlQuery.h

430 lines
18 KiB
C++

#pragma once
#include <string>
#include <sstream>
#include <cstring>
#include <sqlite3.h>
#include <functional>
#include <utility>
#include <ThreadPool/ThreadPool.h>
#include <ThreadPool/Future.h>
#include "../Variable.h"
#include <misc/memtracker.h>
#include <misc/lambda.h>
#define ALLOW_STACK_ALLOCATION
#define LOG_SQL_CMD [](const sql::result &res){ if(!res) logCritical(LOG_GENERAL, "Failed to execute sql command: " + std::to_string(res.code()) + "/" + res.msg() + " (" __FILE__ + ":" + std::to_string(__LINE__) + ")"); }
namespace sql {
class result;
class SqlManager;
class AsyncSqlPool;
class command;
class model;
namespace impl {
template <typename SelfType> class command_base;
}
inline std::ostream& operator<<(std::ostream& s,const result& res);
inline std::ostream& operator<<(std::ostream& s,const result* res);
using QueryCallback = std::function<int(int, std::string*, std::string*)>;
class result {
public:
static result success;
result() : result(success) { }
result(std::string query, int code, std::string msg) : _code(code), _msg(std::move(msg)), _sql(std::move(query)) { }
result(int code, const std::string &msg) : _code(code), _msg(std::move(msg)) { }
result(const result& ref) : _code(ref._code), _msg(ref._msg), _sql(ref._sql) { }
result(result&& ref) : _code(ref._code), _msg(std::move(ref._msg)), _sql(std::move(ref._sql)) { }
virtual ~result() { };
int code() const { return _code; }
std::string msg() const { return _msg; }
std::string sql() const { return _sql; }
//Returns true on success
operator bool() const { return _code == 0; }
result&operator=(const result& other) {
this->_code = other._code;
this->_msg = other._msg;
this->_sql = other._sql;
return *this;
}
std::string fmtStr() const {
std::stringstream s;
operator<<(s, *this);
return s.str();
}
private:
int _code = 0;
std::string _msg{};
std::string _sql{};
};
enum SqlType {
TYPE_SQLITE,
TYPE_MYSQL
};
class CommandData {
public:
CommandData() = default;
~CommandData() = default;
SqlManager* handle = nullptr;
template <typename H = SqlManager>
inline H* sqlHandle() { return dynamic_cast<H*>(handle); }
std::string sql_command; //variable :<varname>
std::vector<variable> variables{};
threads::Mutex lock;
};
class SqlManager {
template <typename SelfType> friend class impl::command_base;
friend class command;
friend class model;
public:
explicit SqlManager(SqlType);
virtual ~SqlManager();
virtual result connect(const std::string&) = 0;
virtual bool connected() = 0;
virtual result disconnect() = 0;
AsyncSqlPool* pool;
SqlType getType(){ return this->type; }
protected:
virtual std::shared_ptr<CommandData> allocateCommandData() = 0;
virtual std::shared_ptr<CommandData> copyCommandData(std::shared_ptr<CommandData>) = 0;
virtual result executeCommand(std::shared_ptr<CommandData>) = 0;
virtual result queryCommand(std::shared_ptr<CommandData>, const QueryCallback& fn) = 0;
private:
SqlType type;
};
#define SQL_FWD(...) ::std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)
namespace impl {
template <typename ...>
struct merge;
template <typename... A, typename... B>
struct merge<std::tuple<A...>, std::tuple<B...>> {
using result = std::tuple<A..., B...>;
};
/* let me stuff reverse */
template<typename, typename>
struct append_reversed { };
template<typename T, typename... Ts>
struct append_reversed<T, std::tuple<Ts...>> {
using type = std::tuple<Ts..., T>;
};
template<typename... Ts>
struct reverse_types {
using type = std::tuple<>;
};
template<typename T, typename... Ts>
struct reverse_types<T, Ts...> {
using type = typename append_reversed<T, typename reverse_types<Ts...>::type>::type;
};
/* Return type stuff */
typedef int stardart_return_type;
template <typename... args>
using stardart_return = std::function<int(args...)>;
template <typename type_return, typename enabled = void>
struct transformer_return {
using supported = std::false_type;
};
/* transforming (no transform) from int(...) to int(...) */
template <typename return_type>
struct transformer_return<return_type, typename std::enable_if<std::is_same<return_type, int>::value>::type> {
using supported = std::true_type;
template <typename... args>
static stardart_return<args...> transform(const std::function<return_type(args...)>& function) {
return function;
}
};
/* transforming from void(...) to int(...) */
template <typename return_type>
struct transformer_return<return_type, typename std::enable_if<std::is_void<return_type>::value>::type> {
using supported = std::true_type;
template <typename... args>
static stardart_return<args...> transform(const std::function<return_type(args...)>& function) {
return [function](args... parms) -> int {
function(parms...);
return 0;
};
}
};
/* transforming from ~integral~(...) to int(...) */
template <typename return_type>
struct transformer_return<return_type, typename std::enable_if<std::is_integral<return_type>::value && !std::is_same<return_type, int>::value>::type> {
using supported = std::true_type;
template <typename... args>
static stardart_return<args...> transform(const std::function<return_type(args...)>& function) {
return [function](args... parms) -> int {
return (int) function(parms...);
};
}
};
/* method stuff */
using stardart_function = std::function<stardart_return_type(int, std::string*, std::string*)>;
template <typename, typename>
struct transformer_arguments {
using supported = std::false_type;
using arguments_reversed = std::tuple<>;
static constexpr int argument_count = 0;
};
/* we don't need to proxy a standard function */
template <>
struct transformer_arguments<std::tuple<std::string*, std::string*, int>, std::tuple<>> {
using supported = std::true_type;
using arguments_reversed = std::tuple<>;
static constexpr int argument_count = 0;
typedef std::function<stardart_return_type(int, std::string*, std::string*)> typed_function;
static stardart_function transform(const typed_function& function) {
return function;
}
};
/* proxy a standard function with left sided arguments */
template <typename... additional_reversed, typename... additional>
struct transformer_arguments<std::tuple<std::string*, std::string*, int, additional_reversed...>, std::tuple<additional...>> {
using supported = std::true_type;
using arguments_reversed = std::tuple<typename std::remove_const<additional_reversed>::type...>; /* note: these are still reversed */
static constexpr int argument_count = sizeof...(additional_reversed);
typedef std::function<stardart_return_type(additional..., int, std::string*, std::string*)> typed_function;
static stardart_function transform(const typed_function& function, additional&&... args) {
return [function, &args...](int length, std::string* values, std::string* names) {
return function(args..., length, values, names);
};
}
};
/* proxy int(..., int, char**, char**) function with left sided arguments */
template <typename... additional_reversed, typename... additional>
struct transformer_arguments<std::tuple<char**, char**, int, additional_reversed...>, std::tuple<additional...>> {
using supported = std::true_type;
using arguments_reversed = std::tuple<typename std::remove_const<additional_reversed>::type...>;
static constexpr int argument_count = sizeof...(additional);
typedef std::function<stardart_return_type(additional..., int, char**, char**)> typed_function;
static stardart_function transform(const typed_function& function, additional&&... args) {
return [function, &args...](int length, std::string* values, std::string* names) {
#ifdef ALLOW_STACK_ALLOCATION
char* array_values[length];
char* array_names[length];
#else
char** array_values = (char**) malloc(length * sizeof(char*));
char** array_names = (char**) malloc(length * sizeof(char*));
#endif
for(int i = 0; i < length; i++) {
array_values[i] = (char*) values[i].c_str();
array_names[i] = (char*) names[i].c_str();
}
auto result = function(args..., length, array_values, array_names);
#ifndef ALLOW_STACK_ALLOCATION
free(array_values);
free(array_names);
#endif
return result;
};
}
};
template <typename SelfType>
class command_base {
friend class ::sql::command;
friend class ::sql::model;
public:
command_base(SqlManager* handle, const std::string &sql, std::initializer_list<variable> values) {
assert(handle);
assert(!sql.empty());
this->_data = handle->allocateCommandData();
this->_data->handle = handle;
this->_data->sql_command = sql;
this->__data = this->_data.get();
for(const auto& val : values) this->value(val);
}
template<typename... Ts>
command_base(SqlManager* handle, std::string sql, Ts&&... vars) : command_base(handle, sql, {}) { values(vars...); }
command_base(const command_base<SelfType>& ref) : _data(ref._data), __data(ref._data.get()) {}
command_base(command_base<SelfType>&& ref) noexcept : _data(ref._data), __data(ref._data.get()) { }
virtual ~command_base() = default;
virtual SelfType& value(const variable& val) {
this->_data->variables.push_back(val);
return *(SelfType*) this;
}
template <typename T>
SelfType& value(const std::string& key, T&& value) {
this->_data->variables.push_back(variable{key, value});
return *(SelfType*) this;
}
SelfType& values(){ return *(SelfType*) this; }
template<typename value_t, typename... values_t>
SelfType& values(const value_t& firstValue, values_t&&... values){
this->value(firstValue);
this->values(values...);
return *(SelfType*) this;
}
std::string sqlCommand(){ return _data->sql_command; }
SqlManager* handle(){ return _data->handle; }
protected:
explicit command_base(const std::shared_ptr<CommandData>& data) : _data(data), __data(data.get()) {}
std::shared_ptr<CommandData> _data;
CommandData* __data = nullptr;
};
}
class model : public impl::command_base<model> {
public:
model(SqlManager* db, const std::string &sql, std::initializer_list<variable> values) : command_base(db, sql, values){};
template<typename... Ts>
model(SqlManager* handle, const std::string &sql, Ts... vars) : model(handle, sql, {}) { values(vars...); }
model(const model& v) : command_base(v) {};
model(model&& v) noexcept : command_base(v){};
~model() override {};
sql::command command();
sql::model copy();
private:
explicit model(const std::shared_ptr<CommandData>&);
};
class command : public impl::command_base<command> {
public:
command(SqlManager* db, const std::string &sql, std::initializer_list<variable> values) : command_base(db, sql, values) {};
template<typename... Ts>
command(SqlManager* handle, const std::string &sql, Ts... vars) : command_base(handle, sql, {}) { values(SQL_FWD(vars)...); }
/*
template<typename arg_0_t, typename arg_1_t>
command(SqlManager* handle, const std::string &sql, std::initializer_list<typename V> arg_0, std::initializer_list<arg_1_t> arg_1) : command_base(handle, sql, {}) {
//static_assert(false, "testing");
}
command(SqlManager* handle, const std::string &sql) : command_base(handle, sql, {}) {}
*/
explicit command(model& c) : command_base(c.handle(), c.sqlCommand()) {
this->_data = c._data->handle->copyCommandData(c._data);
}
command(const command& v): command_base(v) {};
command(command&& v) noexcept : command_base(v){};
~command() override = default;;
result execute() {
return this->_data->handle->executeCommand(this->_data);
}
threads::Future<result> executeLater();
//Convert lambdas to std::function
template <typename lambda, typename... arguments>
result query(const lambda& lam, arguments&&... args) {
typedef stx::lambda_type<lambda> info;
return this->query((typename info::invoker_function) lam, SQL_FWD(args)...);
}
template <typename call_ret, typename... call_args, typename... args>
result query(const std::function<call_ret(call_args...)>& callback, args&&... parms) { //Query without data
typedef impl::transformer_return<call_ret> ret_transformer;
typedef impl::transformer_arguments<typename impl::reverse_types<call_args...>::type, std::tuple<args...>> args_transformer;
constexpr bool valid_return_type = ret_transformer::supported::value;
constexpr bool valid_arguments = args_transformer::supported::value;
constexpr bool valid_argument_count =
!valid_arguments || //Don't throw when function is invalid
!valid_return_type || //Don't throw when function is invalid
args_transformer::argument_count == sizeof...(args);
constexpr bool valid_argument_types =
!valid_arguments || //Don't throw when function is invalid
!valid_return_type || //Don't throw when function is invalid
!valid_argument_count || //Don't throw when arg count is invalid
std::is_same<typename args_transformer::arguments_reversed, typename impl::reverse_types<args...>::type>::value;
static_assert(valid_return_type, "Return type isn't supported!");
static_assert(valid_arguments, "Arguments not supported!");
static_assert(valid_argument_count, "Invalid argument count!");
static_assert(valid_argument_types, "Invalid argument types!");
return this->query_invoke<ret_transformer, args_transformer>(callback, SQL_FWD(parms)...);
};
private:
template <typename ret_transformer, typename args_transformer, typename type_call, typename... args>
inline result query_invoke(const type_call& callback, args&&... parms) {
auto standard_return = ret_transformer::transform(callback);
auto standard_function = args_transformer::transform(standard_return, SQL_FWD(parms)...);
return this->_data->handle->queryCommand(this->_data, standard_function);
}
};
class AsyncSqlPool {
public:
explicit AsyncSqlPool(size_t threads);
~AsyncSqlPool();
AsyncSqlPool(AsyncSqlPool&) = delete;
AsyncSqlPool(const AsyncSqlPool&) = delete;
AsyncSqlPool(AsyncSqlPool&&) = delete;
threads::Future<result> executeLater(const command& cmd);
threads::ThreadPool* threads(){ return _threads; }
private:
threads::ThreadPool* _threads = nullptr;
};
}
inline std::ostream& sql::operator<<(std::ostream& s,const result* res){
if(!res) s << "nullptr";
else {
if(!res->sql().empty())
s << " sql: " << res->sql() << " returned -> ";
s << res->code() << "/" << res->msg();
}
return s;
}
inline std::ostream& sql::operator<<(std::ostream& s,const result& res){
return sql::operator<<(s, &res);
}