· 6 years ago · May 10, 2019, 02:46 PM
1--[[
2 MySQLite - Abstraction mechanism for SQLite and MySQL by FPtje
3 Why use this?
4 - Easy to use interface for MySQL
5 - No need to modify code when switching between SQLite and MySQL
6 - Queued queries: execute a bunch of queries in order an run the callback when all queries are done
7 License: LGPL V2.1 (read here: https://www.gnu.org/licenses/lgpl-2.1.html)
8]]
9
10local bit = bit
11local debug = debug
12local error = error
13local ErrorNoHalt = ErrorNoHalt
14local hook = hook
15local include = include
16local pairs = pairs
17local require = require
18local sql = sql
19local string = string
20local table = table
21local timer = timer
22local tostring = tostring
23local GAMEMODE = GM or GAMEMODE
24local mysqlOO
25local TMySQL
26local _G = _G
27local print = print
28
29local multistatements
30
31local MySQLite_config = MySQLite_config or RP_MySQLConfig or FPP_MySQLConfig
32local moduleLoaded
33
34local function loadMySQLModule()
35 if moduleLoaded or not MySQLite_config or not MySQLite_config.EnableMySQL then return end
36
37 local moo, tmsql = file.Exists("bin/gmsv_mysqloo_*.dll", "LUA"), file.Exists("bin/gmsv_tmysql4_*.dll", "LUA")
38
39 if not moo and not tmsql then
40 error("Could not find a suitable MySQL module. Supported modules are MySQLOO and tmysql4.")
41 end
42 moduleLoaded = true
43
44 require(moo and tmsql and MySQLite_config.Preferred_module or
45 moo and "mysqloo" or
46 "tmysql4")
47
48 multistatements = CLIENT_MULTI_STATEMENTS
49
50 mysqlOO = mysqloo
51 TMySQL = tmysql
52end
53loadMySQLModule()
54
55module("MySQLite_AWarn")
56
57
58function initialize(config)
59 MySQLite_config = config or MySQLite_config
60
61 if not MySQLite_config then
62 ErrorNoHalt("Warning: No MySQL config!")
63 end
64
65 loadMySQLModule()
66
67 if MySQLite_config.EnableMySQL then
68 connectToMySQL(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
69 else
70 timer.Simple(0, function()
71 --hook.Call("DatabaseInitialized", GAMEMODE)
72 end)
73 end
74end
75
76local CONNECTED_TO_MYSQL = false
77local msOOConnect
78databaseObject = nil
79
80local queuedQueries
81local cachedQueries
82
83function isMySQL()
84 return CONNECTED_TO_MYSQL
85end
86
87function begin()
88 if not CONNECTED_TO_MYSQL then
89 sql.Begin()
90 else
91 if queuedQueries then
92 debug.Trace()
93 error("Transaction ongoing!")
94 end
95 queuedQueries = {}
96 end
97end
98
99function commit(onFinished)
100 if not CONNECTED_TO_MYSQL then
101 sql.Commit()
102 if onFinished then onFinished() end
103 return
104 end
105
106 if not queuedQueries then
107 error("No queued queries! Call begin() first!")
108 end
109
110 if #queuedQueries == 0 then
111 queuedQueries = nil
112 return
113 end
114
115 -- Copy the table so other scripts can create their own queue
116 local queue = table.Copy(queuedQueries)
117 queuedQueries = nil
118
119 -- Handle queued queries in order
120 local queuePos = 0
121 local call
122
123 -- Recursion invariant: queuePos > 0 and queue[queuePos] <= #queue
124 call = function(...)
125 queuePos = queuePos + 1
126
127 if queue[queuePos].callback then
128 queue[queuePos].callback(...)
129 end
130
131 -- Base case, end of the queue
132 if queuePos + 1 > #queue then
133 if onFinished then onFinished() end -- All queries have finished
134 return
135 end
136
137 -- Recursion
138 local nextQuery = queue[queuePos + 1]
139 query(nextQuery.query, call, nextQuery.onError)
140 end
141
142 query(queue[1].query, call, queue[1].onError)
143end
144
145function queueQuery(sqlText, callback, errorCallback)
146 if CONNECTED_TO_MYSQL then
147 table.insert(queuedQueries, {query = sqlText, callback = callback, onError = errorCallback})
148 return
149 end
150 -- SQLite is instantaneous, simply running the query is equal to queueing it
151 query(sqlText, callback, errorCallback)
152end
153
154local function msOOQuery(sqlText, callback, errorCallback, queryValue)
155 local query = databaseObject:query(sqlText)
156 local data
157 query.onData = function(Q, D)
158 data = data or {}
159 data[#data + 1] = D
160 end
161
162 query.onError = function(Q, E)
163 if databaseObject:status() == mysqlOO.DATABASE_NOT_CONNECTED then
164 table.insert(cachedQueries, {sqlText, callback, queryValue})
165
166 -- Immediately try reconnecting
167 msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
168 return
169 end
170
171 local supp = errorCallback and errorCallback(E, sqlText)
172 if not supp then error(E .. " (" .. sqlText .. ")") end
173 end
174
175 query.onSuccess = function()
176 local res = queryValue and data and data[1] and table.GetFirstValue(data[1]) or not queryValue and data or nil
177 if callback then callback(res, query:lastInsert()) end
178 end
179 query:start()
180end
181
182local function tmsqlQuery(sqlText, callback, errorCallback, queryValue)
183 local call = function(res)
184 res = res[1] -- For now only support one result set
185 if not res.status then
186 local supp = errorCallback and errorCallback(res.error, sqlText)
187 if not supp then error(res.error .. " (" .. sqlText .. ")") end
188 return
189 end
190
191 if not res.data or #res.data == 0 then res.data = nil end -- compatibility with other backends
192 if queryValue and callback then return callback(res.data and res.data[1] and table.GetFirstValue(res.data[1]) or nil) end
193 if callback then callback(res.data, res.lastid) end
194 end
195
196 databaseObject:Query(sqlText, call)
197end
198
199local function SQLiteQuery(sqlText, callback, errorCallback, queryValue)
200 local lastError = sql.LastError()
201 local Result = queryValue and sql.QueryValue(sqlText) or sql.Query(sqlText)
202
203 if sql.LastError() and sql.LastError() ~= lastError then
204 local err = sql.LastError()
205 local supp = errorCallback and errorCallback(err, sqlText)
206 if not supp then error(err .. " (" .. sqlText .. ")") end
207 return
208 end
209
210 if callback then callback(Result) end
211 return Result
212end
213
214function query(sqlText, callback, errorCallback)
215 local qFunc = (CONNECTED_TO_MYSQL and
216 mysqlOO and msOOQuery or
217 TMySQL and tmsqlQuery) or
218 SQLiteQuery
219 return qFunc(sqlText, callback, errorCallback, false)
220end
221
222function queryValue(sqlText, callback, errorCallback)
223 local qFunc = (CONNECTED_TO_MYSQL and
224 mysqlOO and msOOQuery or
225 TMySQL and tmsqlQuery) or
226 SQLiteQuery
227 return qFunc(sqlText, callback, errorCallback, true)
228end
229
230local function onConnected()
231 CONNECTED_TO_MYSQL = true
232
233 -- Run the queries that were called before the connection was made
234 for k, v in pairs(cachedQueries or {}) do
235 cachedQueries[k] = nil
236 if v[3] then
237 queryValue(v[1], v[2])
238 else
239 query(v[1], v[2])
240 end
241 end
242 cachedQueries = {}
243
244 --hook.Call("DatabaseInitialized", GAMEMODE.DatabaseInitialized and GAMEMODE or nil)
245end
246
247msOOConnect = function(host, username, password, database_name, database_port)
248 databaseObject = mysqlOO.connect(host, username, password, database_name, database_port)
249
250 if timer.Exists("darkrp_check_mysql_status") then timer.Remove("darkrp_check_mysql_status") end
251
252 databaseObject.onConnectionFailed = function(_, msg)
253 timer.Simple(5, function()
254 msOOConnect(MySQLite_config.Host, MySQLite_config.Username, MySQLite_config.Password, MySQLite_config.Database_name, MySQLite_config.Database_port)
255 end)
256 error("Connection failed! " .. tostring(msg) .. "\nTrying again in 5 seconds.")
257 end
258
259 databaseObject.onConnected = onConnected
260
261 databaseObject:connect()
262end
263
264local function tmsqlConnect(host, username, password, database_name, database_port)
265 local db, err = TMySQL.initialize(host, username, password, database_name, database_port, nil, MySQLite_config.MultiStatements and multistatements or nil)
266 if err then error("Connection failed! " .. err .. "\n") end
267
268 databaseObject = db
269 onConnected()
270end
271
272function connectToMySQL(host, username, password, database_name, database_port)
273 database_port = database_port or 3306
274 local func = mysqlOO and msOOConnect or TMySQL and tmsqlConnect or function() end
275 func(host, username, password, database_name, database_port)
276end
277
278function SQLStr(str)
279 local escape =
280 not CONNECTED_TO_MYSQL and sql.SQLStr or
281 mysqlOO and function(str) return "\"" .. databaseObject:escape(tostring(str)) .. "\"" end or
282 TMySQL and function(str) return "\"" .. databaseObject:Escape(tostring(str)) .. "\"" end
283
284 return escape(str)
285end
286
287function tableExists(tbl, callback, errorCallback)
288 if not CONNECTED_TO_MYSQL then
289 local exists = sql.TableExists(tbl)
290 callback(exists)
291 return exists
292 end
293
294 queryValue(string.format("SHOW TABLES LIKE %s", SQLStr(tbl)), function(v)
295 callback(v ~= nil)
296 end, errorCallback)
297end