2023-12-30 22:42:11 +00:00
|
|
|
//SPDX-FileCopyrightText: 2023 Yury Gubich <blue@macaw.me>
|
|
|
|
//SPDX-License-Identifier: GPL-3.0-or-later
|
2023-12-10 23:23:15 +00:00
|
|
|
|
2023-12-08 22:26:16 +00:00
|
|
|
#include "statement.h"
|
|
|
|
|
2023-12-22 23:25:20 +00:00
|
|
|
#include "mysqld_error.h"
|
|
|
|
|
|
|
|
#include "database/exceptions.h"
|
|
|
|
|
2023-12-08 22:26:16 +00:00
|
|
|
static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME);
|
|
|
|
|
2023-12-29 17:40:00 +00:00
|
|
|
DB::MySQL::Statement::Statement(MYSQL* connection, const char* statement):
|
2023-12-08 22:26:16 +00:00
|
|
|
stmt(mysql_stmt_init(connection)),
|
2023-12-20 22:42:13 +00:00
|
|
|
param()
|
2023-12-08 22:26:16 +00:00
|
|
|
{
|
|
|
|
int result = mysql_stmt_prepare(stmt.get(), statement, strlen(statement));
|
|
|
|
if (result != 0)
|
|
|
|
throw std::runtime_error(std::string("Error preparing statement: ") + mysql_stmt_error(stmt.get()));
|
|
|
|
}
|
|
|
|
|
2023-12-29 17:40:00 +00:00
|
|
|
void DB::MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) {
|
2023-12-08 22:26:16 +00:00
|
|
|
MYSQL_BIND& result = param.emplace_back();
|
|
|
|
std::memset(&result, 0, sizeof(result));
|
|
|
|
|
|
|
|
result.buffer_type = type;
|
|
|
|
result.buffer = value;
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case MYSQL_TYPE_STRING:
|
|
|
|
case MYSQL_TYPE_VAR_STRING:
|
2023-12-20 22:42:13 +00:00
|
|
|
result.buffer_length = strlen(static_cast<char*>(value));
|
2023-12-08 22:26:16 +00:00
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_DATE:
|
2023-12-20 22:42:13 +00:00
|
|
|
result.buffer_length = TIME_LENGTH;
|
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONG:
|
|
|
|
case MYSQL_TYPE_LONGLONG:
|
|
|
|
case MYSQL_TYPE_SHORT:
|
|
|
|
case MYSQL_TYPE_TINY:
|
|
|
|
result.is_unsigned = usigned;
|
2023-12-08 22:26:16 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw std::runtime_error("Type: " + std::to_string(type) + " is not yet supported in bind");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-29 17:40:00 +00:00
|
|
|
void DB::MySQL::Statement::execute() {
|
2023-12-22 23:25:20 +00:00
|
|
|
MYSQL_STMT* raw = stmt.get();
|
|
|
|
int result = mysql_stmt_bind_param(raw, param.data());
|
2023-12-08 22:26:16 +00:00
|
|
|
if (result != 0)
|
2023-12-22 23:25:20 +00:00
|
|
|
throw std::runtime_error(std::string("Error binding statement: ") + mysql_stmt_error(raw));
|
2023-12-08 22:26:16 +00:00
|
|
|
|
2023-12-22 23:25:20 +00:00
|
|
|
result = mysql_stmt_execute(raw);
|
|
|
|
if (result != 0) {
|
|
|
|
int errcode = mysql_stmt_errno(raw);
|
|
|
|
std::string text = mysql_stmt_error(raw);
|
|
|
|
switch (errcode) {
|
|
|
|
case ER_DUP_ENTRY:
|
|
|
|
throw Duplicate("Error executing statement: " + text);
|
|
|
|
default:
|
|
|
|
throw std::runtime_error("Error executing statement: " + text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-29 17:40:00 +00:00
|
|
|
std::vector<std::vector<std::any>> DB::MySQL::Statement::fetchResult() {
|
2023-12-22 23:25:20 +00:00
|
|
|
MYSQL_STMT* raw = stmt.get();
|
|
|
|
if (mysql_stmt_store_result(raw) != 0)
|
|
|
|
throw std::runtime_error(std::string("Error fetching statement result: ") + mysql_stmt_error(raw)); //TODO not sure if it's valid here
|
|
|
|
|
|
|
|
MYSQL_RES* meta = mysql_stmt_result_metadata(raw);
|
|
|
|
if (meta == nullptr)
|
|
|
|
throw std::runtime_error(std::string("Error fetching statement result: ") + mysql_stmt_error(raw)); //TODO not sure if it's valid here
|
|
|
|
|
|
|
|
std::unique_ptr<MYSQL_RES, ResDeleter> mt(meta);
|
|
|
|
unsigned int numColumns = mysql_num_fields(meta);
|
|
|
|
MYSQL_BIND bind[numColumns];
|
2023-12-23 20:23:38 +00:00
|
|
|
std::memset(bind, 0, sizeof(bind));
|
2023-12-22 23:25:20 +00:00
|
|
|
|
2023-12-23 20:23:38 +00:00
|
|
|
std::vector<std::any> line(numColumns);
|
2023-12-22 23:25:20 +00:00
|
|
|
std::vector<long unsigned int> lengths(numColumns);
|
|
|
|
for (unsigned int i = 0; i < numColumns; ++i) {
|
|
|
|
MYSQL_FIELD *field = mysql_fetch_field_direct(meta, i);
|
|
|
|
|
|
|
|
switch (field->type) {
|
|
|
|
case MYSQL_TYPE_STRING:
|
|
|
|
case MYSQL_TYPE_VAR_STRING:
|
2023-12-23 20:23:38 +00:00
|
|
|
case MYSQL_TYPE_VARCHAR: {
|
|
|
|
line[i] = std::string();
|
|
|
|
std::string& str = std::any_cast<std::string&>(line[i]);
|
|
|
|
str.resize(field->length);
|
|
|
|
bind[i].buffer = str.data();
|
|
|
|
} break;
|
|
|
|
case MYSQL_TYPE_TINY:
|
|
|
|
line[i] = uint8_t{0};
|
2024-01-09 17:02:56 +00:00
|
|
|
bind[i].buffer = &std::any_cast<uint8_t&>(line[i]);
|
2023-12-23 20:23:38 +00:00
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_SHORT:
|
|
|
|
line[i] = uint16_t{0};
|
2024-01-09 17:02:56 +00:00
|
|
|
bind[i].buffer = &std::any_cast<uint16_t&>(line[i]);
|
2023-12-23 20:23:38 +00:00
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONG:
|
|
|
|
line[i] = uint32_t{0};
|
2024-01-09 17:02:56 +00:00
|
|
|
bind[i].buffer = &std::any_cast<uint32_t&>(line[i]);
|
2023-12-23 20:23:38 +00:00
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONGLONG:
|
|
|
|
line[i] = uint64_t{0};
|
2024-01-09 17:02:56 +00:00
|
|
|
bind[i].buffer = &std::any_cast<uint64_t&>(line[i]);
|
2023-12-22 23:25:20 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(field->type));
|
|
|
|
}
|
|
|
|
bind[i].buffer_type = field->type;
|
|
|
|
bind[i].buffer_length = field->length;
|
|
|
|
bind[i].length = &lengths[i];
|
2024-03-28 23:20:21 +00:00
|
|
|
if (field->flags & UNSIGNED_FLAG)
|
|
|
|
bind[i].is_unsigned = 1;
|
2023-12-22 23:25:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mysql_stmt_bind_result(raw, bind) != 0)
|
|
|
|
throw std::runtime_error(std::string("Error binding on fetching statement result: ") + mysql_stmt_error(raw));
|
|
|
|
|
2023-12-23 20:23:38 +00:00
|
|
|
std::vector<std::vector<std::any>> result;
|
2024-03-28 23:20:21 +00:00
|
|
|
int rc;
|
|
|
|
while ((rc = mysql_stmt_fetch(raw)) == 0) {
|
2023-12-23 20:23:38 +00:00
|
|
|
std::vector<std::any>& row = result.emplace_back(numColumns);
|
|
|
|
for (unsigned int i = 0; i < numColumns; ++i) {
|
|
|
|
switch (bind[i].buffer_type) {
|
|
|
|
case MYSQL_TYPE_STRING:
|
|
|
|
case MYSQL_TYPE_VAR_STRING:
|
|
|
|
case MYSQL_TYPE_VARCHAR: {
|
|
|
|
row[i] = std::string(std::any_cast<const std::string&>(line[i]).data(), lengths[i]);
|
|
|
|
} break;
|
|
|
|
case MYSQL_TYPE_TINY:
|
|
|
|
row[i] = std::any_cast<uint8_t>(line[i]);
|
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_SHORT:
|
|
|
|
row[i] = std::any_cast<uint16_t>(line[i]);
|
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONG:
|
|
|
|
row[i] = std::any_cast<uint32_t>(line[i]);
|
|
|
|
break;
|
|
|
|
case MYSQL_TYPE_LONGLONG:
|
|
|
|
row[i] = std::any_cast<uint64_t>(line[i]);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(bind[i].buffer_type));
|
|
|
|
}
|
|
|
|
}
|
2023-12-22 23:25:20 +00:00
|
|
|
}
|
2024-03-28 23:20:21 +00:00
|
|
|
if (rc == 1)
|
|
|
|
throw std::runtime_error(std::string("Error occured fetching data ") + mysql_stmt_error(raw));
|
|
|
|
else if (rc == MYSQL_DATA_TRUNCATED)
|
|
|
|
throw std::runtime_error("Data has been truncated");
|
2023-12-22 23:25:20 +00:00
|
|
|
|
|
|
|
return result;
|
2023-12-08 22:26:16 +00:00
|
|
|
}
|
2023-12-22 23:25:20 +00:00
|
|
|
|
2024-01-21 19:23:48 +00:00
|
|
|
unsigned int DB::MySQL::Statement::affectedRows () {
|
|
|
|
return mysql_stmt_affected_rows(stmt.get());
|
|
|
|
}
|