rp_core (sessions, scrypt auth, self-written MySQL-wire driver rp_db), county-records NUI: residency-file auth, identity-record character intake, survey-grid spawn, procedural night-city loading screen. Secrets replaced with .example files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
88 lines
2.8 KiB
Lua
88 lines
2.8 KiB
Lua
-- ---------------------------------------------------------------------------
|
|
-- DB - Lua front end for rp_db.
|
|
--
|
|
-- Loaded *into* the consuming resource with:
|
|
-- server_scripts { '@rp_db/lib/db.lua', 'server/whatever.lua' }
|
|
--
|
|
-- so that Citizen.Await suspends the caller's own coroutine instead of trying
|
|
-- to yield across a cross-resource export call.
|
|
--
|
|
-- Every function comes in two flavours:
|
|
-- DB.Query(sql, params) -> rows, err (blocking, inside a thread)
|
|
-- DB.QueryAsync(sql, params, cb) -> cb(rows, err) (callback, anywhere)
|
|
--
|
|
-- Values are always passed as `?` parameters. Never build SQL by concatenation.
|
|
-- Use DB.NULL where you need a literal SQL NULL (Lua nil cannot survive a table).
|
|
-- ---------------------------------------------------------------------------
|
|
|
|
DB = {}
|
|
|
|
DB.NULL = 'RP_NULL'
|
|
|
|
local backend = exports.rp_db
|
|
|
|
local function async(method)
|
|
return function(sql, params, cb)
|
|
backend[method](backend, sql, params or {}, cb)
|
|
end
|
|
end
|
|
|
|
local function blocking(method)
|
|
return function(sql, params)
|
|
local p = promise.new()
|
|
backend[method](backend, sql, params or {}, function(result, err)
|
|
-- wrapped: a promise resolved with nil would never wake the coroutine
|
|
p:resolve({ result = result, err = err })
|
|
end)
|
|
local r = Citizen.Await(p)
|
|
if r.err then return nil, r.err end
|
|
return r.result
|
|
end
|
|
end
|
|
|
|
--- Returns all rows as an array of tables (empty table when nothing matched).
|
|
DB.Query = blocking('query')
|
|
--- Returns the first row as a table, or nil.
|
|
DB.Single = blocking('single')
|
|
--- Returns the first column of the first row, or nil.
|
|
DB.Scalar = blocking('scalar')
|
|
--- Returns the AUTO_INCREMENT id produced by an INSERT.
|
|
DB.Insert = blocking('insert')
|
|
--- Returns the number of rows changed by an UPDATE/DELETE.
|
|
DB.Update = blocking('update')
|
|
|
|
DB.QueryAsync = async('query')
|
|
DB.SingleAsync = async('single')
|
|
DB.ScalarAsync = async('scalar')
|
|
DB.InsertAsync = async('insert')
|
|
DB.UpdateAsync = async('update')
|
|
|
|
--- All statements commit together or none of them do.
|
|
--- statements: { { query = 'UPDATE ..', values = { 1, 2 } }, ... }
|
|
function DB.Transaction(statements)
|
|
local p = promise.new()
|
|
backend:transaction(statements, function(ok, err)
|
|
p:resolve({ ok = ok, err = err })
|
|
end)
|
|
local r = Citizen.Await(p)
|
|
if not r.ok then return false, r.err end
|
|
return true
|
|
end
|
|
|
|
function DB.TransactionAsync(statements, cb)
|
|
backend:transaction(statements, cb)
|
|
end
|
|
|
|
--- Blocks until the connection pool has at least one live connection.
|
|
--- Returns false if the database never came up within `timeout` ms.
|
|
function DB.WaitReady(timeout)
|
|
local waited, step = 0, 100
|
|
timeout = timeout or 30000
|
|
while not backend:isReady() do
|
|
if waited >= timeout then return false end
|
|
Citizen.Wait(step)
|
|
waited = waited + step
|
|
end
|
|
return true
|
|
end
|