update .gitignore, disable [mysql_base] and [mysql_auth]

master
elite 2017-07-29 17:34:48 -04:00
parent 5c35e36c4f
commit 43832e74ab
13 changed files with 5 additions and 4048 deletions

5
.gitignore vendored
View File

@ -8,3 +8,8 @@ irc_tokens
.*
homes
schems/
*.dat
*.data
*.home
beds_spawns
*.json

View File

@ -1,123 +0,0 @@
# mysql_auth
Plug Minetest's auth mechanism into a MySQL database.
# Configuration
First, if mod security is enabled (`secure.enable_security = true`), this mod must be added as
a trusted mod (in the `secure.trusted_mods` config entry). There is **no** other solution to
make it work under mod security.
By default `mysql_auth` doesn't run in singleplayer. This can be overriden by setting
`mysql_auth.enable_singleplayer` to `true`.
Configuration may be done as regular Minetest settings entries, or using a config file, allowing
for more configuration options; to do so specify the path as `mysql_auth.cfgfile`. This config
must contain a Lua table that can be read by `minetest.deserialize`, i.e. a regular table
definition follwing a `return` statement (see the example below).
When using flat Minetest configuation entries, all the following option names must be prefixed
with `mysql_auth.`. When using a config file, entries are to be hierarchised as per the dot
separator.
Values written next to option names are default values.
## Database connection
### Flat config file
```lua
db.host = 'localhost'
db.user = nil -- MySQL connector defaults to current username
db.pass = nil -- Using password: NO
db.port = nil -- MySQL connector defaults to either 3306, or no port if using localhost/unix socket
db.db = nil -- <== Setting this is required
```
### Lua table config file
Connection options are passed as a table through the `db.connopts` entry.
Its format must be the same as [LuaPower's MySQL module `mysql.connect(options_t)` function][mycn],
that is (all members are optional);
```lua
connopts = {
host = ...,
user = ...,
pass = ...,
db = ...,
port = ...,
unix_socket = ...,
flags = { ... },
options = { ... },
attrs = { ... },
-- Also key, cert, ca, cpath, cipher
}
```
## Auth table schema finetuning
```lua
db.tables.auths.name = 'auths'
db.tables.auths.schema.userid = 'userid'
db.tables.auths.schema.userid_type = 'INT'
db.tables.auths.schema.username = 'username'
db.tables.auths.schema.username_type = 'VARCHAR(32)'
db.tables.auths.schema.password = 'password'
db.tables.auths.schema.password_type = 'VARCHAR(512)'
db.tables.auths.schema.privs = 'privs'
db.tables.auths.schema.privs_type = 'VARCHAR(512)'
db.tables.auths.schema.lastlogin = 'lastlogin'
db.tables.auths.schema.lastlogin_type = 'BIGINT'
```
The `_type` config entries are only used when creating an auth table, i.e. when
`db.tables.auths.name` doesn't exist.
## Examples
### Example 1
#### Using a Lua config file
`minetest.conf`:
```
mysql_auth.cfgfile = /srv/minetest/skyblock/mysql_auth_config
```
`/srv/minetest/skyblock/mysql_auth_config`:
```lua
return {
db = {
connopts = {
user = 'minetest',
pass = 'BQy77wK$Um6es3Bi($iZ*w3N',
db = 'minetest'
},
tables = {
auths = {
name = 'skyblock_auths'
}
}
}
}
```
#### Using only Minetest config entries
`minetest.conf`:
```
mysql_auth.db.user = minetest
mysql_auth.db.pass = BQy77wK$Um6es3Bi($iZ*w3N
mysql_auth.db.db = minetest
mysql_auth.db.tables.auth.name = skyblock_auths
```
# License
`mysql_auth` is licensed under [LGPLv3](https://www.gnu.org/licenses/lgpl.html).
Using the Public Domain-licensed LuaPower `mysql` module.
[mycn]: https://luapower.com/mysql#mysql.connectoptions_t---conn

View File

@ -1,47 +0,0 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local thismod = _G[modname]
function thismod.import_auth_txt()
minetest.log('action', modname .. ": Importing auth.txt")
local auth_file_path = minetest.get_worldpath() .. '/auth.txt'
local create_auth_stmt = thismod.create_auth_stmt
local create_auth_params = thismod.create_auth_params
local conn = thismod.conn
local file, errmsg = io.open(auth_file_path, 'rb')
if not file then
minetest.log('action', modname .. ": " .. auth_file_path .. " could not be opened for reading" ..
"(" .. errmsg .. "); no auth entries imported")
return
end
conn:query('SET autocommit=0')
conn:query('START TRANSACTION')
for line in file:lines() do
if line ~= "" then
local fields = line:split(":", true)
local name, password, privilege_string, last_login = unpack(fields)
last_login = tonumber(last_login)
if not (name and password and privilege_string) then
minetest.log('warning', modname .. ": Invalid line in auth.txt, skipped: " .. dump(line))
end
minetest.log('info', modname .. " importing player '"..name.."'")
create_auth_params:set(1, name)
create_auth_params:set(2, password)
create_auth_params:set(3, privilege_string)
create_auth_params:set(4, last_login)
local success, msg = pcall(create_auth_stmt.exec, create_auth_stmt)
if not success then
error(modname .. ": import failed: " .. msg)
end
if create_auth_stmt:affected_rows() ~= 1 then
error(modname .. ": create_auth failed: affected row count is " ..
create_auth_stmt:affected_rows() .. ", expected 1")
end
end
end
conn:query('COMMIT')
conn:query('SET autocommit=1')
io.close(file)
minetest.log('action', modname .. ": Finished importing auth.txt")
end

View File

@ -1,2 +0,0 @@
mysql_base

View File

@ -1,299 +0,0 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local thismod = {
enabled = false
}
_G[modname] = thismod
if not mysql_base.enabled then
minetest.log('action', modname .. ": mysql_base disabled, not loading mod")
return
end
local singleplayer = minetest.is_singleplayer() -- Caching is OK since you can't open a game to
-- multiplayer unless you restart it.
if not minetest.setting_get(modname .. '.enable_singleplayer') and singleplayer then
minetest.log('action', modname .. ": Not adding auth handler because of singleplayer game")
return
end
enabled = true
do
local get = mysql_base.mkget(modname)
local conn, dbname = mysql_base.conn, mysql_base.dbname
local tables = {}
do -- Tables and schema settings
local t_auths = get('db.tables.auths')
if type(t_auths) == 'table' then
tables.auths = t_auths
else
tables.auths = {}
tables.auths.name = get('db.tables.auths.name')
tables.auths.schema = {}
local S = tables.auths.schema
S.userid = get('db.tables.auths.schema.userid')
S.username = get('db.tables.auths.schema.username')
S.password = get('db.tables.auths.schema.password')
S.privs = get('db.tables.auths.schema.privs')
S.lastlogin = get('db.tables.auths.schema.lastlogin')
S.userid_type = get('db.tables.auths.schema.userid_type')
S.username_type = get('db.tables.auths.schema.username_type')
S.password_type = get('db.tables.auths.schema.password_type')
S.privs_type = get('db.tables.auths.schema.privs_type')
S.lastlogin_type = get('db.tables.auths.schema.lastlogin_type')
end
do -- Default values
tables.auths.name = tables.auths.name or 'auths'
tables.auths.schema = tables.auths.schema or {}
local S = tables.auths.schema
S.userid = S.userid or 'userid'
S.username = S.username or 'username'
S.password = S.password or 'password'
S.privs = S.privs or 'privs'
S.lastlogin = S.lastlogin or 'lastlogin'
S.userid_type = S.userid_type or 'INT'
S.username_type = S.username_type or 'VARCHAR(32)'
S.password_type = S.password_type or 'VARCHAR(512)'
S.privs_type = S.privs_type or 'VARCHAR(512)'
S.lastlogin_type = S.lastlogin_type or 'BIGINT'
-- Note lastlogin doesn't use the TIMESTAMP type, which is 32-bit and therefore
-- subject to the year 2038 problem.
end
end
local auth_table_created
-- Auth table existence check and setup
if not mysql_base.table_exists(tables.auths.name) then
-- Auth table doesn't exist, create it
local S = tables.auths.schema
conn:query('CREATE TABLE ' .. tables.auths.name .. ' (' ..
S.userid .. ' ' .. S.userid_type .. ' NOT NULL AUTO_INCREMENT,' ..
S.username .. ' ' .. S.username_type .. ' NOT NULL,' ..
S.password .. ' ' .. S.password_type .. ' NOT NULL,' ..
S.privs .. ' ' .. S.privs_type .. ' NOT NULL,' ..
S.lastlogin .. ' ' .. S.lastlogin_type .. ',' ..
'PRIMARY KEY (' .. S.userid .. '),' ..
'UNIQUE (' .. S.username .. ')' ..
')')
minetest.log('action', modname .. " created table '" .. dbname .. "." .. tables.auths.name ..
"'")
auth_table_created = true
end
local S = tables.auths.schema
local get_auth_stmt = conn:prepare('SELECT ' .. S.userid .. ',' .. S.password .. ',' .. S.privs ..
',' .. S.lastlogin .. ' FROM ' .. tables.auths.name .. ' WHERE ' .. S.username .. '=?')
thismod.get_auth_stmt = get_auth_stmt
local get_auth_params = get_auth_stmt:bind_params({S.username_type})
thismod.get_auth_params = get_auth_params
local get_auth_results = get_auth_stmt:bind_result({S.userid_type, S.password_type, S.privs_type,
S.lastlogin_type})
thismod.get_auth_results = get_auth_results
local create_auth_stmt = conn:prepare('INSERT INTO ' .. tables.auths.name .. '(' .. S.username ..
',' .. S.password .. ',' .. S.privs .. ',' .. S.lastlogin .. ') VALUES (?,?,?,?)')
thismod.create_auth_stmt = create_auth_stmt
local create_auth_params = create_auth_stmt:bind_params({S.username_type, S.password_type,
S.privs_type, S.lastlogin_type})
thismod.create_auth_params = create_auth_params
local delete_auth_stmt = conn:prepare('DELETE FROM ' .. tables.auths.name .. ' WHERE ' ..
S.username .. '=?')
thismod.delete_auth_stmt = delete_auth_stmt
local delete_auth_params = delete_auth_stmt:bind_params({S.username_type})
thismod.delete_auth_params = delete_auth_params
local set_password_stmt = conn:prepare('UPDATE ' .. tables.auths.name .. ' SET ' .. S.password ..
'=? WHERE ' .. S.username .. '=?')
thismod.set_password_stmt = set_password_stmt
local set_password_params = set_password_stmt:bind_params({S.password_type, S.username_type})
thismod.set_password_params = set_password_params
local set_privileges_stmt = conn:prepare('UPDATE ' .. tables.auths.name .. ' SET ' .. S.privs ..
'=? WHERE ' .. S.username .. '=?')
thismod.set_privileges_stmt = set_privileges_stmt
local set_privileges_params = set_privileges_stmt:bind_params({S.privs_type, S.username_type})
thismod.set_privileges_params = set_privileges_params
local record_login_stmt = conn:prepare('UPDATE ' .. tables.auths.name .. ' SET ' ..
S.lastlogin .. '=? WHERE ' .. S.username .. '=?')
thismod.record_login_stmt = record_login_stmt
local record_login_params = record_login_stmt:bind_params({S.lastlogin_type, S.username_type})
thismod.record_login_params = record_login_params
local enumerate_auths_query = 'SELECT ' .. S.username .. ',' .. S.password .. ',' .. S.privs ..
',' .. S.lastlogin .. ' FROM ' .. tables.auths.name
thismod.enumerate_auths_query = enumerate_auths_query
if auth_table_created and get('import_auth_txt_on_table_create') ~= 'false' then
if not thismod.import_auth_txt then
dofile(modpath .. '/auth_txt_import.lua')
end
thismod.import_auth_txt()
end
thismod.auth_handler = {
get_auth = function(name)
assert(type(name) == 'string')
get_auth_params:set(1, name)
local success, msg = pcall(get_auth_stmt.exec, get_auth_stmt)
if not success then
minetest.log('error', modname .. ": get_auth(" .. name .. ") failed: " .. msg)
return nil
end
get_auth_stmt:store_result()
if not get_auth_stmt:fetch() then
-- No such auth row exists
return nil
end
while get_auth_stmt:fetch() do
minetest.log('warning', modname .. ": get_auth(" .. name .. "): multiples lines were" ..
" returned")
end
local userid, password, privs_str, lastlogin = get_auth_results:get(1),
get_auth_results:get(2), get_auth_results:get(3), get_auth_results:get(4)
local admin = (name == minetest.setting_get("name"))
local privs
if singleplayer or admin then
privs = {}
-- If admin, grant all privs, if singleplayer, grant all privs w/ give_to_singleplayer
for priv, def in pairs(core.registered_privileges) do
if (singleplayer and def.give_to_singleplayer) or admin then
privs[priv] = true
end
end
if admin and not thismod.admin_get_auth_called then
thismod.admin_get_auth_called = true
thismod.auth_handler.set_privileges(name, privs)
end
else
privs = minetest.string_to_privs(privs_str)
end
return {
userid = userid,
password = password,
privileges = privs,
last_login = tonumber(lastlogin)
}
end,
create_auth = function(name, password, reason)
assert(type(name) == 'string')
assert(type(password) == 'string')
minetest.log('info', modname .. " creating player '"..name.."'" .. (reason or ""))
create_auth_params:set(1, name)
create_auth_params:set(2, password)
create_auth_params:set(3, minetest.setting_get("default_privs"))
create_auth_params:set(4, math.floor(os.time()))
local success, msg = pcall(create_auth_stmt.exec, create_auth_stmt)
if not success then
minetest.log('error', modname .. ": create_auth(" .. name .. ") failed: " .. msg)
return false
end
if create_auth_stmt:affected_rows() ~= 1 then
minetest.log('error', modname .. ": create_auth(" .. name .. ") failed: affected row" ..
" count is " .. create_auth_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end,
delete_auth = function(name)
assert(type(name) == 'string')
minetest.log('info', modname .. " deleting player '"..name.."'")
delete_auth_params:set(1, name)
local success, msg = pcall(delete_auth_stmt.exec, delete_auth_stmt)
if not success then
minetest.log('error', modname .. ": delete_auth(" .. name .. ") failed: " .. msg)
return false
end
if delete_auth_stmt:affected_rows() ~= 1 then
minetest.log('error', modname .. ": delete_auth(" .. name .. ") failed: affected row" ..
" count is " .. delete_auth_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end,
set_password = function(name, password)
assert(type(name) == 'string')
assert(type(password) == 'string')
if not thismod.auth_handler.get_auth(name) then
return thismod.auth_handler.create_auth(name, password, " because set_password was requested")
else
minetest.log('info', modname .. " setting password of player '" .. name .. "'")
set_password_params:set(1, password)
set_password_params:set(2, name)
local success, msg = pcall(set_password_stmt.exec, set_password_stmt)
if not success then
minetest.log('error', modname .. ": set_password(" .. name .. ") failed: " .. msg)
return false
end
if set_password_stmt:affected_rows() ~= 1 then
minetest.log('error', modname .. ": set_password(" .. name .. ") failed: affected row" ..
" count is " .. set_password_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end
end,
set_privileges = function(name, privileges)
assert(type(name) == 'string')
assert(type(privileges) == 'table')
set_privileges_params:set(1, minetest.privs_to_string(privileges))
set_privileges_params:set(2, name)
local success, msg = pcall(set_privileges_stmt.exec, set_privileges_stmt)
if not success then
minetest.log('error', modname .. ": set_privileges(" .. name .. ") failed: " .. msg)
return false
end
minetest.notify_authentication_modified(name)
return true
end,
reload = function()
return true
end,
record_login = function(name)
assert(type(name) == 'string')
record_login_params:set(1, math.floor(os.time()))
record_login_params:set(2, name)
local success, msg = pcall(record_login_stmt.exec, record_login_stmt)
if not success then
minetest.log('error', modname .. ": record_login(" .. name .. ") failed: " .. msg)
return false
end
if record_login_stmt:affected_rows() ~= 1 then
minetest.log('error', modname .. ": record_login(" .. name .. ") failed: affected row" ..
" count is " .. record_login_stmt:affected_rows() .. ", expected 1")
return false
end
return true
end,
enumerate_auths = function()
conn:query(enumerate_auths_query)
local res = conn:store_result()
return function()
local row = res:fetch('n')
if not row then
return nil
end
local username, password, privs_str, lastlogin = unpack(row)
return username, {
password = password,
privileges = minetest.string_to_privs(privs_str),
last_login = tonumber(lastlogin)
}
end
end
}
end
minetest.register_authentication_handler(thismod.auth_handler)
minetest.log('action', modname .. ": Registered auth handler")
mysql_base.register_on_shutdown(function()
thismod.get_auth_stmt:free_result()
end)

View File

@ -1,165 +0,0 @@
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.

View File

@ -1,108 +0,0 @@
# mysql_base
Base Minetest mod to connect to a MySQL database. Used by other mods to read/write data.
# Installing
Get this repository's contents using `git`, and make sure to fetch submodules
(`git submodule update --init`).
# Configuration
First, if mod security is enabled (`secure.enable_security = true`), this mod must be added as
a trusted mod (in the `secure.trusted_mods` config entry). There is **no** other solution to
make it work under mod security.
By default `mysql_base` doesn't run in singleplayer. This can be overriden by setting
`mysql_base.enable_singleplayer` to `true`.
Configuration may be done as regular Minetest settings entries, or using a config file, allowing
for more configuration options; to do so specify the path as `mysql_base.cfgfile`. This config
must contain a Lua table that can be read by `minetest.deserialize`, i.e. a regular table
definition follwing a `return` statement (see the example below).
When using flat Minetest configuation entries, all the following option names must be prefixed
with `mysql_base.`. When using a config file, entries are to be hierarchised as per the dot
separator.
Values written next to option names are default values.
## Database connection
### Minetest flat config file
Values after the "`=`" are the default values used if unspecified.
```
mysql_base.db.host = 'localhost'
mysql_base.db.user = nil -- MySQL connector defaults to current username
mysql_base.db.pass = nil -- Using password: NO
mysql_base.db.port = nil -- MySQL connector defaults to either 3306, or no port if using localhost/unix socket
mysql_base.db.db = nil -- <== Setting this is required
```
### Lua table config file
Connection options are passed as a table through the `db.connopts` entry.
Its format must be the same as [LuaPower's MySQL module `mysql.connect(options_t)` function][mycn],
that is (all members are optional);
```lua
return {
db = {
connopts = {
host = ...,
user = ...,
pass = ...,
db = ...,
port = ...,
unix_socket = ...,
flags = { ... },
options = { ... },
attrs = { ... },
-- Also key, cert, ca, cpath, cipher
}
}
}
```
## Examples
### Example 1
#### Using a Lua config file
`minetest.conf`:
```
mysql_auth.cfgfile = /srv/minetest/skyblock/mysql_auth_config
```
`/srv/minetest/skyblock/mysql_auth_config`:
```lua
return {
db = {
connopts = {
user = 'minetest',
pass = 'BQy77wK$Um6es3Bi($iZ*w3N',
db = 'minetest'
},
}
}
```
#### Using only Minetest config entries
`minetest.conf`:
```
mysql_auth.db.user = minetest
mysql_auth.db.pass = BQy77wK$Um6es3Bi($iZ*w3N
mysql_auth.db.db = minetest
```
# License
`mysql_base` is licensed under [LGPLv3](https://www.gnu.org/licenses/lgpl.html).
Using the Public Domain-licensed LuaPower `mysql` module.
[mycn]: https://luapower.com/mysql#mysql.connectoptions_t---conn

View File

@ -1,186 +0,0 @@
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local thismod = {
enabled = false,
}
_G[modname] = thismod
local singleplayer = minetest.is_singleplayer() -- Caching is OK since you can't open a game to
-- multiplayer unless you restart it.
if not minetest.setting_get(modname .. '.enable_singleplayer') and singleplayer then
minetest.log('action', modname .. ": Not enabling because of singleplayer game")
return
end
thismod.enabled = true
local function setoverlay(tab, orig)
local mt = getmetatable(tab) or {}
mt.__index = function (tab, key)
if rawget(tab, key) ~= nil then
return rawget(tab, key)
else
return orig[key]
end
end
setmetatable(tab, mt)
end
local function string_splitdots(s)
local temp = {}
local index = 0
local last_index = string.len(s)
while true do
local i, e = string.find(s, '%.', index)
if i and e then
local next_index = e + 1
local word_bound = i - 1
table.insert(temp, string.sub(s, index, word_bound))
index = next_index
else
if index > 0 and index <= last_index then
table.insert(temp, string.sub(s, index, last_index))
elseif index == 0 then
temp = nil
end
break
end
end
return temp
end
local mysql
do -- MySQL module loading
local env = {
require = function (module)
if module == 'mysql_h' then
return dofile(modpath .. '/mysql/mysql_h.lua')
else
return require(module)
end
end
}
setoverlay(env, _G)
local fn, msg = loadfile(modpath .. '/mysql/mysql.lua')
if not fn then error(msg) end
setfenv(fn, env)
local status
status, mysql = pcall(fn, {})
if not status then
error(modname .. ' failed to load MySQL FFI interface: ' .. tostring(mysql))
end
thismod.mysql = mysql
end
function thismod.mkget(modname)
local get = function (name) return minetest.setting_get(modname .. '.' .. name) end
local cfgfile = get('cfgfile')
if type(cfgfile) == 'string' and cfgfile ~= '' then
local file = io.open(cfgfile, 'rb')
if not file then
error(modname .. ' failed to load specified config file at ' .. cfgfile)
end
local cfg, msg = minetest.deserialize(file:read('*a'))
file:close()
if not cfg then
error(modname .. ' failed to parse specified config file at ' .. cfgfile .. ': ' .. msg)
end
get = function (name)
if type(name) ~= 'string' or name == '' then
return nil
end
local parts = string_splitdots(name)
if not parts then
return cfg[name]
end
local tbl = cfg[parts[1]]
for n = 2, #parts do
if tbl == nil then
return nil
end
tbl = tbl[parts[n]]
end
return tbl
end
end
return get
end
local get = thismod.mkget(modname)
do
local conn, dbname
-- MySQL API backend
mysql.config(get('db.api'))
local connopts = get('db.connopts')
if (get('db.db') == nil) and (type(connopts) == 'table' and connopts.db == nil) then
error(modname .. ": missing database name parameter")
end
if type(connopts) ~= 'table' then
connopts = {}
-- Traditional connection parameters
connopts.host, connopts.user, connopts.port, connopts.pass, connopts.db =
get('db.host') or 'localhost', get('db.user'), get('db.port'), get('db.pass'), get('db.db')
end
connopts.charset = 'utf8'
connopts.options = connopts.options or {}
connopts.options.MYSQL_OPT_RECONNECT = true
conn = mysql.connect(connopts)
dbname = connopts.db
minetest.log('action', modname .. ": Connected to MySQL database " .. dbname)
thismod.conn = conn
thismod.dbname = dbname
-- LuaPower's MySQL interface throws an error when the connection fails, no need to check if
-- it succeeded.
-- Ensure UTF-8 is in use.
-- If you use another encoding, kill yourself (unless it's UTF-32).
conn:query("SET NAMES 'utf8'")
conn:query("SET CHARACTER SET utf8")
conn:query("SET character_set_results = 'utf8', character_set_client = 'utf8'," ..
"character_set_connection = 'utf8', character_set_database = 'utf8'," ..
"character_set_server = 'utf8'")
local set = function(setting, val) conn:query('SET ' .. setting .. '=' .. val) end
pcall(set, 'wait_timeout', 3600)
pcall(set, 'autocommit', 1)
pcall(set, 'max_allowed_packet', 67108864)
end
local function ping()
if thismod.conn then
if not thismod.conn:ping() then
minetest.log('error', modname .. ": failed to ping database")
end
end
minetest.after(1800, ping)
end
minetest.after(10, ping)
local shutdown_callbacks = {}
function thismod.register_on_shutdown(func)
table.insert(shutdown_callbacks, func)
end
minetest.register_on_shutdown(function()
if thismod.conn then
minetest.log('action', modname .. ": Shutting down, running callbacks")
for _, func in ipairs(shutdown_callbacks) do
func()
end
thismod.conn:close()
thismod.conn = nil
minetest.log('action', modname .. ": Cosed database connection")
end
end)
function thismod.table_exists(name)
thismod.conn:query("SHOW TABLES LIKE '" .. name .. "'")
local res = thismod.conn:store_result()
local exists = (res:row_count() ~= 0)
res:free()
return exists
end

File diff suppressed because it is too large Load Diff

View File

@ -1,548 +0,0 @@
---
tagline: mysql database client
---
## `local mysql = require'mysql'`
A complete, lightweight ffi binding of the mysql client library.
> NOTE: binaries are in separate packages [libmysql] and [libmariadb].
## Summary
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
**[Initialization]**
`mysql.config(['mysql'|'mariadb'|libname|clib]) -> mysql`
**[Connections]**
`mysql.connect(host, [user], [pass], [db], [charset], [port]) -> conn` connect to a mysql server
`mysql.connect(options_t) -> conn` connect to a mysql server
`conn:close()` close the connection
**[Queries]**
`conn:query(s)` execute a query
`conn:escape(s) -> s` escape an SQL string
**[Fetching results]**
`conn:store_result() -> result` get a cursor for buffered read ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-store-result.html))
`conn:use_result() -> result` get a cursor for unbuffered read ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-use-result.html))
`result:fetch([mode[, row_t]]) -> true, v1, v2, ... | row_t | nil` fetch the next row from the result
`result:rows([mode[, row_t]]) -> iterator() -> row_num, val1, val2, ...` row iterator
`result:rows([mode[, row_t]]) -> iterator() -> row_num, row_t` row iterator
`result:free()` free the cursor
`result:row_count() -> n` number of rows
`result:eof() -> true | false` check if no more rows
`result:seek(row_number)` seek to row number
**[Query info]**
`conn:field_count() -> n` number of result fields in the executed query
`conn:affected_rows() -> n` number of affected rows in the executed query
`conn:insert_id() -> n` the id of the autoincrement column in the executed query
`conn:errno() -> n` mysql error code (0 if no error) from the executed query
`conn:sqlstate() -> s`
`conn:warning_count() -> n` number of errors, warnings, and notes from executed query
`conn:info() -> s`
**[Field info]**
`result:field_count() -> n` number of fields in the result
`result:field_name(field_number) -> s` field name given field index
`result:field_type(field_number) -> type, length, unsigned, decimals` field type given field index
`result:field_info(field_number) -> info_t` field info table
`result:fields() -> iterator() -> i, info_t` field info iterator
**[Result bookmarks]**
`result:tell() -> bookmark` bookmark the current row for later seek
`result:seek(bookmark)` seek to a row bookmark
**[Multiple statement queries]**
`conn:next_result() -> true | false` skip to the next result set ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-next-result.html))
`conn:more_results() -> true | false` are there more result sets?
**[Prepared statements]**
`conn:prepare(query) -> stmt` prepare a query for multiple executions
`stmt:param_count() -> n` number of params
`stmt:exec()` execute a prepared statement
`stmt:store_result()` store all the resulted rows to the client
`stmt:fetch() -> true | false | true, 'truncated'` fetch the next row
`stmt:free_result()` free the current result buffers
`stmt:close()` close the statement
`stmt:next_result()` skip to the next result set
`stmt:row_count() -> n` number of rows in the result, if the result was stored
`stmt:affected_rows() -> n` number of affected rows after execution
`stmt:insert_id() -> n` the id of the autoincrement column after execution
`stmt:field_count() -> n` number of fields in the result after execution
`stmt:errno() -> n` mysql error code, if any, from the executed statement
`stmt:sqlstate() -> s`
`stmt:result_metadata() -> result` get a result for accessing the field info
`stmt:fields() -> iterator() -> i, info_t` iterate the result fields info
`stmt:reset()` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-reset.html)
`stmt:seek(row_number)` seek to row number
`stmt:tell() -> bookmark` get a bookmark in the current result
`stmt:seek(bookmark)` seek to a row bookmark in the current result
**[Prepared statements I/O]**
`stmt:bind_params(type1, ... | types_t) -> params` bind query parameters based on type definitions
`params:set(i, number | int64_t | uint64_t | true | false)` set an integer, float or bit param
`params:set(i, s[, size])` set a variable sized param
`params:set(i, cdata, size)` set a variable sized param
`params:set(i, {year=, month=, ...})` set a time/date param
`params:set_date(i, [year], [month], [day], [hour], [min], [sec], [frac])` set a time/date param
`stmt:write(param_number, data[, size])` send a long param in chunks
`stmt:bind_result([type1, ... | types_t | maxsize]) -> fields` bind query result fields based on type definitions
`fields:get(i) -> value` get the current row value of a field
`fields:get_datetime(i) -> year, month, day, hour, min, sec, frac` get the value of a date/time field directly
`fields:is_null(i) -> true | false` is field null?
`fields:is_truncated(i) -> true | false` was field value truncated?
**[Prepared statements settings]**
`stmt:update_max_length() -> true | false` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
`stmt:set_update_max_length(true | false)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
`stmt:cursor_type() -> mysql.C.MYSQL_CURSOR_TYPE_*` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
`stmt:set_cursor_type('CURSOR_TYPE_...')` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
`stmt:set_cursor_type(mysql.C.MYSQL_CURSOR_TYPE_...)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
`stmt:prefetch_rows() -> n` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
`stmt:set_prefetch_rows(stmt, n)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html)
**[Connection info]**
`conn:set_charset(charset)` change the current charset
`conn:select_db(dbname)` change the current database
`conn:change_user(user, [pass], [db])` change the current user (and database)
`conn:set_multiple_statements(true | false)` enable/disable support for multiple statements
`conn:charset() -> s` get current charset's name
`conn:charset_info() -> info_t` get info about the current charset
`conn:ping() -> true | false` check if the connection is still alive
`conn:thread_id() -> id`
`conn:stat() -> s`
`conn:server_info() -> s`
`conn:host_info() -> s`
`conn:server_version() -> n`
`conn:proto_info() -> n`
`conn:ssl_cipher() -> s`
**[Transactions]**
`conn:commit()` commit the current transaction
`conn:rollback()` rollback the current transaction
`conn:set_autocommit([true | false])` enable/disable autocommit on the current connection
**[Reflection]**
`conn:list_dbs([wildcard]) -> result` return info about databases as a result object
`conn:list_tables([wildcard]) -> result` return info about tables as a result object
`conn:list_processes() -> result` return info about processes as a result object
**[Remote control]**
`conn:kill(pid)` kill a connection based on process id
`conn:shutdown([level])` shutdown the server
`conn:refresh(options)` flush tables or caches
`conn:dump_debug_info()` dump debug info in the log file
**[Client library info]**
`mysql.thread_safe() -> true | false` was the client library compiled as thread-safe?
`mysql.client_info() -> s`
`mysql.client_version() -> n`
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
## Features
* covers all of the functionality provided by the mysql C API
* all data types are supported with options for conversion
* prepared statements, avoiding dynamic allocations and format conversions when fetching rows
* all C calls are checked for errors and Lua errors are raised
* all C objects are tied to Lua's garbage collector
* lightweight OOP-style API using only `ffi.metatype`
* no external dependencies
## Example
~~~{.lua}
function print_help(search)
local mysql = require'mysql'
local conn = mysql.connect('localhost', 'root', nil, 'mysql', 'utf8')
conn:query("select name, description, example from help_topic where name like '" ..
conn:escape(search) .. "'")
local result = conn:store_result()
print('Found:')
for i,name in result:rows() do
print(' ' .. name)
end
print()
for i, name, description, example in result:rows() do
print(name)
print'-------------------------------------------'
print(description)
print'Example:'
print'-------------------------------------------'
print(example)
print()
end
result:free()
conn:close()
end
print_help'CONCAT%'
~~~
## Initialization
### `mysql.config(['mysql'|'mariadb'|libname|clib]) -> mysql`
Load the mysql client library to use (default is 'mysql').
This function is called on every module-level function.
Calling this function again is a no-op.
## Connections
### `mysql.connect(host, [user], [pass], [db], [charset], [port]) -> conn`
### `mysql.connect(options_t) -> conn`
Connect to a mysql server, optionally selecting a working database and charset.
In the second form, `options_t` is a table that besides `host`, `user`, `pass`, `db`, `charset`, `port`
can have the following fields:
* `unix_socket`: specify a unix socket filename to connect to
* `flags`: bit field corresponding to mysql [client_flag](http://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html) parameter
* can be a table of form `{CLIENT_... = true | false, ...}`, or
* a number of form `bit.bor(mysql.C.CLIENT_..., ...)`
* `options`: a table of form `{MYSQL_OPT_... = value, ...}`, containing options per [mysql_options()](http://dev.mysql.com/doc/refman/5.7/en/mysql-options.html) (values are properly converted from Lua types)
* `attrs`: a table of form `{attr = value, ...}` containing attributes to be passed to the server per [mysql_options4()](http://dev.mysql.com/doc/refman/5.7/en/mysql-options4.html)
* `key`, `cert`, `ca`, `cpath`, `cipher`: parameters used to establish a [SSL connection](http://dev.mysql.com/doc/refman/5.7/en/mysql-ssl-set.html)
### `conn:close()`
Close a mysql connection freeing all associated resources (otherwise called when `conn` is garbage collected).
## Queries
### `conn:query(s)`
Execute a query. If the query string contains multiple statements, only the first statement is executed
(see the section on multiple statements).
### `conn:escape(s) -> s`
Escape a value to be safely embedded in SQL queries. Assumes the current charset.
## Fetching results
### `conn:store_result() -> result`
Fetch all the rows in the current result set from the server and return a result object to read them one by one.
### `conn:use_result() -> result`
Return a result object that will fetch the rows in the current result set from the server on demand.
### `result:fetch([mode[, row_t]]) -> true, v1, v2, ... | row_t | nil`
Fetch and return the next row of values from the current result set. Returns nil if there are no more rows to fetch.
* the `mode` arg can contain any combination of the following letters:
* `"n"` - return values in a table with numeric indices as keys.
* `"a"` - return values in a table with field names as keys.
* `"s"` - do not convert numeric and time values to Lua types.
* the `row_t` arg is an optional table to store the row values in, instead of creating a new one on each fetch.
* options "a" and "n" can be combined to get a table with both numeric and field name indices.
* if `mode` is missing or if neither "a" nor "n" is specified, the values
are returned to the caller unpacked, after a first value that is always
true, to make it easy to distinguish between a valid `NULL` value in the
first column and eof.
* in "n" mode, the result table may contain `nil` values so `#row_t` and `ipairs(row_t)` are out; instead iterate from 1 to `result:field_count()`.
* in "a" mode, for fields with duplicate names only the last field will be present.
* if `mode` does not specify `"s"`, the following conversions are applied on the returned values:
* integer types are returned as Lua numbers, except bigint which is returned as an `int64_t` cdata (or `uint64` if unsigned).
* date/time types are returned as tables in the usual `os.date"*t"` format (date fields are missing for time-only types and viceversa).
* decimal/numeric types are returned as Lua strings.
* bit types are returned as Lua numbers, and as `uint64_t` for bit types larger than 48 bits.
* enum and set types are always returned as strings.
### `result:rows([mode[, row_t]]) -> iterator() -> row_num, val1, val2, ...`
### `result:rows([mode[, row_t]]) -> iterator() -> row_num, row_t`
Convenience iterator for fetching (or refetching) all the rows from the current result set. The `mode` arg
is the same as for `result:fetch()`, with the exception that in unpacked mode, the first `true` value is not present.
### `result:free()`
Free the result buffer (otherwise called when `result` is garbage collected).
### `result:row_count() -> n`
Return the number of rows in the current result set . This value is only correct if `result:store_result()` was
previously called or if all the rows were fetched, in other words if `result:eof()` is true.
### `result:eof() -> true | false`
Check if there are no more rows to fetch. If `result:store_result()` was previously called, then all rows were
already fetched, so `result:eof()` always returns `true` in this case.
### `result:seek(row_number)`
Seek back to a particular row number to refetch the rows from there.
## Query info
### `conn:field_count() -> n`
### `conn:affected_rows() -> n`
### `conn:insert_id() -> n`
### `conn:errno() -> n`
### `conn:sqlstate() -> s`
### `conn:warning_count() -> n`
### `conn:info() -> s`
Return various pieces of information about the previously executed query.
## Field info
### `result:field_count() -> n`
### `result:field_name(field_number) -> s`
### `result:field_type(field_number) -> type, length, decimals, unsigned`
### `result:field_info(field_number) -> info_t`
### `result:fields() -> iterator() -> i, info_t`
Return information about the fields (columns) in the current result set.
## Result bookmarks
### `result:tell() -> bookmark`
Get a bookmark to the current row to be later seeked into with `seek()`.
### `result:seek(bookmark)`
Seek to a previous saved row bookmark, or to a specific row number, fetching more rows as needed.
## Multiple statement queries
### `conn:next_result() -> true | false`
Skip over to the next result set in a multiple statement query, and make that the current result set.
Return true if there more result sets after this one.
### `conn:more_results() -> true | false`
Check if there are more result sets after this one.
## Prepared statements
Prepared statements are a way to run queries and retrieve results more efficiently from the database, in particular:
* parametrized queries allow sending query parameters in their native format, avoiding having to convert values into strings and escaping those strings.
* running the same query multiple times with different parameters each time allows the server to reuse the parsed query and possibly the query plan between runs.
* fetching the result rows in preallocated buffers avoids dynamic allocation on each row fetch.
The flow for prepared statements is like this:
* call `conn:prepare()` to prepare a query and get a statement object.
* call `stmt:bind_params()` and `stmt:bind_result()` to get the buffer objects for setting params and getting row values.
* run the query multiple times; each time:
* call `params:set()` for each param to set param values.
* call `stmt:exec()` to run the query.
* fetch the resulting rows one by one; for each row:
* call `stmt:fetch()` to get the next row (it returns false if it was the last row).
* call `fields:get()` to read the values of the fetched row.
* call `stmt:close()` to free the statement object and all the associated resources from the server and client.
### `conn:prepare(query) -> stmt, params`
Prepare a query for multiple execution and return a statement object.
### `stmt:param_count() -> n`
Number of parameters.
### `stmt:exec()`
Execute a prepared statement.
### `stmt:store_result()`
Fetch all the rows in the current result set from the server, otherwise the rows are fetched on demand.
### `stmt:fetch() -> true | false | true, 'truncated'`
Fetch the next row from the current result set. Use a binding buffer (see prepared statements I/O section)
to get the row values. If present, second value indicates that at least one of the rows were truncated because
the receiving buffer was too small for it.
### `stmt:free_result()`
Free the current result and all associated resources (otherwise the result is closed when the statement is closed).
### `stmt:close()`
Close a prepared statement and free all associated resources (otherwise the statement is closed when garbage collected).
### `stmt:next_result()`
Skip over to the next result set in a multiple statement query.
### `stmt:row_count() -> n`
### `stmt:affected_rows() -> n`
### `stmt:insert_id() -> n`
### `stmt:field_count() -> n`
### `stmt:errno() -> n`
### `stmt:sqlstate() -> s`
### `stmt:result_metadata() -> result`
### `stmt:fields() -> iterator() -> i, info_t`
Return various pieces of information on the executed statement.
### `stmt:reset()`
See [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-reset.html).
### `stmt:seek(row_number)`
### `stmt:tell() -> bookmark`
### `stmt:seek(bookmark)`
Seek into the current result set.
## Prepared statements I/O
### `stmt:bind_params(type1, ... | types_t) -> params`
Bind query parameters according to a list of type definitions (which can be given either packed or unpacked).
Return a binding buffer object to be used for setting parameters.
The types must be valid, fully specified SQL types, eg.
* `smallint unsigned` specifies a 16bit unsigned integer
* `bit(32)` specifies a 32bit bit field
* `varchar(200)` specifies a 200 byte varchar.
### `params:set(i, number | int64_t | uint64_t | true | false)`
### `params:set(i, s[, size])`
### `params:set(i, cdata, size)`
### `params:set(i, {year=, month=, ...})`
### `params:set_date(i, [year], [month], [day], [hour], [min], [sec], [frac])`
Set a parameter value.
* the first form is for setting integers and bit fields.
* the second and third forms are for setting variable-sized fields and decimal/numeric fields.
* the last forms are for setting date/time/datetime/timestamp fields.
* the null type cannot be set (raises an error if attempted).
### `stmt:write(param_number, data[, size])`
Send a parameter value in chunks (for long, var-sized values).
### `stmt:bind_result([type1, ... | types_t | maxsize]) -> fields`
Bind result fields according to a list of type definitions (same as for params).
Return a binding buffer object to be used for getting row values.
If no types are specified, appropriate type definitions will be created automatically as to minimize type conversions.
Variable-sized fields will get a buffer sized according to data type's maximum allowed size
and `maxsize` (which defaults to 64k).
### `fields:get(i) -> value`
### `fields:get_datetime(i) -> year, month, day, hour, min, sec, frac`
Get a row value from the last fetched row. The same type conversions as for `result:fetch()` apply.
### `fields:is_null(i) -> true | false`
Check if a value is null without having to get it if it's not.
### `fields:is_truncated(i) -> true | false`
Check if a value was truncated due to insufficient buffer space.
### `stmt:bind_result_types([maxsize]) -> types_t`
Return the list of type definitions that describe the result of a prepared statement.
## Prepared statements settings
### `stmt:update_max_length() -> true | false`
### `stmt:set_update_max_length(true | false)`
### `stmt:cursor_type() -> mysql.C.MYSQL_CURSOR_TYPE_*`
### `stmt:set_cursor_type('CURSOR_TYPE_...')`
### `stmt:set_cursor_type(mysql.C.MYSQL_CURSOR_TYPE_...)`
### `stmt:prefetch_rows() -> n`
### `stmt:set_prefetch_rows(stmt, n)`
See [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) for these.
## Connection info
### `conn:set_charset(charset)`
Change the current charset.
### `conn:select_db(dbname)`
Change the current database.
### `conn:change_user(user, [pass], [db])`
Change the current user and optionally select a database.
### `conn:set_multiple_statements(true | false)`
Enable or disable support for query strings containing multiple statements separated by a semi-colon.
### `conn:charset() -> s`
Get the current charset.
### `conn:charset_info() -> info_t`
Return a table of information about the current charset.
### `conn:ping() -> true | false`
Check if the connection to the server is still alive.
### `conn:thread_id() -> id`
### `conn:stat() -> s`
### `conn:server_info() -> s`
### `conn:host_info() -> s`
### `conn:server_version() -> n`
### `conn:proto_info() -> n`
### `conn:ssl_cipher() -> s`
Return various pieces of information about the connection and server.
## Transactions
### `conn:commit()`
### `conn:rollback()`
Commit/rollback the current transaction.
### `conn:set_autocommit([true | false])`
Set autocommit on the connection (set to true if no argument is given).
## Reflection
### `conn:list_dbs([wildcard]) -> result`
### `conn:list_tables([wildcard]) -> result`
### `conn:list_processes() -> result`
Return information about databases, tables and proceses as a stored result object that can be iterated etc.
using the methods of result objects. The optional `wild` parameter may contain the wildcard characters
`"%"` or `"_"`, similar to executing the query `SHOW DATABASES [LIKE wild]`.
## Remote control
### `conn:kill(pid)`
Kill a connection with a specific `pid`.
### `conn:shutdown([level])`
Shutdown the server. `SHUTDOWN` priviledge needed. The level argument is reserved for future versions of mysql.
### `conn:refresh(options)`
Flush tables or caches, or resets replication server information. `RELOAD` priviledge needed. Options are either
a table of form `{REFRESH_... = true | false, ...}` or a number of form `bit.bor(mysql.C.MYSQL_REFRESH_*, ...)` and
they are as described in the [mysql manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-refresh.html).
### `conn:dump_debug_info()`
Instruct the server to dump debug info in the log file. `SUPER` priviledge needed.
## Client library info
### `mysql.thread_safe() -> true | false`
### `mysql.client_info() -> s`
### `mysql.client_version() -> n`
----
## TODO
* reader function for getting large blobs in chunks using
mysql_stmt_fetch_column: `stmt:chunks(i[, bufsize])` or `stmt:read()` ?

View File

@ -1,571 +0,0 @@
--result of `cpp mysql.h` with lots of cleanup and defines from other headers.
--Written by Cosmin Apreutesei. MySQL Connector/C 6.1.
--NOTE: MySQL Connector/C is GPL software. Is this "derived work" then?
local ffi = require'ffi'
ffi.cdef[[
typedef char my_bool;
typedef unsigned long long my_ulonglong;
enum {
MYSQL_PORT = 3306,
MYSQL_ERRMSG_SIZE = 512,
// status return codes
MYSQL_NO_DATA = 100,
MYSQL_DATA_TRUNCATED = 101
};
// ----------------------------------------------------------- error constants
// NOTE: added MYSQL_ prefix to these.
enum mysql_error_code {
MYSQL_CR_UNKNOWN_ERROR = 2000,
MYSQL_CR_SOCKET_CREATE_ERROR = 2001,
MYSQL_CR_CONNECTION_ERROR = 2002,
MYSQL_CR_CONN_HOST_ERROR = 2003,
MYSQL_CR_IPSOCK_ERROR = 2004,
MYSQL_CR_UNKNOWN_HOST = 2005,
MYSQL_CR_SERVER_GONE_ERROR = 2006,
MYSQL_CR_VERSION_ERROR = 2007,
MYSQL_CR_OUT_OF_MEMORY = 2008,
MYSQL_CR_WRONG_HOST_INFO = 2009,
MYSQL_CR_LOCALHOST_CONNECTION = 2010,
MYSQL_CR_TCP_CONNECTION = 2011,
MYSQL_CR_SERVER_HANDSHAKE_ERR = 2012,
MYSQL_CR_SERVER_LOST = 2013,
MYSQL_CR_COMMANDS_OUT_OF_SYNC = 2014,
MYSQL_CR_NAMEDPIPE_CONNECTION = 2015,
MYSQL_CR_NAMEDPIPEWAIT_ERROR = 2016,
MYSQL_CR_NAMEDPIPEOPEN_ERROR = 2017,
MYSQL_CR_NAMEDPIPESETSTATE_ERROR = 2018,
MYSQL_CR_CANT_READ_CHARSET = 2019,
MYSQL_CR_NET_PACKET_TOO_LARGE = 2020,
MYSQL_CR_EMBEDDED_CONNECTION = 2021,
MYSQL_CR_PROBE_SLAVE_STATUS = 2022,
MYSQL_CR_PROBE_SLAVE_HOSTS = 2023,
MYSQL_CR_PROBE_SLAVE_CONNECT = 2024,
MYSQL_CR_PROBE_MASTER_CONNECT = 2025,
MYSQL_CR_SSL_CONNECTION_ERROR = 2026,
MYSQL_CR_MALFORMED_PACKET = 2027,
MYSQL_CR_WRONG_LICENSE = 2028,
/* new 4.1 error codes */
MYSQL_CR_NULL_POINTER = 2029,
MYSQL_CR_NO_PREPARE_STMT = 2030,
MYSQL_CR_PARAMS_NOT_BOUND = 2031,
MYSQL_CR_DATA_TRUNCATED = 2032,
MYSQL_CR_NO_PARAMETERS_EXISTS = 2033,
MYSQL_CR_INVALID_PARAMETER_NO = 2034,
MYSQL_CR_INVALID_BUFFER_USE = 2035,
MYSQL_CR_UNSUPPORTED_PARAM_TYPE = 2036,
MYSQL_CR_SHARED_MEMORY_CONNECTION = 2037,
MYSQL_CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038,
MYSQL_CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039,
MYSQL_CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040,
MYSQL_CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041,
MYSQL_CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042,
MYSQL_CR_SHARED_MEMORY_MAP_ERROR = 2043,
MYSQL_CR_SHARED_MEMORY_EVENT_ERROR = 2044,
MYSQL_CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045,
MYSQL_CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046,
MYSQL_CR_CONN_UNKNOW_PROTOCOL = 2047,
MYSQL_CR_INVALID_CONN_HANDLE = 2048,
MYSQL_CR_SECURE_AUTH = 2049,
MYSQL_CR_FETCH_CANCELED = 2050,
MYSQL_CR_NO_DATA = 2051,
MYSQL_CR_NO_STMT_METADATA = 2052,
MYSQL_CR_NO_RESULT_SET = 2053,
MYSQL_CR_NOT_IMPLEMENTED = 2054,
MYSQL_CR_SERVER_LOST_EXTENDED = 2055,
MYSQL_CR_STMT_CLOSED = 2056,
MYSQL_CR_NEW_STMT_METADATA = 2057,
MYSQL_CR_ALREADY_CONNECTED = 2058,
MYSQL_CR_AUTH_PLUGIN_CANNOT_LOAD = 2059,
MYSQL_CR_DUPLICATE_CONNECTION_ATTR = 2060,
MYSQL_CR_AUTH_PLUGIN_ERR = 2061
};
// ------------------------------------------------------------ client library
unsigned int mysql_thread_safe(void); // is the client library thread safe?
const char *mysql_get_client_info(void);
unsigned long mysql_get_client_version(void);
// --------------------------------------------------------------- connections
typedef struct MYSQL_ MYSQL;
MYSQL * mysql_init(MYSQL *mysql);
enum mysql_protocol_type
{
MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET,
MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY
};
enum mysql_option
{
MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE,
MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP,
MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE,
MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT,
MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT,
MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION,
MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH,
MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT,
MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MYSQL_PLUGIN_DIR, MYSQL_DEFAULT_AUTH,
MYSQL_OPT_BIND,
MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT,
MYSQL_OPT_SSL_CA, MYSQL_OPT_SSL_CAPATH, MYSQL_OPT_SSL_CIPHER,
MYSQL_OPT_SSL_CRL, MYSQL_OPT_SSL_CRLPATH,
MYSQL_OPT_CONNECT_ATTR_RESET, MYSQL_OPT_CONNECT_ATTR_ADD,
MYSQL_OPT_CONNECT_ATTR_DELETE,
MYSQL_SERVER_PUBLIC_KEY,
MYSQL_ENABLE_CLEARTEXT_PLUGIN,
MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS
};
int mysql_options(MYSQL *mysql, enum mysql_option option, const void *arg);
int mysql_options4(MYSQL *mysql, enum mysql_option option, const void *arg1, const void *arg2);
// NOTE: added MYSQL_ prefix to these. Also, these are bit flags not exclusive enum values.
enum {
MYSQL_CLIENT_LONG_PASSWORD = 1, /* new more secure passwords */
MYSQL_CLIENT_FOUND_ROWS = 2, /* Found instead of affected rows */
MYSQL_CLIENT_LONG_FLAG = 4, /* Get all column flags */
MYSQL_CLIENT_CONNECT_WITH_DB = 8, /* One can specify db on connect */
MYSQL_CLIENT_NO_SCHEMA = 16, /* Don't allow database.table.column */
MYSQL_CLIENT_COMPRESS = 32, /* Can use compression protocol */
MYSQL_CLIENT_ODBC = 64, /* ODBC client */
MYSQL_CLIENT_LOCAL_FILES = 128, /* Can use LOAD DATA LOCAL */
MYSQL_CLIENT_IGNORE_SPACE = 256, /* Ignore spaces before '(' */
MYSQL_CLIENT_PROTOCOL_41 = 512, /* New 4.1 protocol */
MYSQL_CLIENT_INTERACTIVE = 1024, /* This is an interactive client */
MYSQL_CLIENT_SSL = 2048, /* Switch to SSL after handshake */
MYSQL_CLIENT_IGNORE_SIGPIPE = 4096, /* IGNORE sigpipes */
MYSQL_CLIENT_TRANSACTIONS = 8192, /* Client knows about transactions */
MYSQL_CLIENT_RESERVED = 16384, /* Old flag for 4.1 protocol */
MYSQL_CLIENT_SECURE_CONNECTION = (1U << 15), /* New 4.1 authentication */
MYSQL_CLIENT_MULTI_STATEMENTS = (1U << 16), /* Enable/disable multi-stmt support */
MYSQL_CLIENT_MULTI_RESULTS = (1U << 17), /* Enable/disable multi-results */
MYSQL_CLIENT_PS_MULTI_RESULTS = (1U << 18), /* Multi-results in PS-protocol */
MYSQL_CLIENT_PLUGIN_AUTH = (1U << 19), /* Client supports plugin authentication */
MYSQL_CLIENT_CONNECT_ATTRS = (1U << 20), /* Client supports connection attributes */
/* Enable authentication response packet to be larger than 255 bytes. */
MYSQL_CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1U << 21),
/* Don't close the connection for a connection with expired password. */
MYSQL_CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1U << 22),
MYSQL_CLIENT_SSL_VERIFY_SERVER_CERT = (1U << 30),
MYSQL_CLIENT_REMEMBER_OPTIONS = (1U << 31)
};
MYSQL * mysql_real_connect(MYSQL *mysql, const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long clientflag);
void mysql_close(MYSQL *sock);
int mysql_set_character_set(MYSQL *mysql, const char *csname);
int mysql_select_db(MYSQL *mysql, const char *db);
my_bool mysql_change_user(MYSQL *mysql, const char *user, const char *passwd,
const char *db);
my_bool mysql_ssl_set(MYSQL *mysql, const char *key,
const char *cert, const char *ca,
const char *capath, const char *cipher);
enum enum_mysql_set_option
{
MYSQL_OPTION_MULTI_STATEMENTS_ON,
MYSQL_OPTION_MULTI_STATEMENTS_OFF
};
int mysql_set_server_option(MYSQL *mysql, enum enum_mysql_set_option option);
// ----------------------------------------------------------- connection info
const char * mysql_character_set_name(MYSQL *mysql);
typedef struct character_set
{
unsigned int number;
unsigned int state;
const char *csname;
const char *name;
const char *comment;
const char *dir;
unsigned int mbminlen;
unsigned int mbmaxlen;
} MY_CHARSET_INFO;
void mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset);
int mysql_ping(MYSQL *mysql);
unsigned long mysql_thread_id(MYSQL *mysql);
const char * mysql_stat(MYSQL *mysql);
const char * mysql_get_server_info(MYSQL *mysql);
const char * mysql_get_host_info(MYSQL *mysql);
unsigned long mysql_get_server_version(MYSQL *mysql);
unsigned int mysql_get_proto_info(MYSQL *mysql);
const char * mysql_get_ssl_cipher(MYSQL *mysql);
// -------------------------------------------------------------- transactions
my_bool mysql_commit(MYSQL * mysql);
my_bool mysql_rollback(MYSQL * mysql);
my_bool mysql_autocommit(MYSQL * mysql, my_bool auto_mode);
// ------------------------------------------------------------------- queries
unsigned long mysql_real_escape_string(MYSQL *mysql, char *to,
const char *from, unsigned long length);
int mysql_real_query(MYSQL *mysql, const char *q, unsigned long length);
// ---------------------------------------------------------------- query info
unsigned int mysql_field_count(MYSQL *mysql);
my_ulonglong mysql_affected_rows(MYSQL *mysql);
my_ulonglong mysql_insert_id(MYSQL *mysql);
unsigned int mysql_errno(MYSQL *mysql);
const char * mysql_error(MYSQL *mysql);
const char * mysql_sqlstate(MYSQL *mysql);
unsigned int mysql_warning_count(MYSQL *mysql);
const char * mysql_info(MYSQL *mysql);
// ------------------------------------------------------------- query results
int mysql_next_result(MYSQL *mysql);
my_bool mysql_more_results(MYSQL *mysql);
// NOTE: normally we would've made this an opaque handle, but we need to expose
// the connection handle from it so we can report errors for unbuffered reads.
typedef struct st_mysql_res {
my_ulonglong __row_count;
void *__fields;
void *__data;
void *__data_cursor;
void *__lengths;
MYSQL *conn; /* for unbuffered reads */
} MYSQL_RES;
MYSQL_RES *mysql_store_result(MYSQL *mysql);
MYSQL_RES *mysql_use_result(MYSQL *mysql);
void mysql_free_result(MYSQL_RES *result);
my_ulonglong mysql_num_rows(MYSQL_RES *res);
unsigned int mysql_num_fields(MYSQL_RES *res);
my_bool mysql_eof(MYSQL_RES *res);
unsigned long * mysql_fetch_lengths(MYSQL_RES *result);
typedef char **MYSQL_ROW;
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset);
typedef struct MYSQL_ROWS_ MYSQL_ROWS;
typedef MYSQL_ROWS *MYSQL_ROW_OFFSET;
MYSQL_ROW_OFFSET mysql_row_tell(MYSQL_RES *res);
MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset);
// ---------------------------------------------------------- query field info
enum enum_field_types {
MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY,
MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG,
MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE,
MYSQL_TYPE_NULL, MYSQL_TYPE_TIMESTAMP,
MYSQL_TYPE_LONGLONG,MYSQL_TYPE_INT24,
MYSQL_TYPE_DATE, MYSQL_TYPE_TIME,
MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR,
MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR,
MYSQL_TYPE_BIT,
MYSQL_TYPE_TIMESTAMP2,
MYSQL_TYPE_DATETIME2,
MYSQL_TYPE_TIME2,
MYSQL_TYPE_NEWDECIMAL=246,
MYSQL_TYPE_ENUM=247,
MYSQL_TYPE_SET=248,
MYSQL_TYPE_TINY_BLOB=249,
MYSQL_TYPE_MEDIUM_BLOB=250,
MYSQL_TYPE_LONG_BLOB=251,
MYSQL_TYPE_BLOB=252,
MYSQL_TYPE_VAR_STRING=253,
MYSQL_TYPE_STRING=254,
MYSQL_TYPE_GEOMETRY=255
};
// NOTE: added MYSQL_ prefix to these. Also, these are bit flags, not exclusive enum values.
enum {
MYSQL_NOT_NULL_FLAG = 1, /* Field can't be NULL */
MYSQL_PRI_KEY_FLAG = 2, /* Field is part of a primary key */
MYSQL_UNIQUE_KEY_FLAG = 4, /* Field is part of a unique key */
MYSQL_MULTIPLE_KEY_FLAG = 8, /* Field is part of a key */
MYSQL_BLOB_FLAG = 16, /* Field is a blob */
MYSQL_UNSIGNED_FLAG = 32, /* Field is unsigned */
MYSQL_ZEROFILL_FLAG = 64, /* Field is zerofill */
MYSQL_BINARY_FLAG = 128, /* Field is binary */
/* The following are only sent to new clients */
MYSQL_ENUM_FLAG = 256, /* field is an enum */
MYSQL_AUTO_INCREMENT_FLAG = 512, /* field is a autoincrement field */
MYSQL_TIMESTAMP_FLAG = 1024, /* Field is a timestamp */
MYSQL_SET_FLAG = 2048, /* field is a set */
MYSQL_NO_DEFAULT_VALUE_FLAG = 4096, /* Field doesn't have default value */
MYSQL_ON_UPDATE_NOW_FLAG = 8192, /* Field is set to NOW on UPDATE */
MYSQL_NUM_FLAG = 32768, /* Field is num (for clients) */
MYSQL_PART_KEY_FLAG = 16384, /* Intern; Part of some key */
MYSQL_GROUP_FLAG = 32768, /* Intern: Group field */
MYSQL_UNIQUE_FLAG = 65536, /* Intern: Used by sql_yacc */
MYSQL_BINCMP_FLAG = 131072, /* Intern: Used by sql_yacc */
MYSQL_GET_FIXED_FIELDS_FLAG = (1 << 18), /* Used to get fields in item tree */
MYSQL_FIELD_IN_PART_FUNC_FLAG = (1 << 19) /* Field part of partition func */
};
typedef struct st_mysql_field {
char *name;
char *org_name;
char *table;
char *org_table;
char *db;
char *catalog;
char *def;
unsigned long length;
unsigned long max_length;
unsigned int name_length;
unsigned int org_name_length;
unsigned int table_length;
unsigned int org_table_length;
unsigned int db_length;
unsigned int catalog_length;
unsigned int def_length;
unsigned int flags;
unsigned int decimals;
unsigned int charsetnr;
enum enum_field_types type;
void *extension;
} MYSQL_FIELD;
MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr);
// ---------------------------------------------------------------- reflection
MYSQL_RES *mysql_list_dbs(MYSQL *mysql, const char *wild);
MYSQL_RES *mysql_list_tables(MYSQL *mysql, const char *wild);
MYSQL_RES *mysql_list_processes(MYSQL *mysql);
// ------------------------------------------------------------ remote control
int mysql_kill(MYSQL *mysql, unsigned long pid);
// NOTE: added MYSQL_ prefix.
enum mysql_enum_shutdown_level {
MYSQL_SHUTDOWN_DEFAULT = 0,
MYSQL_SHUTDOWN_WAIT_CONNECTIONS = 1,
MYSQL_SHUTDOWN_WAIT_TRANSACTIONS = 2,
MYSQL_SHUTDOWN_WAIT_UPDATES = 8,
MYSQL_SHUTDOWN_WAIT_ALL_BUFFERS = 16,
MYSQL_SHUTDOWN_WAIT_CRITICAL_BUFFERS = 17,
MYSQL_KILL_QUERY = 254,
MYSQL_KILL_CONNECTION = 255
};
int mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level); // needs SHUTDOWN priviledge
// NOTE: added MYSQL_ prefix. not really enum values either, just bit flags.
enum {
MYSQL_REFRESH_GRANT = 1, /* Refresh grant tables */
MYSQL_REFRESH_LOG = 2, /* Start on new log file */
MYSQL_REFRESH_TABLES = 4, /* close all tables */
MYSQL_REFRESH_HOSTS = 8, /* Flush host cache */
MYSQL_REFRESH_STATUS = 16, /* Flush status variables */
MYSQL_REFRESH_THREADS = 32, /* Flush thread cache */
MYSQL_REFRESH_SLAVE = 64, /* Reset master info and restart slave thread */
MYSQL_REFRESH_MASTER = 128, /* Remove all bin logs in the index and truncate the index */
MYSQL_REFRESH_ERROR_LOG = 256, /* Rotate only the erorr log */
MYSQL_REFRESH_ENGINE_LOG = 512, /* Flush all storage engine logs */
MYSQL_REFRESH_BINARY_LOG = 1024, /* Flush the binary log */
MYSQL_REFRESH_RELAY_LOG = 2048, /* Flush the relay log */
MYSQL_REFRESH_GENERAL_LOG = 4096, /* Flush the general log */
MYSQL_REFRESH_SLOW_LOG = 8192, /* Flush the slow query log */
/* The following can't be set with mysql_refresh() */
MYSQL_REFRESH_READ_LOCK = 16384, /* Lock tables for read */
MYSQL_REFRESH_FAST = 32768, /* Intern flag */
/* RESET (remove all queries) from query cache */
MYSQL_REFRESH_QUERY_CACHE = 65536,
MYSQL_REFRESH_QUERY_CACHE_FREE = 0x20000, /* pack query cache */
MYSQL_REFRESH_DES_KEY_FILE = 0x40000,
MYSQL_REFRESH_USER_RESOURCES = 0x80000,
MYSQL_REFRESH_FOR_EXPORT = 0x100000, /* FLUSH TABLES ... FOR EXPORT */
};
int mysql_refresh(MYSQL *mysql, unsigned int refresh_options); // needs RELOAD priviledge
int mysql_dump_debug_info(MYSQL *mysql); // needs SUPER priviledge
// ------------------------------------------------------- prepared statements
typedef struct MYSQL_STMT_ MYSQL_STMT;
MYSQL_STMT * mysql_stmt_init(MYSQL *mysql);
my_bool mysql_stmt_close(MYSQL_STMT * stmt);
int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length);
int mysql_stmt_execute(MYSQL_STMT *stmt);
int mysql_stmt_next_result(MYSQL_STMT *stmt);
int mysql_stmt_store_result(MYSQL_STMT *stmt);
my_bool mysql_stmt_free_result(MYSQL_STMT *stmt);
MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt);
my_ulonglong mysql_stmt_num_rows(MYSQL_STMT *stmt);
my_ulonglong mysql_stmt_affected_rows(MYSQL_STMT *stmt);
my_ulonglong mysql_stmt_insert_id(MYSQL_STMT *stmt);
unsigned int mysql_stmt_field_count(MYSQL_STMT *stmt);
unsigned int mysql_stmt_errno(MYSQL_STMT * stmt);
const char *mysql_stmt_error(MYSQL_STMT * stmt);
const char *mysql_stmt_sqlstate(MYSQL_STMT * stmt);
int mysql_stmt_fetch(MYSQL_STMT *stmt);
my_bool mysql_stmt_reset(MYSQL_STMT * stmt);
void mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset);
MYSQL_ROW_OFFSET mysql_stmt_row_tell(MYSQL_STMT *stmt);
MYSQL_ROW_OFFSET mysql_stmt_row_seek(MYSQL_STMT *stmt, MYSQL_ROW_OFFSET offset);
// NOTE: added MYSQL_ prefix to these.
enum enum_cursor_type
{
MYSQL_CURSOR_TYPE_NO_CURSOR= 0,
MYSQL_CURSOR_TYPE_READ_ONLY= 1,
MYSQL_CURSOR_TYPE_FOR_UPDATE= 2,
MYSQL_CURSOR_TYPE_SCROLLABLE= 4
};
enum enum_stmt_attr_type
{
STMT_ATTR_UPDATE_MAX_LENGTH,
STMT_ATTR_CURSOR_TYPE,
STMT_ATTR_PREFETCH_ROWS
};
my_bool mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr);
my_bool mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, void *attr);
my_bool mysql_stmt_send_long_data(MYSQL_STMT *stmt,
unsigned int param_number,
const char *data,
unsigned long length);
// -------------------------------------------- prepared statements / bindings
enum enum_mysql_timestamp_type
{
MYSQL_TIMESTAMP_NONE= -2, MYSQL_TIMESTAMP_ERROR= -1,
MYSQL_TIMESTAMP_DATE= 0, MYSQL_TIMESTAMP_DATETIME= 1, MYSQL_TIMESTAMP_TIME= 2
};
typedef struct st_mysql_time
{
unsigned int year, month, day, hour, minute, second;
unsigned long second_part; /**< microseconds */
my_bool neg;
enum enum_mysql_timestamp_type time_type;
} MYSQL_TIME;
unsigned long mysql_stmt_param_count(MYSQL_STMT * stmt);
typedef struct NET_ NET;
typedef struct st_mysql_bind
{
unsigned long *length;
my_bool *is_null;
void *buffer;
my_bool *error;
unsigned char *row_ptr;
void (*store_param_func)(NET *net, struct st_mysql_bind *param);
void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *,
unsigned char **row);
void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *,
unsigned char **row);
unsigned long buffer_length;
unsigned long offset;
unsigned long length_value;
unsigned int param_number;
unsigned int pack_length;
enum enum_field_types buffer_type;
my_bool error_value;
my_bool is_unsigned;
my_bool long_data_used;
my_bool is_null_value;
void *extension;
} MYSQL_BIND;
my_bool mysql_stmt_bind_param(MYSQL_STMT * stmt, MYSQL_BIND * bnd);
my_bool mysql_stmt_bind_result(MYSQL_STMT * stmt, MYSQL_BIND * bnd);
int mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind_arg,
unsigned int column,
unsigned long offset);
// ---------------------------------------------- LOAD DATA LOCAL INFILE hooks
void mysql_set_local_infile_handler(MYSQL *mysql,
int (*local_infile_init)(void **, const char *, void *),
int (*local_infile_read)(void *, char *, unsigned int),
void (*local_infile_end)(void *),
int (*local_infile_error)(void *, char*, unsigned int),
void *);
void mysql_set_local_infile_default(MYSQL *mysql);
// ----------------------------------------------------- mysql proxy scripting
my_bool mysql_read_query_result(MYSQL *mysql);
// ----------------------------------------------------------------- debugging
void mysql_debug(const char *debug);
// ------------------------------------------------ present but not documented
int mysql_server_init(int argc, char **argv, char **groups);
void mysql_server_end(void);
char *get_tty_password(const char *opt_message);
void myodbc_remove_escape(MYSQL *mysql, char *name);
my_bool mysql_embedded(void);
int mysql_send_query(MYSQL *mysql, const char *q, unsigned long length);
// ------------------------------------------------------- redundant functions
my_bool mysql_thread_init(void); // called anyway
void mysql_thread_end(void); // called anyway
const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); // use mysql_sqlstate
unsigned long mysql_hex_string(char *to, const char *from,
unsigned long from_length); // bad taste
// redundant ways to get field info.
// we use use mysql_field_count and mysql_fetch_field_direct instead.
MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result);
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);
typedef unsigned int MYSQL_FIELD_OFFSET;
MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res);
MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset);
MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt);
// ------------------------------------------------------ deprecated functions
unsigned long mysql_escape_string(char *to, const char *from,
unsigned long from_length); // use mysql_real_escape_string
int mysql_query(MYSQL *mysql, const char *q); // use mysql_real_query
MYSQL_RES *mysql_list_fields(MYSQL *mysql, const char *table,
const char *wild); // use "SHOW COLUMNS FROM table"
]]

View File

@ -1,144 +0,0 @@
--mysql table pretty printing
local function ellipsis(s,n)
return #s > n and (s:sub(1,n-3) .. '...') or s
end
local align = {}
function align.left(s,n)
s = s..(' '):rep(n - #s)
return ellipsis(s,n)
end
function align.right(s,n)
s = (' '):rep(n - #s)..s
return ellipsis(s,n)
end
function align.center(s,n)
local total = n - #s
local left = math.floor(total / 2)
local right = math.ceil(total / 2)
s = (' '):rep(left)..s..(' '):rep(right)
return ellipsis(s,n)
end
local function fit(s,n,al)
return align[al or 'left'](s,n)
end
local function print_table(fields, rows, aligns, minsize, print)
print = print or _G.print
minsize = minsize or math.huge
local max_sizes = {}
for i=1,#rows do
for j=1,#fields do
max_sizes[j] = math.min(minsize, math.max(max_sizes[j] or 0, #rows[i][j]))
end
end
local totalsize = 0
for j=1,#fields do
max_sizes[j] = math.max(max_sizes[j] or 0, #fields[j])
totalsize = totalsize + max_sizes[j] + 3
end
print()
local s, ps = '', ''
for j=1,#fields do
s = s .. fit(fields[j], max_sizes[j], 'center') .. ' | '
ps = ps .. ('-'):rep(max_sizes[j]) .. ' + '
end
print(s)
print(ps)
for i=1,#rows do
local s = ''
for j=1,#fields do
local val = rows[i][j]
s = s .. fit(val, max_sizes[j], aligns and aligns[j]) .. ' | '
end
print(s)
end
print()
end
local function invert_table(fields, rows, minsize)
local ft, rt = {'field'}, {}
for i=1,#rows do
ft[i+1] = tostring(i)
end
for j=1,#fields do
local row = {fields[j]}
for i=1,#rows do
row[i+1] = rows[i][j]
end
rt[j] = row
end
return ft, rt
end
local function format_cell(v)
if v == nil then
return 'NULL'
else
return tostring(v)
end
end
local function cell_align(current_align, cell_value)
if current_align == 'left' then return 'left' end
if type(cell_value) == 'number' or type(cell_value) == 'cdata' then return 'right' end
return 'left'
end
local function print_result(res, minsize, print)
local fields = {}
for i,field in res:fields() do
fields[i] = field.name
end
local rows = {}
local aligns = {} --deduced from values
for i,row in res:rows'n' do
local t = {}
for j=1,#fields do
t[j] = format_cell(row[j])
aligns[j] = cell_align(aligns[j], row[j])
end
rows[i] = t
end
print_table(fields, rows, aligns, minsize, print)
end
local function print_statement(stmt, minsize, print)
local res = stmt:bind_result()
local fields = {}
for i,field in stmt:fields() do
fields[i] = field.name
end
local rows = {}
local aligns = {}
while stmt:fetch() do
local row = {}
for i=1,#fields do
local v = res:get(i)
row[i] = format_cell(v)
aligns[i] = cell_align(aligns[i], v)
end
rows[#rows+1] = row
end
stmt:close()
print_table(fields, rows, aligns, minsize, print)
end
if not ... then require'mysql_test' end
return {
fit = fit,
format_cell = format_cell,
table = print_table,
result = print_result,
statement = print_statement,
}

View File

@ -1,552 +0,0 @@
--mysql test unit (see comments for problems with libmariadb)
--NOTE: create a database called 'test' first to run these tests!
local mysql = require'mysql'
local glue = require'glue'
local pp = require'pp'
local myprint = require'mysql_print'
local ffi = require'ffi'
--helpers
local print_table = myprint.table
local print_result = myprint.result
local fit = myprint.fit
local function assert_deepequal(t1, t2) --assert the equality of two values
assert(type(t1) == type(t2), type(t1)..' ~= '..type(t2))
if type(t1) == 'table' then
for k,v in pairs(t1) do assert_deepequal(t2[k], v) end
for k,v in pairs(t2) do assert_deepequal(t1[k], v) end
else
assert(t1 == t2, pp.format(t1) .. ' ~= ' .. pp.format(t2))
end
end
local function print_fields(fields_iter)
local fields = {'name', 'type', 'type_flag', 'length', 'max_length', 'decimals', 'charsetnr',
'org_name', 'table', 'org_table', 'db', 'catalog', 'def', 'extension'}
local rows = {}
local aligns = {}
for i,field in fields_iter do
rows[i] = {}
for j=1,#fields do
local v = field[fields[j]]
rows[i][j] = tostring(v)
aligns[j] = type(v) == 'number' and 'right' or 'left'
end
end
print_table(fields, rows, aligns)
end
--client library
print('mysql.thread_safe() ', '->', pp.format(mysql.thread_safe()))
print('mysql.client_info() ', '->', pp.format(mysql.client_info()))
print('mysql.client_version()', '->', pp.format(mysql.client_version()))
--connections
local t = {
host = 'localhost',
user = 'root',
db = 'test',
options = {
MYSQL_SECURE_AUTH = false, --not supported by libmariadb
MYSQL_OPT_READ_TIMEOUT = 1,
},
flags = {
CLIENT_LONG_PASSWORD = true,
},
}
local conn = mysql.connect(t)
print('mysql.connect ', pp.format(t, ' '), '->', conn)
print('conn:change_user( ', pp.format(t.user), ')', conn:change_user(t.user))
print('conn:select_db( ', pp.format(t.db), ')', conn:select_db(t.db))
print('conn:set_multiple_statements(', pp.format(true), ')', conn:set_multiple_statements(true))
print('conn:set_charset( ', pp.format('utf8'), ')', conn:set_charset('utf8'))
--conn info
print('conn:charset_name() ', '->', pp.format(conn:charset())); assert(conn:charset() == 'utf8')
print('conn:charset_info() ', '->', pp.format(conn:charset_info(), ' ')) --crashes libmariadb
print('conn:ping() ', '->', pp.format(conn:ping()))
print('conn:thread_id() ', '->', pp.format(conn:thread_id()))
print('conn:stat() ', '->', pp.format(conn:stat()))
print('conn:server_info() ', '->', pp.format(conn:server_info()))
print('conn:host_info() ', '->', pp.format(conn:host_info()))
print('conn:server_version() ', '->', pp.format(conn:server_version()))
print('conn:proto_info() ', '->', pp.format(conn:proto_info()))
print('conn:ssl_cipher() ', '->', pp.format(conn:ssl_cipher()))
--transactions
print('conn:commit() ', conn:commit())
print('conn:rollback() ', conn:rollback())
print('conn:set_autocommit() ', conn:set_autocommit(true))
--test types and values
local test_fields = {
'fdecimal',
'fnumeric',
'ftinyint',
'futinyint',
'fsmallint',
'fusmallint',
'finteger',
'fuinteger',
'ffloat',
'fdouble',
'fdouble2',
'fdouble3',
'fdouble4',
'freal',
'fbigint',
'fubigint',
'fmediumint',
'fumediumint',
'fdate',
'ftime',
'ftime2',
'fdatetime',
'fdatetime2',
'ftimestamp',
'ftimestamp2',
'fyear',
'fbit2',
'fbit22',
'fbit64',
'fenum',
'fset',
'ftinyblob',
'fmediumblob',
'flongblob',
'ftext',
'fblob',
'fvarchar',
'fvarbinary',
'fchar',
'fbinary',
'fnull',
}
local field_indices = glue.index(test_fields)
local field_types = {
fdecimal = 'decimal(8,2)',
fnumeric = 'numeric(6,4)',
ftinyint = 'tinyint',
futinyint = 'tinyint unsigned',
fsmallint = 'smallint',
fusmallint = 'smallint unsigned',
finteger = 'int',
fuinteger = 'int unsigned',
ffloat = 'float',
fdouble = 'double',
fdouble2 = 'double',
fdouble3 = 'double',
fdouble4 = 'double',
freal = 'real',
fbigint = 'bigint',
fubigint = 'bigint unsigned',
fmediumint = 'mediumint',
fumediumint = 'mediumint unsigned',
fdate = 'date',
ftime = 'time(0)',
ftime2 = 'time(6)',
fdatetime = 'datetime(0)',
fdatetime2 = 'datetime(6)',
ftimestamp = 'timestamp(0) null',
ftimestamp2 = 'timestamp(6) null',
fyear = 'year',
fbit2 = 'bit(2)',
fbit22 = 'bit(22)',
fbit64 = 'bit(64)',
fenum = "enum('yes', 'no')",
fset = "set('e1', 'e2', 'e3')",
ftinyblob = 'tinyblob',
fmediumblob = 'mediumblob',
flongblob = 'longblob',
ftext = 'text',
fblob = 'blob',
fvarchar = 'varchar(200)',
fvarbinary = 'varbinary(200)',
fchar = 'char(200)',
fbinary = 'binary(20)',
fnull = 'int'
}
local test_values = {
fdecimal = '42.12',
fnumeric = '42.1234',
ftinyint = 42,
futinyint = 255,
fsmallint = 42,
fusmallint = 65535,
finteger = 42,
fuinteger = 2^32-1,
ffloat = tonumber(ffi.cast('float', 42.33)),
fdouble = 42.33,
fdouble2 = nil, --null from mysql 5.1.24+
fdouble3 = nil, --null from mysql 5.1.24+
fdouble4 = nil, --null from mysql 5.1.24+
freal = 42.33,
fbigint = 420LL,
fubigint = 0ULL - 1,
fmediumint = 440,
fumediumint = 2^24-1,
fdate = {year = 2013, month = 10, day = 05},
ftime = {hour = 21, min = 30, sec = 15, frac = 0},
ftime2 = {hour = 21, min = 30, sec = 16, frac = 123456},
fdatetime = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 17, frac = 0},
fdatetime2 = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 18, frac = 123456},
ftimestamp = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 19, frac = 0},
ftimestamp2 = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 20, frac = 123456},
fyear = 2013,
fbit2 = 2,
fbit22 = 2 * 2^8 + 2,
fbit64 = 2ULL * 2^(64-8) + 2 * 2^8 + 2,
fenum = 'yes',
fset = 'e2,e3',
ftinyblob = 'tiny tiny blob',
fmediumblob = 'medium blob',
flongblob = 'loong blob',
ftext = 'just a text',
fblob = 'bloob',
fvarchar = 'just a varchar',
fvarbinary = 'a varbinary',
fchar = 'a char',
fbinary = 'a binary char\0\0\0\0\0\0\0',
fnull = nil,
}
local set_values = {
fdecimal = "'42.12'",
fnumeric = "42.1234",
ftinyint = "'42'",
futinyint = "'255'",
fsmallint = "42",
fusmallint = "65535",
finteger = "'42'",
fuinteger = tostring(2^32-1),
ffloat = "42.33",
fdouble = "'42.33'",
fdouble2 = "0/0",
fdouble3 = "1/0",
fdouble4 = "-1/0",
freal = "42.33",
fbigint = "'420'",
fubigint = tostring(0ULL-1):sub(1,-4), --remove 'ULL'
fmediumint = "440",
fumediumint = tostring(2^24-1),
fdate = "'2013-10-05'",
ftime = "'21:30:15'",
ftime2 = "'21:30:16.123456'",
fdatetime = "'2013-10-05 21:30:17'",
fdatetime2 = "'2013-10-05 21:30:18.123456'",
ftimestamp = "'2013-10-05 21:30:19'",
ftimestamp2 = "'2013-10-05 21:30:20.123456'",
fyear = "2013",
fbit2 = "b'10'",
fbit22 = "b'1000000010'",
fbit64 = "b'0000001000000000000000000000000000000000000000000000001000000010'",
fenum = "'yes'",
fset = "('e3,e2')",
ftinyblob = "'tiny tiny blob'",
fmediumblob = "'medium blob'",
flongblob = "'loong blob'",
ftext = "'just a text'",
fblob = "'bloob'",
fvarchar = "'just a varchar'",
fvarbinary = "'a varbinary'",
fchar = "'a char'",
fbinary = "'a binary char'",
fnull = "null"
}
local bind_types = {
fdecimal = 'decimal(20)', --TODO: truncation
fnumeric = 'numeric(20)',
ftinyint = 'tinyint',
futinyint = 'tinyint unsigned',
fsmallint = 'smallint',
fusmallint = 'smallint unsigned',
finteger = 'int',
fuinteger = 'int unsigned',
ffloat = 'float',
fdouble = 'double',
fdouble2 = 'double',
fdouble3 = 'double',
fdouble4 = 'double',
freal = 'real',
fbigint = 'bigint',
fubigint = 'bigint unsigned',
fmediumint = 'mediumint',
fumediumint = 'mediumint unsigned',
fdate = 'date',
ftime = 'time',
ftime2 = 'time',
fdatetime = 'datetime',
fdatetime2 = 'datetime',
ftimestamp = 'timestamp',
ftimestamp2 = 'timestamp',
fyear = 'year',
fbit2 = 'bit(2)',
fbit22 = 'bit(22)',
fbit64 = 'bit(64)',
fenum = 'enum(200)',
fset = 'set(200)',
ftinyblob = 'tinyblob(200)',
fmediumblob = 'mediumblob(200)',
flongblob = 'longblob(200)',
ftext = 'text(200)',
fblob = 'blob(200)',
fvarchar = 'varchar(200)',
fvarbinary = 'varbinary(200)',
fchar = 'char(200)',
fbinary = 'binary(200)',
fnull = 'int',
}
--queries
local esc = "'escape me'"
print('conn:escape( ', pp.format(esc), ')', '->', pp.format(conn:escape(esc)))
local q1 = 'drop table if exists binding_test'
print('conn:query( ', pp.format(q1), ')', conn:query(q1))
local field_defs = ''
for i,field in ipairs(test_fields) do
field_defs = field_defs .. field .. ' ' .. field_types[field] .. (i == #test_fields and '' or ', ')
end
local field_sets = ''
for i,field in ipairs(test_fields) do
field_sets = field_sets .. field .. ' = ' .. set_values[field] .. (i == #test_fields and '' or ', ')
end
conn:query([[
create table binding_test ( ]] .. field_defs .. [[ );
insert into binding_test set ]] .. field_sets .. [[ ;
insert into binding_test values ();
select * from binding_test;
]])
--query info
print('conn:field_count() ', '->', pp.format(conn:field_count()))
print('conn:affected_rows() ', '->', pp.format(conn:affected_rows()))
print('conn:insert_id() ', '->', conn:insert_id())
print('conn:errno() ', '->', pp.format(conn:errno()))
print('conn:sqlstate() ', '->', pp.format(conn:sqlstate()))
print('conn:warning_count() ', '->', pp.format(conn:warning_count()))
print('conn:info() ', '->', pp.format(conn:info()))
for i=1,3 do
print('conn:more_results() ', '->', pp.format(conn:more_results())); assert(conn:more_results())
print('conn:next_result() ', '->', pp.format(conn:next_result()))
end
assert(not conn:more_results())
--query results
local res = conn:store_result() --TODO: local res = conn:use_result()
print('conn:store_result() ', '->', res)
print('res:row_count() ', '->', pp.format(res:row_count())); assert(res:row_count() == 2)
print('res:field_count() ', '->', pp.format(res:field_count())); assert(res:field_count() == #test_fields)
print('res:eof() ', '->', pp.format(res:eof())); assert(res:eof() == true)
print('res:fields() ', '->') print_fields(res:fields())
print('res:field_info(1) ', '->', pp.format(res:field_info(1)))
--first row: fetch as array and test values
local row = assert(res:fetch'n')
print("res:fetch'n' ", '->', pp.format(row))
for i,field in res:fields() do
assert_deepequal(row[i], test_values[field.name])
end
--first row again: fetch as assoc. array and test values
print('res:seek(1) ', '->', res:seek(1))
local row = assert(res:fetch'a')
print("res:fetch'a' ", '->', pp.format(row))
for i,field in res:fields() do
assert_deepequal(row[field.name], test_values[field.name])
end
--first row again: fetch unpacked and test values
print('res:seek(1) ', '->', res:seek(1))
local function pack(_, ...)
local t = {}
for i=1,select('#', ...) do
t[i] = select(i, ...)
end
return t
end
local row = pack(res:fetch())
print("res:fetch() ", '-> packed: ', pp.format(row))
for i,field in res:fields() do
assert_deepequal(row[i], test_values[field.name])
end
--first row again: print its values parsed and unparsed for comparison
res:seek(1)
local row = assert(res:fetch'n')
res:seek(1)
local row_s = assert(res:fetch'ns')
print()
print(fit('', 4, 'right') .. ' ' .. fit('field', 20) .. fit('unparsed', 40) .. ' ' .. 'parsed')
print(('-'):rep(4 + 2 + 20 + 40 + 40))
for i,field in res:fields() do
print(fit(tostring(i), 4, 'right') .. ' ' .. fit(field.name, 20) .. fit(pp.format(row_s[i]), 40) .. ' ' .. pp.format(row[i]))
end
print()
--second row: all nulls
local row = assert(res:fetch'n')
print("res:fetch'n' ", '->', pp.format(row))
assert(#row == 0)
for i=1,res:field_count() do
assert(row[i] == nil)
end
assert(not res:fetch'n')
--all rows again: test iterator
res:seek(1)
local n = 0
for i,row in res:rows'nas' do
n = n + 1
assert(i == n)
end
print("for i,row in res:rows'nas' do <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())