mysql/mysql_test.lua

555 lines
17 KiB
Lua

--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'
mysql.bind'mariadb'
--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())