Add SQL abstraction layer
This commit is contained in:
parent
b47400ec41
commit
fc093abb9f
102
abstraction.lua
Normal file
102
abstraction.lua
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
local modname = minetest.get_current_modname()
|
||||||
|
|
||||||
|
local thismod = _G[modname]
|
||||||
|
|
||||||
|
---- Table creation & deletion
|
||||||
|
|
||||||
|
function thismod.create_table_sql(name, params)
|
||||||
|
local lines = {}
|
||||||
|
for _, coldata in ipairs(params.columns) do
|
||||||
|
local line = (coldata.name or coldata[1]) .. ' ' .. (coldata.type or coldata[2])
|
||||||
|
if coldata.notnull then
|
||||||
|
line = line .. ' NOT NULL'
|
||||||
|
end
|
||||||
|
if coldata.autoincrement then
|
||||||
|
line = line .. ' AUTO_INCREMENT'
|
||||||
|
end
|
||||||
|
table.insert(lines, line)
|
||||||
|
end
|
||||||
|
table.insert(lines, 'PRIMARY KEY (' .. table.concat(params.pkey, ',') .. ')')
|
||||||
|
for fkeyname, fkeydata in pairs(params.fkeys or {}) do
|
||||||
|
table.insert(lines, 'FOREIGN KEY (' .. fkeyname .. ') REFERENCES ' .. fkeydata.table ..
|
||||||
|
'(' .. fkeydata.column .. ')')
|
||||||
|
end
|
||||||
|
for _, ucol in pairs(params.unique or {}) do
|
||||||
|
if type(ucol) == 'table' then
|
||||||
|
table.insert(lines, 'UNIQUE (' .. table.concat(ucol, ',') .. ')')
|
||||||
|
else
|
||||||
|
table.insert(lines, 'UNIQUE (' .. ucol .. ')')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return 'CREATE TABLE ' .. name .. ' (' .. table.concat(lines, ',') .. ')'
|
||||||
|
end
|
||||||
|
function thismod.create_table(name, params)
|
||||||
|
thismod.conn:query(thismod.create_table_sql(name, params))
|
||||||
|
end
|
||||||
|
|
||||||
|
function thismod.drop_table_sql(name)
|
||||||
|
return 'DROP TABLE ' .. name
|
||||||
|
end
|
||||||
|
function thismod.drop_table(name)
|
||||||
|
thismod.conn:query(thismod.drop_table_sql(name))
|
||||||
|
end
|
||||||
|
|
||||||
|
---- INSERT prepare
|
||||||
|
|
||||||
|
function thismod.prepare_insert_sql(tablename, colnames)
|
||||||
|
local qmarks = {}
|
||||||
|
for i = 1, #colnames do
|
||||||
|
qmarks[i] = '?'
|
||||||
|
end
|
||||||
|
return 'INSERT INTO ' .. tablename .. '(' .. table.concat(colnames, ',') .. ') VALUES (' ..
|
||||||
|
table.concat(qmarks, ',') .. ')'
|
||||||
|
end
|
||||||
|
function thismod.prepare_insert(tablename, cols)
|
||||||
|
local colnames, coltypes = {}, {}
|
||||||
|
for _, col in ipairs(cols) do
|
||||||
|
table.insert(colnames, col.name or col[1])
|
||||||
|
table.insert(coltypes, col.type or col[2])
|
||||||
|
end
|
||||||
|
local stmt = thismod.conn:prepare(thismod.prepare_insert_sql(tablename, colnames))
|
||||||
|
return stmt, stmt:bind_params(coltypes)
|
||||||
|
end
|
||||||
|
|
||||||
|
---- UPDATE prepare
|
||||||
|
|
||||||
|
function thismod.prepare_update_sql(tablename, colnames, where)
|
||||||
|
return 'UPDATE ' .. tablename .. ' SET ' .. table.concat(colnames, ',') .. ' WHERE ' .. where
|
||||||
|
end
|
||||||
|
function thismod.prepare_update(tablename, cols, where, wheretypes)
|
||||||
|
local colnames, paramtypes = {}, {}
|
||||||
|
for _, col in ipairs(cols) do
|
||||||
|
table.insert(colnames, (col.name or col[1]) .. '=?')
|
||||||
|
table.insert(paramtypes, col.type or col[2])
|
||||||
|
end
|
||||||
|
for _, wheretype in ipairs(wheretypes) do
|
||||||
|
table.insert(paramtypes, wheretype)
|
||||||
|
end
|
||||||
|
local stmt = thismod.conn:prepare(thismod.prepare_update_sql(tablename, colnames, where))
|
||||||
|
return stmt, stmt:bind_params(paramtypes)
|
||||||
|
end
|
||||||
|
|
||||||
|
---- DELETE prepare
|
||||||
|
|
||||||
|
function thismod.prepare_delete(tablename, where, wheretypes)
|
||||||
|
local stmt = thismod.conn:prepare('DELETE FROM ' .. tablename .. ' WHERE ' .. where)
|
||||||
|
return stmt, stmt:bind_params(wheretypes)
|
||||||
|
end
|
||||||
|
|
||||||
|
---- SELECT prepare
|
||||||
|
|
||||||
|
function thismod.prepare_select_sql(tablename, colnames, where)
|
||||||
|
return 'SELECT ' .. table.concat(colnames, ',') .. ' FROM ' .. tablename .. ' WHERE ' .. where
|
||||||
|
end
|
||||||
|
function thismod.prepare_select(tablename, cols, where, wheretypes)
|
||||||
|
local colnames, coltypes = {}, {}
|
||||||
|
for _, col in ipairs(cols) do
|
||||||
|
table.insert(colnames, col.name or col[1])
|
||||||
|
table.insert(coltypes, col.type or col[2])
|
||||||
|
end
|
||||||
|
local stmt = thismod.conn:prepare(thismod.prepare_select_sql(tablename, colnames, where))
|
||||||
|
return stmt, stmt:bind_params(wheretypes), stmt:bind_result(coltypes)
|
||||||
|
end
|
82
init.lua
82
init.lua
@ -6,10 +6,18 @@ local thismod = {
|
|||||||
}
|
}
|
||||||
_G[modname] = thismod
|
_G[modname] = thismod
|
||||||
|
|
||||||
|
function thismod.mklog(level, modname)
|
||||||
|
return function(str)
|
||||||
|
minetest.log(level, "[" .. modname .. "] " .. str)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local LogI = thismod.mklog('action', modname)
|
||||||
|
local LogE = thismod.mklog('error', modname)
|
||||||
|
|
||||||
local singleplayer = minetest.is_singleplayer() -- Caching is OK since you can't open a game to
|
local singleplayer = minetest.is_singleplayer() -- Caching is OK since you can't open a game to
|
||||||
-- multiplayer unless you restart it.
|
-- multiplayer unless you restart it.
|
||||||
if not minetest.setting_get(modname .. '.enable_singleplayer') and singleplayer then
|
if minetest.setting_get(modname .. '.enable_singleplayer') ~= 'true' and singleplayer then
|
||||||
minetest.log('action', modname .. ": Not enabling because of singleplayer game")
|
LogI("Not enabling because of singleplayer game")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -27,6 +35,27 @@ local function setoverlay(tab, orig)
|
|||||||
setmetatable(tab, mt)
|
setmetatable(tab, mt)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local insecrequire = _G.require
|
||||||
|
local ffi, bit
|
||||||
|
do
|
||||||
|
if minetest.request_insecure_environment then
|
||||||
|
insecrequire = minetest.request_insecure_environment().require
|
||||||
|
LogI("Fetched require() from insecure env")
|
||||||
|
end
|
||||||
|
local test_fn = function()
|
||||||
|
return { require('ffi'), require('bit') }
|
||||||
|
end
|
||||||
|
local env = { require = insecrequire }
|
||||||
|
setoverlay(env, _G)
|
||||||
|
setfenv(test_fn, env)
|
||||||
|
local ffi_ok, ret = pcall(test_fn)
|
||||||
|
if not ffi_ok then
|
||||||
|
error("Cannot access LuaJIT FFI. Either you are not using LuaJIT, or mod security is enabled" ..
|
||||||
|
" and mysql_base is not an exception.")
|
||||||
|
end
|
||||||
|
ffi, bit = unpack(ret)
|
||||||
|
end
|
||||||
|
|
||||||
local function string_splitdots(s)
|
local function string_splitdots(s)
|
||||||
local temp = {}
|
local temp = {}
|
||||||
local index = 0
|
local index = 0
|
||||||
@ -52,21 +81,32 @@ end
|
|||||||
|
|
||||||
local mysql
|
local mysql
|
||||||
do -- MySQL module loading
|
do -- MySQL module loading
|
||||||
local env = {
|
local env = {}
|
||||||
require = function (module)
|
|
||||||
if module == 'mysql_h' then
|
|
||||||
return dofile(modpath .. '/mysql/mysql_h.lua')
|
|
||||||
else
|
|
||||||
return require(module)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
setoverlay(env, _G)
|
setoverlay(env, _G)
|
||||||
local fn, msg = loadfile(modpath .. '/mysql/mysql.lua')
|
local function secexec(path)
|
||||||
|
local fn, msg = loadfile(path)
|
||||||
if not fn then error(msg) end
|
if not fn then error(msg) end
|
||||||
setfenv(fn, env)
|
setfenv(fn, env)
|
||||||
|
local status, ret = pcall(fn, {})
|
||||||
|
if not status then
|
||||||
|
error(ret)
|
||||||
|
end
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
local function secrequire(module)
|
||||||
|
if module == 'mysql_h' then
|
||||||
|
return secexec(modpath .. '/mysql/mysql_h.lua')
|
||||||
|
elseif module == 'ffi' then
|
||||||
|
return ffi
|
||||||
|
elseif module == 'bit' then
|
||||||
|
return bit
|
||||||
|
else
|
||||||
|
error("mysql.lua tried to require('" .. module .. "')")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
env.require = secrequire
|
||||||
local status
|
local status
|
||||||
status, mysql = pcall(fn, {})
|
status, mysql = pcall(secexec, modpath .. '/mysql/mysql.lua')
|
||||||
if not status then
|
if not status then
|
||||||
error(modname .. ' failed to load MySQL FFI interface: ' .. tostring(mysql))
|
error(modname .. ' failed to load MySQL FFI interface: ' .. tostring(mysql))
|
||||||
end
|
end
|
||||||
@ -74,7 +114,12 @@ do -- MySQL module loading
|
|||||||
end
|
end
|
||||||
|
|
||||||
function thismod.mkget(modname)
|
function thismod.mkget(modname)
|
||||||
local get = function (name) return minetest.setting_get(modname .. '.' .. name) end
|
local get
|
||||||
|
if minetest.settings then
|
||||||
|
get = function (name) return minetest.settings:get(modname .. '.' .. name) end
|
||||||
|
else
|
||||||
|
get = function (name) return minetest.setting_get(modname .. '.' .. name) end
|
||||||
|
end
|
||||||
local cfgfile = get('cfgfile')
|
local cfgfile = get('cfgfile')
|
||||||
if type(cfgfile) == 'string' and cfgfile ~= '' then
|
if type(cfgfile) == 'string' and cfgfile ~= '' then
|
||||||
local file = io.open(cfgfile, 'rb')
|
local file = io.open(cfgfile, 'rb')
|
||||||
@ -128,7 +173,7 @@ do
|
|||||||
connopts.options.MYSQL_OPT_RECONNECT = true
|
connopts.options.MYSQL_OPT_RECONNECT = true
|
||||||
conn = mysql.connect(connopts)
|
conn = mysql.connect(connopts)
|
||||||
dbname = connopts.db
|
dbname = connopts.db
|
||||||
minetest.log('action', modname .. ": Connected to MySQL database " .. dbname)
|
LogI("Connected to MySQL database " .. dbname)
|
||||||
thismod.conn = conn
|
thismod.conn = conn
|
||||||
thismod.dbname = dbname
|
thismod.dbname = dbname
|
||||||
|
|
||||||
@ -152,7 +197,7 @@ end
|
|||||||
local function ping()
|
local function ping()
|
||||||
if thismod.conn then
|
if thismod.conn then
|
||||||
if not thismod.conn:ping() then
|
if not thismod.conn:ping() then
|
||||||
minetest.log('error', modname .. ": failed to ping database")
|
LogE('error', modname .. ": failed to ping database")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
minetest.after(1800, ping)
|
minetest.after(1800, ping)
|
||||||
@ -166,13 +211,13 @@ end
|
|||||||
|
|
||||||
minetest.register_on_shutdown(function()
|
minetest.register_on_shutdown(function()
|
||||||
if thismod.conn then
|
if thismod.conn then
|
||||||
minetest.log('action', modname .. ": Shutting down, running callbacks")
|
LogI("Shutting down, running callbacks")
|
||||||
for _, func in ipairs(shutdown_callbacks) do
|
for _, func in ipairs(shutdown_callbacks) do
|
||||||
func()
|
func()
|
||||||
end
|
end
|
||||||
thismod.conn:close()
|
thismod.conn:close()
|
||||||
thismod.conn = nil
|
thismod.conn = nil
|
||||||
minetest.log('action', modname .. ": Cosed database connection")
|
LogI("Closed database connection")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -184,3 +229,4 @@ function thismod.table_exists(name)
|
|||||||
return exists
|
return exists
|
||||||
end
|
end
|
||||||
|
|
||||||
|
dofile(modpath .. '/abstraction.lua')
|
||||||
|
Loading…
Reference in New Issue
Block a user