forked from ThomasMonroe314/ugxrealms
enable mysql, update settings
This commit is contained in:
parent
c965adf069
commit
705997b67f
@ -24,7 +24,8 @@ debug_log_level = action
|
|||||||
strict_protocol_version_checking = false
|
strict_protocol_version_checking = false
|
||||||
secure.trusted_mods = mysql_auth,mysql_base,sql,irc,stacktraceplus,multiskin
|
secure.trusted_mods = mysql_auth,mysql_base,sql,irc,stacktraceplus,multiskin
|
||||||
secure.enable_security = false
|
secure.enable_security = false
|
||||||
mysql_auth.cfgfile = /home/minetestservers/.minetest/worlds/UGXmr/mysql_auth.cfgfile
|
mysql_auth.cfgfile = /home/minetestservers/.minetest/worlds/UGXmr/mysql.cfgfile
|
||||||
|
mysql_base.cfgfile = /home/minetestservers/.minetest/worlds/UGXmr/mysql.cfgfile
|
||||||
#
|
#
|
||||||
#################Game##################
|
#################Game##################
|
||||||
#
|
#
|
||||||
|
123
worldmods/mysql_auth/README.md
Normal file
123
worldmods/mysql_auth/README.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# 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
|
47
worldmods/mysql_auth/auth_txt_import.lua
Normal file
47
worldmods/mysql_auth/auth_txt_import.lua
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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
|
2
worldmods/mysql_auth/depends.txt
Normal file
2
worldmods/mysql_auth/depends.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mysql_base
|
||||||
|
|
299
worldmods/mysql_auth/init.lua
Normal file
299
worldmods/mysql_auth/init.lua
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
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)
|
165
worldmods/mysql_base/LICENSE.txt
Normal file
165
worldmods/mysql_base/LICENSE.txt
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
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.
|
108
worldmods/mysql_base/README.md
Normal file
108
worldmods/mysql_base/README.md
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# 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
|
186
worldmods/mysql_base/init.lua
Normal file
186
worldmods/mysql_base/init.lua
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
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
|
||||||
|
|
1303
worldmods/mysql_base/mysql/mysql.lua
Normal file
1303
worldmods/mysql_base/mysql/mysql.lua
Normal file
File diff suppressed because it is too large
Load Diff
548
worldmods/mysql_base/mysql/mysql.md
Normal file
548
worldmods/mysql_base/mysql/mysql.md
Normal file
@ -0,0 +1,548 @@
|
|||||||
|
---
|
||||||
|
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()` ?
|
571
worldmods/mysql_base/mysql/mysql_h.lua
Normal file
571
worldmods/mysql_base/mysql/mysql_h.lua
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
|
||||||
|
--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"
|
||||||
|
|
||||||
|
]]
|
144
worldmods/mysql_base/mysql/mysql_print.lua
Normal file
144
worldmods/mysql_base/mysql/mysql_print.lua
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
--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,
|
||||||
|
}
|
||||||
|
|
552
worldmods/mysql_base/mysql/mysql_test.lua
Normal file
552
worldmods/mysql_base/mysql/mysql_test.lua
Normal file
@ -0,0 +1,552 @@
|
|||||||
|
--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 <count-rows>", '->', 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())
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user