Initial commit
This commit is contained in:
519
gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua
Normal file
519
gamemodes/darkrp/gamemode/libraries/mysqlite/mysqlite.lua
Normal file
@@ -0,0 +1,519 @@
|
||||
--[[
|
||||
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"
|
||||
MultiStatements :: Bool - Only available in tmysql4: allow multiple SQL statements per query
|
||||
}
|
||||
|
||||
----------------------------- 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.
|
||||
|
||||
MySQLite.prepare(sqlText :: String, sqlParams :: Table, callback :: function, errorCallback :: function)
|
||||
Calls a prepared statement, faster for queries that are ran multiple times. Params do not need to be escaped.
|
||||
This does not work with SQLite, and will instead call a regular query
|
||||
|
||||
callback format:
|
||||
function(result :: table, lastInsert :: number, rowsChanged :: 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)
|
||||
rowsChanged is the amount of rows that were changed with the query
|
||||
|
||||
Note: lastInsert is NOT supported when using SQLite.
|
||||
|
||||
The errorCallback format is the same as in MySQLite.query.
|
||||
|
||||
Example:
|
||||
MySQLite.prepare("UPDATE `darkrp_player` SET `rpname` = ? WHERE `rpname` = ?", { "players_old_name", "players_new_name"}, success_callback, error_callback)
|
||||
|
||||
----------------------------- 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 debug = debug
|
||||
local error = error
|
||||
local ErrorNoHalt = ErrorNoHalt
|
||||
local hook = hook
|
||||
local pairs = pairs
|
||||
local require = require
|
||||
local sql = sql
|
||||
local string = string
|
||||
local table = table
|
||||
local timer = timer
|
||||
local tostring = tostring
|
||||
local mysqlOO
|
||||
local TMySQL
|
||||
local _G = _G
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local unpack = unpack
|
||||
|
||||
local multistatements
|
||||
|
||||
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
|
||||
|
||||
local moo, tmsql = util.IsBinaryModuleInstalled("mysqloo"), util.IsBinaryModuleInstalled("tmysql4")
|
||||
|
||||
if not moo and not tmsql then
|
||||
error("Could not find a suitable MySQL module. Please either:\n - Install tmysql. It can be obtained from https://github.com/SuperiorServers/gm_tmysql4\n - Install MySQLOO. It can be obtained from https://github.com/FredyH/MySQLOO\nDue to this error, MySQL is disabled. This means that SQLite is used instead to store data.")
|
||||
end
|
||||
moduleLoaded = true
|
||||
|
||||
require(
|
||||
moo and tmsql and MySQLite_config.Preferred_module or
|
||||
moo and "mysqloo" or
|
||||
tmsql and "tmysql4"
|
||||
)
|
||||
|
||||
multistatements = CLIENT_MULTI_STATEMENTS
|
||||
|
||||
mysqlOO = mysqloo
|
||||
TMySQL = tmysql
|
||||
|
||||
if MySQLite_config.Preferred_module == "tmysql4" then
|
||||
if not tmsql then
|
||||
ErrorNoHalt("The preferred module for MySQL is selected to be tmysql4. However, tmysql4 does not appear to be installed. Please either:\n - Install tmysql. It can be obtained from https://github.com/SuperiorServers/gm_tmysql4\n - Select MySQLOO as the preferred module for MySQL. MySQLOO appears to be installed.")
|
||||
return
|
||||
end
|
||||
|
||||
if not tmysql.Version or tmysql.Version < 4.1 then
|
||||
MsgC(Color(255, 0, 0), "Using older tmysql version, please consider updating!\n")
|
||||
MsgC(Color(255, 0, 0), "Newer Version: https://github.com/SuperiorServers/gm_tmysql4\n")
|
||||
end
|
||||
|
||||
-- Turns tmysql.Connect into tmysql.Initialize if they're using an older version.
|
||||
TMySQL.Connect = tmysql.Version and tmysql.Version >= 4.1 and TMySQL.Connect or TMySQL.initialize
|
||||
TMySQL.SetOption = tmysql.Version and tmysql.Version >= 4.1 and TMySQL.SetOption or TMySQL.Option
|
||||
else
|
||||
if not moo then
|
||||
ErrorNoHalt("The preferred module for MySQL is selected to be MySQLOO. However, MySQLOO does not appear to be installed. Please either:\n - Install MySQLOO. It can be obtained from https://github.com/FredyH/MySQLOO\n - Select tmysql4 as the preferred module for MySQL. tmysql4 appears to be installed.")
|
||||
end
|
||||
end
|
||||
end
|
||||
loadMySQLModule()
|
||||
|
||||
module("MySQLite")
|
||||
|
||||
-- Helper function to return the first value found when iterating over a table.
|
||||
-- Replaces the now deprecated table.GetFirstValue
|
||||
local function arbitraryTableValue(tbl)
|
||||
for _, v in pairs(tbl) do return v end
|
||||
end
|
||||
|
||||
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
|
||||
connectToMySQL(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
|
||||
else
|
||||
timer.Simple(0, function()
|
||||
_G.GAMEMODE.DatabaseInitialized = _G.GAMEMODE.DatabaseInitialized or function() end
|
||||
hook.Call("DatabaseInitialized", _G.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
|
||||
if onFinished then onFinished() end
|
||||
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 queryObject = databaseObject:query(sqlText)
|
||||
local data
|
||||
queryObject.onData = function(Q, D)
|
||||
data = data or {}
|
||||
data[#data + 1] = D
|
||||
end
|
||||
|
||||
queryObject.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
|
||||
|
||||
queryObject.onSuccess = function()
|
||||
local res = queryValue and data and data[1] and arbitraryTableValue(data[1]) or not queryValue and data or nil
|
||||
if callback then callback(res, queryObject:lastInsert()) end
|
||||
end
|
||||
queryObject:start()
|
||||
end
|
||||
|
||||
local preparedStatements = {}
|
||||
local paramTypes = {}
|
||||
paramTypes["number"] = function(queryObj, paramIndex, paramValue) return queryObj:setNumber(paramIndex, paramValue) end
|
||||
paramTypes["string"] = function(queryObj, paramIndex, paramValue) return queryObj:setString(paramIndex, paramValue) end
|
||||
paramTypes["boolean"] = function(queryObj, paramIndex, paramValue) return queryObj:setBoolean(paramIndex, paramValue) end
|
||||
|
||||
local function msOOPrepare(sqlText, sqlParams, callback, errorCallback)
|
||||
local queryObject
|
||||
|
||||
if preparedStatements[sqlText] then
|
||||
queryObject = preparedStatements[sqlText]
|
||||
else
|
||||
queryObject = databaseObject:prepare(sqlText)
|
||||
preparedStatements[sqlText] = queryObject
|
||||
end
|
||||
|
||||
for i, param in ipairs(sqlParams) do
|
||||
local paramType = type(param)
|
||||
|
||||
if paramTypes[paramType] then
|
||||
paramTypes[paramType](queryObject, i, param)
|
||||
else
|
||||
queryObject:setString(i, param)
|
||||
end
|
||||
end
|
||||
|
||||
queryObject.onError = function(_, E)
|
||||
local supp = errorCallback and errorCallback(E, sqlText)
|
||||
if not supp then error(E .. " (" .. sqlText .. ")") end
|
||||
end
|
||||
|
||||
queryObject.onSuccess = function(_, data)
|
||||
if callback then callback(data, queryObject:lastInsert(), queryObject:affectedRows()) end
|
||||
end
|
||||
|
||||
queryObject:start()
|
||||
end
|
||||
|
||||
local function tmsqlPrepare(sqlText, sqlParams, callback, errorCallback)
|
||||
local queryObject
|
||||
|
||||
if preparedStatements[sqlText] then
|
||||
queryObject = preparedStatements[sqlText]
|
||||
else
|
||||
queryObject = databaseObject:Prepare(sqlText)
|
||||
preparedStatements[sqlText] = queryObject
|
||||
end
|
||||
|
||||
for i, param in ipairs(sqlParams) do
|
||||
param = SQLStr(param, true)
|
||||
end
|
||||
|
||||
local varcount = queryObject:GetArgCount()
|
||||
|
||||
sqlParams[varcount + 1] = function(results)
|
||||
if results[1].error ~= nil then
|
||||
local supp = errorCallback and errorCallback(E, results[1].error)
|
||||
if not supp then error(E .. " (" .. results[1].error .. ")") end
|
||||
end
|
||||
|
||||
if callback then callback(data, results[1].lastid, results[1].affected) end
|
||||
end
|
||||
|
||||
queryObject:Run(unpack(sqlParams, 1, varcount + 2))
|
||||
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 arbitraryTableValue(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)
|
||||
sql.m_strError = "" -- reset last error
|
||||
|
||||
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 supp == false then error(err .. " (" .. sqlText .. ")", 2) end
|
||||
return
|
||||
end
|
||||
|
||||
if callback then callback(Result) end
|
||||
return Result
|
||||
end
|
||||
|
||||
-- SQLite doesn't support preparedStatements, so convert it to a regular query.
|
||||
local function SQLitePrepare(sqlText, sqlParams, callback, errorCallback)
|
||||
for _, param in ipairs(sqlParams) do
|
||||
sqlText = sqlText:gsub("%?", sql.SQLStr(param), 1)
|
||||
end
|
||||
|
||||
return SQLiteQuery(sqlText, callback, errorCallback, false)
|
||||
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
|
||||
|
||||
function prepare(sqlText, sqlParams, callback, errorCallback)
|
||||
local qFunc = (CONNECTED_TO_MYSQL and ((mysqlOO and msOOPrepare) or (TMySQL and tmsqlPrepare))) or SQLitePrepare
|
||||
return qFunc(sqlText, sqlParams, callback, errorCallback, false)
|
||||
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 = {}
|
||||
local GM = _G.GAMEMODE or _G.GM
|
||||
|
||||
hook.Call("DatabaseInitialized", GM.DatabaseInitialized and GM or nil)
|
||||
|
||||
end
|
||||
|
||||
msOOConnect = function(host, username, password, database_name, database_port)
|
||||
databaseObject = mysqlOO.connect(host, username, password, database_name, database_port)
|
||||
|
||||
if timer.Exists("darkrp_check_mysql_status") then timer.Remove("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)
|
||||
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.Connect(host, username, password, database_name, database_port, nil, MySQLite_config.MultiStatements and multistatements or nil)
|
||||
if err then error("Connection failed! " .. err .. "\n") end
|
||||
|
||||
databaseObject = db
|
||||
onConnected()
|
||||
|
||||
if (TMySQL.Version and TMySQL.Version >= 4.1) then
|
||||
hook.Add("Think", "MySQLite:tmysqlPoll", function()
|
||||
db:Poll()
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function connectToMySQL(host, username, password, database_name, database_port)
|
||||
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)
|
||||
end
|
||||
|
||||
function SQLStr(sqlStr)
|
||||
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(sqlStr)
|
||||
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
|
||||
Reference in New Issue
Block a user