Fixed prepared statements within transactions not being re-prepared after a reconnect.
This commit is contained in:
parent
f5dc8132e9
commit
b9d5b74621
@ -3,7 +3,7 @@ project(mysqloo)
|
||||
add_subdirectory(GmodLUA)
|
||||
|
||||
file(GLOB_RECURSE MYSQLOO_SRC "src/*.h" "src/*.cpp")
|
||||
set(SOURCE_FILES ${MYSQLOO_SRC} src/Main.cpp)
|
||||
set(SOURCE_FILES ${MYSQLOO_SRC})
|
||||
set(CMAKE_BUILD_TYPE RelWithDebInfo)
|
||||
set (CMAKE_CXX_STANDARD 14)
|
||||
|
||||
|
46
src/Main.cpp
46
src/Main.cpp
@ -1,46 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include "mysql/Database.h"
|
||||
|
||||
static std::shared_ptr<Database> db;
|
||||
|
||||
int main() {
|
||||
mysql_library_init(0, nullptr, nullptr);
|
||||
std::cout << "Test" << std::endl;
|
||||
db = Database::createDatabase("127.0.0.1", "root", "", "mysql", 3306, "");
|
||||
db->connect();
|
||||
db->wait();
|
||||
std::cout << "DB Connected" << std::endl;
|
||||
std::cout << "Ping returned: " << db->ping() << std::endl;
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
auto query = db->prepare("SELECT ?");
|
||||
query->setNumber(1, 2.0);
|
||||
auto queryData = std::dynamic_pointer_cast<PreparedQueryData>(query->buildQueryData());
|
||||
query->start(queryData);
|
||||
query->wait(true);
|
||||
auto firstResultSet = queryData->getResult();
|
||||
auto &firstRow = firstResultSet.getRows().front();
|
||||
auto &firstValue = firstRow.getValues().front();
|
||||
std::cout << "Result: " << firstValue << std::endl;
|
||||
}
|
||||
|
||||
auto transaction = db->transaction();
|
||||
auto transactionQuery1 = db->prepare("SELECT ?");
|
||||
transactionQuery1->setNumber(1, 3.0);
|
||||
auto transactionData1 = transactionQuery1->buildQueryData();
|
||||
auto transactionQuery2 = db->query("SELECT 12");
|
||||
auto transactionData2 = transactionQuery1->buildQueryData();
|
||||
std::deque<std::pair<std::shared_ptr<Query>, std::shared_ptr<IQueryData>>> transactionQueries;
|
||||
transactionQueries.emplace_back(transactionQuery1, transactionData1);
|
||||
transactionQueries.emplace_back(transactionQuery2, transactionData2);
|
||||
auto transactionData = transaction->buildQueryData(transactionQueries);
|
||||
transaction->start(transactionData);
|
||||
transaction->wait(true);
|
||||
auto firstResultSet = transactionData2->getResult();
|
||||
auto &firstRow = firstResultSet.getRows().front();
|
||||
auto &firstValue = firstRow.getValues().front();
|
||||
std::cout << "Transaction Result: " << firstValue << std::endl;
|
||||
|
||||
mysql_library_end();
|
||||
}
|
@ -299,7 +299,7 @@ void Database::setSQLAutoReconnect(bool shouldReconnect) {
|
||||
}
|
||||
|
||||
//Should only be called from the db thread
|
||||
bool Database::getAutoReconnect() {
|
||||
bool Database::getSQLAutoReconnect() {
|
||||
my_bool autoReconnect;
|
||||
mysql_get_optionv(m_sql, MYSQL_OPT_RECONNECT, &autoReconnect);
|
||||
return (bool) autoReconnect;
|
||||
|
@ -55,8 +55,6 @@ public:
|
||||
|
||||
void setShouldAutoReconnect(bool autoReconnect);
|
||||
|
||||
bool getAutoReconnect();
|
||||
|
||||
bool shouldCachePreparedStatements() {
|
||||
return cachePreparedStatements;
|
||||
}
|
||||
@ -107,6 +105,8 @@ public:
|
||||
|
||||
void setSQLAutoReconnect(bool autoReconnect);
|
||||
|
||||
bool getSQLAutoReconnect();
|
||||
|
||||
private:
|
||||
Database(std::string host, std::string username, std::string pw, std::string database, unsigned int port,
|
||||
std::string unixSocket);
|
||||
|
@ -16,7 +16,7 @@ PingQuery::~PingQuery() = default;
|
||||
/* Executes the ping query
|
||||
*/
|
||||
void PingQuery::executeQuery(Database &database, MYSQL *connection, const std::shared_ptr<IQueryData> &data) {
|
||||
bool oldAutoReconnect = database.getAutoReconnect();
|
||||
bool oldAutoReconnect = database.getSQLAutoReconnect();
|
||||
database.setSQLAutoReconnect(true);
|
||||
this->pingSuccess = mysql_ping(connection) == 0;
|
||||
database.setSQLAutoReconnect(oldAutoReconnect);
|
||||
|
@ -181,7 +181,7 @@ void PreparedQuery::generateMysqlBinds(MYSQL_BIND *binds,
|
||||
*/
|
||||
void PreparedQuery::executeQuery(Database &database, MYSQL *connection, const std::shared_ptr<IQueryData> &ptr) {
|
||||
std::shared_ptr<PreparedQueryData> data = std::dynamic_pointer_cast<PreparedQueryData>(ptr);
|
||||
bool shouldReconnect = database.getAutoReconnect();
|
||||
bool shouldReconnect = database.getSQLAutoReconnect();
|
||||
//Autoreconnect has to be disabled for prepared statement since prepared statements
|
||||
//get reset on the server if the connection fails and auto reconnects
|
||||
try {
|
||||
@ -235,9 +235,20 @@ void PreparedQuery::executeQuery(Database &database, MYSQL *connection, const st
|
||||
}
|
||||
} catch (const MySQLException &error) {
|
||||
unsigned int errorCode = error.getErrorCode();
|
||||
if (errorCode == CR_SERVER_LOST || errorCode == CR_SERVER_GONE_ERROR ||
|
||||
errorCode == ER_MAX_PREPARED_STMT_COUNT_REACHED || errorCode == CR_NO_PREPARE_STMT ||
|
||||
errorCode == ER_UNKNOWN_STMT_HANDLER) {
|
||||
if (errorCode == ER_UNKNOWN_STMT_HANDLER || errorCode == CR_NO_PREPARE_STMT) {
|
||||
//In this case, the statement is lost on the server (usually after a reconnect).
|
||||
//Since the statement is unknown, nothing has been executed yet (i.e. no side effects),
|
||||
//and we are perfectly fine to re-prepare the statement and try again, even if auto-reconnect
|
||||
//is disabled.
|
||||
database.freeStatement(this->cachedStatement);
|
||||
this->cachedStatement = nullptr;
|
||||
if (data->firstAttempt) {
|
||||
data->firstAttempt = false;
|
||||
executeQuery(database, connection, ptr);
|
||||
return;
|
||||
}
|
||||
} else if (errorCode == CR_SERVER_LOST || errorCode == CR_SERVER_GONE_ERROR ||
|
||||
errorCode == ER_MAX_PREPARED_STMT_COUNT_REACHED) {
|
||||
database.freeStatement(this->cachedStatement);
|
||||
this->cachedStatement = nullptr;
|
||||
//Because autoreconnect is disabled we want to try and explicitly execute the prepared query once more
|
||||
|
@ -3,27 +3,28 @@
|
||||
#include <utility>
|
||||
#include "errmsg.h"
|
||||
#include "Database.h"
|
||||
#include "mysqld_error.h"
|
||||
|
||||
bool Transaction::executeStatement(Database &database, MYSQL* connection, std::shared_ptr<IQueryData> ptr) {
|
||||
bool Transaction::executeStatement(Database &database, MYSQL *connection, std::shared_ptr<IQueryData> ptr) {
|
||||
std::shared_ptr<TransactionData> data = std::dynamic_pointer_cast<TransactionData>(ptr);
|
||||
data->setStatus(QUERY_RUNNING);
|
||||
//This temporarily disables reconnect, since a reconnect
|
||||
//would rollback (and cancel) a transaction
|
||||
//Which could lead to parts of the transaction being executed outside of a transaction
|
||||
//If they are being executed after the reconnect
|
||||
bool oldReconnectStatus = database.getAutoReconnect();
|
||||
bool oldReconnectStatus = database.getSQLAutoReconnect();
|
||||
database.setSQLAutoReconnect(false);
|
||||
auto resetReconnectStatus = finally([&] { database.setSQLAutoReconnect(oldReconnectStatus); });
|
||||
try {
|
||||
Transaction::mysqlAutocommit(connection, false);
|
||||
{
|
||||
for (auto& query : data->m_queries) {
|
||||
for (auto &query: data->m_queries) {
|
||||
try {
|
||||
//Errors are cleared in case this is retrying after losing connection
|
||||
query.second->setResultStatus(QUERY_NONE);
|
||||
query.second->setError("");
|
||||
query.first->executeQuery(database, connection, query.second);
|
||||
} catch (const MySQLException& error) {
|
||||
} catch (const MySQLException &error) {
|
||||
query.second->setError(error.what());
|
||||
query.second->setResultStatus(QUERY_ERROR);
|
||||
throw error;
|
||||
@ -33,7 +34,7 @@ bool Transaction::executeStatement(Database &database, MYSQL* connection, std::s
|
||||
mysql_commit(connection);
|
||||
data->setResultStatus(QUERY_SUCCESS);
|
||||
Transaction::mysqlAutocommit(connection, true);
|
||||
} catch (const MySQLException& error) {
|
||||
} catch (const MySQLException &error) {
|
||||
//This check makes sure that setting mysqlAutocommit back to true doesn't cause the transaction to fail
|
||||
//Even though the transaction was executed successfully
|
||||
if (data->getResultStatus() != QUERY_SUCCESS) {
|
||||
@ -61,7 +62,7 @@ bool Transaction::executeStatement(Database &database, MYSQL* connection, std::s
|
||||
mysql_autocommit(connection, true);
|
||||
data->setError(error.what());
|
||||
}
|
||||
for (auto& pair : data->m_queries) {
|
||||
for (auto &pair: data->m_queries) {
|
||||
pair.second->setResultStatus(data->getResultStatus());
|
||||
pair.second->setStatus(QUERY_COMPLETE);
|
||||
}
|
||||
@ -70,7 +71,8 @@ bool Transaction::executeStatement(Database &database, MYSQL* connection, std::s
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<TransactionData> Transaction::buildQueryData(const std::deque<std::pair<std::shared_ptr<Query>, std::shared_ptr<IQueryData>>>& queries) {
|
||||
std::shared_ptr<TransactionData>
|
||||
Transaction::buildQueryData(const std::deque<std::pair<std::shared_ptr<Query>, std::shared_ptr<IQueryData>>> &queries) {
|
||||
//At this point the transaction is guaranteed to have a referenced table
|
||||
//since this is always called shortly after transaction:start()
|
||||
return std::shared_ptr<TransactionData>(new TransactionData(queries));
|
||||
|
Loading…
Reference in New Issue
Block a user