From fc093abb9fcab07f55b87b623d59f2075f9a626a Mon Sep 17 00:00:00 2001 From: Dorian Wouters Date: Sun, 24 Sep 2017 16:43:24 +0200 Subject: [PATCH] Add SQL abstraction layer --- abstraction.lua | 102 ++++++++++++++++++++++++++++++++++++++++++++++++ init.lua | 86 ++++++++++++++++++++++++++++++---------- 2 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 abstraction.lua diff --git a/abstraction.lua b/abstraction.lua new file mode 100644 index 0000000..cc66f4c --- /dev/null +++ b/abstraction.lua @@ -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 diff --git a/init.lua b/init.lua index c0f0591..6ef9f3a 100644 --- a/init.lua +++ b/init.lua @@ -6,10 +6,18 @@ local 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 -- multiplayer unless you restart it. -if not minetest.setting_get(modname .. '.enable_singleplayer') and singleplayer then - minetest.log('action', modname .. ": Not enabling because of singleplayer game") +if minetest.setting_get(modname .. '.enable_singleplayer') ~= 'true' and singleplayer then + LogI("Not enabling because of singleplayer game") return end @@ -27,6 +35,27 @@ local function setoverlay(tab, orig) setmetatable(tab, mt) 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 temp = {} local index = 0 @@ -52,21 +81,32 @@ end local mysql do -- MySQL module loading - local env = { - require = function (module) - if module == 'mysql_h' then - return dofile(modpath .. '/mysql/mysql_h.lua') - else - return require(module) - end - end - } + local env = {} setoverlay(env, _G) - local fn, msg = loadfile(modpath .. '/mysql/mysql.lua') - if not fn then error(msg) end - setfenv(fn, env) + local function secexec(path) + local fn, msg = loadfile(path) + if not fn then error(msg) end + 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 - status, mysql = pcall(fn, {}) + status, mysql = pcall(secexec, modpath .. '/mysql/mysql.lua') if not status then error(modname .. ' failed to load MySQL FFI interface: ' .. tostring(mysql)) end @@ -74,7 +114,12 @@ do -- MySQL module loading end 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') if type(cfgfile) == 'string' and cfgfile ~= '' then local file = io.open(cfgfile, 'rb') @@ -128,7 +173,7 @@ do connopts.options.MYSQL_OPT_RECONNECT = true conn = mysql.connect(connopts) dbname = connopts.db - minetest.log('action', modname .. ": Connected to MySQL database " .. dbname) + LogI("Connected to MySQL database " .. dbname) thismod.conn = conn thismod.dbname = dbname @@ -152,7 +197,7 @@ end local function ping() if thismod.conn then if not thismod.conn:ping() then - minetest.log('error', modname .. ": failed to ping database") + LogE('error', modname .. ": failed to ping database") end end minetest.after(1800, ping) @@ -166,13 +211,13 @@ end minetest.register_on_shutdown(function() if thismod.conn then - minetest.log('action', modname .. ": Shutting down, running callbacks") + LogI("Shutting down, running callbacks") for _, func in ipairs(shutdown_callbacks) do func() end thismod.conn:close() thismod.conn = nil - minetest.log('action', modname .. ": Cosed database connection") + LogI("Closed database connection") end end) @@ -184,3 +229,4 @@ function thismod.table_exists(name) return exists end +dofile(modpath .. '/abstraction.lua')