diff --git a/.gitignore b/.gitignore index 081a826..99c0b11 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ irc_tokens .* homes schems/ +*.dat +*.data +*.home +beds_spawns +*.json \ No newline at end of file diff --git a/worldmods/mysql_auth/README.md b/worldmods/mysql_auth/README.md deleted file mode 100644 index 8de3bc4..0000000 --- a/worldmods/mysql_auth/README.md +++ /dev/null @@ -1,123 +0,0 @@ -# mysql_auth - -Plug Minetest's auth mechanism into a MySQL database. - -# Configuration - -First, if mod security is enabled (`secure.enable_security = true`), this mod must be added as -a trusted mod (in the `secure.trusted_mods` config entry). There is **no** other solution to -make it work under mod security. - -By default `mysql_auth` doesn't run in singleplayer. This can be overriden by setting -`mysql_auth.enable_singleplayer` to `true`. - -Configuration may be done as regular Minetest settings entries, or using a config file, allowing -for more configuration options; to do so specify the path as `mysql_auth.cfgfile`. This config -must contain a Lua table that can be read by `minetest.deserialize`, i.e. a regular table -definition follwing a `return` statement (see the example below). - -When using flat Minetest configuation entries, all the following option names must be prefixed -with `mysql_auth.`. When using a config file, entries are to be hierarchised as per the dot -separator. - -Values written next to option names are default values. - -## Database connection - -### Flat config file - -```lua -db.host = 'localhost' -db.user = nil -- MySQL connector defaults to current username -db.pass = nil -- Using password: NO -db.port = nil -- MySQL connector defaults to either 3306, or no port if using localhost/unix socket -db.db = nil -- <== Setting this is required -``` - -### Lua table config file - -Connection options are passed as a table through the `db.connopts` entry. -Its format must be the same as [LuaPower's MySQL module `mysql.connect(options_t)` function][mycn], -that is (all members are optional); - -```lua -connopts = { - host = ..., - user = ..., - pass = ..., - db = ..., - port = ..., - unix_socket = ..., - flags = { ... }, - options = { ... }, - attrs = { ... }, - -- Also key, cert, ca, cpath, cipher -} -``` - -## Auth table schema finetuning - -```lua -db.tables.auths.name = 'auths' -db.tables.auths.schema.userid = 'userid' -db.tables.auths.schema.userid_type = 'INT' -db.tables.auths.schema.username = 'username' -db.tables.auths.schema.username_type = 'VARCHAR(32)' -db.tables.auths.schema.password = 'password' -db.tables.auths.schema.password_type = 'VARCHAR(512)' -db.tables.auths.schema.privs = 'privs' -db.tables.auths.schema.privs_type = 'VARCHAR(512)' -db.tables.auths.schema.lastlogin = 'lastlogin' -db.tables.auths.schema.lastlogin_type = 'BIGINT' -``` - -The `_type` config entries are only used when creating an auth table, i.e. when -`db.tables.auths.name` doesn't exist. - -## Examples - -### Example 1 - -#### Using a Lua config file - -`minetest.conf`: -``` -mysql_auth.cfgfile = /srv/minetest/skyblock/mysql_auth_config -``` - -`/srv/minetest/skyblock/mysql_auth_config`: -```lua -return { - db = { - connopts = { - user = 'minetest', - pass = 'BQy77wK$Um6es3Bi($iZ*w3N', - db = 'minetest' - }, - tables = { - auths = { - name = 'skyblock_auths' - } - } - } -} -``` - -#### Using only Minetest config entries - -`minetest.conf`: -``` -mysql_auth.db.user = minetest -mysql_auth.db.pass = BQy77wK$Um6es3Bi($iZ*w3N -mysql_auth.db.db = minetest -mysql_auth.db.tables.auth.name = skyblock_auths -``` - -# License - -`mysql_auth` is licensed under [LGPLv3](https://www.gnu.org/licenses/lgpl.html). - -Using the Public Domain-licensed LuaPower `mysql` module. - - -[mycn]: https://luapower.com/mysql#mysql.connectoptions_t---conn diff --git a/worldmods/mysql_auth/auth_txt_import.lua b/worldmods/mysql_auth/auth_txt_import.lua deleted file mode 100644 index d65b55d..0000000 --- a/worldmods/mysql_auth/auth_txt_import.lua +++ /dev/null @@ -1,47 +0,0 @@ -local modname = minetest.get_current_modname() -local modpath = minetest.get_modpath(modname) - -local thismod = _G[modname] - -function thismod.import_auth_txt() - minetest.log('action', modname .. ": Importing auth.txt") - local auth_file_path = minetest.get_worldpath() .. '/auth.txt' - local create_auth_stmt = thismod.create_auth_stmt - local create_auth_params = thismod.create_auth_params - local conn = thismod.conn - local file, errmsg = io.open(auth_file_path, 'rb') - if not file then - minetest.log('action', modname .. ": " .. auth_file_path .. " could not be opened for reading" .. - "(" .. errmsg .. "); no auth entries imported") - return - end - conn:query('SET autocommit=0') - conn:query('START TRANSACTION') - for line in file:lines() do - if line ~= "" then - local fields = line:split(":", true) - local name, password, privilege_string, last_login = unpack(fields) - last_login = tonumber(last_login) - if not (name and password and privilege_string) then - minetest.log('warning', modname .. ": Invalid line in auth.txt, skipped: " .. dump(line)) - end - minetest.log('info', modname .. " importing player '"..name.."'") - create_auth_params:set(1, name) - create_auth_params:set(2, password) - create_auth_params:set(3, privilege_string) - create_auth_params:set(4, last_login) - local success, msg = pcall(create_auth_stmt.exec, create_auth_stmt) - if not success then - error(modname .. ": import failed: " .. msg) - end - if create_auth_stmt:affected_rows() ~= 1 then - error(modname .. ": create_auth failed: affected row count is " .. - create_auth_stmt:affected_rows() .. ", expected 1") - end - end - end - conn:query('COMMIT') - conn:query('SET autocommit=1') - io.close(file) - minetest.log('action', modname .. ": Finished importing auth.txt") -end diff --git a/worldmods/mysql_auth/depends.txt b/worldmods/mysql_auth/depends.txt deleted file mode 100644 index 2170244..0000000 --- a/worldmods/mysql_auth/depends.txt +++ /dev/null @@ -1,2 +0,0 @@ -mysql_base - diff --git a/worldmods/mysql_auth/init.lua b/worldmods/mysql_auth/init.lua deleted file mode 100644 index 5d85313..0000000 --- a/worldmods/mysql_auth/init.lua +++ /dev/null @@ -1,299 +0,0 @@ -local modname = minetest.get_current_modname() -local modpath = minetest.get_modpath(modname) - -local thismod = { - enabled = false -} -_G[modname] = thismod - -if not mysql_base.enabled then - minetest.log('action', modname .. ": mysql_base disabled, not loading mod") - return -end - -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 adding auth handler because of singleplayer game") - return -end - -enabled = true - -do - local get = mysql_base.mkget(modname) - - local conn, dbname = mysql_base.conn, mysql_base.dbname - - local tables = {} - do -- Tables and schema settings - local t_auths = get('db.tables.auths') - if type(t_auths) == 'table' then - tables.auths = t_auths - else - tables.auths = {} - tables.auths.name = get('db.tables.auths.name') - tables.auths.schema = {} - local S = tables.auths.schema - S.userid = get('db.tables.auths.schema.userid') - S.username = get('db.tables.auths.schema.username') - S.password = get('db.tables.auths.schema.password') - S.privs = get('db.tables.auths.schema.privs') - S.lastlogin = get('db.tables.auths.schema.lastlogin') - S.userid_type = get('db.tables.auths.schema.userid_type') - S.username_type = get('db.tables.auths.schema.username_type') - S.password_type = get('db.tables.auths.schema.password_type') - S.privs_type = get('db.tables.auths.schema.privs_type') - S.lastlogin_type = get('db.tables.auths.schema.lastlogin_type') - end - - do -- Default values - tables.auths.name = tables.auths.name or 'auths' - tables.auths.schema = tables.auths.schema or {} - local S = tables.auths.schema - S.userid = S.userid or 'userid' - S.username = S.username or 'username' - S.password = S.password or 'password' - S.privs = S.privs or 'privs' - S.lastlogin = S.lastlogin or 'lastlogin' - - S.userid_type = S.userid_type or 'INT' - S.username_type = S.username_type or 'VARCHAR(32)' - S.password_type = S.password_type or 'VARCHAR(512)' - S.privs_type = S.privs_type or 'VARCHAR(512)' - S.lastlogin_type = S.lastlogin_type or 'BIGINT' - -- Note lastlogin doesn't use the TIMESTAMP type, which is 32-bit and therefore - -- subject to the year 2038 problem. - end - end - - local auth_table_created - -- Auth table existence check and setup - if not mysql_base.table_exists(tables.auths.name) then - -- Auth table doesn't exist, create it - local S = tables.auths.schema - conn:query('CREATE TABLE ' .. tables.auths.name .. ' (' .. - S.userid .. ' ' .. S.userid_type .. ' NOT NULL AUTO_INCREMENT,' .. - S.username .. ' ' .. S.username_type .. ' NOT NULL,' .. - S.password .. ' ' .. S.password_type .. ' NOT NULL,' .. - S.privs .. ' ' .. S.privs_type .. ' NOT NULL,' .. - S.lastlogin .. ' ' .. S.lastlogin_type .. ',' .. - 'PRIMARY KEY (' .. S.userid .. '),' .. - 'UNIQUE (' .. S.username .. ')' .. - ')') - minetest.log('action', modname .. " created table '" .. dbname .. "." .. tables.auths.name .. - "'") - auth_table_created = true - end - - local S = tables.auths.schema - local get_auth_stmt = conn:prepare('SELECT ' .. S.userid .. ',' .. S.password .. ',' .. S.privs .. - ',' .. S.lastlogin .. ' FROM ' .. tables.auths.name .. ' WHERE ' .. S.username .. '=?') - thismod.get_auth_stmt = get_auth_stmt - local get_auth_params = get_auth_stmt:bind_params({S.username_type}) - thismod.get_auth_params = get_auth_params - local get_auth_results = get_auth_stmt:bind_result({S.userid_type, S.password_type, S.privs_type, - S.lastlogin_type}) - thismod.get_auth_results = get_auth_results - - local create_auth_stmt = conn:prepare('INSERT INTO ' .. tables.auths.name .. '(' .. S.username .. - ',' .. S.password .. ',' .. S.privs .. ',' .. S.lastlogin .. ') VALUES (?,?,?,?)') - thismod.create_auth_stmt = create_auth_stmt - local create_auth_params = create_auth_stmt:bind_params({S.username_type, S.password_type, - S.privs_type, S.lastlogin_type}) - thismod.create_auth_params = create_auth_params - - local delete_auth_stmt = conn:prepare('DELETE FROM ' .. tables.auths.name .. ' WHERE ' .. - S.username .. '=?') - thismod.delete_auth_stmt = delete_auth_stmt - local delete_auth_params = delete_auth_stmt:bind_params({S.username_type}) - thismod.delete_auth_params = delete_auth_params - - local set_password_stmt = conn:prepare('UPDATE ' .. tables.auths.name .. ' SET ' .. S.password .. - '=? WHERE ' .. S.username .. '=?') - thismod.set_password_stmt = set_password_stmt - local set_password_params = set_password_stmt:bind_params({S.password_type, S.username_type}) - thismod.set_password_params = set_password_params - - local set_privileges_stmt = conn:prepare('UPDATE ' .. tables.auths.name .. ' SET ' .. S.privs .. - '=? WHERE ' .. S.username .. '=?') - thismod.set_privileges_stmt = set_privileges_stmt - local set_privileges_params = set_privileges_stmt:bind_params({S.privs_type, S.username_type}) - thismod.set_privileges_params = set_privileges_params - - local record_login_stmt = conn:prepare('UPDATE ' .. tables.auths.name .. ' SET ' .. - S.lastlogin .. '=? WHERE ' .. S.username .. '=?') - thismod.record_login_stmt = record_login_stmt - local record_login_params = record_login_stmt:bind_params({S.lastlogin_type, S.username_type}) - thismod.record_login_params = record_login_params - - local enumerate_auths_query = 'SELECT ' .. S.username .. ',' .. S.password .. ',' .. S.privs .. - ',' .. S.lastlogin .. ' FROM ' .. tables.auths.name - thismod.enumerate_auths_query = enumerate_auths_query - - if auth_table_created and get('import_auth_txt_on_table_create') ~= 'false' then - if not thismod.import_auth_txt then - dofile(modpath .. '/auth_txt_import.lua') - end - thismod.import_auth_txt() - end - - thismod.auth_handler = { - get_auth = function(name) - assert(type(name) == 'string') - get_auth_params:set(1, name) - local success, msg = pcall(get_auth_stmt.exec, get_auth_stmt) - if not success then - minetest.log('error', modname .. ": get_auth(" .. name .. ") failed: " .. msg) - return nil - end - get_auth_stmt:store_result() - if not get_auth_stmt:fetch() then - -- No such auth row exists - return nil - end - while get_auth_stmt:fetch() do - minetest.log('warning', modname .. ": get_auth(" .. name .. "): multiples lines were" .. - " returned") - end - local userid, password, privs_str, lastlogin = get_auth_results:get(1), - get_auth_results:get(2), get_auth_results:get(3), get_auth_results:get(4) - local admin = (name == minetest.setting_get("name")) - local privs - if singleplayer or admin then - privs = {} - -- If admin, grant all privs, if singleplayer, grant all privs w/ give_to_singleplayer - for priv, def in pairs(core.registered_privileges) do - if (singleplayer and def.give_to_singleplayer) or admin then - privs[priv] = true - end - end - if admin and not thismod.admin_get_auth_called then - thismod.admin_get_auth_called = true - thismod.auth_handler.set_privileges(name, privs) - end - else - privs = minetest.string_to_privs(privs_str) - end - return { - userid = userid, - password = password, - privileges = privs, - last_login = tonumber(lastlogin) - } - end, - create_auth = function(name, password, reason) - assert(type(name) == 'string') - assert(type(password) == 'string') - minetest.log('info', modname .. " creating player '"..name.."'" .. (reason or "")) - create_auth_params:set(1, name) - create_auth_params:set(2, password) - create_auth_params:set(3, minetest.setting_get("default_privs")) - create_auth_params:set(4, math.floor(os.time())) - local success, msg = pcall(create_auth_stmt.exec, create_auth_stmt) - if not success then - minetest.log('error', modname .. ": create_auth(" .. name .. ") failed: " .. msg) - return false - end - if create_auth_stmt:affected_rows() ~= 1 then - minetest.log('error', modname .. ": create_auth(" .. name .. ") failed: affected row" .. - " count is " .. create_auth_stmt:affected_rows() .. ", expected 1") - return false - end - return true - end, - delete_auth = function(name) - assert(type(name) == 'string') - minetest.log('info', modname .. " deleting player '"..name.."'") - delete_auth_params:set(1, name) - local success, msg = pcall(delete_auth_stmt.exec, delete_auth_stmt) - if not success then - minetest.log('error', modname .. ": delete_auth(" .. name .. ") failed: " .. msg) - return false - end - if delete_auth_stmt:affected_rows() ~= 1 then - minetest.log('error', modname .. ": delete_auth(" .. name .. ") failed: affected row" .. - " count is " .. delete_auth_stmt:affected_rows() .. ", expected 1") - return false - end - return true - end, - set_password = function(name, password) - assert(type(name) == 'string') - assert(type(password) == 'string') - if not thismod.auth_handler.get_auth(name) then - return thismod.auth_handler.create_auth(name, password, " because set_password was requested") - else - minetest.log('info', modname .. " setting password of player '" .. name .. "'") - set_password_params:set(1, password) - set_password_params:set(2, name) - local success, msg = pcall(set_password_stmt.exec, set_password_stmt) - if not success then - minetest.log('error', modname .. ": set_password(" .. name .. ") failed: " .. msg) - return false - end - if set_password_stmt:affected_rows() ~= 1 then - minetest.log('error', modname .. ": set_password(" .. name .. ") failed: affected row" .. - " count is " .. set_password_stmt:affected_rows() .. ", expected 1") - return false - end - return true - end - end, - set_privileges = function(name, privileges) - assert(type(name) == 'string') - assert(type(privileges) == 'table') - set_privileges_params:set(1, minetest.privs_to_string(privileges)) - set_privileges_params:set(2, name) - local success, msg = pcall(set_privileges_stmt.exec, set_privileges_stmt) - if not success then - minetest.log('error', modname .. ": set_privileges(" .. name .. ") failed: " .. msg) - return false - end - minetest.notify_authentication_modified(name) - return true - end, - reload = function() - return true - end, - record_login = function(name) - assert(type(name) == 'string') - record_login_params:set(1, math.floor(os.time())) - record_login_params:set(2, name) - local success, msg = pcall(record_login_stmt.exec, record_login_stmt) - if not success then - minetest.log('error', modname .. ": record_login(" .. name .. ") failed: " .. msg) - return false - end - if record_login_stmt:affected_rows() ~= 1 then - minetest.log('error', modname .. ": record_login(" .. name .. ") failed: affected row" .. - " count is " .. record_login_stmt:affected_rows() .. ", expected 1") - return false - end - return true - end, - enumerate_auths = function() - conn:query(enumerate_auths_query) - local res = conn:store_result() - return function() - local row = res:fetch('n') - if not row then - return nil - end - local username, password, privs_str, lastlogin = unpack(row) - return username, { - password = password, - privileges = minetest.string_to_privs(privs_str), - last_login = tonumber(lastlogin) - } - end - end - } -end - -minetest.register_authentication_handler(thismod.auth_handler) -minetest.log('action', modname .. ": Registered auth handler") - -mysql_base.register_on_shutdown(function() - thismod.get_auth_stmt:free_result() -end) diff --git a/worldmods/mysql_base/LICENSE.txt b/worldmods/mysql_base/LICENSE.txt deleted file mode 100644 index 65c5ca8..0000000 --- a/worldmods/mysql_base/LICENSE.txt +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/worldmods/mysql_base/README.md b/worldmods/mysql_base/README.md deleted file mode 100644 index 56cd50b..0000000 --- a/worldmods/mysql_base/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# mysql_base - -Base Minetest mod to connect to a MySQL database. Used by other mods to read/write data. - -# Installing - -Get this repository's contents using `git`, and make sure to fetch submodules -(`git submodule update --init`). - -# Configuration - -First, if mod security is enabled (`secure.enable_security = true`), this mod must be added as -a trusted mod (in the `secure.trusted_mods` config entry). There is **no** other solution to -make it work under mod security. - -By default `mysql_base` doesn't run in singleplayer. This can be overriden by setting -`mysql_base.enable_singleplayer` to `true`. - -Configuration may be done as regular Minetest settings entries, or using a config file, allowing -for more configuration options; to do so specify the path as `mysql_base.cfgfile`. This config -must contain a Lua table that can be read by `minetest.deserialize`, i.e. a regular table -definition follwing a `return` statement (see the example below). - -When using flat Minetest configuation entries, all the following option names must be prefixed -with `mysql_base.`. When using a config file, entries are to be hierarchised as per the dot -separator. - -Values written next to option names are default values. - -## Database connection - -### Minetest flat config file - -Values after the "`=`" are the default values used if unspecified. -``` -mysql_base.db.host = 'localhost' -mysql_base.db.user = nil -- MySQL connector defaults to current username -mysql_base.db.pass = nil -- Using password: NO -mysql_base.db.port = nil -- MySQL connector defaults to either 3306, or no port if using localhost/unix socket -mysql_base.db.db = nil -- <== Setting this is required -``` - -### Lua table config file - -Connection options are passed as a table through the `db.connopts` entry. -Its format must be the same as [LuaPower's MySQL module `mysql.connect(options_t)` function][mycn], -that is (all members are optional); - -```lua -return { - db = { - connopts = { - host = ..., - user = ..., - pass = ..., - db = ..., - port = ..., - unix_socket = ..., - flags = { ... }, - options = { ... }, - attrs = { ... }, - -- Also key, cert, ca, cpath, cipher - } - } -} -``` - -## Examples - -### Example 1 - -#### Using a Lua config file - -`minetest.conf`: -``` -mysql_auth.cfgfile = /srv/minetest/skyblock/mysql_auth_config -``` - -`/srv/minetest/skyblock/mysql_auth_config`: -```lua -return { - db = { - connopts = { - user = 'minetest', - pass = 'BQy77wK$Um6es3Bi($iZ*w3N', - db = 'minetest' - }, - } -} -``` - -#### Using only Minetest config entries - -`minetest.conf`: -``` -mysql_auth.db.user = minetest -mysql_auth.db.pass = BQy77wK$Um6es3Bi($iZ*w3N -mysql_auth.db.db = minetest -``` - -# License - -`mysql_base` is licensed under [LGPLv3](https://www.gnu.org/licenses/lgpl.html). - -Using the Public Domain-licensed LuaPower `mysql` module. - - -[mycn]: https://luapower.com/mysql#mysql.connectoptions_t---conn diff --git a/worldmods/mysql_base/init.lua b/worldmods/mysql_base/init.lua deleted file mode 100644 index c0f0591..0000000 --- a/worldmods/mysql_base/init.lua +++ /dev/null @@ -1,186 +0,0 @@ -local modname = minetest.get_current_modname() -local modpath = minetest.get_modpath(modname) - -local thismod = { - enabled = false, -} -_G[modname] = thismod - -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") - return -end - -thismod.enabled = true - -local function setoverlay(tab, orig) - local mt = getmetatable(tab) or {} - mt.__index = function (tab, key) - if rawget(tab, key) ~= nil then - return rawget(tab, key) - else - return orig[key] - end - end - setmetatable(tab, mt) -end - -local function string_splitdots(s) - local temp = {} - local index = 0 - local last_index = string.len(s) - while true do - local i, e = string.find(s, '%.', index) - if i and e then - local next_index = e + 1 - local word_bound = i - 1 - table.insert(temp, string.sub(s, index, word_bound)) - index = next_index - else - if index > 0 and index <= last_index then - table.insert(temp, string.sub(s, index, last_index)) - elseif index == 0 then - temp = nil - end - break - end - end - return temp -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 - } - setoverlay(env, _G) - local fn, msg = loadfile(modpath .. '/mysql/mysql.lua') - if not fn then error(msg) end - setfenv(fn, env) - local status - status, mysql = pcall(fn, {}) - if not status then - error(modname .. ' failed to load MySQL FFI interface: ' .. tostring(mysql)) - end - thismod.mysql = mysql -end - -function thismod.mkget(modname) - local get = function (name) return minetest.setting_get(modname .. '.' .. name) end - local cfgfile = get('cfgfile') - if type(cfgfile) == 'string' and cfgfile ~= '' then - local file = io.open(cfgfile, 'rb') - if not file then - error(modname .. ' failed to load specified config file at ' .. cfgfile) - end - local cfg, msg = minetest.deserialize(file:read('*a')) - file:close() - if not cfg then - error(modname .. ' failed to parse specified config file at ' .. cfgfile .. ': ' .. msg) - end - get = function (name) - if type(name) ~= 'string' or name == '' then - return nil - end - local parts = string_splitdots(name) - if not parts then - return cfg[name] - end - local tbl = cfg[parts[1]] - for n = 2, #parts do - if tbl == nil then - return nil - end - tbl = tbl[parts[n]] - end - return tbl - end - end - return get -end - -local get = thismod.mkget(modname) -do - local conn, dbname - -- MySQL API backend - mysql.config(get('db.api')) - - local connopts = get('db.connopts') - if (get('db.db') == nil) and (type(connopts) == 'table' and connopts.db == nil) then - error(modname .. ": missing database name parameter") - end - if type(connopts) ~= 'table' then - connopts = {} - -- Traditional connection parameters - connopts.host, connopts.user, connopts.port, connopts.pass, connopts.db = - get('db.host') or 'localhost', get('db.user'), get('db.port'), get('db.pass'), get('db.db') - end - connopts.charset = 'utf8' - connopts.options = connopts.options or {} - connopts.options.MYSQL_OPT_RECONNECT = true - conn = mysql.connect(connopts) - dbname = connopts.db - minetest.log('action', modname .. ": Connected to MySQL database " .. dbname) - thismod.conn = conn - thismod.dbname = dbname - - -- LuaPower's MySQL interface throws an error when the connection fails, no need to check if - -- it succeeded. - - -- Ensure UTF-8 is in use. - -- If you use another encoding, kill yourself (unless it's UTF-32). - conn:query("SET NAMES 'utf8'") - conn:query("SET CHARACTER SET utf8") - conn:query("SET character_set_results = 'utf8', character_set_client = 'utf8'," .. - "character_set_connection = 'utf8', character_set_database = 'utf8'," .. - "character_set_server = 'utf8'") - - local set = function(setting, val) conn:query('SET ' .. setting .. '=' .. val) end - pcall(set, 'wait_timeout', 3600) - pcall(set, 'autocommit', 1) - pcall(set, 'max_allowed_packet', 67108864) -end - -local function ping() - if thismod.conn then - if not thismod.conn:ping() then - minetest.log('error', modname .. ": failed to ping database") - end - end - minetest.after(1800, ping) -end -minetest.after(10, ping) - -local shutdown_callbacks = {} -function thismod.register_on_shutdown(func) - table.insert(shutdown_callbacks, func) -end - -minetest.register_on_shutdown(function() - if thismod.conn then - minetest.log('action', modname .. ": 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") - end -end) - -function thismod.table_exists(name) - thismod.conn:query("SHOW TABLES LIKE '" .. name .. "'") - local res = thismod.conn:store_result() - local exists = (res:row_count() ~= 0) - res:free() - return exists -end - diff --git a/worldmods/mysql_base/mysql/mysql.lua b/worldmods/mysql_base/mysql/mysql.lua deleted file mode 100644 index 9ce07d2..0000000 --- a/worldmods/mysql_base/mysql/mysql.lua +++ /dev/null @@ -1,1303 +0,0 @@ - ---mySQL client library ffi binding. ---Written by Cosmin Apreutesei. Public domain. - ---Supports mysql Connector/C 6.1. ---Based on mySQL 5.7 manual. - -if not ... then require'mysql_test' end - -local ffi = require'ffi' -local bit = require'bit' -require'mysql_h' - -local C -local M = {} - ---select a mysql client library implementation. -local function config(lib) - if not C then - if not lib or lib == 'mysql' then - C = ffi.load(ffi.abi'win' and 'libmysql' or 'mysqlclient') - elseif lib == 'mariadb' then - C = ffi.load(ffi.abi'win' and 'libmariadb' or 'mariadb') - elseif type(lib) == 'string' then - C = ffi.load(lib) - else - C = lib - end - M.C = C - end - return M -end - -M.config = config - ---we compare NULL pointers against NULL instead of nil for compatibility with luaffi. -local NULL = ffi.cast('void*', nil) - -local function ptr(p) --convert NULLs to nil - if p == NULL then return nil end - return p -end - -local function cstring(data) --convert null-term non-empty C strings to lua strings - if data == NULL or data[0] == 0 then return nil end - return ffi.string(data) -end - ---error reporting - -local function myerror(mysql, stacklevel) - local err = cstring(C.mysql_error(mysql)) - if not err then return end - error(string.format('mysql error: %s', err), stacklevel or 3) -end - -local function checkz(mysql, ret) - if ret == 0 then return end - myerror(mysql, 4) -end - -local function checkh(mysql, ret) - if ret ~= NULL then return ret end - myerror(mysql, 4) -end - -local function enum(e, prefix) - local v = type(e) == 'string' and (prefix and C[prefix..e] or C[e]) or e - return assert(v, 'invalid enum value') -end - ---client library info - -function M.thread_safe() - config() - return C.mysql_thread_safe() == 1 -end - -function M.client_info() - config() - return cstring(C.mysql_get_client_info()) -end - -function M.client_version() - config() - return tonumber(C.mysql_get_client_version()) -end - ---connections - -local function bool_ptr(b) - return ffi.new('my_bool[1]', b or false) -end - -local function uint_bool_ptr(b) - return ffi.new('uint32_t[1]', b or false) -end - -local function uint_ptr(i) - return ffi.new('uint32_t[1]', i) -end - -local function proto_ptr(proto) --proto is 'MYSQL_PROTOCOL_*' or mysql.C.MYSQL_PROTOCOL_* - return ffi.new('uint32_t[1]', enum(proto)) -end - -local function ignore_arg() - return nil -end - -local option_encoders = { - MYSQL_ENABLE_CLEARTEXT_PLUGIN = bool_ptr, - MYSQL_OPT_LOCAL_INFILE = uint_bool_ptr, - MYSQL_OPT_PROTOCOL = proto_ptr, - MYSQL_OPT_READ_TIMEOUT = uint_ptr, - MYSQL_OPT_WRITE_TIMEOUT = uint_ptr, - MYSQL_OPT_USE_REMOTE_CONNECTION = ignore_arg, - MYSQL_OPT_USE_EMBEDDED_CONNECTION = ignore_arg, - MYSQL_OPT_GUESS_CONNECTION = ignore_arg, - MYSQL_SECURE_AUTH = bool_ptr, - MYSQL_REPORT_DATA_TRUNCATION = bool_ptr, - MYSQL_OPT_RECONNECT = bool_ptr, - MYSQL_OPT_SSL_VERIFY_SERVER_CERT = bool_ptr, - MYSQL_ENABLE_CLEARTEXT_PLUGIN = bool_ptr, - MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS = bool_ptr, -} - -function M.connect(t, ...) - config() - local host, user, pass, db, charset, port - local unix_socket, flags, options, attrs - local key, cert, ca, capath, cipher - if type(t) == 'string' then - host, user, pass, db, charset, port = t, ... - else - host, user, pass, db, charset, port = t.host, t.user, t.pass, t.db, t.charset, t.port - unix_socket, flags, options, attrs = t.unix_socket, t.flags, t.options, t.attrs - key, cert, ca, capath, cipher = t.key, t.cert, t.ca, t.capath, t.cipher - end - port = port or 0 - - local client_flag = 0 - if type(flags) == 'number' then - client_flag = flags - elseif flags then - for k,v in pairs(flags) do - local flag = enum(k, 'MYSQL_') --'CLIENT_*' or mysql.C.MYSQL_CLIENT_* enum - client_flag = v and bit.bor(client_flag, flag) or bit.band(client_flag, bit.bnot(flag)) - end - end - - local mysql = assert(C.mysql_init(nil)) - ffi.gc(mysql, C.mysql_close) - - if options then - for k,v in pairs(options) do - local opt = enum(k) --'MYSQL_OPT_*' or mysql.C.MYSQL_OPT_* enum - local encoder = option_encoders[k] - if encoder then v = encoder(v) end - assert(C.mysql_options(mysql, opt, ffi.cast('const void*', v)) == 0, 'invalid option') - end - end - - if attrs then - for k,v in pairs(attrs) do - assert(C.mysql_options4(mysql, C.MYSQL_OPT_CONNECT_ATTR_ADD, k, v) == 0) - end - end - - if key then - checkz(mysql, C.mysql_ssl_set(mysql, key, cert, ca, capath, cipher)) - end - - checkh(mysql, C.mysql_real_connect(mysql, host, user, pass, db, port, unix_socket, client_flag)) - - if charset then mysql:set_charset(charset) end - - return mysql -end - -local conn = {} --connection methods - -function conn.close(mysql) - C.mysql_close(mysql) - ffi.gc(mysql, nil) -end - -function conn.set_charset(mysql, charset) - checkz(mysql, C.mysql_set_character_set(mysql, charset)) -end - -function conn.select_db(mysql, db) - checkz(mysql, C.mysql_select_db(mysql, db)) -end - -function conn.change_user(mysql, user, pass, db) - checkz(mysql, C.mysql_change_user(mysql, user, pass, db)) -end - -function conn.set_multiple_statements(mysql, yes) - checkz(mysql, C.mysql_set_server_option(mysql, yes and C.MYSQL_OPTION_MULTI_STATEMENTS_ON or - C.MYSQL_OPTION_MULTI_STATEMENTS_OFF)) -end - ---connection info - -function conn.charset(mysql) - return cstring(C.mysql_character_set_name(mysql)) -end - -function conn.charset_info(mysql) - local info = ffi.new'MY_CHARSET_INFO' - checkz(C.mysql_get_character_set_info(mysql, info)) - assert(info.name ~= NULL) - assert(info.csname ~= NULL) - return { - number = info.number, - state = info.state, - name = cstring(info.csname), --csname and name are inverted from the spec - collation = cstring(info.name), - comment = cstring(info.comment), - dir = cstring(info.dir), - mbminlen = info.mbminlen, - mbmaxlen = info.mbmaxlen, - } -end - -function conn.ping(mysql) - local ret = C.mysql_ping(mysql) - if ret == 0 then - return true - elseif C.mysql_error(mysql) == C.MYSQL_CR_SERVER_GONE_ERROR then - return false - end - myerror(mysql) -end - -function conn.thread_id(mysql) - return C.mysql_thread_id(mysql) --NOTE: result is cdata on x64! -end - -function conn.stat(mysql) - return cstring(checkh(mysql, C.mysql_stat(mysql))) -end - -function conn.server_info(mysql) - return cstring(checkh(mysql, C.mysql_get_server_info(mysql))) -end - -function conn.host_info(mysql) - return cstring(checkh(mysql, C.mysql_get_host_info(mysql))) -end - -function conn.server_version(mysql) - return tonumber(C.mysql_get_server_version(mysql)) -end - -function conn.proto_info(...) - return C.mysql_get_proto_info(...) -end - -function conn.ssl_cipher(mysql) - return cstring(C.mysql_get_ssl_cipher(mysql)) -end - ---transactions - -function conn.commit(mysql) checkz(mysql, C.mysql_commit(mysql)) end -function conn.rollback(mysql) checkz(mysql, C.mysql_rollback(mysql)) end -function conn.set_autocommit(mysql, yes) - checkz(mysql, C.mysql_autocommit(mysql, yes == nil or yes)) -end - ---queries - -function conn.escape_tobuffer(mysql, data, size, buf, sz) - size = size or #data - assert(sz >= size * 2 + 1) - return tonumber(C.mysql_real_escape_string(mysql, buf, data, size)) -end - -function conn.escape(mysql, data, size) - size = size or #data - local sz = size * 2 + 1 - local buf = ffi.new('uint8_t[?]', sz) - sz = conn.escape_tobuffer(mysql, data, size, buf, sz) - return ffi.string(buf, sz) -end - -function conn.query(mysql, data, size) - checkz(mysql, C.mysql_real_query(mysql, data, size or #data)) -end - ---query info - -function conn.field_count(...) - return C.mysql_field_count(...) -end - -local minus1_uint64 = ffi.cast('uint64_t', ffi.cast('int64_t', -1)) -function conn.affected_rows(mysql) - local n = C.mysql_affected_rows(mysql) - if n == minus1_uint64 then myerror(mysql) end - return tonumber(n) -end - -function conn.insert_id(...) - return C.mysql_insert_id(...) --NOTE: result is cdata on x64! -end - -function conn.errno(conn) - local err = C.mysql_errno(conn) - if err == 0 then return end - return err -end - -function conn.sqlstate(mysql) - return cstring(C.mysql_sqlstate(mysql)) -end - -function conn.warning_count(...) - return C.mysql_warning_count(...) -end - -function conn.info(mysql) - return cstring(C.mysql_info(mysql)) -end - ---query results - -function conn.next_result(mysql) --multiple statement queries return multiple results - local ret = C.mysql_next_result(mysql) - if ret == 0 then return true end - if ret == -1 then return false end - myerror(mysql) -end - -function conn.more_results(mysql) - return C.mysql_more_results(mysql) == 1 -end - -local function result_function(func) - return function(mysql) - local res = checkh(mysql, C[func](mysql)) - return ffi.gc(res, C.mysql_free_result) - end -end - -conn.store_result = result_function'mysql_store_result' -conn.use_result = result_function'mysql_use_result' - -local res = {} --result methods - -function res.free(res) - C.mysql_free_result(res) - ffi.gc(res, nil) -end - -function res.row_count(res) - return tonumber(C.mysql_num_rows(res)) -end - -function res.field_count(...) - return C.mysql_num_fields(...) -end - -function res.eof(res) - return C.mysql_eof(res) ~= 0 -end - ---field info - -local field_type_names = { - [ffi.C.MYSQL_TYPE_DECIMAL] = 'decimal', --DECIMAL or NUMERIC - [ffi.C.MYSQL_TYPE_TINY] = 'tinyint', - [ffi.C.MYSQL_TYPE_SHORT] = 'smallint', - [ffi.C.MYSQL_TYPE_LONG] = 'int', - [ffi.C.MYSQL_TYPE_FLOAT] = 'float', - [ffi.C.MYSQL_TYPE_DOUBLE] = 'double', --DOUBLE or REAL - [ffi.C.MYSQL_TYPE_NULL] = 'null', - [ffi.C.MYSQL_TYPE_TIMESTAMP] = 'timestamp', - [ffi.C.MYSQL_TYPE_LONGLONG] = 'bigint', - [ffi.C.MYSQL_TYPE_INT24] = 'mediumint', - [ffi.C.MYSQL_TYPE_DATE] = 'date', --pre mysql 5.0, storage = 4 bytes - [ffi.C.MYSQL_TYPE_TIME] = 'time', - [ffi.C.MYSQL_TYPE_DATETIME] = 'datetime', - [ffi.C.MYSQL_TYPE_YEAR] = 'year', - [ffi.C.MYSQL_TYPE_NEWDATE] = 'date', --mysql 5.0+, storage = 3 bytes - [ffi.C.MYSQL_TYPE_VARCHAR] = 'varchar', - [ffi.C.MYSQL_TYPE_BIT] = 'bit', - [ffi.C.MYSQL_TYPE_TIMESTAMP2] = 'timestamp', --mysql 5.6+, can store fractional seconds - [ffi.C.MYSQL_TYPE_DATETIME2] = 'datetime', --mysql 5.6+, can store fractional seconds - [ffi.C.MYSQL_TYPE_TIME2] = 'time', --mysql 5.6+, can store fractional seconds - [ffi.C.MYSQL_TYPE_NEWDECIMAL] = 'decimal', --mysql 5.0+, Precision math DECIMAL or NUMERIC - [ffi.C.MYSQL_TYPE_ENUM] = 'enum', - [ffi.C.MYSQL_TYPE_SET] = 'set', - [ffi.C.MYSQL_TYPE_TINY_BLOB] = 'tinyblob', - [ffi.C.MYSQL_TYPE_MEDIUM_BLOB] = 'mediumblob', - [ffi.C.MYSQL_TYPE_LONG_BLOB] = 'longblob', - [ffi.C.MYSQL_TYPE_BLOB] = 'text', --TEXT or BLOB - [ffi.C.MYSQL_TYPE_VAR_STRING] = 'varchar', --VARCHAR or VARBINARY - [ffi.C.MYSQL_TYPE_STRING] = 'char', --CHAR or BINARY - [ffi.C.MYSQL_TYPE_GEOMETRY] = 'spatial', --Spatial field -} - -local binary_field_type_names = { - [ffi.C.MYSQL_TYPE_BLOB] = 'blob', - [ffi.C.MYSQL_TYPE_VAR_STRING] = 'varbinary', - [ffi.C.MYSQL_TYPE_STRING] = 'binary', -} - -local field_flag_names = { - [ffi.C.MYSQL_NOT_NULL_FLAG] = 'not_null', - [ffi.C.MYSQL_PRI_KEY_FLAG] = 'pri_key', - [ffi.C.MYSQL_UNIQUE_KEY_FLAG] = 'unique_key', - [ffi.C.MYSQL_MULTIPLE_KEY_FLAG] = 'key', - [ffi.C.MYSQL_BLOB_FLAG] = 'is_blob', - [ffi.C.MYSQL_UNSIGNED_FLAG] = 'unsigned', - [ffi.C.MYSQL_ZEROFILL_FLAG] = 'zerofill', - [ffi.C.MYSQL_BINARY_FLAG] = 'is_binary', - [ffi.C.MYSQL_ENUM_FLAG] = 'is_enum', - [ffi.C.MYSQL_AUTO_INCREMENT_FLAG] = 'autoincrement', - [ffi.C.MYSQL_TIMESTAMP_FLAG] = 'is_timestamp', - [ffi.C.MYSQL_SET_FLAG] = 'is_set', - [ffi.C.MYSQL_NO_DEFAULT_VALUE_FLAG] = 'no_default', - [ffi.C.MYSQL_ON_UPDATE_NOW_FLAG] = 'on_update_now', - [ffi.C.MYSQL_NUM_FLAG] = 'is_number', -} - -local function field_type_name(info) - local type_flag = tonumber(info.type) - local field_type = field_type_names[type_flag] - --charsetnr 63 changes CHAR into BINARY, VARCHAR into VARBYNARY, TEXT into BLOB - field_type = info.charsetnr == 63 and binary_field_type_names[type_flag] or field_type - return field_type -end - ---convenience field type fetcher (less garbage) -function res.field_type(res, i) - assert(i >= 1 and i <= res:field_count(), 'index out of range') - local info = C.mysql_fetch_field_direct(res, i-1) - local unsigned = bit.bor(info.flags, C.MYSQL_UNSIGNED_FLAG) ~= 0 - return field_type_name(info), tonumber(info.length), unsigned, info.decimals -end - -function res.field_info(res, i) - assert(i >= 1 and i <= res:field_count(), 'index out of range') - local info = C.mysql_fetch_field_direct(res, i-1) - local t = { - name = cstring(info.name, info.name_length), - org_name = cstring(info.org_name, info.org_name_length), - table = cstring(info.table, info.table_length), - org_table = cstring(info.org_table, info.org_table_length), - db = cstring(info.db, info.db_length), - catalog = cstring(info.catalog, info.catalog_length), - def = cstring(info.def, info.def_length), - length = tonumber(info.length), - max_length = tonumber(info.max_length), - decimals = info.decimals, - charsetnr = info.charsetnr, - type_flag = tonumber(info.type), - type = field_type_name(info), - flags = info.flags, - extension = ptr(info.extension), - } - for flag, name in pairs(field_flag_names) do - t[name] = bit.band(flag, info.flags) ~= 0 - end - return t -end - ---convenience field name fetcher (less garbage) -function res.field_name(res, i) - assert(i >= 1 and i <= res:field_count(), 'index out of range') - local info = C.mysql_fetch_field_direct(res, i-1) - return cstring(info.name, info.name_length) -end - ---convenience field iterator, shortcut for: for i=1,res:field_count() do local field = res:field_info(i) ... end -function res.fields(res) - local n = res:field_count() - local i = 0 - return function() - if i == n then return end - i = i + 1 - return i, res:field_info(i) - end -end - ---row data fetching and parsing - -ffi.cdef('double strtod(const char*, char**);') -local function parse_int(data, sz) --using strtod to avoid string creation - return ffi.C.strtod(data, nil) -end - -local function parse_float(data, sz) - return tonumber(ffi.cast('float', ffi.C.strtod(data, nil))) --because windows is missing strtof() -end - -local function parse_double(data, sz) - return ffi.C.strtod(data, nil) -end - -ffi.cdef('int64_t strtoll(const char*, char**, int) ' ..(ffi.os == 'Windows' and ' asm("_strtoi64")' or '') .. ';') -local function parse_int64(data, sz) - return ffi.C.strtoll(data, nil, 10) -end - -ffi.cdef('uint64_t strtoull(const char*, char**, int) ' ..(ffi.os == 'Windows' and ' asm("_strtoui64")' or '') .. ';') -local function parse_uint64(data, sz) - return ffi.C.strtoull(data, nil, 10) -end - -local function parse_bit(data, sz) - data = ffi.cast('uint8_t*', data) --force unsigned - local n = data[0] --this is the msb: bit fields always come in big endian byte order - if sz > 6 then --we can cover up to 6 bytes with only Lua numbers - n = ffi.new('uint64_t', n) - end - for i=1,sz-1 do - n = n * 256 + data[i] - end - return n -end - -local function parse_date_(data, sz) - assert(sz >= 10) - local z = ('0'):byte() - local year = (data[0] - z) * 1000 + (data[1] - z) * 100 + (data[2] - z) * 10 + (data[3] - z) - local month = (data[5] - z) * 10 + (data[6] - z) - local day = (data[8] - z) * 10 + (data[9] - z) - return year, month, day -end - -local function parse_time_(data, sz) - assert(sz >= 8) - local z = ('0'):byte() - local hour = (data[0] - z) * 10 + (data[1] - z) - local min = (data[3] - z) * 10 + (data[4] - z) - local sec = (data[6] - z) * 10 + (data[7] - z) - local frac = 0 - for i = 9, sz-1 do - frac = frac * 10 + (data[i] - z) - end - return hour, min, sec, frac -end - -local function format_date(year, month, day) - return string.format('%04d-%02d-%02d', year, month, day) -end - -local function format_time(hour, min, sec, frac) - if frac and frac ~= 0 then - return string.format('%02d:%02d:%02d.%d', hour, min, sec, frac) - else - return string.format('%02d:%02d:%02d', hour, min, sec) - end -end - -local function datetime_tostring(t) - local date, time - if t.year then - date = format_date(t.year, t.month, t.day) - end - if t.sec then - time = format_time(t.hour, t.min, t.sec, t.frac) - end - if date and time then - return date .. ' ' .. time - else - return assert(date or time) - end -end - -local datetime_meta = {__tostring = datetime_tostring} -local function datetime(t) - return setmetatable(t, datetime_meta) -end - -local function parse_date(data, sz) - local year, month, day = parse_date_(data, sz) - return datetime{year = year, month = month, day = day} -end - -local function parse_time(data, sz) - local hour, min, sec, frac = parse_time_(data, sz) - return datetime{hour = hour, min = min, sec = sec, frac = frac} -end - -local function parse_datetime(data, sz) - local year, month, day = parse_date_(data, sz) - local hour, min, sec, frac = parse_time_(data + 11, sz - 11) - return datetime{year = year, month = month, day = day, hour = hour, min = min, sec = sec, frac = frac} -end - -local field_decoders = { --other field types not present here are returned as strings, unparsed - [ffi.C.MYSQL_TYPE_TINY] = parse_int, - [ffi.C.MYSQL_TYPE_SHORT] = parse_int, - [ffi.C.MYSQL_TYPE_LONG] = parse_int, - [ffi.C.MYSQL_TYPE_FLOAT] = parse_float, - [ffi.C.MYSQL_TYPE_DOUBLE] = parse_double, - [ffi.C.MYSQL_TYPE_TIMESTAMP] = parse_datetime, - [ffi.C.MYSQL_TYPE_LONGLONG] = parse_int64, - [ffi.C.MYSQL_TYPE_INT24] = parse_int, - [ffi.C.MYSQL_TYPE_DATE] = parse_date, - [ffi.C.MYSQL_TYPE_TIME] = parse_time, - [ffi.C.MYSQL_TYPE_DATETIME] = parse_datetime, - [ffi.C.MYSQL_TYPE_NEWDATE] = parse_date, - [ffi.C.MYSQL_TYPE_TIMESTAMP2] = parse_datetime, - [ffi.C.MYSQL_TYPE_DATETIME2] = parse_datetime, - [ffi.C.MYSQL_TYPE_TIME2] = parse_time, - [ffi.C.MYSQL_TYPE_YEAR] = parse_int, - [ffi.C.MYSQL_TYPE_BIT] = parse_bit, -} - -local unsigned_decoders = { - [ffi.C.MYSQL_TYPE_LONGLONG] = parse_uint64, -} - -local function mode_flags(mode) - local assoc = mode and mode:find'a' - local numeric = not mode or not assoc or mode:find'n' - local decode = not mode or not mode:find's' - local packed = mode and mode:find'[an]' - local fetch_fields = assoc or decode --if assoc we need field_name, if decode we need field_type - return numeric, assoc, decode, packed, fetch_fields -end - -local function fetch_row(res, numeric, assoc, decode, field_count, fields, t) - local values = C.mysql_fetch_row(res) - if values == NULL then - if res.conn ~= NULL then --buffered read: check for errors - myerror(res.conn, 4) - end - return nil - end - local sizes = C.mysql_fetch_lengths(res) - for i=0,field_count-1 do - local v = values[i] - if v ~= NULL then - local decoder - if decode then - local ftype = tonumber(fields[i].type) - local unsigned = bit.bor(fields[i].flags, C.MYSQL_UNSIGNED_FLAG) ~= 0 - decoder = unsigned and unsigned_decoders[ftype] or field_decoders[ftype] or ffi.string - else - decoder = ffi.string - end - v = decoder(values[i], tonumber(sizes[i])) - if numeric then - t[i+1] = v - end - if assoc then - local k = ffi.string(fields[i].name, fields[i].name_length) - t[k] = v - end - end - end - return t -end - -function res.fetch(res, mode, t) - local numeric, assoc, decode, packed, fetch_fields = mode_flags(mode) - local field_count = C.mysql_num_fields(res) - local fields = fetch_fields and C.mysql_fetch_fields(res) - local row = fetch_row(res, numeric, assoc, decode, field_count, fields, t or {}) - if packed then - return row - else - return true, unpack(row) - end -end - -function res.rows(res, mode, t) - local numeric, assoc, decode, packed, fetch_fields = mode_flags(mode) - local field_count = C.mysql_num_fields(res) - local fields = fetch_fields and C.mysql_fetch_fields(res) - local i = 0 - res:seek(1) - return function() - local row = fetch_row(res, numeric, assoc, decode, field_count, fields, t or {}) - if not row then return end - i = i + 1 - if packed then - return i, row - else - return i, unpack(row) - end - end -end - -function res.tell(...) - return C.mysql_row_tell(...) -end - -function res.seek(res, where) --use in conjunction with res:row_count() - if type(where) == 'number' then - C.mysql_data_seek(res, where-1) - else - C.mysql_row_seek(res, where) - end -end - ---reflection - -local function list_function(func) - return function(mysql, wild) - local res = checkh(mysql, C[func](mysql, wild)) - return ffi.gc(res, C.mysql_free_result) - end -end - -conn.list_dbs = list_function'mysql_list_dbs' -conn.list_tables = list_function'mysql_list_tables' -conn.list_processes = result_function'mysql_list_processes' - ---remote control - -function conn.kill(mysql, pid) - checkz(mysql, C.mysql_kill(mysql, pid)) -end - -function conn.shutdown(mysql, level) - checkz(mysql, C.mysql_shutdown(mysql, enum(level or C.MYSQL_SHUTDOWN_DEFAULT, 'MYSQL_'))) -end - -function conn.refresh(mysql, t) --options are 'REFRESH_*' or mysql.C.MYSQL_REFRESH_* enums - local options = 0 - if type(t) == 'number' then - options = t - else - for k,v in pairs(t) do - if v then - options = bit.bor(options, enum(k, 'MYSQL_')) - end - end - end - checkz(mysql, C.mysql_refresh(mysql, options)) -end - -function conn.dump_debug_info(mysql) - checkz(mysql, C.mysql_dump_debug_info(mysql)) -end - ---prepared statements - -local function sterror(stmt, stacklevel) - local err = cstring(C.mysql_stmt_error(stmt)) - if not err then return end - error(string.format('mysql error: %s', err), stacklevel or 3) -end - -local function stcheckz(stmt, ret) - if ret == 0 then return end - sterror(stmt, 4) -end - -local function stcheckbool(stmt, ret) - if ret == 1 then return end - sterror(stmt, 4) -end - -local function stcheckh(stmt, ret) - if ret ~= NULL then return ret end - sterror(stmt, 4) -end - -function conn.prepare(mysql, query) - local stmt = checkh(mysql, C.mysql_stmt_init(mysql)) - ffi.gc(stmt, C.mysql_stmt_close) - stcheckz(stmt, C.mysql_stmt_prepare(stmt, query, #query)) - return stmt -end - -local stmt = {} --statement methods - -function stmt.close(stmt) - stcheckbool(stmt, C.mysql_stmt_close(stmt)) - ffi.gc(stmt, nil) -end - -function stmt.exec(stmt) - stcheckz(stmt, C.mysql_stmt_execute(stmt)) -end - -function stmt.next_result(stmt) - local ret = C.mysql_stmt_next_result(stmt) - if ret == 0 then return true end - if ret == -1 then return false end - sterror(stmt) -end - -function stmt.store_result(stmt) - stcheckz(stmt, C.mysql_stmt_store_result(stmt)) -end - -function stmt.free_result(stmt) - stcheckbool(stmt, C.mysql_stmt_free_result(stmt)) -end - -function stmt.row_count(stmt) - return tonumber(C.mysql_stmt_num_rows(stmt)) -end - -function stmt.affected_rows(stmt) - local n = C.mysql_stmt_affected_rows(stmt) - if n == minus1_uint64 then sterror(stmt) end - return tonumber(n) -end - -function stmt.insert_id(...) - return C.mysql_stmt_insert_id(...) -end - -function stmt.field_count(stmt) - return tonumber(C.mysql_stmt_field_count(stmt)) -end - -function stmt.param_count(stmt) - return tonumber(C.mysql_stmt_param_count(stmt)) -end - -function stmt.errno(stmt) - local err = C.mysql_stmt_errno(stmt) - if err == 0 then return end - return err -end - -function stmt.sqlstate(stmt) - return cstring(C.mysql_stmt_sqlstate(stmt)) -end - -function stmt.result_metadata(stmt) - local res = stcheckh(stmt, C.mysql_stmt_result_metadata(stmt)) - return res and ffi.gc(res, C.mysql_free_result) -end - -function stmt.fields(stmt) - local res = stmt:result_metadata() - if not res then return nil end - local fields = res:fields() - return function() - local i, info = fields() - if not i then - res:free() - end - return i, info - end -end - -function stmt.fetch(stmt) - local ret = C.mysql_stmt_fetch(stmt) - if ret == 0 then return true end - if ret == C.MYSQL_NO_DATA then return false end - if ret == C.MYSQL_DATA_TRUNCATED then return true, 'truncated' end - sterror(stmt) -end - -function stmt.reset(stmt) - stcheckz(stmt, C.mysql_stmt_reset(stmt)) -end - -function stmt.tell(...) - return C.mysql_stmt_row_tell(...) -end - -function stmt.seek(stmt, where) --use in conjunction with stmt:row_count() - if type(where) == 'number' then - C.mysql_stmt_data_seek(stmt, where-1) - else - C.mysql_stmt_row_seek(stmt, where) - end -end - -function stmt.write(stmt, param_number, data, size) - stcheckz(stmt, C.mysql_stmt_send_long_data(stmt, param_number, data, size or #data)) -end - -function stmt.update_max_length(stmt) - local attr = ffi.new'my_bool[1]' - stcheckz(stmt, C.mysql_stmt_attr_get(stmt, C.STMT_ATTR_UPDATE_MAX_LENGTH, attr)) - return attr[0] == 1 -end - -function stmt.set_update_max_length(stmt, yes) - local attr = ffi.new('my_bool[1]', yes == nil or yes) - stcheckz(stmt, C.mysql_stmt_attr_set(stmt, C.STMT_ATTR_CURSOR_TYPE, attr)) -end - -function stmt.cursor_type(stmt) - local attr = ffi.new'uint32_t[1]' - stcheckz(stmt, C.mysql_stmt_attr_get(stmt, C.STMT_ATTR_CURSOR_TYPE, attr)) - return attr[0] -end - -function stmt.set_cursor_type(stmt, cursor_type) - local attr = ffi.new('uint32_t[1]', enum(cursor_type, 'MYSQL_')) - stcheckz(stmt, C.mysql_stmt_attr_set(stmt, C.STMT_ATTR_CURSOR_TYPE, attr)) -end - -function stmt.prefetch_rows(stmt) - local attr = ffi.new'uint32_t[1]' - stcheckz(stmt, C.mysql_stmt_attr_get(stmt, C.STMT_ATTR_PREFETCH_ROWS, attr)) - return attr[0] -end - -function stmt.set_prefetch_rows(stmt, n) - local attr = ffi.new('uint32_t[1]', n) - stcheckz(stmt, C.mysql_stmt_attr_set(stmt, C.STMT_ATTR_PREFETCH_ROWS, attr)) -end - ---prepared statements / bind buffers - ---see http://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-codes.html -local bb_types_input = { - --conversion-free types - tinyint = ffi.C.MYSQL_TYPE_TINY, - smallint = ffi.C.MYSQL_TYPE_SHORT, - int = ffi.C.MYSQL_TYPE_LONG, - integer = ffi.C.MYSQL_TYPE_LONG, --alias of int - bigint = ffi.C.MYSQL_TYPE_LONGLONG, - float = ffi.C.MYSQL_TYPE_FLOAT, - double = ffi.C.MYSQL_TYPE_DOUBLE, - time = ffi.C.MYSQL_TYPE_TIME, - date = ffi.C.MYSQL_TYPE_DATE, - datetime = ffi.C.MYSQL_TYPE_DATETIME, - timestamp = ffi.C.MYSQL_TYPE_TIMESTAMP, - text = ffi.C.MYSQL_TYPE_STRING, - char = ffi.C.MYSQL_TYPE_STRING, - varchar = ffi.C.MYSQL_TYPE_STRING, - blob = ffi.C.MYSQL_TYPE_BLOB, - binary = ffi.C.MYSQL_TYPE_BLOB, - varbinary = ffi.C.MYSQL_TYPE_BLOB, - null = ffi.C.MYSQL_TYPE_NULL, - --conversion types (can only use one of the above C types) - mediumint = ffi.C.MYSQL_TYPE_LONG, - real = ffi.C.MYSQL_TYPE_DOUBLE, - decimal = ffi.C.MYSQL_TYPE_BLOB, - numeric = ffi.C.MYSQL_TYPE_BLOB, - year = ffi.C.MYSQL_TYPE_SHORT, - tinyblob = ffi.C.MYSQL_TYPE_BLOB, - tinytext = ffi.C.MYSQL_TYPE_BLOB, - mediumblob = ffi.C.MYSQL_TYPE_BLOB, - mediumtext = ffi.C.MYSQL_TYPE_BLOB, - longblob = ffi.C.MYSQL_TYPE_BLOB, - longtext = ffi.C.MYSQL_TYPE_BLOB, - bit = ffi.C.MYSQL_TYPE_LONGLONG, --MYSQL_TYPE_BIT is not available for input params - set = ffi.C.MYSQL_TYPE_BLOB, - enum = ffi.C.MYSQL_TYPE_BLOB, -} - -local bb_types_output = { - --conversion-free types - tinyint = ffi.C.MYSQL_TYPE_TINY, - smallint = ffi.C.MYSQL_TYPE_SHORT, - mediumint = ffi.C.MYSQL_TYPE_INT24, --int32 - int = ffi.C.MYSQL_TYPE_LONG, - integer = ffi.C.MYSQL_TYPE_LONG, --alias of int - bigint = ffi.C.MYSQL_TYPE_LONGLONG, - float = ffi.C.MYSQL_TYPE_FLOAT, - double = ffi.C.MYSQL_TYPE_DOUBLE, - real = ffi.C.MYSQL_TYPE_DOUBLE, - decimal = ffi.C.MYSQL_TYPE_NEWDECIMAL, --char[] - numeric = ffi.C.MYSQL_TYPE_NEWDECIMAL, --char[] - year = ffi.C.MYSQL_TYPE_SHORT, - time = ffi.C.MYSQL_TYPE_TIME, - date = ffi.C.MYSQL_TYPE_DATE, - datetime = ffi.C.MYSQL_TYPE_DATETIME, - timestamp = ffi.C.MYSQL_TYPE_TIMESTAMP, - char = ffi.C.MYSQL_TYPE_STRING, - binary = ffi.C.MYSQL_TYPE_STRING, - varchar = ffi.C.MYSQL_TYPE_VAR_STRING, - varbinary = ffi.C.MYSQL_TYPE_VAR_STRING, - tinyblob = ffi.C.MYSQL_TYPE_TINY_BLOB, - tinytext = ffi.C.MYSQL_TYPE_TINY_BLOB, - blob = ffi.C.MYSQL_TYPE_BLOB, - text = ffi.C.MYSQL_TYPE_BLOB, - mediumblob = ffi.C.MYSQL_TYPE_MEDIUM_BLOB, - mediumtext = ffi.C.MYSQL_TYPE_MEDIUM_BLOB, - longblob = ffi.C.MYSQL_TYPE_LONG_BLOB, - longtext = ffi.C.MYSQL_TYPE_LONG_BLOB, - bit = ffi.C.MYSQL_TYPE_BIT, - --conversion types (can only use one of the above C types) - null = ffi.C.MYSQL_TYPE_TINY, - set = ffi.C.MYSQL_TYPE_BLOB, - enum = ffi.C.MYSQL_TYPE_BLOB, -} - -local number_types = { - [ffi.C.MYSQL_TYPE_TINY] = 'int8_t[1]', - [ffi.C.MYSQL_TYPE_SHORT] = 'int16_t[1]', - [ffi.C.MYSQL_TYPE_LONG] = 'int32_t[1]', - [ffi.C.MYSQL_TYPE_INT24] = 'int32_t[1]', - [ffi.C.MYSQL_TYPE_LONGLONG] = 'int64_t[1]', - [ffi.C.MYSQL_TYPE_FLOAT] = 'float[1]', - [ffi.C.MYSQL_TYPE_DOUBLE] = 'double[1]', -} - -local uint_types = { - [ffi.C.MYSQL_TYPE_TINY] = 'uint8_t[1]', - [ffi.C.MYSQL_TYPE_SHORT] = 'uint16_t[1]', - [ffi.C.MYSQL_TYPE_LONG] = 'uint32_t[1]', - [ffi.C.MYSQL_TYPE_INT24] = 'uint32_t[1]', - [ffi.C.MYSQL_TYPE_LONGLONG] = 'uint64_t[1]', -} - -local time_types = { - [ffi.C.MYSQL_TYPE_TIME] = true, - [ffi.C.MYSQL_TYPE_DATE] = true, - [ffi.C.MYSQL_TYPE_DATETIME] = true, - [ffi.C.MYSQL_TYPE_TIMESTAMP] = true, -} - -local time_struct_types = { - [ffi.C.MYSQL_TYPE_TIME] = ffi.C.MYSQL_TIMESTAMP_TIME, - [ffi.C.MYSQL_TYPE_DATE] = ffi.C.MYSQL_TIMESTAMP_DATE, - [ffi.C.MYSQL_TYPE_DATETIME] = ffi.C.MYSQL_TIMESTAMP_DATETIME, - [ffi.C.MYSQL_TYPE_TIMESTAMP] = ffi.C.MYSQL_TIMESTAMP_DATETIME, -} - -local params = {} --params bind buffer methods -local params_meta = {__index = params} -local fields = {} --params bind buffer methods -local fields_meta = {__index = fields} - --- "varchar(200)" -> "varchar", 200; "decimal(10,4)" -> "decimal", 12; "int unsigned" -> "int", nil, true -local function parse_type(s) - s = s:lower() - local unsigned = false - local rest = s:match'(.-)%s+unsigned$' - if rest then s, unsigned = rest, true end - local rest, sz = s:match'^%s*([^%(]+)%s*%(%s*(%d+)[^%)]*%)%s*$' - if rest then - s, sz = rest, assert(tonumber(sz), 'invalid type') - if s == 'decimal' or s == 'numeric' then --make room for the dot and the minus sign - sz = sz + 2 - end - end - return s, sz, unsigned -end - -local function bind_buffer(bb_types, meta, types) - local self = setmetatable({}, meta) - - self.count = #types - self.buffer = ffi.new('MYSQL_BIND[?]', #types) - self.data = {} --data buffers, one for each field - self.lengths = ffi.new('unsigned long[?]', #types) --length buffers, one for each field - self.null_flags = ffi.new('my_bool[?]', #types) --null flag buffers, one for each field - self.error_flags = ffi.new('my_bool[?]', #types) --error (truncation) flag buffers, one for each field - - for i,typedef in ipairs(types) do - local stype, size, unsigned = parse_type(typedef) - local btype = assert(bb_types[stype], 'invalid type') - local data - if stype == 'bit' then - if btype == C.MYSQL_TYPE_LONGLONG then --for input: use unsigned int64 and ignore size - data = ffi.new'uint64_t[1]' - self.buffer[i-1].is_unsigned = 1 - size = 0 - elseif btype == C.MYSQL_TYPE_BIT then --for output: use mysql conversion-free type - size = size or 64 --if missing size, assume maximum - size = math.ceil(size / 8) - assert(size >= 1 and size <= 8, 'invalid size') - data = ffi.new('uint8_t[?]', size) - end - elseif number_types[btype] then - assert(not size, 'fixed size type') - data = ffi.new(unsigned and uint_types[btype] or number_types[btype]) - self.buffer[i-1].is_unsigned = unsigned - size = ffi.sizeof(data) - elseif time_types[btype] then - assert(not size, 'fixed size type') - data = ffi.new'MYSQL_TIME' - data.time_type = time_struct_types[btype] - size = 0 - elseif btype == C.MYSQL_TYPE_NULL then - assert(not size, 'fixed size type') - size = 0 - else - assert(size, 'missing size') - data = size > 0 and ffi.new('uint8_t[?]', size) or nil - end - self.null_flags[i-1] = true - self.data[i] = data - self.lengths[i-1] = 0 - self.buffer[i-1].buffer_type = btype - self.buffer[i-1].buffer = data - self.buffer[i-1].buffer_length = size - self.buffer[i-1].is_null = self.null_flags + (i - 1) - self.buffer[i-1].error = self.error_flags + (i - 1) - self.buffer[i-1].length = self.lengths + (i - 1) - end - return self -end - -local function params_bind_buffer(types) - return bind_buffer(bb_types_input, params_meta, types) -end - -local function fields_bind_buffer(types) - return bind_buffer(bb_types_output, fields_meta, types) -end - -local function bind_check_range(self, i) - assert(i >= 1 and i <= self.count, 'index out of bounds') -end - ---realloc a buffer using supplied size. only for varsize fields. -function params:realloc(i, size) - bind_check_range(self, i) - assert(ffi.istype(data, 'uint8_t[?]'), 'attempt to realloc a fixed size field') - local data = size > 0 and ffi.new('uint8_t[?]', size) or nil - self.null_flags[i-1] = true - self.data[i] = data - self.lengths[i-1] = 0 - self.buffer[i-1].buffer = data - self.buffer[i-1].buffer_length = size -end - -fields.realloc = params.realloc - -function fields:get_date(i) - bind_check_range(self, i) - local btype = tonumber(self.buffer[i-1].buffer_type) - local date = btype == C.MYSQL_TYPE_DATE or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP - local time = btype == C.MYSQL_TYPE_TIME or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP - assert(date or time, 'not a date/time type') - if self.null_flags[i-1] == 1 then return nil end - local tm = self.data[i] - return - date and tm.year or nil, - date and tm.month or nil, - date and tm.day or nil, - time and tm.hour or nil, - time and tm.minute or nil, - time and tm.second or nil, - time and tonumber(tm.second_part) or nil -end - -function params:set_date(i, year, month, day, hour, min, sec, frac) - bind_check_range(self, i) - local tm = self.data[i] - local btype = tonumber(self.buffer[i-1].buffer_type) - local date = btype == C.MYSQL_TYPE_DATE or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP - local time = btype == C.MYSQL_TYPE_TIME or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP - assert(date or time, 'not a date/time type') - local tm = self.data[i] - tm.year = date and math.max(0, math.min(year or 0, 9999)) or 0 - tm.month = date and math.max(1, math.min(month or 0, 12)) or 0 - tm.day = date and math.max(1, math.min(day or 0, 31)) or 0 - tm.hour = time and math.max(0, math.min(hour or 0, 59)) or 0 - tm.minute = time and math.max(0, math.min(min or 0, 59)) or 0 - tm.second = time and math.max(0, math.min(sec or 0, 59)) or 0 - tm.second_part = time and math.max(0, math.min(frac or 0, 999999)) or 0 - self.null_flags[i-1] = false -end - -function params:set(i, v, size) - bind_check_range(self, i) - v = ptr(v) - if v == nil then - self.null_flags[i-1] = true - return - end - local btype = tonumber(self.buffer[i-1].buffer_type) - if btype == C.MYSQL_TYPE_NULL then - error('attempt to set a null type param') - elseif number_types[btype] then --this includes bit type which is LONGLONG - self.data[i][0] = v - self.null_flags[i-1] = false - elseif time_types[btype] then - self:set_date(i, v.year, v.month, v.day, v.hour, v.min, v.sec, v.frac) - else --var-sized types and raw bit blobs - size = size or #v - local bsize = tonumber(self.buffer[i-1].buffer_length) - assert(bsize >= size, 'string too long') - ffi.copy(self.data[i], v, size) - self.lengths[i-1] = size - self.null_flags[i-1] = false - end -end - -function fields:get(i) - bind_check_range(self, i) - local btype = tonumber(self.buffer[i-1].buffer_type) - if btype == C.MYSQL_TYPE_NULL or self.null_flags[i-1] == 1 then - return nil - end - if number_types[btype] then - return self.data[i][0] --ffi converts this to a number or int64 type, which maches result:fetch() decoding - elseif time_types[btype] then - local t = self.data[i] - if t.time_type == C.MYSQL_TIMESTAMP_TIME then - return datetime{hour = t.hour, min = t.minute, sec = t.second, frac = tonumber(t.second_part)} - elseif t.time_type == C.MYSQL_TIMESTAMP_DATE then - return datetime{year = t.year, month = t.month, day = t.day} - elseif t.time_type == C.MYSQL_TIMESTAMP_DATETIME then - return datetime{year = t.year, month = t.month, day = t.day, - hour = t.hour, min = t.minute, sec = t.second, frac = tonumber(t.second_part)} - else - error'invalid time' - end - else - local sz = math.min(tonumber(self.buffer[i-1].buffer_length), tonumber(self.lengths[i-1])) - if btype == C.MYSQL_TYPE_BIT then - return parse_bit(self.data[i], sz) - else - return ffi.string(self.data[i], sz) - end - end -end - -function fields:is_null(i) --returns true if the field is null - bind_check_range(self, i) - local btype = self.buffer[i-1].buffer_type - return btype == C.MYSQL_TYPE_NULL or self.null_flags[i-1] == 1 -end - -function fields:is_truncated(i) --returns true if the field value was truncated - bind_check_range(self, i) - return self.error_flags[i-1] == 1 -end - -local varsize_types = { - char = true, - binary = true, - varchar = true, - varbinary = true, - tinyblob = true, - tinytext = true, - blob = true, - text = true, - mediumblob = true, - mediumtext = true, - longblob = true, - longtext = true, - bit = true, - set = true, - enum = true, -} - -function stmt.bind_result_types(stmt, maxsize) - local types = {} - local field_count = stmt:field_count() - local res = stmt:result_metadata() - if not res then return nil end - for i=1,field_count do - local ftype, size, unsigned, decimals = res:field_type(i) - if ftype == 'decimal' then - ftype = string.format('%s(%d,%d)', ftype, size-2, decimals) - elseif varsize_types[ftype] then - size = math.min(size, maxsize or 65535) - ftype = string.format('%s(%d)', ftype, size) - end - ftype = unsigned and ftype..' unsigned' or ftype - types[i] = ftype - end - res:free() - return types -end - -function stmt.bind_params(stmt, ...) - local types = type((...)) == 'string' and {...} or ... or {} - assert(stmt:param_count() == #types, 'wrong number of param types') - local bb = params_bind_buffer(types) - stcheckz(stmt, C.mysql_stmt_bind_param(stmt, bb.buffer)) - return bb -end - -function stmt.bind_result(stmt, arg1, ...) - local types - if type(arg1) == 'string' then - types = {arg1, ...} - elseif type(arg1) == 'number' then - types = stmt:bind_result_types(arg1) - elseif arg1 then - types = arg1 - else - types = stmt:bind_result_types() - end - assert(stmt:field_count() == #types, 'wrong number of field types') - local bb = fields_bind_buffer(types) - stcheckz(stmt, C.mysql_stmt_bind_result(stmt, bb.buffer)) - return bb -end - ---publish methods - -ffi.metatype('MYSQL', {__index = conn}) -ffi.metatype('MYSQL_RES', {__index = res}) -ffi.metatype('MYSQL_STMT', {__index = stmt}) - ---publish classes (for introspection, not extending) - -M.conn = conn -M.res = res -M.stmt = stmt -M.params = params -M.fields = fields - -return M diff --git a/worldmods/mysql_base/mysql/mysql.md b/worldmods/mysql_base/mysql/mysql.md deleted file mode 100644 index d0102d7..0000000 --- a/worldmods/mysql_base/mysql/mysql.md +++ /dev/null @@ -1,548 +0,0 @@ ---- -tagline: mysql database client ---- - -## `local mysql = require'mysql'` - -A complete, lightweight ffi binding of the mysql client library. - -> NOTE: binaries are in separate packages [libmysql] and [libmariadb]. - -## Summary - --------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -**[Initialization]** -`mysql.config(['mysql'|'mariadb'|libname|clib]) -> mysql` -**[Connections]** -`mysql.connect(host, [user], [pass], [db], [charset], [port]) -> conn` connect to a mysql server -`mysql.connect(options_t) -> conn` connect to a mysql server -`conn:close()` close the connection -**[Queries]** -`conn:query(s)` execute a query -`conn:escape(s) -> s` escape an SQL string -**[Fetching results]** -`conn:store_result() -> result` get a cursor for buffered read ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-store-result.html)) -`conn:use_result() -> result` get a cursor for unbuffered read ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-use-result.html)) -`result:fetch([mode[, row_t]]) -> true, v1, v2, ... | row_t | nil` fetch the next row from the result -`result:rows([mode[, row_t]]) -> iterator() -> row_num, val1, val2, ...` row iterator -`result:rows([mode[, row_t]]) -> iterator() -> row_num, row_t` row iterator -`result:free()` free the cursor -`result:row_count() -> n` number of rows -`result:eof() -> true | false` check if no more rows -`result:seek(row_number)` seek to row number -**[Query info]** -`conn:field_count() -> n` number of result fields in the executed query -`conn:affected_rows() -> n` number of affected rows in the executed query -`conn:insert_id() -> n` the id of the autoincrement column in the executed query -`conn:errno() -> n` mysql error code (0 if no error) from the executed query -`conn:sqlstate() -> s` -`conn:warning_count() -> n` number of errors, warnings, and notes from executed query -`conn:info() -> s` -**[Field info]** -`result:field_count() -> n` number of fields in the result -`result:field_name(field_number) -> s` field name given field index -`result:field_type(field_number) -> type, length, unsigned, decimals` field type given field index -`result:field_info(field_number) -> info_t` field info table -`result:fields() -> iterator() -> i, info_t` field info iterator -**[Result bookmarks]** -`result:tell() -> bookmark` bookmark the current row for later seek -`result:seek(bookmark)` seek to a row bookmark -**[Multiple statement queries]** -`conn:next_result() -> true | false` skip to the next result set ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-next-result.html)) -`conn:more_results() -> true | false` are there more result sets? -**[Prepared statements]** -`conn:prepare(query) -> stmt` prepare a query for multiple executions -`stmt:param_count() -> n` number of params -`stmt:exec()` execute a prepared statement -`stmt:store_result()` store all the resulted rows to the client -`stmt:fetch() -> true | false | true, 'truncated'` fetch the next row -`stmt:free_result()` free the current result buffers -`stmt:close()` close the statement -`stmt:next_result()` skip to the next result set -`stmt:row_count() -> n` number of rows in the result, if the result was stored -`stmt:affected_rows() -> n` number of affected rows after execution -`stmt:insert_id() -> n` the id of the autoincrement column after execution -`stmt:field_count() -> n` number of fields in the result after execution -`stmt:errno() -> n` mysql error code, if any, from the executed statement -`stmt:sqlstate() -> s` -`stmt:result_metadata() -> result` get a result for accessing the field info -`stmt:fields() -> iterator() -> i, info_t` iterate the result fields info -`stmt:reset()` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-reset.html) -`stmt:seek(row_number)` seek to row number -`stmt:tell() -> bookmark` get a bookmark in the current result -`stmt:seek(bookmark)` seek to a row bookmark in the current result -**[Prepared statements I/O]** -`stmt:bind_params(type1, ... | types_t) -> params` bind query parameters based on type definitions -`params:set(i, number | int64_t | uint64_t | true | false)` set an integer, float or bit param -`params:set(i, s[, size])` set a variable sized param -`params:set(i, cdata, size)` set a variable sized param -`params:set(i, {year=, month=, ...})` set a time/date param -`params:set_date(i, [year], [month], [day], [hour], [min], [sec], [frac])` set a time/date param -`stmt:write(param_number, data[, size])` send a long param in chunks -`stmt:bind_result([type1, ... | types_t | maxsize]) -> fields` bind query result fields based on type definitions -`fields:get(i) -> value` get the current row value of a field -`fields:get_datetime(i) -> year, month, day, hour, min, sec, frac` get the value of a date/time field directly -`fields:is_null(i) -> true | false` is field null? -`fields:is_truncated(i) -> true | false` was field value truncated? -**[Prepared statements settings]** -`stmt:update_max_length() -> true | false` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -`stmt:set_update_max_length(true | false)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -`stmt:cursor_type() -> mysql.C.MYSQL_CURSOR_TYPE_*` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -`stmt:set_cursor_type('CURSOR_TYPE_...')` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -`stmt:set_cursor_type(mysql.C.MYSQL_CURSOR_TYPE_...)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -`stmt:prefetch_rows() -> n` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -`stmt:set_prefetch_rows(stmt, n)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) -**[Connection info]** -`conn:set_charset(charset)` change the current charset -`conn:select_db(dbname)` change the current database -`conn:change_user(user, [pass], [db])` change the current user (and database) -`conn:set_multiple_statements(true | false)` enable/disable support for multiple statements -`conn:charset() -> s` get current charset's name -`conn:charset_info() -> info_t` get info about the current charset -`conn:ping() -> true | false` check if the connection is still alive -`conn:thread_id() -> id` -`conn:stat() -> s` -`conn:server_info() -> s` -`conn:host_info() -> s` -`conn:server_version() -> n` -`conn:proto_info() -> n` -`conn:ssl_cipher() -> s` -**[Transactions]** -`conn:commit()` commit the current transaction -`conn:rollback()` rollback the current transaction -`conn:set_autocommit([true | false])` enable/disable autocommit on the current connection -**[Reflection]** -`conn:list_dbs([wildcard]) -> result` return info about databases as a result object -`conn:list_tables([wildcard]) -> result` return info about tables as a result object -`conn:list_processes() -> result` return info about processes as a result object -**[Remote control]** -`conn:kill(pid)` kill a connection based on process id -`conn:shutdown([level])` shutdown the server -`conn:refresh(options)` flush tables or caches -`conn:dump_debug_info()` dump debug info in the log file -**[Client library info]** -`mysql.thread_safe() -> true | false` was the client library compiled as thread-safe? -`mysql.client_info() -> s` -`mysql.client_version() -> n` --------------------------------------------------------------------------------- -------------------------------------------------------------------------------- - -## Features - - * covers all of the functionality provided by the mysql C API - * all data types are supported with options for conversion - * prepared statements, avoiding dynamic allocations and format conversions when fetching rows - * all C calls are checked for errors and Lua errors are raised - * all C objects are tied to Lua's garbage collector - * lightweight OOP-style API using only `ffi.metatype` - * no external dependencies - -## Example - -~~~{.lua} -function print_help(search) - local mysql = require'mysql' - - local conn = mysql.connect('localhost', 'root', nil, 'mysql', 'utf8') - conn:query("select name, description, example from help_topic where name like '" .. - conn:escape(search) .. "'") - local result = conn:store_result() - - print('Found:') - for i,name in result:rows() do - print(' ' .. name) - end - - print() - for i, name, description, example in result:rows() do - print(name) - print'-------------------------------------------' - print(description) - print'Example:' - print'-------------------------------------------' - print(example) - print() - end - - result:free() - conn:close() -end - -print_help'CONCAT%' -~~~ - -## Initialization - -### `mysql.config(['mysql'|'mariadb'|libname|clib]) -> mysql` - -Load the mysql client library to use (default is 'mysql'). -This function is called on every module-level function. -Calling this function again is a no-op. - -## Connections - -### `mysql.connect(host, [user], [pass], [db], [charset], [port]) -> conn` -### `mysql.connect(options_t) -> conn` - -Connect to a mysql server, optionally selecting a working database and charset. - -In the second form, `options_t` is a table that besides `host`, `user`, `pass`, `db`, `charset`, `port` -can have the following fields: - - * `unix_socket`: specify a unix socket filename to connect to - * `flags`: bit field corresponding to mysql [client_flag](http://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html) parameter - * can be a table of form `{CLIENT_... = true | false, ...}`, or - * a number of form `bit.bor(mysql.C.CLIENT_..., ...)` - * `options`: a table of form `{MYSQL_OPT_... = value, ...}`, containing options per [mysql_options()](http://dev.mysql.com/doc/refman/5.7/en/mysql-options.html) (values are properly converted from Lua types) - * `attrs`: a table of form `{attr = value, ...}` containing attributes to be passed to the server per [mysql_options4()](http://dev.mysql.com/doc/refman/5.7/en/mysql-options4.html) - * `key`, `cert`, `ca`, `cpath`, `cipher`: parameters used to establish a [SSL connection](http://dev.mysql.com/doc/refman/5.7/en/mysql-ssl-set.html) - -### `conn:close()` - -Close a mysql connection freeing all associated resources (otherwise called when `conn` is garbage collected). - -## Queries - -### `conn:query(s)` - -Execute a query. If the query string contains multiple statements, only the first statement is executed -(see the section on multiple statements). - -### `conn:escape(s) -> s` - -Escape a value to be safely embedded in SQL queries. Assumes the current charset. - -## Fetching results - -### `conn:store_result() -> result` - -Fetch all the rows in the current result set from the server and return a result object to read them one by one. - -### `conn:use_result() -> result` - -Return a result object that will fetch the rows in the current result set from the server on demand. - -### `result:fetch([mode[, row_t]]) -> true, v1, v2, ... | row_t | nil` - -Fetch and return the next row of values from the current result set. Returns nil if there are no more rows to fetch. - - * the `mode` arg can contain any combination of the following letters: - * `"n"` - return values in a table with numeric indices as keys. - * `"a"` - return values in a table with field names as keys. - * `"s"` - do not convert numeric and time values to Lua types. - * the `row_t` arg is an optional table to store the row values in, instead of creating a new one on each fetch. - * options "a" and "n" can be combined to get a table with both numeric and field name indices. - * if `mode` is missing or if neither "a" nor "n" is specified, the values - are returned to the caller unpacked, after a first value that is always - true, to make it easy to distinguish between a valid `NULL` value in the - first column and eof. - * in "n" mode, the result table may contain `nil` values so `#row_t` and `ipairs(row_t)` are out; instead iterate from 1 to `result:field_count()`. - * in "a" mode, for fields with duplicate names only the last field will be present. - * if `mode` does not specify `"s"`, the following conversions are applied on the returned values: - * integer types are returned as Lua numbers, except bigint which is returned as an `int64_t` cdata (or `uint64` if unsigned). - * date/time types are returned as tables in the usual `os.date"*t"` format (date fields are missing for time-only types and viceversa). - * decimal/numeric types are returned as Lua strings. - * bit types are returned as Lua numbers, and as `uint64_t` for bit types larger than 48 bits. - * enum and set types are always returned as strings. - -### `result:rows([mode[, row_t]]) -> iterator() -> row_num, val1, val2, ...` -### `result:rows([mode[, row_t]]) -> iterator() -> row_num, row_t` - -Convenience iterator for fetching (or refetching) all the rows from the current result set. The `mode` arg -is the same as for `result:fetch()`, with the exception that in unpacked mode, the first `true` value is not present. - -### `result:free()` - -Free the result buffer (otherwise called when `result` is garbage collected). - -### `result:row_count() -> n` - -Return the number of rows in the current result set . This value is only correct if `result:store_result()` was -previously called or if all the rows were fetched, in other words if `result:eof()` is true. - -### `result:eof() -> true | false` - -Check if there are no more rows to fetch. If `result:store_result()` was previously called, then all rows were -already fetched, so `result:eof()` always returns `true` in this case. - -### `result:seek(row_number)` - -Seek back to a particular row number to refetch the rows from there. - -## Query info - -### `conn:field_count() -> n` -### `conn:affected_rows() -> n` -### `conn:insert_id() -> n` -### `conn:errno() -> n` -### `conn:sqlstate() -> s` -### `conn:warning_count() -> n` -### `conn:info() -> s` - -Return various pieces of information about the previously executed query. - -## Field info - -### `result:field_count() -> n` -### `result:field_name(field_number) -> s` -### `result:field_type(field_number) -> type, length, decimals, unsigned` -### `result:field_info(field_number) -> info_t` -### `result:fields() -> iterator() -> i, info_t` - -Return information about the fields (columns) in the current result set. - -## Result bookmarks - -### `result:tell() -> bookmark` - -Get a bookmark to the current row to be later seeked into with `seek()`. - -### `result:seek(bookmark)` - -Seek to a previous saved row bookmark, or to a specific row number, fetching more rows as needed. - -## Multiple statement queries - -### `conn:next_result() -> true | false` - -Skip over to the next result set in a multiple statement query, and make that the current result set. -Return true if there more result sets after this one. - -### `conn:more_results() -> true | false` - -Check if there are more result sets after this one. - -## Prepared statements - -Prepared statements are a way to run queries and retrieve results more efficiently from the database, in particular: - - * parametrized queries allow sending query parameters in their native format, avoiding having to convert values into strings and escaping those strings. - * running the same query multiple times with different parameters each time allows the server to reuse the parsed query and possibly the query plan between runs. - * fetching the result rows in preallocated buffers avoids dynamic allocation on each row fetch. - -The flow for prepared statements is like this: - - * call `conn:prepare()` to prepare a query and get a statement object. - * call `stmt:bind_params()` and `stmt:bind_result()` to get the buffer objects for setting params and getting row values. - * run the query multiple times; each time: - * call `params:set()` for each param to set param values. - * call `stmt:exec()` to run the query. - * fetch the resulting rows one by one; for each row: - * call `stmt:fetch()` to get the next row (it returns false if it was the last row). - * call `fields:get()` to read the values of the fetched row. - * call `stmt:close()` to free the statement object and all the associated resources from the server and client. - -### `conn:prepare(query) -> stmt, params` - -Prepare a query for multiple execution and return a statement object. - -### `stmt:param_count() -> n` - -Number of parameters. - -### `stmt:exec()` - -Execute a prepared statement. - -### `stmt:store_result()` - -Fetch all the rows in the current result set from the server, otherwise the rows are fetched on demand. - -### `stmt:fetch() -> true | false | true, 'truncated'` - -Fetch the next row from the current result set. Use a binding buffer (see prepared statements I/O section) -to get the row values. If present, second value indicates that at least one of the rows were truncated because -the receiving buffer was too small for it. - -### `stmt:free_result()` - -Free the current result and all associated resources (otherwise the result is closed when the statement is closed). - -### `stmt:close()` - -Close a prepared statement and free all associated resources (otherwise the statement is closed when garbage collected). - -### `stmt:next_result()` - -Skip over to the next result set in a multiple statement query. - -### `stmt:row_count() -> n` -### `stmt:affected_rows() -> n` -### `stmt:insert_id() -> n` -### `stmt:field_count() -> n` -### `stmt:errno() -> n` -### `stmt:sqlstate() -> s` -### `stmt:result_metadata() -> result` -### `stmt:fields() -> iterator() -> i, info_t` - -Return various pieces of information on the executed statement. - -### `stmt:reset()` - -See [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-reset.html). - -### `stmt:seek(row_number)` -### `stmt:tell() -> bookmark` -### `stmt:seek(bookmark)` - -Seek into the current result set. - -## Prepared statements I/O - -### `stmt:bind_params(type1, ... | types_t) -> params` - -Bind query parameters according to a list of type definitions (which can be given either packed or unpacked). -Return a binding buffer object to be used for setting parameters. - -The types must be valid, fully specified SQL types, eg. - - * `smallint unsigned` specifies a 16bit unsigned integer - * `bit(32)` specifies a 32bit bit field - * `varchar(200)` specifies a 200 byte varchar. - -### `params:set(i, number | int64_t | uint64_t | true | false)` -### `params:set(i, s[, size])` -### `params:set(i, cdata, size)` -### `params:set(i, {year=, month=, ...})` -### `params:set_date(i, [year], [month], [day], [hour], [min], [sec], [frac])` - -Set a parameter value. - - * the first form is for setting integers and bit fields. - * the second and third forms are for setting variable-sized fields and decimal/numeric fields. - * the last forms are for setting date/time/datetime/timestamp fields. - * the null type cannot be set (raises an error if attempted). - -### `stmt:write(param_number, data[, size])` - -Send a parameter value in chunks (for long, var-sized values). - -### `stmt:bind_result([type1, ... | types_t | maxsize]) -> fields` - -Bind result fields according to a list of type definitions (same as for params). -Return a binding buffer object to be used for getting row values. -If no types are specified, appropriate type definitions will be created automatically as to minimize type conversions. -Variable-sized fields will get a buffer sized according to data type's maximum allowed size -and `maxsize` (which defaults to 64k). - -### `fields:get(i) -> value` -### `fields:get_datetime(i) -> year, month, day, hour, min, sec, frac` - -Get a row value from the last fetched row. The same type conversions as for `result:fetch()` apply. - -### `fields:is_null(i) -> true | false` - -Check if a value is null without having to get it if it's not. - -### `fields:is_truncated(i) -> true | false` - -Check if a value was truncated due to insufficient buffer space. - -### `stmt:bind_result_types([maxsize]) -> types_t` - -Return the list of type definitions that describe the result of a prepared statement. - -## Prepared statements settings - -### `stmt:update_max_length() -> true | false` -### `stmt:set_update_max_length(true | false)` -### `stmt:cursor_type() -> mysql.C.MYSQL_CURSOR_TYPE_*` -### `stmt:set_cursor_type('CURSOR_TYPE_...')` -### `stmt:set_cursor_type(mysql.C.MYSQL_CURSOR_TYPE_...)` -### `stmt:prefetch_rows() -> n` -### `stmt:set_prefetch_rows(stmt, n)` - -See [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) for these. - -## Connection info - -### `conn:set_charset(charset)` - -Change the current charset. - -### `conn:select_db(dbname)` - -Change the current database. - -### `conn:change_user(user, [pass], [db])` - -Change the current user and optionally select a database. - -### `conn:set_multiple_statements(true | false)` - -Enable or disable support for query strings containing multiple statements separated by a semi-colon. - -### `conn:charset() -> s` - -Get the current charset. - -### `conn:charset_info() -> info_t` - -Return a table of information about the current charset. - -### `conn:ping() -> true | false` - -Check if the connection to the server is still alive. - -### `conn:thread_id() -> id` -### `conn:stat() -> s` -### `conn:server_info() -> s` -### `conn:host_info() -> s` -### `conn:server_version() -> n` -### `conn:proto_info() -> n` -### `conn:ssl_cipher() -> s` - -Return various pieces of information about the connection and server. - -## Transactions - -### `conn:commit()` -### `conn:rollback()` - -Commit/rollback the current transaction. - -### `conn:set_autocommit([true | false])` - -Set autocommit on the connection (set to true if no argument is given). - -## Reflection - -### `conn:list_dbs([wildcard]) -> result` -### `conn:list_tables([wildcard]) -> result` -### `conn:list_processes() -> result` - -Return information about databases, tables and proceses as a stored result object that can be iterated etc. -using the methods of result objects. The optional `wild` parameter may contain the wildcard characters -`"%"` or `"_"`, similar to executing the query `SHOW DATABASES [LIKE wild]`. - -## Remote control - -### `conn:kill(pid)` - -Kill a connection with a specific `pid`. - -### `conn:shutdown([level])` - -Shutdown the server. `SHUTDOWN` priviledge needed. The level argument is reserved for future versions of mysql. - -### `conn:refresh(options)` - -Flush tables or caches, or resets replication server information. `RELOAD` priviledge needed. Options are either -a table of form `{REFRESH_... = true | false, ...}` or a number of form `bit.bor(mysql.C.MYSQL_REFRESH_*, ...)` and -they are as described in the [mysql manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-refresh.html). - -### `conn:dump_debug_info()` - -Instruct the server to dump debug info in the log file. `SUPER` priviledge needed. - -## Client library info - -### `mysql.thread_safe() -> true | false` -### `mysql.client_info() -> s` -### `mysql.client_version() -> n` - ----- - -## TODO - - * reader function for getting large blobs in chunks using - mysql_stmt_fetch_column: `stmt:chunks(i[, bufsize])` or `stmt:read()` ? diff --git a/worldmods/mysql_base/mysql/mysql_h.lua b/worldmods/mysql_base/mysql/mysql_h.lua deleted file mode 100644 index 474ad5a..0000000 --- a/worldmods/mysql_base/mysql/mysql_h.lua +++ /dev/null @@ -1,571 +0,0 @@ - ---result of `cpp mysql.h` with lots of cleanup and defines from other headers. ---Written by Cosmin Apreutesei. MySQL Connector/C 6.1. - ---NOTE: MySQL Connector/C is GPL software. Is this "derived work" then? - -local ffi = require'ffi' - -ffi.cdef[[ - -typedef char my_bool; -typedef unsigned long long my_ulonglong; - -enum { - MYSQL_PORT = 3306, - MYSQL_ERRMSG_SIZE = 512, - - // status return codes - MYSQL_NO_DATA = 100, - MYSQL_DATA_TRUNCATED = 101 -}; - -// ----------------------------------------------------------- error constants - -// NOTE: added MYSQL_ prefix to these. -enum mysql_error_code { - MYSQL_CR_UNKNOWN_ERROR = 2000, - MYSQL_CR_SOCKET_CREATE_ERROR = 2001, - MYSQL_CR_CONNECTION_ERROR = 2002, - MYSQL_CR_CONN_HOST_ERROR = 2003, - MYSQL_CR_IPSOCK_ERROR = 2004, - MYSQL_CR_UNKNOWN_HOST = 2005, - MYSQL_CR_SERVER_GONE_ERROR = 2006, - MYSQL_CR_VERSION_ERROR = 2007, - MYSQL_CR_OUT_OF_MEMORY = 2008, - MYSQL_CR_WRONG_HOST_INFO = 2009, - MYSQL_CR_LOCALHOST_CONNECTION = 2010, - MYSQL_CR_TCP_CONNECTION = 2011, - MYSQL_CR_SERVER_HANDSHAKE_ERR = 2012, - MYSQL_CR_SERVER_LOST = 2013, - MYSQL_CR_COMMANDS_OUT_OF_SYNC = 2014, - MYSQL_CR_NAMEDPIPE_CONNECTION = 2015, - MYSQL_CR_NAMEDPIPEWAIT_ERROR = 2016, - MYSQL_CR_NAMEDPIPEOPEN_ERROR = 2017, - MYSQL_CR_NAMEDPIPESETSTATE_ERROR = 2018, - MYSQL_CR_CANT_READ_CHARSET = 2019, - MYSQL_CR_NET_PACKET_TOO_LARGE = 2020, - MYSQL_CR_EMBEDDED_CONNECTION = 2021, - MYSQL_CR_PROBE_SLAVE_STATUS = 2022, - MYSQL_CR_PROBE_SLAVE_HOSTS = 2023, - MYSQL_CR_PROBE_SLAVE_CONNECT = 2024, - MYSQL_CR_PROBE_MASTER_CONNECT = 2025, - MYSQL_CR_SSL_CONNECTION_ERROR = 2026, - MYSQL_CR_MALFORMED_PACKET = 2027, - MYSQL_CR_WRONG_LICENSE = 2028, - - /* new 4.1 error codes */ - MYSQL_CR_NULL_POINTER = 2029, - MYSQL_CR_NO_PREPARE_STMT = 2030, - MYSQL_CR_PARAMS_NOT_BOUND = 2031, - MYSQL_CR_DATA_TRUNCATED = 2032, - MYSQL_CR_NO_PARAMETERS_EXISTS = 2033, - MYSQL_CR_INVALID_PARAMETER_NO = 2034, - MYSQL_CR_INVALID_BUFFER_USE = 2035, - MYSQL_CR_UNSUPPORTED_PARAM_TYPE = 2036, - - MYSQL_CR_SHARED_MEMORY_CONNECTION = 2037, - MYSQL_CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038, - MYSQL_CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039, - MYSQL_CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040, - MYSQL_CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041, - MYSQL_CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042, - MYSQL_CR_SHARED_MEMORY_MAP_ERROR = 2043, - MYSQL_CR_SHARED_MEMORY_EVENT_ERROR = 2044, - MYSQL_CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045, - MYSQL_CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046, - MYSQL_CR_CONN_UNKNOW_PROTOCOL = 2047, - MYSQL_CR_INVALID_CONN_HANDLE = 2048, - MYSQL_CR_SECURE_AUTH = 2049, - MYSQL_CR_FETCH_CANCELED = 2050, - MYSQL_CR_NO_DATA = 2051, - MYSQL_CR_NO_STMT_METADATA = 2052, - MYSQL_CR_NO_RESULT_SET = 2053, - MYSQL_CR_NOT_IMPLEMENTED = 2054, - MYSQL_CR_SERVER_LOST_EXTENDED = 2055, - MYSQL_CR_STMT_CLOSED = 2056, - MYSQL_CR_NEW_STMT_METADATA = 2057, - MYSQL_CR_ALREADY_CONNECTED = 2058, - MYSQL_CR_AUTH_PLUGIN_CANNOT_LOAD = 2059, - MYSQL_CR_DUPLICATE_CONNECTION_ATTR = 2060, - MYSQL_CR_AUTH_PLUGIN_ERR = 2061 -}; - -// ------------------------------------------------------------ client library - -unsigned int mysql_thread_safe(void); // is the client library thread safe? -const char *mysql_get_client_info(void); -unsigned long mysql_get_client_version(void); - -// --------------------------------------------------------------- connections - -typedef struct MYSQL_ MYSQL; - -MYSQL * mysql_init(MYSQL *mysql); - -enum mysql_protocol_type -{ - MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, - MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY -}; -enum mysql_option -{ - MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE, - MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, - MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE, - MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT, - MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT, - MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, - MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, - MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT, - MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MYSQL_PLUGIN_DIR, MYSQL_DEFAULT_AUTH, - MYSQL_OPT_BIND, - MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT, - MYSQL_OPT_SSL_CA, MYSQL_OPT_SSL_CAPATH, MYSQL_OPT_SSL_CIPHER, - MYSQL_OPT_SSL_CRL, MYSQL_OPT_SSL_CRLPATH, - MYSQL_OPT_CONNECT_ATTR_RESET, MYSQL_OPT_CONNECT_ATTR_ADD, - MYSQL_OPT_CONNECT_ATTR_DELETE, - MYSQL_SERVER_PUBLIC_KEY, - MYSQL_ENABLE_CLEARTEXT_PLUGIN, - MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS -}; -int mysql_options(MYSQL *mysql, enum mysql_option option, const void *arg); -int mysql_options4(MYSQL *mysql, enum mysql_option option, const void *arg1, const void *arg2); - -// NOTE: added MYSQL_ prefix to these. Also, these are bit flags not exclusive enum values. -enum { - MYSQL_CLIENT_LONG_PASSWORD = 1, /* new more secure passwords */ - MYSQL_CLIENT_FOUND_ROWS = 2, /* Found instead of affected rows */ - MYSQL_CLIENT_LONG_FLAG = 4, /* Get all column flags */ - MYSQL_CLIENT_CONNECT_WITH_DB = 8, /* One can specify db on connect */ - MYSQL_CLIENT_NO_SCHEMA = 16, /* Don't allow database.table.column */ - MYSQL_CLIENT_COMPRESS = 32, /* Can use compression protocol */ - MYSQL_CLIENT_ODBC = 64, /* ODBC client */ - MYSQL_CLIENT_LOCAL_FILES = 128, /* Can use LOAD DATA LOCAL */ - MYSQL_CLIENT_IGNORE_SPACE = 256, /* Ignore spaces before '(' */ - MYSQL_CLIENT_PROTOCOL_41 = 512, /* New 4.1 protocol */ - MYSQL_CLIENT_INTERACTIVE = 1024, /* This is an interactive client */ - MYSQL_CLIENT_SSL = 2048, /* Switch to SSL after handshake */ - MYSQL_CLIENT_IGNORE_SIGPIPE = 4096, /* IGNORE sigpipes */ - MYSQL_CLIENT_TRANSACTIONS = 8192, /* Client knows about transactions */ - MYSQL_CLIENT_RESERVED = 16384, /* Old flag for 4.1 protocol */ - MYSQL_CLIENT_SECURE_CONNECTION = (1U << 15), /* New 4.1 authentication */ - MYSQL_CLIENT_MULTI_STATEMENTS = (1U << 16), /* Enable/disable multi-stmt support */ - MYSQL_CLIENT_MULTI_RESULTS = (1U << 17), /* Enable/disable multi-results */ - MYSQL_CLIENT_PS_MULTI_RESULTS = (1U << 18), /* Multi-results in PS-protocol */ - MYSQL_CLIENT_PLUGIN_AUTH = (1U << 19), /* Client supports plugin authentication */ - MYSQL_CLIENT_CONNECT_ATTRS = (1U << 20), /* Client supports connection attributes */ - - /* Enable authentication response packet to be larger than 255 bytes. */ - MYSQL_CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1U << 21), - - /* Don't close the connection for a connection with expired password. */ - MYSQL_CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1U << 22), - - MYSQL_CLIENT_SSL_VERIFY_SERVER_CERT = (1U << 30), - MYSQL_CLIENT_REMEMBER_OPTIONS = (1U << 31) -}; -MYSQL * mysql_real_connect(MYSQL *mysql, const char *host, - const char *user, - const char *passwd, - const char *db, - unsigned int port, - const char *unix_socket, - unsigned long clientflag); - -void mysql_close(MYSQL *sock); - -int mysql_set_character_set(MYSQL *mysql, const char *csname); - -int mysql_select_db(MYSQL *mysql, const char *db); - -my_bool mysql_change_user(MYSQL *mysql, const char *user, const char *passwd, - const char *db); - -my_bool mysql_ssl_set(MYSQL *mysql, const char *key, - const char *cert, const char *ca, - const char *capath, const char *cipher); - -enum enum_mysql_set_option -{ - MYSQL_OPTION_MULTI_STATEMENTS_ON, - MYSQL_OPTION_MULTI_STATEMENTS_OFF -}; -int mysql_set_server_option(MYSQL *mysql, enum enum_mysql_set_option option); - -// ----------------------------------------------------------- connection info - -const char * mysql_character_set_name(MYSQL *mysql); - -typedef struct character_set -{ - unsigned int number; - unsigned int state; - const char *csname; - const char *name; - const char *comment; - const char *dir; - unsigned int mbminlen; - unsigned int mbmaxlen; -} MY_CHARSET_INFO; -void mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset); - -int mysql_ping(MYSQL *mysql); -unsigned long mysql_thread_id(MYSQL *mysql); -const char * mysql_stat(MYSQL *mysql); -const char * mysql_get_server_info(MYSQL *mysql); -const char * mysql_get_host_info(MYSQL *mysql); -unsigned long mysql_get_server_version(MYSQL *mysql); -unsigned int mysql_get_proto_info(MYSQL *mysql); -const char * mysql_get_ssl_cipher(MYSQL *mysql); - -// -------------------------------------------------------------- transactions - -my_bool mysql_commit(MYSQL * mysql); -my_bool mysql_rollback(MYSQL * mysql); -my_bool mysql_autocommit(MYSQL * mysql, my_bool auto_mode); - -// ------------------------------------------------------------------- queries - -unsigned long mysql_real_escape_string(MYSQL *mysql, char *to, - const char *from, unsigned long length); -int mysql_real_query(MYSQL *mysql, const char *q, unsigned long length); - -// ---------------------------------------------------------------- query info - -unsigned int mysql_field_count(MYSQL *mysql); -my_ulonglong mysql_affected_rows(MYSQL *mysql); -my_ulonglong mysql_insert_id(MYSQL *mysql); -unsigned int mysql_errno(MYSQL *mysql); -const char * mysql_error(MYSQL *mysql); -const char * mysql_sqlstate(MYSQL *mysql); -unsigned int mysql_warning_count(MYSQL *mysql); -const char * mysql_info(MYSQL *mysql); - -// ------------------------------------------------------------- query results - -int mysql_next_result(MYSQL *mysql); -my_bool mysql_more_results(MYSQL *mysql); - -// NOTE: normally we would've made this an opaque handle, but we need to expose -// the connection handle from it so we can report errors for unbuffered reads. -typedef struct st_mysql_res { - my_ulonglong __row_count; - void *__fields; - void *__data; - void *__data_cursor; - void *__lengths; - MYSQL *conn; /* for unbuffered reads */ -} MYSQL_RES; - -MYSQL_RES *mysql_store_result(MYSQL *mysql); -MYSQL_RES *mysql_use_result(MYSQL *mysql); -void mysql_free_result(MYSQL_RES *result); - -my_ulonglong mysql_num_rows(MYSQL_RES *res); -unsigned int mysql_num_fields(MYSQL_RES *res); -my_bool mysql_eof(MYSQL_RES *res); - -unsigned long * mysql_fetch_lengths(MYSQL_RES *result); -typedef char **MYSQL_ROW; -MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); - -void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset); - -typedef struct MYSQL_ROWS_ MYSQL_ROWS; -typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; -MYSQL_ROW_OFFSET mysql_row_tell(MYSQL_RES *res); -MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset); - -// ---------------------------------------------------------- query field info - -enum enum_field_types { - MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, - MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG, - MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE, - MYSQL_TYPE_NULL, MYSQL_TYPE_TIMESTAMP, - MYSQL_TYPE_LONGLONG,MYSQL_TYPE_INT24, - MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, - MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR, - MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR, - MYSQL_TYPE_BIT, - MYSQL_TYPE_TIMESTAMP2, - MYSQL_TYPE_DATETIME2, - MYSQL_TYPE_TIME2, - MYSQL_TYPE_NEWDECIMAL=246, - MYSQL_TYPE_ENUM=247, - MYSQL_TYPE_SET=248, - MYSQL_TYPE_TINY_BLOB=249, - MYSQL_TYPE_MEDIUM_BLOB=250, - MYSQL_TYPE_LONG_BLOB=251, - MYSQL_TYPE_BLOB=252, - MYSQL_TYPE_VAR_STRING=253, - MYSQL_TYPE_STRING=254, - MYSQL_TYPE_GEOMETRY=255 -}; - -// NOTE: added MYSQL_ prefix to these. Also, these are bit flags, not exclusive enum values. -enum { - MYSQL_NOT_NULL_FLAG = 1, /* Field can't be NULL */ - MYSQL_PRI_KEY_FLAG = 2, /* Field is part of a primary key */ - MYSQL_UNIQUE_KEY_FLAG = 4, /* Field is part of a unique key */ - MYSQL_MULTIPLE_KEY_FLAG = 8, /* Field is part of a key */ - MYSQL_BLOB_FLAG = 16, /* Field is a blob */ - MYSQL_UNSIGNED_FLAG = 32, /* Field is unsigned */ - MYSQL_ZEROFILL_FLAG = 64, /* Field is zerofill */ - MYSQL_BINARY_FLAG = 128, /* Field is binary */ - - /* The following are only sent to new clients */ - MYSQL_ENUM_FLAG = 256, /* field is an enum */ - MYSQL_AUTO_INCREMENT_FLAG = 512, /* field is a autoincrement field */ - MYSQL_TIMESTAMP_FLAG = 1024, /* Field is a timestamp */ - MYSQL_SET_FLAG = 2048, /* field is a set */ - MYSQL_NO_DEFAULT_VALUE_FLAG = 4096, /* Field doesn't have default value */ - MYSQL_ON_UPDATE_NOW_FLAG = 8192, /* Field is set to NOW on UPDATE */ - MYSQL_NUM_FLAG = 32768, /* Field is num (for clients) */ - MYSQL_PART_KEY_FLAG = 16384, /* Intern; Part of some key */ - MYSQL_GROUP_FLAG = 32768, /* Intern: Group field */ - MYSQL_UNIQUE_FLAG = 65536, /* Intern: Used by sql_yacc */ - MYSQL_BINCMP_FLAG = 131072, /* Intern: Used by sql_yacc */ - MYSQL_GET_FIXED_FIELDS_FLAG = (1 << 18), /* Used to get fields in item tree */ - MYSQL_FIELD_IN_PART_FUNC_FLAG = (1 << 19) /* Field part of partition func */ -}; - -typedef struct st_mysql_field { - char *name; - char *org_name; - char *table; - char *org_table; - char *db; - char *catalog; - char *def; - unsigned long length; - unsigned long max_length; - unsigned int name_length; - unsigned int org_name_length; - unsigned int table_length; - unsigned int org_table_length; - unsigned int db_length; - unsigned int catalog_length; - unsigned int def_length; - unsigned int flags; - unsigned int decimals; - unsigned int charsetnr; - enum enum_field_types type; - void *extension; -} MYSQL_FIELD; - -MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr); - -// ---------------------------------------------------------------- reflection - -MYSQL_RES *mysql_list_dbs(MYSQL *mysql, const char *wild); -MYSQL_RES *mysql_list_tables(MYSQL *mysql, const char *wild); -MYSQL_RES *mysql_list_processes(MYSQL *mysql); - -// ------------------------------------------------------------ remote control - -int mysql_kill(MYSQL *mysql, unsigned long pid); - -// NOTE: added MYSQL_ prefix. -enum mysql_enum_shutdown_level { - MYSQL_SHUTDOWN_DEFAULT = 0, - MYSQL_SHUTDOWN_WAIT_CONNECTIONS = 1, - MYSQL_SHUTDOWN_WAIT_TRANSACTIONS = 2, - MYSQL_SHUTDOWN_WAIT_UPDATES = 8, - MYSQL_SHUTDOWN_WAIT_ALL_BUFFERS = 16, - MYSQL_SHUTDOWN_WAIT_CRITICAL_BUFFERS = 17, - MYSQL_KILL_QUERY = 254, - MYSQL_KILL_CONNECTION = 255 -}; -int mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level); // needs SHUTDOWN priviledge - -// NOTE: added MYSQL_ prefix. not really enum values either, just bit flags. -enum { - MYSQL_REFRESH_GRANT = 1, /* Refresh grant tables */ - MYSQL_REFRESH_LOG = 2, /* Start on new log file */ - MYSQL_REFRESH_TABLES = 4, /* close all tables */ - MYSQL_REFRESH_HOSTS = 8, /* Flush host cache */ - MYSQL_REFRESH_STATUS = 16, /* Flush status variables */ - MYSQL_REFRESH_THREADS = 32, /* Flush thread cache */ - MYSQL_REFRESH_SLAVE = 64, /* Reset master info and restart slave thread */ - MYSQL_REFRESH_MASTER = 128, /* Remove all bin logs in the index and truncate the index */ - MYSQL_REFRESH_ERROR_LOG = 256, /* Rotate only the erorr log */ - MYSQL_REFRESH_ENGINE_LOG = 512, /* Flush all storage engine logs */ - MYSQL_REFRESH_BINARY_LOG = 1024, /* Flush the binary log */ - MYSQL_REFRESH_RELAY_LOG = 2048, /* Flush the relay log */ - MYSQL_REFRESH_GENERAL_LOG = 4096, /* Flush the general log */ - MYSQL_REFRESH_SLOW_LOG = 8192, /* Flush the slow query log */ - - /* The following can't be set with mysql_refresh() */ - MYSQL_REFRESH_READ_LOCK = 16384, /* Lock tables for read */ - MYSQL_REFRESH_FAST = 32768, /* Intern flag */ - - /* RESET (remove all queries) from query cache */ - MYSQL_REFRESH_QUERY_CACHE = 65536, - MYSQL_REFRESH_QUERY_CACHE_FREE = 0x20000, /* pack query cache */ - MYSQL_REFRESH_DES_KEY_FILE = 0x40000, - MYSQL_REFRESH_USER_RESOURCES = 0x80000, - MYSQL_REFRESH_FOR_EXPORT = 0x100000, /* FLUSH TABLES ... FOR EXPORT */ -}; -int mysql_refresh(MYSQL *mysql, unsigned int refresh_options); // needs RELOAD priviledge -int mysql_dump_debug_info(MYSQL *mysql); // needs SUPER priviledge - -// ------------------------------------------------------- prepared statements - -typedef struct MYSQL_STMT_ MYSQL_STMT; - -MYSQL_STMT * mysql_stmt_init(MYSQL *mysql); -my_bool mysql_stmt_close(MYSQL_STMT * stmt); - -int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length); -int mysql_stmt_execute(MYSQL_STMT *stmt); - -int mysql_stmt_next_result(MYSQL_STMT *stmt); -int mysql_stmt_store_result(MYSQL_STMT *stmt); -my_bool mysql_stmt_free_result(MYSQL_STMT *stmt); - -MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt); -my_ulonglong mysql_stmt_num_rows(MYSQL_STMT *stmt); -my_ulonglong mysql_stmt_affected_rows(MYSQL_STMT *stmt); -my_ulonglong mysql_stmt_insert_id(MYSQL_STMT *stmt); -unsigned int mysql_stmt_field_count(MYSQL_STMT *stmt); - -unsigned int mysql_stmt_errno(MYSQL_STMT * stmt); -const char *mysql_stmt_error(MYSQL_STMT * stmt); -const char *mysql_stmt_sqlstate(MYSQL_STMT * stmt); - -int mysql_stmt_fetch(MYSQL_STMT *stmt); -my_bool mysql_stmt_reset(MYSQL_STMT * stmt); - -void mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset); - -MYSQL_ROW_OFFSET mysql_stmt_row_tell(MYSQL_STMT *stmt); -MYSQL_ROW_OFFSET mysql_stmt_row_seek(MYSQL_STMT *stmt, MYSQL_ROW_OFFSET offset); - -// NOTE: added MYSQL_ prefix to these. -enum enum_cursor_type -{ - MYSQL_CURSOR_TYPE_NO_CURSOR= 0, - MYSQL_CURSOR_TYPE_READ_ONLY= 1, - MYSQL_CURSOR_TYPE_FOR_UPDATE= 2, - MYSQL_CURSOR_TYPE_SCROLLABLE= 4 -}; - -enum enum_stmt_attr_type -{ - STMT_ATTR_UPDATE_MAX_LENGTH, - STMT_ATTR_CURSOR_TYPE, - STMT_ATTR_PREFETCH_ROWS -}; -my_bool mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr); -my_bool mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, void *attr); - -my_bool mysql_stmt_send_long_data(MYSQL_STMT *stmt, - unsigned int param_number, - const char *data, - unsigned long length); - -// -------------------------------------------- prepared statements / bindings - -enum enum_mysql_timestamp_type -{ - MYSQL_TIMESTAMP_NONE= -2, MYSQL_TIMESTAMP_ERROR= -1, - MYSQL_TIMESTAMP_DATE= 0, MYSQL_TIMESTAMP_DATETIME= 1, MYSQL_TIMESTAMP_TIME= 2 -}; -typedef struct st_mysql_time -{ - unsigned int year, month, day, hour, minute, second; - unsigned long second_part; /**< microseconds */ - my_bool neg; - enum enum_mysql_timestamp_type time_type; -} MYSQL_TIME; - -unsigned long mysql_stmt_param_count(MYSQL_STMT * stmt); - -typedef struct NET_ NET; -typedef struct st_mysql_bind -{ - unsigned long *length; - my_bool *is_null; - void *buffer; - my_bool *error; - unsigned char *row_ptr; - void (*store_param_func)(NET *net, struct st_mysql_bind *param); - void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *, - unsigned char **row); - void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *, - unsigned char **row); - unsigned long buffer_length; - unsigned long offset; - unsigned long length_value; - unsigned int param_number; - unsigned int pack_length; - enum enum_field_types buffer_type; - my_bool error_value; - my_bool is_unsigned; - my_bool long_data_used; - my_bool is_null_value; - void *extension; -} MYSQL_BIND; - -my_bool mysql_stmt_bind_param(MYSQL_STMT * stmt, MYSQL_BIND * bnd); -my_bool mysql_stmt_bind_result(MYSQL_STMT * stmt, MYSQL_BIND * bnd); - -int mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind_arg, - unsigned int column, - unsigned long offset); - -// ---------------------------------------------- LOAD DATA LOCAL INFILE hooks - -void mysql_set_local_infile_handler(MYSQL *mysql, - int (*local_infile_init)(void **, const char *, void *), - int (*local_infile_read)(void *, char *, unsigned int), - void (*local_infile_end)(void *), - int (*local_infile_error)(void *, char*, unsigned int), - void *); -void mysql_set_local_infile_default(MYSQL *mysql); - -// ----------------------------------------------------- mysql proxy scripting - -my_bool mysql_read_query_result(MYSQL *mysql); - -// ----------------------------------------------------------------- debugging - -void mysql_debug(const char *debug); - -// ------------------------------------------------ present but not documented - -int mysql_server_init(int argc, char **argv, char **groups); -void mysql_server_end(void); -char *get_tty_password(const char *opt_message); -void myodbc_remove_escape(MYSQL *mysql, char *name); -my_bool mysql_embedded(void); -int mysql_send_query(MYSQL *mysql, const char *q, unsigned long length); - -// ------------------------------------------------------- redundant functions - -my_bool mysql_thread_init(void); // called anyway -void mysql_thread_end(void); // called anyway -const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); // use mysql_sqlstate -unsigned long mysql_hex_string(char *to, const char *from, - unsigned long from_length); // bad taste - -// redundant ways to get field info. -// we use use mysql_field_count and mysql_fetch_field_direct instead. -MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result); -MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res); -typedef unsigned int MYSQL_FIELD_OFFSET; -MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res); -MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset); -MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt); - -// ------------------------------------------------------ deprecated functions - -unsigned long mysql_escape_string(char *to, const char *from, - unsigned long from_length); // use mysql_real_escape_string -int mysql_query(MYSQL *mysql, const char *q); // use mysql_real_query -MYSQL_RES *mysql_list_fields(MYSQL *mysql, const char *table, - const char *wild); // use "SHOW COLUMNS FROM table" - -]] diff --git a/worldmods/mysql_base/mysql/mysql_print.lua b/worldmods/mysql_base/mysql/mysql_print.lua deleted file mode 100644 index bb1c3d3..0000000 --- a/worldmods/mysql_base/mysql/mysql_print.lua +++ /dev/null @@ -1,144 +0,0 @@ ---mysql table pretty printing - -local function ellipsis(s,n) - return #s > n and (s:sub(1,n-3) .. '...') or s -end - -local align = {} - -function align.left(s,n) - s = s..(' '):rep(n - #s) - return ellipsis(s,n) -end - -function align.right(s,n) - s = (' '):rep(n - #s)..s - return ellipsis(s,n) -end - -function align.center(s,n) - local total = n - #s - local left = math.floor(total / 2) - local right = math.ceil(total / 2) - s = (' '):rep(left)..s..(' '):rep(right) - return ellipsis(s,n) -end - -local function fit(s,n,al) - return align[al or 'left'](s,n) -end - -local function print_table(fields, rows, aligns, minsize, print) - print = print or _G.print - minsize = minsize or math.huge - local max_sizes = {} - for i=1,#rows do - for j=1,#fields do - max_sizes[j] = math.min(minsize, math.max(max_sizes[j] or 0, #rows[i][j])) - end - end - - local totalsize = 0 - for j=1,#fields do - max_sizes[j] = math.max(max_sizes[j] or 0, #fields[j]) - totalsize = totalsize + max_sizes[j] + 3 - end - - print() - local s, ps = '', '' - for j=1,#fields do - s = s .. fit(fields[j], max_sizes[j], 'center') .. ' | ' - ps = ps .. ('-'):rep(max_sizes[j]) .. ' + ' - end - print(s) - print(ps) - - for i=1,#rows do - local s = '' - for j=1,#fields do - local val = rows[i][j] - s = s .. fit(val, max_sizes[j], aligns and aligns[j]) .. ' | ' - end - print(s) - end - print() -end - -local function invert_table(fields, rows, minsize) - local ft, rt = {'field'}, {} - for i=1,#rows do - ft[i+1] = tostring(i) - end - for j=1,#fields do - local row = {fields[j]} - for i=1,#rows do - row[i+1] = rows[i][j] - end - rt[j] = row - end - return ft, rt -end - -local function format_cell(v) - if v == nil then - return 'NULL' - else - return tostring(v) - end -end - -local function cell_align(current_align, cell_value) - if current_align == 'left' then return 'left' end - if type(cell_value) == 'number' or type(cell_value) == 'cdata' then return 'right' end - return 'left' -end - -local function print_result(res, minsize, print) - local fields = {} - for i,field in res:fields() do - fields[i] = field.name - end - local rows = {} - local aligns = {} --deduced from values - for i,row in res:rows'n' do - local t = {} - for j=1,#fields do - t[j] = format_cell(row[j]) - aligns[j] = cell_align(aligns[j], row[j]) - end - rows[i] = t - end - print_table(fields, rows, aligns, minsize, print) -end - -local function print_statement(stmt, minsize, print) - local res = stmt:bind_result() - local fields = {} - for i,field in stmt:fields() do - fields[i] = field.name - end - local rows = {} - local aligns = {} - while stmt:fetch() do - local row = {} - for i=1,#fields do - local v = res:get(i) - row[i] = format_cell(v) - aligns[i] = cell_align(aligns[i], v) - end - rows[#rows+1] = row - end - stmt:close() - print_table(fields, rows, aligns, minsize, print) -end - -if not ... then require'mysql_test' end - -return { - fit = fit, - format_cell = format_cell, - table = print_table, - result = print_result, - statement = print_statement, -} - diff --git a/worldmods/mysql_base/mysql/mysql_test.lua b/worldmods/mysql_base/mysql/mysql_test.lua deleted file mode 100644 index 3f0f496..0000000 --- a/worldmods/mysql_base/mysql/mysql_test.lua +++ /dev/null @@ -1,552 +0,0 @@ ---mysql test unit (see comments for problems with libmariadb) ---NOTE: create a database called 'test' first to run these tests! -local mysql = require'mysql' -local glue = require'glue' -local pp = require'pp' -local myprint = require'mysql_print' -local ffi = require'ffi' - ---helpers - -local print_table = myprint.table -local print_result = myprint.result -local fit = myprint.fit - -local function assert_deepequal(t1, t2) --assert the equality of two values - assert(type(t1) == type(t2), type(t1)..' ~= '..type(t2)) - if type(t1) == 'table' then - for k,v in pairs(t1) do assert_deepequal(t2[k], v) end - for k,v in pairs(t2) do assert_deepequal(t1[k], v) end - else - assert(t1 == t2, pp.format(t1) .. ' ~= ' .. pp.format(t2)) - end -end - -local function print_fields(fields_iter) - local fields = {'name', 'type', 'type_flag', 'length', 'max_length', 'decimals', 'charsetnr', - 'org_name', 'table', 'org_table', 'db', 'catalog', 'def', 'extension'} - local rows = {} - local aligns = {} - for i,field in fields_iter do - rows[i] = {} - for j=1,#fields do - local v = field[fields[j]] - rows[i][j] = tostring(v) - aligns[j] = type(v) == 'number' and 'right' or 'left' - end - end - print_table(fields, rows, aligns) -end - ---client library - -print('mysql.thread_safe() ', '->', pp.format(mysql.thread_safe())) -print('mysql.client_info() ', '->', pp.format(mysql.client_info())) -print('mysql.client_version()', '->', pp.format(mysql.client_version())) - ---connections - -local t = { - host = 'localhost', - user = 'root', - db = 'test', - options = { - MYSQL_SECURE_AUTH = false, --not supported by libmariadb - MYSQL_OPT_READ_TIMEOUT = 1, - }, - flags = { - CLIENT_LONG_PASSWORD = true, - }, -} -local conn = mysql.connect(t) -print('mysql.connect ', pp.format(t, ' '), '->', conn) -print('conn:change_user( ', pp.format(t.user), ')', conn:change_user(t.user)) -print('conn:select_db( ', pp.format(t.db), ')', conn:select_db(t.db)) -print('conn:set_multiple_statements(', pp.format(true), ')', conn:set_multiple_statements(true)) -print('conn:set_charset( ', pp.format('utf8'), ')', conn:set_charset('utf8')) - ---conn info - -print('conn:charset_name() ', '->', pp.format(conn:charset())); assert(conn:charset() == 'utf8') -print('conn:charset_info() ', '->', pp.format(conn:charset_info(), ' ')) --crashes libmariadb -print('conn:ping() ', '->', pp.format(conn:ping())) -print('conn:thread_id() ', '->', pp.format(conn:thread_id())) -print('conn:stat() ', '->', pp.format(conn:stat())) -print('conn:server_info() ', '->', pp.format(conn:server_info())) -print('conn:host_info() ', '->', pp.format(conn:host_info())) -print('conn:server_version() ', '->', pp.format(conn:server_version())) -print('conn:proto_info() ', '->', pp.format(conn:proto_info())) -print('conn:ssl_cipher() ', '->', pp.format(conn:ssl_cipher())) - ---transactions - -print('conn:commit() ', conn:commit()) -print('conn:rollback() ', conn:rollback()) -print('conn:set_autocommit() ', conn:set_autocommit(true)) - ---test types and values - -local test_fields = { - 'fdecimal', - 'fnumeric', - 'ftinyint', - 'futinyint', - 'fsmallint', - 'fusmallint', - 'finteger', - 'fuinteger', - 'ffloat', - 'fdouble', - 'fdouble2', - 'fdouble3', - 'fdouble4', - 'freal', - 'fbigint', - 'fubigint', - 'fmediumint', - 'fumediumint', - 'fdate', - 'ftime', - 'ftime2', - 'fdatetime', - 'fdatetime2', - 'ftimestamp', - 'ftimestamp2', - 'fyear', - 'fbit2', - 'fbit22', - 'fbit64', - 'fenum', - 'fset', - 'ftinyblob', - 'fmediumblob', - 'flongblob', - 'ftext', - 'fblob', - 'fvarchar', - 'fvarbinary', - 'fchar', - 'fbinary', - 'fnull', -} - -local field_indices = glue.index(test_fields) - -local field_types = { - fdecimal = 'decimal(8,2)', - fnumeric = 'numeric(6,4)', - ftinyint = 'tinyint', - futinyint = 'tinyint unsigned', - fsmallint = 'smallint', - fusmallint = 'smallint unsigned', - finteger = 'int', - fuinteger = 'int unsigned', - ffloat = 'float', - fdouble = 'double', - fdouble2 = 'double', - fdouble3 = 'double', - fdouble4 = 'double', - freal = 'real', - fbigint = 'bigint', - fubigint = 'bigint unsigned', - fmediumint = 'mediumint', - fumediumint = 'mediumint unsigned', - fdate = 'date', - ftime = 'time(0)', - ftime2 = 'time(6)', - fdatetime = 'datetime(0)', - fdatetime2 = 'datetime(6)', - ftimestamp = 'timestamp(0) null', - ftimestamp2 = 'timestamp(6) null', - fyear = 'year', - fbit2 = 'bit(2)', - fbit22 = 'bit(22)', - fbit64 = 'bit(64)', - fenum = "enum('yes', 'no')", - fset = "set('e1', 'e2', 'e3')", - ftinyblob = 'tinyblob', - fmediumblob = 'mediumblob', - flongblob = 'longblob', - ftext = 'text', - fblob = 'blob', - fvarchar = 'varchar(200)', - fvarbinary = 'varbinary(200)', - fchar = 'char(200)', - fbinary = 'binary(20)', - fnull = 'int' -} - -local test_values = { - fdecimal = '42.12', - fnumeric = '42.1234', - ftinyint = 42, - futinyint = 255, - fsmallint = 42, - fusmallint = 65535, - finteger = 42, - fuinteger = 2^32-1, - ffloat = tonumber(ffi.cast('float', 42.33)), - fdouble = 42.33, - fdouble2 = nil, --null from mysql 5.1.24+ - fdouble3 = nil, --null from mysql 5.1.24+ - fdouble4 = nil, --null from mysql 5.1.24+ - freal = 42.33, - fbigint = 420LL, - fubigint = 0ULL - 1, - fmediumint = 440, - fumediumint = 2^24-1, - fdate = {year = 2013, month = 10, day = 05}, - ftime = {hour = 21, min = 30, sec = 15, frac = 0}, - ftime2 = {hour = 21, min = 30, sec = 16, frac = 123456}, - fdatetime = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 17, frac = 0}, - fdatetime2 = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 18, frac = 123456}, - ftimestamp = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 19, frac = 0}, - ftimestamp2 = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 20, frac = 123456}, - fyear = 2013, - fbit2 = 2, - fbit22 = 2 * 2^8 + 2, - fbit64 = 2ULL * 2^(64-8) + 2 * 2^8 + 2, - fenum = 'yes', - fset = 'e2,e3', - ftinyblob = 'tiny tiny blob', - fmediumblob = 'medium blob', - flongblob = 'loong blob', - ftext = 'just a text', - fblob = 'bloob', - fvarchar = 'just a varchar', - fvarbinary = 'a varbinary', - fchar = 'a char', - fbinary = 'a binary char\0\0\0\0\0\0\0', - fnull = nil, -} - -local set_values = { - fdecimal = "'42.12'", - fnumeric = "42.1234", - ftinyint = "'42'", - futinyint = "'255'", - fsmallint = "42", - fusmallint = "65535", - finteger = "'42'", - fuinteger = tostring(2^32-1), - ffloat = "42.33", - fdouble = "'42.33'", - fdouble2 = "0/0", - fdouble3 = "1/0", - fdouble4 = "-1/0", - freal = "42.33", - fbigint = "'420'", - fubigint = tostring(0ULL-1):sub(1,-4), --remove 'ULL' - fmediumint = "440", - fumediumint = tostring(2^24-1), - fdate = "'2013-10-05'", - ftime = "'21:30:15'", - ftime2 = "'21:30:16.123456'", - fdatetime = "'2013-10-05 21:30:17'", - fdatetime2 = "'2013-10-05 21:30:18.123456'", - ftimestamp = "'2013-10-05 21:30:19'", - ftimestamp2 = "'2013-10-05 21:30:20.123456'", - fyear = "2013", - fbit2 = "b'10'", - fbit22 = "b'1000000010'", - fbit64 = "b'0000001000000000000000000000000000000000000000000000001000000010'", - fenum = "'yes'", - fset = "('e3,e2')", - ftinyblob = "'tiny tiny blob'", - fmediumblob = "'medium blob'", - flongblob = "'loong blob'", - ftext = "'just a text'", - fblob = "'bloob'", - fvarchar = "'just a varchar'", - fvarbinary = "'a varbinary'", - fchar = "'a char'", - fbinary = "'a binary char'", - fnull = "null" -} - -local bind_types = { - fdecimal = 'decimal(20)', --TODO: truncation - fnumeric = 'numeric(20)', - ftinyint = 'tinyint', - futinyint = 'tinyint unsigned', - fsmallint = 'smallint', - fusmallint = 'smallint unsigned', - finteger = 'int', - fuinteger = 'int unsigned', - ffloat = 'float', - fdouble = 'double', - fdouble2 = 'double', - fdouble3 = 'double', - fdouble4 = 'double', - freal = 'real', - fbigint = 'bigint', - fubigint = 'bigint unsigned', - fmediumint = 'mediumint', - fumediumint = 'mediumint unsigned', - fdate = 'date', - ftime = 'time', - ftime2 = 'time', - fdatetime = 'datetime', - fdatetime2 = 'datetime', - ftimestamp = 'timestamp', - ftimestamp2 = 'timestamp', - fyear = 'year', - fbit2 = 'bit(2)', - fbit22 = 'bit(22)', - fbit64 = 'bit(64)', - fenum = 'enum(200)', - fset = 'set(200)', - ftinyblob = 'tinyblob(200)', - fmediumblob = 'mediumblob(200)', - flongblob = 'longblob(200)', - ftext = 'text(200)', - fblob = 'blob(200)', - fvarchar = 'varchar(200)', - fvarbinary = 'varbinary(200)', - fchar = 'char(200)', - fbinary = 'binary(200)', - fnull = 'int', -} - ---queries - -local esc = "'escape me'" -print('conn:escape( ', pp.format(esc), ')', '->', pp.format(conn:escape(esc))) -local q1 = 'drop table if exists binding_test' -print('conn:query( ', pp.format(q1), ')', conn:query(q1)) - -local field_defs = '' -for i,field in ipairs(test_fields) do - field_defs = field_defs .. field .. ' ' .. field_types[field] .. (i == #test_fields and '' or ', ') -end - -local field_sets = '' -for i,field in ipairs(test_fields) do - field_sets = field_sets .. field .. ' = ' .. set_values[field] .. (i == #test_fields and '' or ', ') -end - -conn:query([[ -create table binding_test ( ]] .. field_defs .. [[ ); - -insert into binding_test set ]] .. field_sets .. [[ ; - -insert into binding_test values (); - -select * from binding_test; -]]) - ---query info - -print('conn:field_count() ', '->', pp.format(conn:field_count())) -print('conn:affected_rows() ', '->', pp.format(conn:affected_rows())) -print('conn:insert_id() ', '->', conn:insert_id()) -print('conn:errno() ', '->', pp.format(conn:errno())) -print('conn:sqlstate() ', '->', pp.format(conn:sqlstate())) -print('conn:warning_count() ', '->', pp.format(conn:warning_count())) -print('conn:info() ', '->', pp.format(conn:info())) -for i=1,3 do -print('conn:more_results() ', '->', pp.format(conn:more_results())); assert(conn:more_results()) -print('conn:next_result() ', '->', pp.format(conn:next_result())) -end -assert(not conn:more_results()) - ---query results - -local res = conn:store_result() --TODO: local res = conn:use_result() -print('conn:store_result() ', '->', res) -print('res:row_count() ', '->', pp.format(res:row_count())); assert(res:row_count() == 2) -print('res:field_count() ', '->', pp.format(res:field_count())); assert(res:field_count() == #test_fields) -print('res:eof() ', '->', pp.format(res:eof())); assert(res:eof() == true) -print('res:fields() ', '->') print_fields(res:fields()) -print('res:field_info(1) ', '->', pp.format(res:field_info(1))) - ---first row: fetch as array and test values -local row = assert(res:fetch'n') -print("res:fetch'n' ", '->', pp.format(row)) -for i,field in res:fields() do - assert_deepequal(row[i], test_values[field.name]) -end - ---first row again: fetch as assoc. array and test values -print('res:seek(1) ', '->', res:seek(1)) -local row = assert(res:fetch'a') -print("res:fetch'a' ", '->', pp.format(row)) -for i,field in res:fields() do - assert_deepequal(row[field.name], test_values[field.name]) -end - ---first row again: fetch unpacked and test values -print('res:seek(1) ', '->', res:seek(1)) -local function pack(_, ...) - local t = {} - for i=1,select('#', ...) do - t[i] = select(i, ...) - end - return t -end -local row = pack(res:fetch()) -print("res:fetch() ", '-> packed: ', pp.format(row)) -for i,field in res:fields() do - assert_deepequal(row[i], test_values[field.name]) -end - ---first row again: print its values parsed and unparsed for comparison -res:seek(1) -local row = assert(res:fetch'n') -res:seek(1) -local row_s = assert(res:fetch'ns') -print() -print(fit('', 4, 'right') .. ' ' .. fit('field', 20) .. fit('unparsed', 40) .. ' ' .. 'parsed') -print(('-'):rep(4 + 2 + 20 + 40 + 40)) -for i,field in res:fields() do - print(fit(tostring(i), 4, 'right') .. ' ' .. fit(field.name, 20) .. fit(pp.format(row_s[i]), 40) .. ' ' .. pp.format(row[i])) -end -print() - ---second row: all nulls -local row = assert(res:fetch'n') -print("res:fetch'n' ", '->', pp.format(row)) -assert(#row == 0) -for i=1,res:field_count() do - assert(row[i] == nil) -end -assert(not res:fetch'n') - ---all rows again: test iterator -res:seek(1) -local n = 0 -for i,row in res:rows'nas' do - n = n + 1 - assert(i == n) -end -print("for i,row in res:rows'nas' do ", '->', n); assert(n == 2) - -print('res:free() ', res:free()) - ---reflection - -print('res:list_dbs() ', '->'); print_result(conn:list_dbs()) -print('res:list_tables() ', '->'); print_result(conn:list_tables()) -print('res:list_processes() ', '->'); print_result(conn:list_processes()) - ---prepared statements - -local query = 'select '.. table.concat(test_fields, ', ')..' from binding_test' -local stmt = conn:prepare(query) - -print('conn:prepare( ', pp.format(query), ')', '->', stmt) -print('stmt:field_count() ', '->', pp.format(stmt:field_count())); assert(stmt:field_count() == #test_fields) ---we can get the fields and their types before execution so we can create create our bind structures. ---max. length is not computed though, but length is, so we can use that. -print('stmt:fields() ', '->'); print_fields(stmt:fields()) - ---binding phase - -local btypes = {} -for i,field in ipairs(test_fields) do - btypes[i] = bind_types[field] -end -local bind = stmt:bind_result(btypes) -print('stmt:bind_result( ', pp.format(btypes), ')', '->', pp.format(bind)) - ---execution and loading - -print('stmt:exec() ', stmt:exec()) -print('stmt:store_result() ', stmt:store_result()) - ---result info - -print('stmt:row_count() ', '->', pp.format(stmt:row_count())) -print('stmt:affected_rows() ', '->', pp.format(stmt:affected_rows())) -print('stmt:insert_id() ', '->', pp.format(stmt:insert_id())) -print('stmt:sqlstate() ', '->', pp.format(stmt:sqlstate())) - ---result data (different API since we don't get a result object) - -print('stmt:fetch() ', stmt:fetch()) - -print('stmt:fields() ', '->'); print_fields(stmt:fields()) - -print('bind:is_truncated(1) ', '->', pp.format(bind:is_truncated(1))); assert(bind:is_truncated(1) == false) -print('bind:is_null(1) ', '->', pp.format(bind:is_null(1))); assert(bind:is_null(1) == false) -print('bind:get(1) ', '->', pp.format(bind:get(1))); assert(bind:get(1) == test_values.fdecimal) -local i = field_indices.fdate -print('bind:get_date( ', i, ')', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5}) -local i = field_indices.ftime -print('bind:get_date( ', i, ')', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {nil, nil, nil, 21, 30, 15, 0}) -local i = field_indices.fdatetime -print('bind:get_date( ', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5, 21, 30, 17, 0}) -local i = field_indices.ftimestamp -print('bind:get_date( ', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5, 21, 30, 19, 0}) -local i = field_indices.ftimestamp2 -print('bind:get_date( ', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5, 21, 30, 20, 123456}) -print('for i=1,bind.field_count do bind:get(i)', '->') - -local function print_bind_buffer(bind) - print() - for i,field in ipairs(test_fields) do - local v = bind:get(i) - assert_deepequal(v, test_values[field]) - assert(bind:is_truncated(i) == false) - assert(bind:is_null(i) == (test_values[field] == nil)) - print(fit(tostring(i), 4, 'right') .. ' ' .. fit(field, 20) .. pp.format(v)) - end - print() -end -print_bind_buffer(bind) - -print('stmt:free_result() ', stmt:free_result()) ---local next_result = stmt:next_result() ---print('stmt:next_result() ', '->', pp.format(next_result)); assert(next_result == false) - -print('stmt:reset() ', stmt:reset()) -print('stmt:close() ', stmt:close()) - ---prepared statements with parameters - -for i,field in ipairs(test_fields) do - local query = 'select * from binding_test where '..field..' = ?' - local stmt = conn:prepare(query) - print('conn:prepare( ', pp.format(query), ')') - local param_bind_def = {bind_types[field]} - - local bind = stmt:bind_params(param_bind_def) - print('stmt:bind_params ', pp.format(param_bind_def)) - - local function exec() - print('stmt:exec() ', stmt:exec()) - print('stmt:store_result() ', stmt:store_result()) - print('stmt:row_count() ', '->', stmt:row_count()) - assert(stmt:row_count() == 1) --libmariadb() returns 0 - end - - local v = test_values[field] - if v ~= nil then - print('bind:set( ', 1, pp.format(v), ')'); bind:set(1, v); exec() - - if field:find'date' or field:find'time' then - print('bind:set_date( ', 1, v.year, v.month, v.day, v.hour, v.min, v.sec, v.frac, ')') - bind:set_date(1, v.year, v.month, v.day, v.hour, v.min, v.sec, v.frac) - exec() --libmariadb crashes the server - end - end - print('stmt:close() ', stmt:close()) -end - ---prepared statements with auto-allocated result bind buffers. - -local query = 'select * from binding_test' -local stmt = conn:prepare(query) -local bind = stmt:bind_result() ---pp(stmt:bind_result_types()) -stmt:exec() -stmt:store_result() -stmt:fetch() -print_bind_buffer(bind) -stmt:close() - -local q = 'drop table binding_test' -print('conn:query( ', pp.format(q), ')', conn:query(q)) -print('conn:commit() ', conn:commit()) -print('conn:close() ', conn:close()) -