//SPDX-FileCopyrightText: 2023 Yury Gubich //SPDX-License-Identifier: GPL-3.0-or-later #include "statement.h" #include "mysqld_error.h" #include "database/exceptions.h" static uint64_t TIME_LENGTH = sizeof(MYSQL_TIME); DB::MySQL::Statement::Statement(MYSQL* connection, const char* statement): stmt(mysql_stmt_init(connection)), param() { 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())); } void DB::MySQL::Statement::bind(void* value, enum_field_types type, bool usigned) { 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: result.buffer_length = strlen(static_cast(value)); break; case MYSQL_TYPE_DATE: 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; break; default: throw std::runtime_error("Type: " + std::to_string(type) + " is not yet supported in bind"); break; } } void DB::MySQL::Statement::execute() { MYSQL_STMT* raw = stmt.get(); int result = mysql_stmt_bind_param(raw, param.data()); if (result != 0) throw std::runtime_error(std::string("Error binding statement: ") + mysql_stmt_error(raw)); 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); } } } std::vector> DB::MySQL::Statement::fetchResult() { 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 mt(meta); unsigned int numColumns = mysql_num_fields(meta); MYSQL_BIND bind[numColumns]; std::memset(bind, 0, sizeof(bind)); std::vector line(numColumns); std::vector 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: case MYSQL_TYPE_VARCHAR: { line[i] = std::string(); std::string& str = std::any_cast(line[i]); str.resize(field->length); bind[i].buffer = str.data(); } break; case MYSQL_TYPE_TINY: line[i] = uint8_t{0}; bind[i].buffer = &std::any_cast(line[i]); break; case MYSQL_TYPE_SHORT: line[i] = uint16_t{0}; bind[i].buffer = &std::any_cast(line[i]); break; case MYSQL_TYPE_LONG: line[i] = uint32_t{0}; bind[i].buffer = &std::any_cast(line[i]); break; case MYSQL_TYPE_LONGLONG: line[i] = uint64_t{0}; bind[i].buffer = &std::any_cast(line[i]); 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]; } if (mysql_stmt_bind_result(raw, bind) != 0) throw std::runtime_error(std::string("Error binding on fetching statement result: ") + mysql_stmt_error(raw)); std::vector> result; while (mysql_stmt_fetch(raw) == 0) { std::vector& 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(line[i]).data(), lengths[i]); } break; case MYSQL_TYPE_TINY: row[i] = std::any_cast(line[i]); break; case MYSQL_TYPE_SHORT: row[i] = std::any_cast(line[i]); break; case MYSQL_TYPE_LONG: row[i] = std::any_cast(line[i]); break; case MYSQL_TYPE_LONGLONG: row[i] = std::any_cast(line[i]); break; default: throw std::runtime_error("Unsupported data fetching statement result " + std::to_string(bind[i].buffer_type)); } } } return result; } unsigned int DB::MySQL::Statement::affectedRows () { return mysql_stmt_affected_rows(stmt.get()); }