#include #include "MySQL.h" #include "mysql_connection.h" #include "src/log/LogUtils.h" #include #include #include 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() || strcmp(*connect_map["hostName"].get(), "")) 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()->asStdString()); if(connections < 1) return {ERROR_MYSQL_INVLID_PROPERTIES, "Invalid connection count"}; for(int i = 0; i < connections; i++) { auto connection = unique_ptr(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(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 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 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 MySQLManager::allocateCommandData() { return make_shared(); } std::shared_ptr MySQLManager::copyCommandData(std::shared_ptr 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(_new); auto __ptr = static_pointer_cast(ptr); //__new->stmt = __ptr->stmt; return __new; } void prepared_statement_release(sql::PreparedStatement* stmt) { if(stmt) stmt->close(); delete stmt; }; typedef unique_ptr PreparedStatementHandle; void statement_release(sql::Statement* stmt) { if(stmt) stmt->close(); delete stmt; }; typedef unique_ptr StatementHandle; void result_release(sql::ResultSet* set) { if(set && !set->isClosed()) set->close(); delete set; } typedef unique_ptr ResultHandle; namespace sql { namespace mysql { bool evaluate_sql_query(string& sql, const std::vector& vars, std::vector& 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& 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()); break; case VARTYPE_INT: stmt->setInt(index, var.as()); break; case VARTYPE_LONG: stmt->setInt64(index, var.as()); break; case VARTYPE_DOUBLE: stmt->setDouble(index, var.as()); break; case VARTYPE_FLOAT: stmt->setDouble(index, var.as()); 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& 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 lock(_mgr->connections_lock); _mgr->connections_condition.notify_all(); } } std::unique_ptr MySQLManager::next_connection() { unique_ptr 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(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 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 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 _ptr) { auto ptr = static_pointer_cast(_ptr); std::lock_guard command_lock(ptr->lock); auto command = ptr->sql_command; auto variables = ptr->variables; vector 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 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 _ptr, const QueryCallback &fn) { auto ptr = static_pointer_cast(_ptr); std::lock_guard lock(ptr->lock); auto command = ptr->sql_command; auto variables = ptr->variables; vector 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 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()}; } }