fix(sql): parameterize SQL helpers and deprecate the module
All values now bind through ? placeholders instead of string
concatenation, Get/Delete take { column = value } filter tables
(raw WHERE strings error loudly), and table/column identifiers are
validated and backtick-quoted. The whole module is deprecated with a
once-per-resource console warning and will be removed on 2026-08-22 —
migrate to oxmysql directly (MySQL.query.await with ? placeholders).
Closes #198
Reported-by: Pajt9whauht283as <https://github.com/Pajt9whauht283as>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
221919b3eb
commit
cd7fa10f54
1 file changed
+80
-14
@@ -1,21 +1,67 @@
|
||||
--[[
|
||||
DEPRECATED: the SQL helper module is deprecated in its entirety and will be
|
||||
REMOVED on 2026-08-22. Migrate to oxmysql directly — MySQL.query.await(query,
|
||||
params) with ? placeholders — which is safer and more flexible than these
|
||||
wrappers ever were.
|
||||
]]
|
||||
|
||||
SQL = {}
|
||||
|
||||
Require("lib/MySQL.lua", "oxmysql")
|
||||
|
||||
local warnedResources = {}
|
||||
|
||||
local function warnDeprecated()
|
||||
local caller = GetInvokingResource() or GetCurrentResourceName()
|
||||
if warnedResources[caller] then return end
|
||||
warnedResources[caller] = true
|
||||
print(("^3[community_bridge] SQL helpers are deprecated and will be REMOVED on 2026-08-22. '%s' should migrate to oxmysql directly (MySQL.query.await with ? placeholders).^0"):format(caller))
|
||||
end
|
||||
|
||||
-- Identifiers (table/column names) cannot be parameterized, so they are
|
||||
-- validated against a strict pattern and backtick-quoted instead. Values are
|
||||
-- never concatenated into queries - they always go through ? placeholders.
|
||||
local function quoteIdentifier(name)
|
||||
assert(type(name) == "string" and name:match("^[%a_][%w_]*$"),
|
||||
("SQL: unsafe identifier '%s' - table/column names may only contain letters, digits and underscores"):format(tostring(name)))
|
||||
return "`" .. name .. "`"
|
||||
end
|
||||
|
||||
--- Builds a parameterized WHERE clause from a table of column = value pairs.
|
||||
-- Raw WHERE strings are intentionally rejected: they made SQL injection
|
||||
-- unavoidable whenever a player-influenced value reached these helpers.
|
||||
local function buildWhere(where)
|
||||
assert(type(where) == "table" and next(where) ~= nil,
|
||||
"SQL: where must be a non-empty table of column = value pairs (raw WHERE strings are no longer accepted)")
|
||||
|
||||
local conditions = {}
|
||||
local params = {}
|
||||
|
||||
for column, value in pairs(where) do
|
||||
table.insert(conditions, quoteIdentifier(column) .. " = ?")
|
||||
table.insert(params, value)
|
||||
end
|
||||
|
||||
return table.concat(conditions, " AND "), params
|
||||
end
|
||||
|
||||
--- Creates a table in the database if it does not exist.
|
||||
-- @param tableName The name of the table to create. Example: {{ name = "identifier", type = "VARCHAR(50)", primary = true }}
|
||||
-- @param columns A table containing column definitions, where each column is a table with 'name' and 'type'.
|
||||
---@return nil
|
||||
|
||||
---@deprecated Removed on 2026-08-22 — use oxmysql directly.
|
||||
function SQL.Create(tableName, columns)
|
||||
warnDeprecated()
|
||||
assert(MySQL, "Tried using module SQL without MySQL being loaded")
|
||||
local columnsList = {}
|
||||
for i, column in pairs(columns) do
|
||||
table.insert(columnsList, string.format("%s %s", column.name, column.type))
|
||||
assert(type(column.type) == "string", "SQL: column type must be a string")
|
||||
table.insert(columnsList, string.format("%s %s", quoteIdentifier(column.name), column.type))
|
||||
end
|
||||
|
||||
local query = string.format("CREATE TABLE IF NOT EXISTS %s (%s);",
|
||||
tableName,
|
||||
quoteIdentifier(tableName),
|
||||
table.concat(columnsList, ", ")
|
||||
)
|
||||
|
||||
@@ -23,50 +69,70 @@ function SQL.Create(tableName, columns)
|
||||
end
|
||||
|
||||
-- insert if not exist otherwise update
|
||||
---@deprecated Removed on 2026-08-22 — use oxmysql directly.
|
||||
function SQL.InsertOrUpdate(tableName, data)
|
||||
warnDeprecated()
|
||||
assert(MySQL, "Tried using module SQL without MySQL being loaded")
|
||||
local columns = {}
|
||||
local values = {}
|
||||
local placeholders = {}
|
||||
local updates = {}
|
||||
local params = {}
|
||||
|
||||
for column, value in pairs(data) do
|
||||
table.insert(columns, column)
|
||||
table.insert(values, "'" .. value .. "'") -- Ensure values are properly quoted
|
||||
table.insert(updates, column .. " = VALUES(" .. column .. ")") -- Use VALUES() for update
|
||||
local quoted = quoteIdentifier(column)
|
||||
table.insert(columns, quoted)
|
||||
table.insert(placeholders, "?")
|
||||
table.insert(updates, quoted .. " = VALUES(" .. quoted .. ")") -- Use VALUES() for update
|
||||
table.insert(params, value)
|
||||
end
|
||||
|
||||
local query = string.format(
|
||||
"INSERT INTO %s (%s) VALUES (%s) ON DUPLICATE KEY UPDATE %s;",
|
||||
tableName,
|
||||
quoteIdentifier(tableName),
|
||||
table.concat(columns, ", "),
|
||||
table.concat(values, ", "),
|
||||
table.concat(placeholders, ", "),
|
||||
table.concat(updates, ", ")
|
||||
)
|
||||
|
||||
MySQL.query.await(query)
|
||||
MySQL.query.await(query, params)
|
||||
end
|
||||
|
||||
--- Selects rows matching the given filters.
|
||||
-- @param tableName The table to query.
|
||||
-- @param where Table of column = value pairs, joined with AND. Example: { citizenid = cid }
|
||||
---@deprecated Removed on 2026-08-22 — use oxmysql directly.
|
||||
function SQL.Get(tableName, where)
|
||||
warnDeprecated()
|
||||
assert(MySQL, "Tried using module SQL without MySQL being loaded")
|
||||
local query = string.format("SELECT * FROM %s WHERE %s;", tableName, where)
|
||||
local result = MySQL.query.await(query)
|
||||
local conditions, params = buildWhere(where)
|
||||
local query = string.format("SELECT * FROM %s WHERE %s;", quoteIdentifier(tableName), conditions)
|
||||
local result = MySQL.query.await(query, params)
|
||||
return result
|
||||
end
|
||||
|
||||
---@deprecated Removed on 2026-08-22 — use oxmysql directly.
|
||||
function SQL.GetAll(tableName)
|
||||
warnDeprecated()
|
||||
assert(MySQL, "Tried using module SQL without MySQL being loaded")
|
||||
local query = string.format("SELECT * FROM %s;", tableName)
|
||||
local query = string.format("SELECT * FROM %s;", quoteIdentifier(tableName))
|
||||
local result = MySQL.query.await(query)
|
||||
return result
|
||||
end
|
||||
|
||||
--- Deletes rows matching the given filters.
|
||||
-- @param tableName The table to delete from.
|
||||
-- @param where Table of column = value pairs, joined with AND. Example: { citizenid = cid }
|
||||
---@deprecated Removed on 2026-08-22 — use oxmysql directly.
|
||||
function SQL.Delete(tableName, where)
|
||||
warnDeprecated()
|
||||
assert(MySQL, "Tried using module SQL without MySQL being loaded")
|
||||
local query = string.format("DELETE FROM %s WHERE %s;", tableName, where)
|
||||
MySQL.query.await(query)
|
||||
local conditions, params = buildWhere(where)
|
||||
local query = string.format("DELETE FROM %s WHERE %s;", quoteIdentifier(tableName), conditions)
|
||||
MySQL.query.await(query, params)
|
||||
end
|
||||
|
||||
exports('SQL', function()
|
||||
warnDeprecated()
|
||||
return SQL
|
||||
end)
|
||||
|
||||
|
||||
Reference in new issue
Block a user