Compare commits

...

5 Commits

Author SHA1 Message Date
FredyH
2d611f19e6
Added documentation for timeout functions 2024-09-16 00:21:13 +02:00
FredyH
95f97501b8
Added onDisconnected callback called after calling db:disconnect. 2024-09-10 00:44:51 +02:00
Frederik Haselmeier
bf3ff37225
Fixed not caching prepared statements in mysqloolib 2023-11-25 20:49:27 +01:00
FredyH
f659497804
Added options to set timeout values on database object 2023-09-04 03:22:34 +02:00
FredyH
9170d617db
Update README.md 2023-08-07 15:14:48 +02:00
8 changed files with 143 additions and 11 deletions

3
.gitignore vendored
View File

@ -11,4 +11,5 @@
*.zip
cmake-build-debug
.idea
.vs
.vs
.cache

View File

@ -13,8 +13,9 @@ Download the latest module for your server's operating system and architecture u
* [Linux (64-bit)](https://github.com/FredyH/MySQLOO/releases/latest/download/gmsv_mysqloo_linux64.dll)
### Notes
* **If your server is using Windows**, you will need to install vcredist 2019, 2015, possibly 2008 and others for the module to load correctly. You can download them [here](https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads).
For the 32-bit version of the module you will need the x86 redistributable, and for the 64 bit version the x64 redistributable.
* If you're unsure of your server's operating system and architecture, type `lua_run print(jit.os, jit.arch)` into the server's console to find out. The output will be something similar to `Windows x86` (x86 is 32-bit, x64 is 64-bit).
* If your server is using Windows, you will need to install vcredist 2019, 2015, possibly 2008 and others for the module to load correctly.
* Previously you were required to place libmysqlclient.dll besides your srcds executable. This is not required anymore since MySQLOO now links statically against libmysqlclient.
# Documentation
@ -57,6 +58,7 @@ Database:connect()
Database:disconnect(shouldWait)
-- Returns nothing
-- disconnects from the database and waits for all queries to finish if shouldWait is true
-- This function calls the onDisconnected callback if it existed on the database before the database was connected.
Database:query( sql )
-- Returns [Query]
@ -141,6 +143,15 @@ Database:setSSLSettings(key, cert, ca, capath, cipher)
-- Every parameter is optional and can be omitted (set to nil) if not required.
-- See https://dev.mysql.com/doc/c-api/8.0/en/mysql-ssl-set.html for the description of each parameter.
Database:setReadTimeout(timeout)
Database:setWriteTimeout(timeout)
Database:setConnectTimeout(timeout)
-- Returns nothing
-- Sets the corresponding timeout value in seconds for any queries operations started by this database instance.
-- The timeout value needs to be at least 1. If this is not called, the default value is used.
-- For information about the timeout values read the documentation here:
-- https://dev.mysql.com/doc/c-api/8.0/en/mysql-options.html
-- Callbacks
Database.onConnected( db )
-- Called when the connection to the MySQL server is successful
@ -148,6 +159,11 @@ Database.onConnected( db )
Database.onConnectionFailed( db, err )
-- Called when the connection to the MySQL server fails, [String] err is why.
Database.onDisconnected( db )
-- Called after Database.disconnect has been called and all queries have finished executing
-- Note: You have to set this callback before calling Database:connect() or it will not be called.
-- Query/PreparedQuery object (transactions also inherit all functions, some have no effect though)
-- Functions

View File

@ -173,6 +173,7 @@ end
function db:PrepareQuery(str, values, callback, ...)
self.CachedStatements = self.CachedStatements or {}
local preparedQuery = self.CachedStatements[str] or self:prepare(str)
self.CachedStatements[str] = preparedQuery
addQueryFunctions(preparedQuery, callback, ...)
setPreparedQueryArguments(preparedQuery, values)
preparedQuery:start()
@ -226,4 +227,4 @@ function db:CreateTransaction()
transaction._db = self
setmetatable(transaction, transactionMT)
return transaction
end
end

View File

@ -103,6 +103,12 @@ MYSQLOO_LUA_FUNCTION(connect) {
LUA->Push(1);
database->m_tableReference = LuaReferenceCreate(LUA);
}
LUA->ReferencePush(database->m_tableReference);
LUA->GetField(-1, "onDisconnected");
database->m_hasOnDisconnected = LUA->IsType(-1, GarrysMod::Lua::Type::Function);
LUA->Pop(2); // callback, table
database->m_database->connect();
return 0;
}
@ -148,6 +154,36 @@ MYSQLOO_LUA_FUNCTION(setSSLSettings) {
return 0;
}
MYSQLOO_LUA_FUNCTION(setReadTimeout) {
auto database = LuaObject::getLuaObject<LuaDatabase>(LUA);
unsigned int timeout = (int) LUA->GetNumber(2);
if (timeout == 0) {
LUA->ThrowError("Timeout must be at least 1");
}
database->m_database->setReadTimeout(timeout);
return 0;
}
MYSQLOO_LUA_FUNCTION(setWriteTimeout) {
auto database = LuaObject::getLuaObject<LuaDatabase>(LUA);
unsigned int timeout = (int) LUA->GetNumber(2);
if (timeout == 0) {
LUA->ThrowError("Timeout must be at least 1");
}
database->m_database->setWriteTimeout(timeout);
return 0;
}
MYSQLOO_LUA_FUNCTION(setConnectTimeout) {
auto database = LuaObject::getLuaObject<LuaDatabase>(LUA);
unsigned int timeout = (int) LUA->GetNumber(2);
if (timeout == 0) {
LUA->ThrowError("Timeout must be at least 1");
}
database->m_database->setConnectTimeout(timeout);
return 0;
}
MYSQLOO_LUA_FUNCTION(disconnect) {
auto database = LuaObject::getLuaObject<LuaDatabase>(LUA);
bool wait = false;
@ -252,6 +288,15 @@ void LuaDatabase::createMetaTable(ILuaBase *LUA) {
LUA->PushCFunction(setSSLSettings);
LUA->SetField(-2, "setSSLSettings");
LUA->PushCFunction(setReadTimeout);
LUA->SetField(-2, "setReadTimeout");
LUA->PushCFunction(setWriteTimeout);
LUA->SetField(-2, "setWriteTimeout");
LUA->PushCFunction(setConnectTimeout);
LUA->SetField(-2, "setConnectTimeout");
LUA->PushCFunction(disconnect);
LUA->SetField(-2, "disconnect");
@ -312,7 +357,6 @@ void LuaDatabase::think(ILuaBase *LUA) {
LUA->ReferencePush(this->m_tableReference);
pcallWithErrorReporter(LUA, 1);
}
LUA->Pop(); //Callback function
} else {
LUA->GetField(-1, "onConnectionFailed");
if (LUA->GetType(-1) == GarrysMod::Lua::Type::Function) {
@ -321,11 +365,15 @@ void LuaDatabase::think(ILuaBase *LUA) {
LUA->PushString(error.c_str());
pcallWithErrorReporter(LUA, 2);
}
LUA->Pop(); //Callback function
}
LUA->Pop(); // DB Table
LuaReferenceFree(LUA, this->m_tableReference);
this->m_tableReference = 0;
if (!this->m_hasOnDisconnected) {
// Only free the table reference if we do not have an onDisconnected callback.
// Otherwise, it will be freed after the onDisconnected callback was called.
LuaReferenceFree(LUA, this->m_tableReference);
this->m_tableReference = 0;
}
}
//Run callbacks of finished queries
@ -333,6 +381,21 @@ void LuaDatabase::think(ILuaBase *LUA) {
for (auto &pair: finishedQueries) {
LuaQuery::runCallback(LUA, pair.first, pair.second);
}
if (database->wasDisconnected() && this->m_hasOnDisconnected && this->m_tableReference != 0) {
this->m_hasOnDisconnected = false;
LUA->ReferencePush(this->m_tableReference);
LUA->GetField(-1, "onDisconnected");
if (LUA->GetType(-1) == GarrysMod::Lua::Type::Function) {
LUA->ReferencePush(this->m_tableReference);
pcallWithErrorReporter(LUA, 1);
}
LUA->Pop(1); // DB Table
LuaReferenceFree(LUA, this->m_tableReference);
}
}
void LuaDatabase::onDestroyedByLua(ILuaBase *LUA) {

View File

@ -17,6 +17,7 @@ public:
void think(ILuaBase *LUA);
int m_tableReference = 0;
bool m_hasOnDisconnected = false;
std::shared_ptr<Database> m_database;
bool m_dbCallbackRan = false;

View File

@ -68,6 +68,11 @@ LUA_FUNCTION(errorReporter) {
return 1;
}
/**
* Similar to LUA->PCall but also uses an error reporter and prints the
* error to the console using ErrorNoHalt (if it exists).
* Consumes the function and all nargs arguments on the stack, does not return any values.
*/
void LuaObject::pcallWithErrorReporter(ILuaBase *LUA, int nargs) {
LUA->PushCFunction(errorReporter);
int errorHandlerIndex = LUA->Top() - nargs - 1;

View File

@ -1,7 +1,6 @@
#include "Database.h"
#include "MySQLOOException.h"
#include <string>
#include <cstring>
#include <iostream>
#include <utility>
#include "mysqld_error.h"
@ -216,7 +215,13 @@ void Database::disconnect(bool wait) {
if (wait && m_thread.joinable()) {
m_thread.join();
}
disconnected = true;
}
/*
* Returns true after the database has been fully disconnected and no more queries are in the queue.
*/
bool Database::wasDisconnected() {
return disconnected;
}
/* Returns the status of the database, constants can be found in GMModule
@ -362,6 +367,7 @@ void Database::connectRun() {
if (m_status == DATABASE_CONNECTED) {
m_status = DATABASE_NOT_CONNECTED;
}
disconnected = true;
});
{
auto connectionSignaler = finally([&] { m_connectWakeupVariable.notify_one(); });
@ -374,6 +380,7 @@ void Database::connectRun() {
m_status = DATABASE_CONNECTION_FAILED;
return;
}
this->applyTimeoutSettings();
this->customSSLSettings.applySSLSettings(this->m_sql);
const char *socketStr = (this->socket.length() == 0) ? nullptr : this->socket.c_str();
unsigned long clientFlag = (this->useMultiStatements) ? CLIENT_MULTI_STATEMENTS : 0;
@ -406,6 +413,8 @@ void Database::connectRun() {
}
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "misc-no-recursion"
void Database::runQuery(const std::shared_ptr<IQuery>& query, const std::shared_ptr<IQueryData>& data, bool retry) {
try {
query->executeStatement(*this, this->m_sql, data);
@ -427,6 +436,7 @@ void Database::runQuery(const std::shared_ptr<IQuery>& query, const std::shared_
}
}
}
#pragma clang diagnostic pop
/* The run method of the thread of the database instance.
*/
@ -469,11 +479,34 @@ void Database::run() {
bool Database::attemptReconnect() {
bool success;
my_bool reconnect = '1';
mysql_optionsv(this->m_sql, MYSQL_OPT_RECONNECT, &reconnect);
success = mariadb_reconnect(this->m_sql) == 0;
reconnect = '0';
mysql_optionsv(this->m_sql, MYSQL_OPT_RECONNECT, &reconnect);
return success;
}
void Database::setConnectTimeout(unsigned int timeout) {
this->connectTimeout = timeout;
}
void Database::setReadTimeout(unsigned int timeout) {
this->readTimeout = timeout;
}
void Database::setWriteTimeout(unsigned int timeout) {
this->writeTimeout = timeout;
}
void Database::applyTimeoutSettings() {
if (this->connectTimeout > 0) {
mysql_optionsv(this->m_sql, MYSQL_OPT_CONNECT_TIMEOUT, &this->connectTimeout);
}
if (this->readTimeout > 0) {
mysql_optionsv(this->m_sql, MYSQL_OPT_READ_TIMEOUT, &this->readTimeout);
}
if (this->writeTimeout > 0) {
mysql_optionsv(this->m_sql, MYSQL_OPT_WRITE_TIMEOUT, &this->writeTimeout);
}
}
StatementHandle::StatementHandle(MYSQL_STMT *stmt, bool valid) : stmt(stmt), valid(valid) {}

View File

@ -95,6 +95,12 @@ public:
void disconnect(bool wait);
void setConnectTimeout(unsigned int timeout);
void setReadTimeout(unsigned int timeout);
void setWriteTimeout(unsigned int timeout);
void setSSLSettings(const SSLSettings &settings);
bool isConnectionDone() { return m_connectionDone; }
@ -109,6 +115,7 @@ public:
return finishedQueries.clear();
}
bool wasDisconnected();
private:
Database(std::string host, std::string username, std::string pw, std::string database, unsigned int port,
std::string unixSocket);
@ -132,6 +139,8 @@ private:
void waitForQuery(const std::shared_ptr<IQuery> &query, const std::shared_ptr<IQueryData> &data);
void applyTimeoutSettings();
BlockingQueue<std::pair<std::shared_ptr<IQuery>, std::shared_ptr<IQueryData>>> finishedQueries{};
BlockingQueue<std::pair<std::shared_ptr<IQuery>, std::shared_ptr<IQueryData>>> queryQueue{};
std::unordered_set<std::shared_ptr<StatementHandle>> cachedStatements{};
@ -150,10 +159,10 @@ private:
bool shouldAutoReconnect = true;
bool useMultiStatements = true;
bool startedConnecting = false;
bool disconnected = false;
bool m_canWait = false;
std::pair<std::shared_ptr<IQuery>, std::shared_ptr<IQueryData>> m_waitingQuery = {nullptr, nullptr};
std::atomic<bool> m_success{true};
std::atomic<bool> disconnected { false };
std::atomic<bool> m_connectionDone{false};
std::atomic<bool> cachePreparedStatements{true};
std::condition_variable m_queryWakeupVariable{};
@ -165,6 +174,9 @@ private:
std::string socket;
unsigned int port;
SSLSettings customSSLSettings{};
unsigned int readTimeout = 0;
unsigned int writeTimeout = 0;
unsigned int connectTimeout = 0;
std::atomic<DatabaseStatus> m_status{DATABASE_NOT_CONNECTED};
};