dobrograd-13-06-2022/garrysmod/gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua
Jonny_Bro (Nikita) e4d5311906 first commit
2023-11-16 15:01:19 +05:00

388 lines
12 KiB
Lua

--[[
MySQLite - Abstraction mechanism for SQLite and MySQL
Why use this?
- Easy to use interface for MySQL
- No need to modify code when switching between SQLite and MySQL
- Queued queries: execute a bunch of queries in order an run the callback when all queries are done
License: LGPL V2.1 (read here: https://www.gnu.org/licenses/lgpl-2.1.html)
Supported MySQL modules:
- MySQLOO
- tmysql4
Note: When both MySQLOO and tmysql4 modules are installed, MySQLOO is used by default.
/*---------------------------------------------------------------------------
Documentation
---------------------------------------------------------------------------*/
MySQLite.initialize([config :: table]) :: No value
Initialize MySQLite. Loads the config from either the config parameter OR the MySQLite_config global.
This loads the module (if necessary) and connects to the MySQL database (if set up).
The config must have this layout:
{
EnableMySQL :: Bool - set to true to use MySQL, false for SQLite
Host :: String - database hostname
Username :: String - database username
Password :: String - database password (keep away from clients!)
Database_name :: String - name of the database
Database_port :: Number - connection port (3306 by default)
Preferred_module :: String - Preferred module, case sensitive, must be either "mysqloo" or "tmysql4"
}
----------------------------- Utility functions -----------------------------
MySQLite.isMySQL() :: Bool
Returns whether MySQLite is set up to use MySQL. True for MySQL, false for SQLite.
Use this when the query syntax between SQLite and MySQL differs (example: AUTOINCREMENT vs AUTO_INCREMENT)
MySQLite.SQLStr(str :: String) :: String
Escapes the string and puts it in quotes.
It uses the escaping method of the module that is currently being used.
MySQLite.tableExists(tbl :: String, callback :: function, errorCallback :: function)
Checks whether table tbl exists.
callback format: function(res :: Bool)
res is a boolean indicating whether the table exists.
The errorCallback format is the same as in MySQLite.query.
----------------------------- Running queries -----------------------------
MySQLite.query(sqlText :: String, callback :: function, errorCallback :: function) :: No value
Runs a query. Calls the callback parameter when finished, calls errorCallback when an error occurs.
callback format:
function(result :: table, lastInsert :: number)
Result is the table with results (nil when there are no results or when the result list is empty)
lastInsert is the row number of the last inserted value (use with AUTOINCREMENT)
Note: lastInsert is NOT supported when using SQLite.
errorCallback format:
function(error :: String, query :: String) :: Bool
error is the error given by the database module.
query is the query that triggered the error.
Return true to suppress the error!
MySQLite.queryValue(sqlText :: String, callback :: function, errorCallback :: function) :: No value
Runs a query and returns the first value it comes across.
callback format:
function(result :: any)
where the result is either a string or a number, depending on the requested database field.
The errorCallback format is the same as in MySQLite.query.
----------------------------- Transactions -----------------------------
MySQLite.begin() :: No value
Starts a transaction. Use in combination with MySQLite.queueQuery and MySQLite.commit.
MySQLite.queueQuery(sqlText :: String, callback :: function, errorCallback :: function) :: No value
Queues a query in the transaction. Note: a transaction must be started with MySQLite.begin() for this to work.
The callback will be called when this specific query has been executed successfully.
The errorCallback function will be called when an error occurs in this specific query.
See MySQLite.query for the callback and errorCallback format.
MySQLite.commit(onFinished)
Commits a transaction and calls onFinished when EVERY queued query has finished.
onFinished is NOT called when an error occurs in one of the queued queries.
onFinished is called without arguments.
----------------------------- Hooks -----------------------------
DatabaseInitialized
Called when a successful connection to the database has been made.
]]
local bit = bit
local debug = debug
local error = error
local ErrorNoHalt = ErrorNoHalt
local hook = hook
local include = include
local pairs = pairs
local require = require
local sql = sql
local string = string
local table = table
local timer = timer
local tostring = tostring
local GAMEMODE = GM or GAMEMODE
local mysqlOO
local TMySQL
local _G = _G
local MySQLite_config = MySQLite_config or RP_MySQLConfig or FPP_MySQLConfig
local moduleLoaded
local function loadMySQLModule()
if moduleLoaded or not MySQLite_config or not MySQLite_config.EnableMySQL then return end
moo, tmsql = file.Exists("bin/gmsv_mysqloo_*.dll", "LUA"), file.Exists("bin/gmsv_tmysql4_*.dll", "LUA")
if not moo and not tmsql then
error("Could not find a suitable MySQL module. Supported modules are MySQLOO and tmysql4.")
end
moduleLoaded = true
require(moo and tmsql and MySQLite_config.Preferred_module or
moo and "mysqloo" or
"tmysql4")
mysqlOO = mysqloo
TMySQL = tmysql
end
loadMySQLModule()
module("MySQLite")
function initialize(config)
MySQLite_config = config or MySQLite_config
if not MySQLite_config then
ErrorNoHalt("Warning: No MySQL config!")
end
loadMySQLModule()
if MySQLite_config.EnableMySQL then
timer.Simple(1, function()
connectToMySQL(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port, MySQLite_config.Database_socket)
end)
else
timer.Simple(0, function()
GAMEMODE.DatabaseInitialized = GAMEMODE.DatabaseInitialized or function() end
hook.Call("DatabaseInitialized", GAMEMODE)
end)
end
end
local CONNECTED_TO_MYSQL = false
local msOOConnect
databaseObject = nil
local queuedQueries
local cachedQueries
function isMySQL()
return CONNECTED_TO_MYSQL
end
function begin()
if not CONNECTED_TO_MYSQL then
sql.Begin()
else
if queuedQueries then
debug.Trace()
error("Transaction ongoing!")
end
queuedQueries = {}
end
end
function commit(onFinished)
if not CONNECTED_TO_MYSQL then
sql.Commit()
if onFinished then onFinished() end
return
end
if not queuedQueries then
error("No queued queries! Call begin() first!")
end
if #queuedQueries == 0 then
queuedQueries = nil
return
end
-- Copy the table so other scripts can create their own queue
local queue = table.Copy(queuedQueries)
queuedQueries = nil
-- Handle queued queries in order
local queuePos = 0
local call
-- Recursion invariant: queuePos > 0 and queue[queuePos] <= #queue
call = function(...)
queuePos = queuePos + 1
if queue[queuePos].callback then
queue[queuePos].callback(...)
end
-- Base case, end of the queue
if queuePos + 1 > #queue then
if onFinished then onFinished() end -- All queries have finished
return
end
-- Recursion
local nextQuery = queue[queuePos + 1]
query(nextQuery.query, call, nextQuery.onError)
end
query(queue[1].query, call, queue[1].onError)
end
function queueQuery(sqlText, callback, errorCallback)
if CONNECTED_TO_MYSQL then
table.insert(queuedQueries, {query = sqlText, callback = callback, onError = errorCallback})
return
end
-- SQLite is instantaneous, simply running the query is equal to queueing it
query(sqlText, callback, errorCallback)
end
local function msOOQuery(sqlText, callback, errorCallback, queryValue)
local query = databaseObject:query(sqlText)
local data
query.onData = function(Q, D)
data = data or {}
data[#data + 1] = D
end
query.onError = function(Q, E)
if databaseObject:status() == mysqlOO.DATABASE_NOT_CONNECTED then
table.insert(cachedQueries, {sqlText, callback, queryValue})
-- Immediately try reconnecting
msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
return
end
local supp = errorCallback and errorCallback(E, sqlText)
if not supp then error(E .. " (" .. sqlText .. ")") end
end
query.onSuccess = function()
local res = queryValue and data and data[1] and table.GetFirstValue(data[1]) or not queryValue and data or nil
if callback then callback(res, query:lastInsert()) end
end
query:start()
end
local function tmsqlQuery(sqlText, callback, errorCallback, queryValue)
local call = function(res)
res = res[1] -- For now only support one result set
if not res.status then
local supp = errorCallback and errorCallback(res.error, sqlText)
if not supp then error(res.error .. " (" .. sqlText .. ")") end
return
end
if not res.data or #res.data == 0 then res.data = nil end -- compatibility with other backends
if queryValue and callback then return callback(res.data and res.data[1] and table.GetFirstValue(res.data[1]) or nil) end
if callback then callback(res.data, res.lastid) end
end
databaseObject:Query(sqlText, call)
end
local function SQLiteQuery(sqlText, callback, errorCallback, queryValue)
local lastError = sql.LastError()
local Result = queryValue and sql.QueryValue(sqlText) or sql.Query(sqlText)
if sql.LastError() and sql.LastError() ~= lastError then
local err = sql.LastError()
local supp = errorCallback and errorCallback(err, sqlText)
if not supp then error(err .. " (" .. sqlText .. ")") end
return
end
if callback then callback(Result) end
return Result
end
function query(sqlText, callback, errorCallback)
local qFunc = (CONNECTED_TO_MYSQL and
mysqlOO and msOOQuery or
TMySQL and tmsqlQuery) or
SQLiteQuery
return qFunc(sqlText, callback, errorCallback, false)
end
function queryValue(sqlText, callback, errorCallback)
local qFunc = (CONNECTED_TO_MYSQL and
mysqlOO and msOOQuery or
TMySQL and tmsqlQuery) or
SQLiteQuery
return qFunc(sqlText, callback, errorCallback, true)
end
local function onConnected()
CONNECTED_TO_MYSQL = true
-- Run the queries that were called before the connection was made
for k, v in pairs(cachedQueries or {}) do
cachedQueries[k] = nil
if v[3] then
queryValue(v[1], v[2])
else
query(v[1], v[2])
end
end
cachedQueries = {}
hook.Call("DatabaseInitialized", GAMEMODE.DatabaseInitialized and GAMEMODE or nil)
end
msOOConnect = function(host, username, password, database_name, database_port, database_socket)
databaseObject = mysqlOO.connect(host, username, password, database_name, database_port, database_socket)
if timer.Exists("darkrp_check_mysql_status") then timer.Destroy("darkrp_check_mysql_status") end
databaseObject.onConnectionFailed = function(_, msg)
timer.Simple(5, function()
msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port, MySQLite_config.Database_socket)
end)
error("Connection failed! " .. tostring(msg) .. "\nTrying again in 5 seconds.")
end
databaseObject.onConnected = onConnected
databaseObject:connect()
end
local function tmsqlConnect(host, username, password, database_name, database_port)
local db, err = TMySQL.initialize(host, username, password, database_name, database_port)
if err then error("Connection failed! " .. err .. "\n") end
databaseObject = db
onConnected()
end
function connectToMySQL(host, username, password, database_name, database_port, database_socket)
database_port = database_port or 3306
local func = mysqlOO and msOOConnect or TMySQL and tmsqlConnect or function() end
func(host, username, password, database_name, database_port, database_socket)
end
function SQLStr(str)
local escape =
not CONNECTED_TO_MYSQL and sql.SQLStr or
mysqlOO and function(str) return "\"" .. databaseObject:escape(tostring(str)) .. "\"" end or
TMySQL and function(str) return "\"" .. databaseObject:Escape(tostring(str)) .. "\"" end
return escape(str)
end
function tableExists(tbl, callback, errorCallback)
if not CONNECTED_TO_MYSQL then
local exists = sql.TableExists(tbl)
callback(exists)
return exists
end
queryValue(string.format("SHOW TABLES LIKE %s", SQLStr(tbl)), function(v)
callback(v ~= nil)
end, errorCallback)
end