From 3fc3cfeecf0474b139a9e91af15630d7feaaaa62 Mon Sep 17 00:00:00 2001 From: Linden <65407488+thelindat@users.noreply.github.com> Date: Fri, 24 Dec 2021 03:09:40 +1100 Subject: [PATCH] feat(lib): setup new export wrappers with mysql-async aliases i.e. MySQL.Async.fetchAll = MySQL.query MySQL.Sync.fetchAll = MySQL.query.await MySQL.Async.fetchScalar = MySQL.scalar MySQL.Sync.fetchScalar = MySQL.scalar.await More information in issue #77 --- README.md | 14 +-- lib/MySQL.lua | 265 ++++++++++++++++++++------------------------------ 2 files changed, 112 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index dd0e409..7e1e3aa 100644 --- a/README.md +++ b/README.md @@ -4,31 +4,33 @@ ### Introduction Oxmysql is an alternative to the unmaintained mysql-async/ghmattimysql resources, utilising [node-mysql2](https://github.com/sidorares/node-mysql2) rather than [mysqljs](https://github.com/mysqljs/mysql). -As of v1.9.0 the preferred method of utilising oxmysql is via lib/MySQL, which can be loaded by adding `@oxmysql/lib/MySQL.lua` to your resource manifests. This resource should be 100% backwards compatible with mysql-async functionality as well as providing extra functions such as fetchSingle and prepare. +As of v1.9.0 the preferred method of utilising oxmysql is via lib/MySQL, which can be loaded by adding `@oxmysql/lib/MySQL.lua` to your resource manifests. This resource should be 100% backwards compatible with mysql-async functionality on top of providing newer export wrappers and functionality. ### Features - Support for URI connection strings and semicolon separated values - Asynchronous queries utilising mysql2/promises connection pool -- Lua promises in `lib/MySQL.lua` files for improved performance when awaiting a response - Javascript async_retval exports supports promises across resources and runtimes - Support for placeholder values (named and unnamed) to improve query speed and increase security against SQL injection - Improved error checking when placeholders and parameters do not match -- Prepared statements using internal MySQL query handling for improved performance and caching (at the cost of losing typecasting) +- Lua promises in `lib/MySQL.lua` files for improved performance when awaiting a response +- Support mysql-async syntax while providing newer (more accurate) names ### Usage ```lua -- Lua -MySQL.Async.fetchAll('SELECT * from users WHERE identifier = ?', {identifier}), function(result) +MySQL.query('SELECT * from users WHERE identifier = ?', {identifier}), function(result) -- callback response + -- same as MySQL.Async.fetchAll end) CreateThread(function() - local result = MySQL.Sync.fetchAll('SELECT * from users WHERE identifier = ?', {identifier}) + local result = MySQL.query.await('SELECT * from users WHERE identifier = ?', {identifier}) -- await a promise to resolve + -- same as MySQL.Sync.fetchAll end) ``` ```js // JS -exports.oxmysql.query_callback('SELECT * from users WHERE identifier = ?', [identifier], (result) => { +exports.oxmysql.query('SELECT * from users WHERE identifier = ?', [identifier], (result) => { // callback response }) (async() => { diff --git a/lib/MySQL.lua b/lib/MySQL.lua index 079b8e0..0547c21 100644 --- a/lib/MySQL.lua +++ b/lib/MySQL.lua @@ -1,11 +1,39 @@ --- lib/MySQL.lua provides complete compatibility for resources using mysql-async functions +-- lib/MySQL.lua provides complete compatibility for resources designed for mysql-async -- As of v2.0.0 this is the preferred method of interacting with oxmysql --- * Though some function names are not 100% accurate, mysql-async provides a "standard" (for better or worse) +-- * You can use mysql-async syntax or oxmysql syntax (refer to issue #77 or line 118) -- * Using this lib provides minor improvements to performance and helps debug poor queries --- * Resources are not bound to using oxmysql where the user may prefer another option (compatibility libraries are common) +-- * If using mysql-async syntax or newer functions, a resource is not explicity bound to using oxmysql +-- todo: new annotations; need to see if I can get it working with metatables, otherwise it'll need stubs + +local MySQL = {} +MySQL.Async = {} +MySQL.Sync = {} local Store = {} +function MySQL.ready(cb) + CreateThread(function() + repeat + Wait(50) + until GetResourceState('oxmysql') == 'started' + cb() + end) +end + +function MySQL.Async.store(query, cb) + assert(type(query) == 'string', 'The SQL Query must be a string') + local store = #Store+1 + Store[store] = query + cb(store) +end + +function MySQL.Sync.store(query) + assert(type(query) == 'string', 'The SQL Query must be a string') + local store = #Store+1 + Store[store] = query + return store +end + local function safeArgs(query, parameters, cb, transaction) if type(query) == 'number' then query = Store[query] end if transaction then @@ -36,177 +64,92 @@ local function Await(fn, query, parameters) return Citizen_Await(p) end -local MySQL = {} ---- Results will be returned to a callback function without halting the execution of the current thread. -MySQL.Async = {} ---- The current thread will yield until the query has resolved, returning results to a variable. -MySQL.Sync = {} - ----@param query string ----@param cb? function ----@return number result ---- returns the id used to reference a stored query string -function MySQL.Async.store(query, cb) - assert(type(query) == 'string', 'The SQL Query must be a string') - local store = #Store+1 - Store[store] = query - cb(store) -end - ----@param query string ----@return number result ---- returns the id used to reference a stored query string -function MySQL.Sync.store(query) - assert(type(query) == 'string', 'The SQL Query must be a string') - local store = #Store+1 - Store[store] = query - return store -end +setmetatable(MySQL, { + __index = function(self, method) + local state = GetResourceState('oxmysql') + if state == 'started' or state == 'starting' then + self[method] = setmetatable({}, { + __call = oxmysql[method], + __index = function(_, await) + assert(await == 'await', ('unable to index MySQL.%s.%s, expected .await'):format(method, await)) + self[method].await = function(query, parameters) + return Await(oxmysql[method], safeArgs(query, parameters)) + end + return self[method].await + end + }) + return self[method] + else + error(('^1oxmysql is currently %s - unable to trigger exports.oxmysql:%s^0'):format(state, method), 0) + end + end +}) + +local alias = { + fetchAll = 'query', + fetchScalar = 'scalar', + fetchSingle = 'single', + insert = 'insert', + execute = 'execute', + transaction = 'transaction', + prepare = 'prepare' +} + +setmetatable(MySQL.Async, { + __index = function(self, key) + if alias[key] then + self[key] = MySQL[alias[key]] + alias[key] = nil + return self[key] + end + end +}) + +setmetatable(MySQL.Sync, { + __index = function(self, key) + if alias[key] then + self[key] = MySQL[alias[key]].await + alias[key] = nil + return self[key] + end + end +}) ----@param query string ----@param parameters? table|function ----@param cb? function ----@return number result ---- returns number of affected rows -function MySQL.Async.execute(query, parameters, cb) - query, parameters, cb = safeArgs(query, parameters, cb) - oxmysql:update_callback(query, parameters, cb, GetCurrentResourceName) -end +--[[ +exports.oxmysql:query (previously exports.oxmysql:execute) +MySQL.Async.fetchAll = MySQL.query +MySQL.Sync.fetchAll = MySQL.query.await ----@param query string ----@param parameters? table ----@return number result ---- returns number of affected rows -function MySQL.Sync.execute(query, parameters) - return Await(oxmysql.update_callback, safeArgs(query, parameters)) -end ----@param query string ----@param parameters? table|function ----@param cb? function ----@return table result ---- returns array of matching rows or result data -function MySQL.Async.fetchAll(query, parameters, cb) - query, parameters, cb = safeArgs(query, parameters, cb) - oxmysql:query_callback(query, parameters, cb, GetCurrentResourceName) -end +exports.oxmysql:scalar +MySQL.Async.fetchScalar = MySQL.scalar +MySQL.Sync.fetchScalar = MySQL.scalar.await ----@param query string ----@param parameters? table ----@return table result ---- returns array of matching rows or result data -function MySQL.Sync.fetchAll(query, parameters) - return Await(oxmysql.query_callback, safeArgs(query, parameters)) -end ----@param query string ----@param parameters? table|function ----@param cb? function ----@return any result ---- returns value of the first column of a single row -function MySQL.Async.fetchScalar(query, parameters, cb) - query, parameters, cb = safeArgs(query, parameters, cb) - oxmysql:scalar_callback(query, parameters, cb, GetCurrentResourceName) -end +exports.oxmysql:single +MySQL.Async.fetchSingle = MySQL.single +MySQL.Sync.fetchSingle = MySQL.single.await ----@param query string ----@param parameters? table ----@return any result ---- returns value of the first column of a single row -function MySQL.Sync.fetchScalar(query, parameters) - return Await(oxmysql.scalar_callback, safeArgs(query, parameters)) -end ----@param query string ----@param parameters? table|function ----@param cb? function ----@return table result ---- returns table containing key value pairs -function MySQL.Async.fetchSingle(query, parameters, cb) - query, parameters, cb = safeArgs(query, parameters, cb) - oxmysql:single_callback(query, parameters, cb, GetCurrentResourceName) -end +exports.oxmysql:insert +MySQL.Async.insert = MySQL.insert +MySQL.Sync.insert = MySQL.insert.await ----@param query string ----@param parameters? table ----@return table result ---- returns table containing key value pairs -function MySQL.Sync.fetchSingle(query, parameters) - return Await(oxmysql.single_callback, safeArgs(query, parameters)) -end ----@param query string ----@param parameters? table|function ----@param cb? function ----@return number result ---- returns the insert id of the executed query -function MySQL.Async.insert(query, parameters, cb) - query, parameters, cb = safeArgs(query, parameters, cb) - oxmysql:insert_callback(query, parameters, cb, GetCurrentResourceName) -end +exports.oxmysql:update +MySQL.Async.execute = MySQL.update +MySQL.Sync.execute = MySQL.update.await ----@param query string ----@param parameters? table ----@return number result ---- returns the insert id of the executed query -function MySQL.Sync.insert(query, parameters) - return Await(oxmysql.insert_callback, safeArgs(query, parameters)) -end ----@param queries table ----@param parameters? table|function ----@param cb? function ----@return boolean result ---- returns true when the transaction has succeeded -function MySQL.Async.transaction(queries, parameters, cb) - queries, parameters, cb = safeArgs(queries, parameters, cb, true) - oxmysql:transaction_callback(queries, parameters, cb, GetCurrentResourceName) -end +exports.oxmysql:transaction +MySQL.Async.transaction = MySQL.transaction +MySQL.Sync.transaction = MySQL.transaction.await ----@param queries table ----@param parameters? table ----@return boolean result ---- returns true when the transaction has succeeded -function MySQL.Sync.transaction(queries, parameters) - return Await(oxmysql.transaction_callback, safeArgs(queries, parameters, false, true)) -end - ----@param query string ----@param parameters table ----@param cb? function ----@return any result ---- Utilises a separate function to execute queries more efficiently. The return type will differ based on the query submitted. ---- Parameters can be a single table containing placeholders (perform one query) or contain multiple tables with a set of placeholders, i.e ---- ```lua ---- MySQL.Async.prepare('SELECT * FROM users WHERE firstname = ?', {{'Dunak'}, {'Linden'}, {'Luke'}}) ---- MySQL.Async.prepare('SELECT * FROM users WHERE firstname = ?', {'Linden'}) ---- ```` ---- When selecting a single row the result will match fetchSingle, or a single column will match fetchScalar. -function MySQL.Async.prepare(query, parameters, cb) - oxmysql:execute_callback(query, parameters, cb, GetCurrentResourceName) -end ----@param query string ----@param parameters table ----@return any result ---- Utilises a separate function to execute queries more efficiently. The return type will differ based on the query submitted. ---- Parameters can be a single table containing placeholders (perform one query) or contain multiple tables with a set of placeholders, i.e ---- ```lua ---- MySQL.Sync.prepare('SELECT * FROM users WHERE firstname = ?', {{'Dunak'}, {'Linden'}, {'Luke'}}) ---- MySQL.Sync.prepare('SELECT * FROM users WHERE firstname = ?', {'Linden'}) ---- ```` ---- When selecting a single row the result will match fetchSingle, or a single column will match fetchScalar. -function MySQL.Sync.prepare(query, parameters) - return Await(oxmysql.execute_callback, safeArgs(query, parameters)) -end +exports.oxmysql:prepare +MySQL.Async.prepare = MySQL.prepare +MySQL.Sync.prepare = MySQL.prepare.await +--]] -function MySQL.ready(cb) - CreateThread(function() - repeat - Wait(50) - until GetResourceState('oxmysql') == 'started' - cb() - end) -end _ENV.MySQL = MySQL