TeaSpeakLibrary/src/sql/mysql/MySQL.cpp

384 lines
13 KiB
C++

#include <memory>
#include "MySQL.h"
#include "mysql_connection.h"
#include "src/log/LogUtils.h"
#include <pipes/misc/http.h>
#include <memory>
#include <utility>
using namespace std;
using namespace sql;
using namespace sql::mysql;
MySQLManager::MySQLManager() : SqlManager(SqlType::TYPE_MYSQL) {}
MySQLManager::~MySQLManager() {}
//Property info: https://dev.mysql.com/doc/connector-j/5.1/en/connector-j-reference-configuration-properties.html
//mysql://[host][:port]/[database][?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
#define MYSQL_PREFIX "mysql://"
inline result parse_url(const string& url, ConnectOptionsMap& connect_map) {
string target_url;
if(url.find(MYSQL_PREFIX) != 0) return {ERROR_MYSQL_INVLID_URL, "Missing mysql:// at begin"};
auto index_parms = url.find('?');
if(index_parms == string::npos) {
target_url = "tcp://" + url.substr(strlen(MYSQL_PREFIX));
} else {
target_url = "tcp://" + url.substr(strlen(MYSQL_PREFIX), index_parms - strlen(MYSQL_PREFIX));
auto parms = url.substr(index_parms + 1);
size_t index = 0;
do {
auto idx = parms.find('&', index);
auto element = parms.substr(index, idx - index);
auto key_idx = element.find('=');
auto key = element.substr(0, key_idx);
auto value = element.substr(key_idx + 1);
connect_map[key] = http::decode_url(value);
logTrace(LOG_GENERAL, "Got mysql property {}. Value: {}", key, value);
index = idx + 1;
} while(index != 0);
}
//if(!connect_map["hostName"].get<const char*>() || strcmp(*connect_map["hostName"].get<const char*>(), ""))
connect_map["hostName"] = target_url;
logTrace(LOG_GENERAL, "Got mysql property {}. Value: {}", "hostName", target_url);
return result::success;
}
result MySQLManager::connect(const std::string &url) {
this->disconnecting = false;
ConnectOptionsMap connect_map;
connect_map["connections"] = "1";
auto res = parse_url(url, connect_map);
if(!res) return res;
this->driver = get_driver_instance();
if(!this->driver) return {ERROR_MYSQL_MISSING_DRIVER, "Missing driver!"};
try {
auto entry = connect_map["connections"];
auto connections = std::stoll(connect_map["connections"].get<sql::SQLString>()->asStdString());
if(connections < 1) return {ERROR_MYSQL_INVLID_PROPERTIES, "Invalid connection count"};
for(int i = 0; i < connections; i++) {
auto connection = unique_ptr<Connection>(this->driver->connect(connect_map));
if(!connection) return {ERROR_MYSQL_INVLID_CONNECT, "Could not spawn new connection"};
if(!connection->isValid()) return {ERROR_MYSQL_INVLID_CONNECT, "Could not validate connection"};
if(connection->getSchema().length() == 0) return {ERROR_MYSQL_INVLID_CONNECT, "Missing schema!"};
this->connections.push_back(shared_ptr<ConnectionEntry>(new ConnectionEntry(std::move(connection), false)));
}
} catch (sql::SQLException& ex) {
return {ERROR_MYSQL_INVLID_CONNECT, ex.what()};
}
return result::success;
}
bool MySQLManager::connected() {
lock_guard<mutex> lock(this->connections_lock);
for(const auto& conn : this->connections)
if(conn->used || conn->connection->isValid()) return true;
return false;
}
result MySQLManager::disconnect() {
lock_guard<mutex> lock(this->connections_lock);
this->disconnecting = true;
for(const auto& entry : this->connections)
entry->connection->close();
this->connections.clear();
this->connections_condition.notify_all();
this->disconnecting = false;
return result::success;
}
std::shared_ptr<CommandData> MySQLManager::allocateCommandData() {
return make_shared<MySQLCommand>();
}
std::shared_ptr<CommandData> MySQLManager::copyCommandData(std::shared_ptr<CommandData> ptr) {
auto _new = this->allocateCommandData();
_new->handle = ptr->handle;
_new->lock = ptr->lock;
_new->sql_command = ptr->sql_command;
_new->variables = ptr->variables;
auto __new = static_pointer_cast<MySQLCommand>(_new);
auto __ptr = static_pointer_cast<MySQLCommand>(ptr);
//__new->stmt = __ptr->stmt;
return __new;
}
void prepared_statement_release(sql::PreparedStatement* stmt) {
if(stmt) stmt->close();
delete stmt;
};
typedef unique_ptr<sql::PreparedStatement, decltype(prepared_statement_release)*> PreparedStatementHandle;
void statement_release(sql::Statement* stmt) {
if(stmt) stmt->close();
delete stmt;
};
typedef unique_ptr<sql::Statement, decltype(statement_release)*> StatementHandle;
void result_release(sql::ResultSet* set) {
if(set && !set->isClosed()) set->close();
delete set;
}
typedef unique_ptr<sql::ResultSet, decltype(result_release)*> ResultHandle;
namespace sql {
namespace mysql {
bool evaluate_sql_query(string& sql, const std::vector<variable>& vars, std::vector<variable>& result) {
char quote = 0;
for(int index = 0; index < sql.length(); index++) {
if(sql[index] == '\'' || sql[index] == '"' || sql[index] == '`') {
if(quote > 0) {
if(quote == sql[index]) quote = 0;
} else {
quote = sql[index];
}
continue;
}
if(quote > 0) continue;
if(sql[index] != ':') continue;
auto index_end = sql.find_first_not_of("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_", index + 1);
if(index_end == string::npos) index_end = sql.length();
string key = sql.substr(index, index_end - index);
//Now we can replace it with a ?
sql.replace(index, index_end - index, "?", 1);
bool insert = false;
for(const auto& e : vars)
if(e.key() == key) {
result.push_back(e);
insert = true;
break;
}
if(!insert)
result.push_back(variable{});
}
return true;
}
inline bool bind_parms(const PreparedStatementHandle& stmt, const std::vector<variable>& vars) {
uint32_t index = 1;
for(const auto& var : vars) {
switch (var.type()) {
case VARTYPE_NULL:
stmt->setNull(index, 0);
break;
case VARTYPE_BOOLEAN:
stmt->setBoolean(index, var.as<bool>());
break;
case VARTYPE_INT:
stmt->setInt(index, var.as<int32_t>());
break;
case VARTYPE_LONG:
stmt->setInt64(index, var.as<int64_t>());
break;
case VARTYPE_DOUBLE:
stmt->setDouble(index, var.as<double>());
break;
case VARTYPE_FLOAT:
stmt->setDouble(index, var.as<float>());
break;
case VARTYPE_TEXT:
stmt->setString(index, var.value());
break;
default:
cerr << "[MySQL] Invalid var type (" << var.type() << ")" << endl;
break;
}
index++;
}
return true;
}
}
}
LocalConnection::LocalConnection(MySQLManager* mgr, const std::shared_ptr<ConnectionEntry>& entry) : _mgr(mgr), _connection(entry) {
_mgr->driver->threadInit();
_connection->used = true;
//logMessage(LOG_GENERAL, "Allocate local connection {} and thread {}", (void*) _connection.get(), (void*) threads::self::id());
}
LocalConnection::~LocalConnection() {
//logMessage(LOG_GENERAL, "Deallocate local connection {} and thread {}", (void*) this->_connection.get(), (void*) threads::self::id());
_mgr->driver->threadEnd();
_connection->used = false;
{
lock_guard<mutex> lock(_mgr->connections_lock);
_mgr->connections_condition.notify_all();
}
}
std::unique_ptr<LocalConnection> MySQLManager::next_connection() {
unique_ptr<LocalConnection> result;
{
unique_lock connections_lock(this->connections_lock);
while(!result) {
size_t available_connections = 0;
for(const auto& connection : this->connections) {
available_connections++;
if(connection->used) continue;
result = std::make_unique<LocalConnection>(this, connection);
break;
}
if(!result) {
if(available_connections == 0) {
if(this->listener_disconnected)
this->listener_disconnected(false);
this->disconnect();
return nullptr;
}
this->connections_condition.wait(connections_lock); /* wait for the next connection */
}
}
}
if(!result->_connection->connection->isValid()) {
try {
logError(0, "MySQL connection is invalid! Closing connection!");
result->_connection->connection->close();
} catch(sql::SQLException& ex) {}
}
if(result->_connection->connection->isClosed()) {
logError(0, "MySQL connection was closed! Attempt reconnect!");
try {
if(!result->_connection->connection->reconnect()) {
logError(0, "MySQL connection reconnect attempt failed! Dropping connection!");
{
lock_guard<mutex> connections_lock(this->connections_lock);
auto index = find(this->connections.begin(), this->connections.end(), result->_connection);
if(index != this->connections.end())
this->connections.erase(index);
}
return this->next_connection();
}
} catch (sql::SQLException& ex) {
logError(0, "Got an exception while reconnecting! Message: " + string(ex.what()));
logError(0, "Dropping connection!");
{
lock_guard<mutex> connections_lock(this->connections_lock);
auto index = find(this->connections.begin(), this->connections.end(), result->_connection);
if(index != this->connections.end())
this->connections.erase(index);
}
return this->next_connection();
}
}
return result;
}
result MySQLManager::executeCommand(std::shared_ptr<CommandData> _ptr) {
auto ptr = static_pointer_cast<MySQLCommand>(_ptr);
std::lock_guard<threads::Mutex> command_lock(ptr->lock);
auto command = ptr->sql_command;
auto variables = ptr->variables;
vector<variable> mapped_variables;
if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) return {ptr->sql_command, -1, "Could not map sqlite vars to mysql!"};
unique_ptr<LocalConnection> connection = this->next_connection();
if(!connection) return {ptr->sql_command, -1, "Could not get a valid connection!"};
try {
PreparedStatementHandle stmt(connection->_connection->connection->prepareStatement(command), prepared_statement_release);
//logMessage(LOG_GENERAL, "Deleting prepered statement {} and thread {}", (void*) stmt.get(), (void*) threads::self::id());
if(!stmt) return {ptr->sql_command, -1, "Could not span a prepared statement"};
if(!sql::mysql::bind_parms(stmt, mapped_variables)) return {ptr->sql_command, -1, "Could not bind variables!"};
auto update_count = stmt->executeUpdate();
if(update_count < 0)
return {ptr->sql_command, -1, "Could not execute update. Code: " + to_string(update_count)};
stmt.reset();
return result::success;
} catch (sql::SQLException& ex) {
logError(0, "SQL Error: {}", ex.what());
return {ptr->sql_command, -1, ex.what()};
}
}
result MySQLManager::queryCommand(shared_ptr<CommandData> _ptr, const QueryCallback &fn) {
auto ptr = static_pointer_cast<MySQLCommand>(_ptr);
std::lock_guard<threads::Mutex> lock(ptr->lock);
auto command = ptr->sql_command;
auto variables = ptr->variables;
vector<variable> mapped_variables;
if(!sql::mysql::evaluate_sql_query(command, variables, mapped_variables)) return {ptr->sql_command, -1, "Could not map sqlite vars to mysql!"};
unique_ptr<LocalConnection> connection = this->next_connection();
if(!connection) return {ptr->sql_command, -1, "Could not get a valid connection!"};
try {
PreparedStatementHandle stmt(connection->_connection->connection->prepareStatement(command), prepared_statement_release);
//logMessage(LOG_GENERAL, "Deleting prepered statement {} and thread {}", (void*) stmt.get(), (void*) threads::self::id());
if(!sql::mysql::bind_parms(stmt, mapped_variables)) return {ptr->sql_command, -1, "Could not bind variables!"};
ResultHandle result(stmt->executeQuery(), result_release);
auto column_count = result->getMetaData()->getColumnCount();
std::string columnNames[column_count];
std::string columnValues[column_count];
for(int index = 0; index < column_count; index++)
columnNames[index] = result->getMetaData()->getColumnName(index + 1);
bool userQuit = false;
while(result->next() && !userQuit) {
for(int index = 0; index < column_count; index++)
columnValues[index] = result->getString(index + 1);
if(fn(column_count, columnValues, columnNames) != 0) {
userQuit = true;
break;
}
}
stmt.reset();
return result::success;
} catch (sql::SQLException& ex) {
return {ptr->sql_command, -1, ex.what()};
}
}
result MySQLManager::execute_raw(const std::string &command) {
auto connection = this->next_connection();
if(!connection)
return {command, -1, "no connection available"};
auto& mysql_connection = connection->_connection->connection;
mysql_connection->clearWarnings();
StatementHandle statement(mysql_connection->createStatement(), statement_release);
statement->clearWarnings();
try {
auto result = statement->execute(command);
if(statement->getWarnings()) {
cerr << "Got some warnings: " << endl;
while(statement->getWarnings()) {
cerr << " - " << statement->getWarnings()->getMessage() << endl;
statement->getWarnings()->setNextWarning(statement->getWarnings()->getNextWarning());
}
}
if(result)
return result::success;
return {command, -1, "return false"};
} catch (sql::SQLException& ex) {
return {command, -1, ex.what()};
}
}