added mysql backend
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
#include "mysql_connection.hpp"
|
||||
#include "mysql_error.hpp"
|
||||
#include "mysql_result_reader.hpp"
|
||||
#include "mysql_statement.hpp"
|
||||
|
||||
#include "matador/sql/record.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
|
||||
namespace matador::backends::mysql {
|
||||
|
||||
mysql_connection::string_to_int_map mysql_connection::statement_name_map_{};
|
||||
|
||||
mysql_connection::mysql_connection(const sql::connection_info &info)
|
||||
: connection_impl(info) {}
|
||||
|
||||
void mysql_connection::open()
|
||||
{
|
||||
if (is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
mysql_ = std::make_unique<MYSQL>();
|
||||
|
||||
if (!mysql_init(mysql_.get())) {
|
||||
throw_mysql_error(mysql_.get(), "mysql_init");
|
||||
}
|
||||
|
||||
if (!mysql_real_connect(mysql_.get(),
|
||||
info().hostname.c_str(),
|
||||
info().user.c_str(),
|
||||
!info().password.empty() ? info().password.c_str() : nullptr,
|
||||
info().database.c_str(),
|
||||
info().port,
|
||||
nullptr,
|
||||
0)) {
|
||||
// disconnect all handles
|
||||
const std::string error_message = mysql_error(mysql_.get());
|
||||
mysql_close(mysql_.get());
|
||||
|
||||
mysql_.reset();
|
||||
// throw exception
|
||||
throw_mysql_error(error_message.c_str(), "mysql_real_connect");
|
||||
}
|
||||
}
|
||||
|
||||
void mysql_connection::close()
|
||||
{
|
||||
if (mysql_) {
|
||||
mysql_close(mysql_.get());
|
||||
mysql_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool mysql_connection::is_open()
|
||||
{
|
||||
return mysql_ != nullptr;
|
||||
}
|
||||
|
||||
sql::data_type_t to_type(enum_field_types type, unsigned int flags)
|
||||
{
|
||||
switch (type) {
|
||||
case MYSQL_TYPE_TINY:
|
||||
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_char : sql::data_type_t::type_char;
|
||||
case MYSQL_TYPE_SHORT:
|
||||
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_short : sql::data_type_t::type_short;
|
||||
case MYSQL_TYPE_LONG:
|
||||
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_int : sql::data_type_t::type_int;
|
||||
case MYSQL_TYPE_LONGLONG:
|
||||
return flags & UNSIGNED_FLAG ? sql::data_type_t::type_unsigned_long_long : sql::data_type_t::type_long_long;
|
||||
case MYSQL_TYPE_FLOAT:
|
||||
return sql::data_type_t::type_float;
|
||||
case MYSQL_TYPE_DOUBLE:
|
||||
return sql::data_type_t::type_double;
|
||||
case MYSQL_TYPE_VARCHAR:
|
||||
case MYSQL_TYPE_VAR_STRING:
|
||||
return sql::data_type_t::type_varchar;
|
||||
case MYSQL_TYPE_BLOB:
|
||||
return sql::data_type_t::type_blob;
|
||||
case MYSQL_TYPE_STRING:
|
||||
return sql::data_type_t::type_text;
|
||||
case MYSQL_TYPE_DATE:
|
||||
return sql::data_type_t::type_date;
|
||||
case MYSQL_TYPE_DATETIME:
|
||||
case MYSQL_TYPE_TIMESTAMP:
|
||||
return sql::data_type_t::type_time;
|
||||
default:
|
||||
return sql::data_type_t::type_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
utils::constraints to_options(unsigned int flags)
|
||||
{
|
||||
utils::constraints options{utils::constraints::NONE};
|
||||
if (flags & NOT_NULL_FLAG) {
|
||||
options |= utils::constraints::NOT_NULL;
|
||||
}
|
||||
if (flags & PRI_KEY_FLAG) {
|
||||
options |= utils::constraints::PRIMARY_KEY;
|
||||
}
|
||||
if (flags & UNIQUE_KEY_FLAG) {
|
||||
options |= utils::constraints::UNIQUE;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
sql::data_type_t string2type(const std::string &type_string)
|
||||
{
|
||||
// if (strcmp(type_string.c_str(), "int")
|
||||
return sql::data_type_t::type_unknown;
|
||||
}
|
||||
|
||||
struct type_info
|
||||
{
|
||||
sql::data_type_t type{sql::data_type_t::type_unknown};
|
||||
size_t size{};
|
||||
};
|
||||
|
||||
type_info determine_type_info(const std::string &type_string)
|
||||
{
|
||||
static const std::regex TYPE_REGEX(R"(^(\w+)(\((\d+)(,(\d+))?\))?$)");
|
||||
std::smatch matcher;
|
||||
|
||||
type_info result;
|
||||
if (std::regex_match(type_string, matcher, TYPE_REGEX)) {
|
||||
result.type = string2type(matcher[1].str());
|
||||
if (matcher[3].matched) {
|
||||
result.size = std::stoi(matcher[3].str());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<sql::query_result_impl> mysql_connection::fetch(const std::string &stmt)
|
||||
{
|
||||
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
auto result = mysql_store_result(mysql_.get());
|
||||
if (result == nullptr) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
auto field_count = mysql_num_fields(result);
|
||||
auto fields = mysql_fetch_fields(result);
|
||||
sql::record prototype;
|
||||
for (unsigned i = 0; i < field_count; ++i) {
|
||||
auto type = to_type(fields[i].type, fields[i].flags);
|
||||
auto options = to_options(fields[i].flags);
|
||||
|
||||
prototype.append({fields[i].name, type, options});
|
||||
}
|
||||
|
||||
return std::move(std::make_unique<sql::query_result_impl>(std::make_unique<mysql_result_reader>(result, field_count), std::move(prototype)));
|
||||
}
|
||||
|
||||
std::unique_ptr<sql::statement_impl> mysql_connection::prepare(sql::query_context context)
|
||||
{
|
||||
MYSQL_STMT *stmt = mysql_stmt_init(mysql_.get());
|
||||
if (stmt == nullptr) {
|
||||
throw_mysql_error(mysql_.get(), "mysql_stmt_init");
|
||||
}
|
||||
|
||||
if (mysql_stmt_prepare(stmt, context.sql.c_str(), static_cast<unsigned long>(context.sql.size())) != 0) {
|
||||
throw_mysql_error(stmt, "mysql_stmt_prepare", context.sql);
|
||||
}
|
||||
|
||||
return std::make_unique<mysql_statement>(stmt, std::move(context));
|
||||
}
|
||||
|
||||
size_t mysql_connection::execute(const std::string &stmt)
|
||||
{
|
||||
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
return mysql_affected_rows(mysql_.get());
|
||||
}
|
||||
|
||||
sql::record mysql_connection::describe(const std::string &table)
|
||||
{
|
||||
std::string stmt("SHOW COLUMNS FROM " + table);
|
||||
|
||||
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
auto result = mysql_store_result(mysql_.get());
|
||||
if (result == nullptr) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
mysql_result_reader reader(result, mysql_num_fields(result));
|
||||
sql::record prototype;
|
||||
while (reader.fetch()) {
|
||||
|
||||
char *end = nullptr;
|
||||
// Todo: Handle error
|
||||
auto index = strtoul(reader.column(0), &end, 10);
|
||||
std::string name = reader.column(1);
|
||||
|
||||
// Todo: extract size
|
||||
auto typeinfo = determine_type_info(reader.column(2));
|
||||
end = nullptr;
|
||||
utils::constraints options{};
|
||||
if (strtoul(reader.column(4), &end, 10) == 0) {
|
||||
options = utils::constraints::NOT_NULL;
|
||||
}
|
||||
// f.default_value(res->column(4));
|
||||
prototype.append({name, typeinfo.type, {typeinfo.size, options}});
|
||||
}
|
||||
|
||||
return prototype;
|
||||
}
|
||||
|
||||
bool mysql_connection::exists(const std::string &/*schema_name*/, const std::string &table_name)
|
||||
{
|
||||
std::string stmt("SELECT 1 FROM information_schema.tables WHERE table_schema = '" + info().database + "' AND table_name = '" + table_name + "'");
|
||||
|
||||
if (mysql_query(mysql_.get(), stmt.c_str())) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
auto result = mysql_store_result(mysql_.get());
|
||||
if (result == nullptr) {
|
||||
throw_mysql_error(mysql_.get(), stmt);
|
||||
}
|
||||
|
||||
return result->row_count == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C"
|
||||
{
|
||||
MATADOR_MYSQL_API matador::sql::connection_impl *create_database(const matador::sql::connection_info &info)
|
||||
{
|
||||
return new matador::backends::mysql::mysql_connection(info);
|
||||
}
|
||||
|
||||
MATADOR_MYSQL_API void destroy_database(matador::sql::connection_impl *db)
|
||||
{
|
||||
delete db;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user