unimportant

This commit is contained in:
Cosmin Apreutesei 2021-08-18 20:36:08 +03:00
parent e704307b1c
commit 0d9be36a39
2 changed files with 476 additions and 176 deletions

View File

@ -3,14 +3,14 @@
-- Written by Yichun Zhang (agentzh). BSD license. -- Written by Yichun Zhang (agentzh). BSD license.
-- Modified by Cosmin Apreutesei. Pulbic domain. -- Modified by Cosmin Apreutesei. Pulbic domain.
local sha1 = require'sha1'.sha1 local ffi = require'ffi'
local bit = require'bit' local bit = require'bit'
local null = require'cjson'.null local sha1 = require'sha1'.sha1
local glue = require'glue'
local sub = string.sub local sub = string.sub
local strbyte = string.byte local strbyte = string.byte
local strchar = string.char local strchar = string.char
local strfind = string.find
local format = string.format local format = string.format
local strrep = string.rep local strrep = string.rep
local band = bit.band local band = bit.band
@ -20,94 +20,413 @@ local lshift = bit.lshift
local rshift = bit.rshift local rshift = bit.rshift
local tohex = bit.tohex local tohex = bit.tohex
local concat = table.concat local concat = table.concat
local unpack = unpack
local setmetatable = setmetatable local buffer = glue.buffer
local error = error local index = glue.index
local tonumber = tonumber local repl = glue.repl
local ok, new_tab = pcall(require, 'table.new') local ok, new_tab = pcall(require, 'table.new')
new_tab = ok and new_tab or function() return {} end new_tab = ok and new_tab or function() return {} end
local mysql = {} local mysql = {}
-- constants
local STATE_CONNECTED = 1
local STATE_COMMAND_SENT = 2
local COM_QUIT = 0x01 local COM_QUIT = 0x01
local COM_QUERY = 0x03 local COM_QUERY = 0x03
local CLIENT_SSL = 0x0800 local CLIENT_SSL = 0x0800
local SERVER_MORE_RESULTS_EXISTS = 8 local SERVER_MORE_RESULTS_EXISTS = 8
-- 16MB - 1, the default max allowed packet size used by libmysqlclient local collation_names = {
local FULL_PACKET_SIZE = 16777215 [ 1] = 'big5_chinese_ci',
[ 2] = 'latin2_czech_cs',
[ 3] = 'dec8_swedish_ci',
[ 4] = 'cp850_general_ci',
[ 5] = 'latin1_german1_ci',
[ 6] = 'hp8_english_ci',
[ 7] = 'koi8r_general_ci',
[ 8] = 'latin1_swedish_ci',
[ 9] = 'latin2_general_ci',
[ 10] = 'swe7_swedish_ci',
[ 11] = 'ascii_general_ci',
[ 12] = 'ujis_japanese_ci',
[ 13] = 'sjis_japanese_ci',
[ 14] = 'cp1251_bulgarian_ci',
[ 15] = 'latin1_danish_ci',
[ 16] = 'hebrew_general_ci',
[ 18] = 'tis620_thai_ci',
[ 19] = 'euckr_korean_ci',
[ 20] = 'latin7_estonian_cs',
[ 21] = 'latin2_hungarian_ci',
[ 22] = 'koi8u_general_ci',
[ 23] = 'cp1251_ukrainian_ci',
[ 24] = 'gb2312_chinese_ci',
[ 25] = 'greek_general_ci',
[ 26] = 'cp1250_general_ci',
[ 27] = 'latin2_croatian_ci',
[ 28] = 'gbk_chinese_ci',
[ 29] = 'cp1257_lithuanian_ci',
[ 30] = 'latin5_turkish_ci',
[ 31] = 'latin1_german2_ci',
[ 32] = 'armscii8_general_ci',
[ 33] = 'utf8_general_ci',
[ 34] = 'cp1250_czech_cs',
[ 35] = 'ucs2_general_ci',
[ 36] = 'cp866_general_ci',
[ 37] = 'keybcs2_general_ci',
[ 38] = 'macce_general_ci',
[ 39] = 'macroman_general_ci',
[ 40] = 'cp852_general_ci',
[ 41] = 'latin7_general_ci',
[ 42] = 'latin7_general_cs',
[ 43] = 'macce_bin',
[ 44] = 'cp1250_croatian_ci',
[ 45] = 'utf8mb4_general_ci',
[ 46] = 'utf8mb4_bin',
[ 47] = 'latin1_bin',
[ 48] = 'latin1_general_ci',
[ 49] = 'latin1_general_cs',
[ 50] = 'cp1251_bin',
[ 51] = 'cp1251_general_ci',
[ 52] = 'cp1251_general_cs',
[ 53] = 'macroman_bin',
[ 54] = 'utf16_general_ci',
[ 55] = 'utf16_bin',
[ 56] = 'utf16le_general_ci',
[ 57] = 'cp1256_general_ci',
[ 58] = 'cp1257_bin',
[ 59] = 'cp1257_general_ci',
[ 60] = 'utf32_general_ci',
[ 61] = 'utf32_bin',
[ 62] = 'utf16le_bin',
[ 63] = 'binary',
[ 64] = 'armscii8_bin',
[ 65] = 'ascii_bin',
[ 66] = 'cp1250_bin',
[ 67] = 'cp1256_bin',
[ 68] = 'cp866_bin',
[ 69] = 'dec8_bin',
[ 70] = 'greek_bin',
[ 71] = 'hebrew_bin',
[ 72] = 'hp8_bin',
[ 73] = 'keybcs2_bin',
[ 74] = 'koi8r_bin',
[ 75] = 'koi8u_bin',
[ 76] = 'utf8_tolower_ci',
[ 77] = 'latin2_bin',
[ 78] = 'latin5_bin',
[ 79] = 'latin7_bin',
[ 80] = 'cp850_bin',
[ 81] = 'cp852_bin',
[ 82] = 'swe7_bin',
[ 83] = 'utf8_bin',
[ 84] = 'big5_bin',
[ 85] = 'euckr_bin',
[ 86] = 'gb2312_bin',
[ 87] = 'gbk_bin',
[ 88] = 'sjis_bin',
[ 89] = 'tis620_bin',
[ 90] = 'ucs2_bin',
[ 91] = 'ujis_bin',
[ 92] = 'geostd8_general_ci',
[ 93] = 'geostd8_bin',
[ 94] = 'latin1_spanish_ci',
[ 95] = 'cp932_japanese_ci',
[ 96] = 'cp932_bin',
[ 97] = 'eucjpms_japanese_ci',
[ 98] = 'eucjpms_bin',
[ 99] = 'cp1250_polish_ci',
[101] = 'utf16_unicode_ci',
[102] = 'utf16_icelandic_ci',
[103] = 'utf16_latvian_ci',
[104] = 'utf16_romanian_ci',
[105] = 'utf16_slovenian_ci',
[106] = 'utf16_polish_ci',
[107] = 'utf16_estonian_ci',
[108] = 'utf16_spanish_ci',
[109] = 'utf16_swedish_ci',
[110] = 'utf16_turkish_ci',
[111] = 'utf16_czech_ci',
[112] = 'utf16_danish_ci',
[113] = 'utf16_lithuanian_ci',
[114] = 'utf16_slovak_ci',
[115] = 'utf16_spanish2_ci',
[116] = 'utf16_roman_ci',
[117] = 'utf16_persian_ci',
[118] = 'utf16_esperanto_ci',
[119] = 'utf16_hungarian_ci',
[120] = 'utf16_sinhala_ci',
[121] = 'utf16_german2_ci',
[122] = 'utf16_croatian_ci',
[123] = 'utf16_unicode_520_ci',
[124] = 'utf16_vietnamese_ci',
[128] = 'ucs2_unicode_ci',
[129] = 'ucs2_icelandic_ci',
[130] = 'ucs2_latvian_ci',
[131] = 'ucs2_romanian_ci',
[132] = 'ucs2_slovenian_ci',
[133] = 'ucs2_polish_ci',
[134] = 'ucs2_estonian_ci',
[135] = 'ucs2_spanish_ci',
[136] = 'ucs2_swedish_ci',
[137] = 'ucs2_turkish_ci',
[138] = 'ucs2_czech_ci',
[139] = 'ucs2_danish_ci',
[140] = 'ucs2_lithuanian_ci',
[141] = 'ucs2_slovak_ci',
[142] = 'ucs2_spanish2_ci',
[143] = 'ucs2_roman_ci',
[144] = 'ucs2_persian_ci',
[145] = 'ucs2_esperanto_ci',
[146] = 'ucs2_hungarian_ci',
[147] = 'ucs2_sinhala_ci',
[148] = 'ucs2_german2_ci',
[149] = 'ucs2_croatian_ci',
[150] = 'ucs2_unicode_520_ci',
[151] = 'ucs2_vietnamese_ci',
[159] = 'ucs2_general_mysql500_ci',
[160] = 'utf32_unicode_ci',
[161] = 'utf32_icelandic_ci',
[162] = 'utf32_latvian_ci',
[163] = 'utf32_romanian_ci',
[164] = 'utf32_slovenian_ci',
[165] = 'utf32_polish_ci',
[166] = 'utf32_estonian_ci',
[167] = 'utf32_spanish_ci',
[168] = 'utf32_swedish_ci',
[169] = 'utf32_turkish_ci',
[170] = 'utf32_czech_ci',
[171] = 'utf32_danish_ci',
[172] = 'utf32_lithuanian_ci',
[173] = 'utf32_slovak_ci',
[174] = 'utf32_spanish2_ci',
[175] = 'utf32_roman_ci',
[176] = 'utf32_persian_ci',
[177] = 'utf32_esperanto_ci',
[178] = 'utf32_hungarian_ci',
[179] = 'utf32_sinhala_ci',
[180] = 'utf32_german2_ci',
[181] = 'utf32_croatian_ci',
[182] = 'utf32_unicode_520_ci',
[183] = 'utf32_vietnamese_ci',
[192] = 'utf8_unicode_ci',
[193] = 'utf8_icelandic_ci',
[194] = 'utf8_latvian_ci',
[195] = 'utf8_romanian_ci',
[196] = 'utf8_slovenian_ci',
[197] = 'utf8_polish_ci',
[198] = 'utf8_estonian_ci',
[199] = 'utf8_spanish_ci',
[200] = 'utf8_swedish_ci',
[201] = 'utf8_turkish_ci',
[202] = 'utf8_czech_ci',
[203] = 'utf8_danish_ci',
[204] = 'utf8_lithuanian_ci',
[205] = 'utf8_slovak_ci',
[206] = 'utf8_spanish2_ci',
[207] = 'utf8_roman_ci',
[208] = 'utf8_persian_ci',
[209] = 'utf8_esperanto_ci',
[210] = 'utf8_hungarian_ci',
[211] = 'utf8_sinhala_ci',
[212] = 'utf8_german2_ci',
[213] = 'utf8_croatian_ci',
[214] = 'utf8_unicode_520_ci',
[215] = 'utf8_vietnamese_ci',
[223] = 'utf8_general_mysql500_ci',
[224] = 'utf8mb4_unicode_ci',
[225] = 'utf8mb4_icelandic_ci',
[226] = 'utf8mb4_latvian_ci',
[227] = 'utf8mb4_romanian_ci',
[228] = 'utf8mb4_slovenian_ci',
[229] = 'utf8mb4_polish_ci',
[230] = 'utf8mb4_estonian_ci',
[231] = 'utf8mb4_spanish_ci',
[232] = 'utf8mb4_swedish_ci',
[233] = 'utf8mb4_turkish_ci',
[234] = 'utf8mb4_czech_ci',
[235] = 'utf8mb4_danish_ci',
[236] = 'utf8mb4_lithuanian_ci',
[237] = 'utf8mb4_slovak_ci',
[238] = 'utf8mb4_spanish2_ci',
[239] = 'utf8mb4_roman_ci',
[240] = 'utf8mb4_persian_ci',
[241] = 'utf8mb4_esperanto_ci',
[242] = 'utf8mb4_hungarian_ci',
[243] = 'utf8mb4_sinhala_ci',
[244] = 'utf8mb4_german2_ci',
[245] = 'utf8mb4_croatian_ci',
[246] = 'utf8mb4_unicode_520_ci',
[247] = 'utf8mb4_vietnamese_ci',
[248] = 'gb18030_chinese_ci',
[249] = 'gb18030_bin',
[250] = 'gb18030_unicode_520_ci',
[255] = 'utf8mb4_0900_ai_ci',
[256] = 'utf8mb4_de_pb_0900_ai_ci',
[257] = 'utf8mb4_is_0900_ai_ci',
[258] = 'utf8mb4_lv_0900_ai_ci',
[259] = 'utf8mb4_ro_0900_ai_ci',
[260] = 'utf8mb4_sl_0900_ai_ci',
[261] = 'utf8mb4_pl_0900_ai_ci',
[262] = 'utf8mb4_et_0900_ai_ci',
[263] = 'utf8mb4_es_0900_ai_ci',
[264] = 'utf8mb4_sv_0900_ai_ci',
[265] = 'utf8mb4_tr_0900_ai_ci',
[266] = 'utf8mb4_cs_0900_ai_ci',
[267] = 'utf8mb4_da_0900_ai_ci',
[268] = 'utf8mb4_lt_0900_ai_ci',
[269] = 'utf8mb4_sk_0900_ai_ci',
[270] = 'utf8mb4_es_trad_0900_ai_ci',
[271] = 'utf8mb4_la_0900_ai_ci',
[273] = 'utf8mb4_eo_0900_ai_ci',
[274] = 'utf8mb4_hu_0900_ai_ci',
[275] = 'utf8mb4_hr_0900_ai_ci',
[277] = 'utf8mb4_vi_0900_ai_ci',
[278] = 'utf8mb4_0900_as_cs',
[279] = 'utf8mb4_de_pb_0900_as_cs',
[280] = 'utf8mb4_is_0900_as_cs',
[281] = 'utf8mb4_lv_0900_as_cs',
[282] = 'utf8mb4_ro_0900_as_cs',
[283] = 'utf8mb4_sl_0900_as_cs',
[284] = 'utf8mb4_pl_0900_as_cs',
[285] = 'utf8mb4_et_0900_as_cs',
[286] = 'utf8mb4_es_0900_as_cs',
[287] = 'utf8mb4_sv_0900_as_cs',
[288] = 'utf8mb4_tr_0900_as_cs',
[289] = 'utf8mb4_cs_0900_as_cs',
[290] = 'utf8mb4_da_0900_as_cs',
[291] = 'utf8mb4_lt_0900_as_cs',
[292] = 'utf8mb4_sk_0900_as_cs',
[293] = 'utf8mb4_es_trad_0900_as_cs',
[294] = 'utf8mb4_la_0900_as_cs',
[296] = 'utf8mb4_eo_0900_as_cs',
[297] = 'utf8mb4_hu_0900_as_cs',
[298] = 'utf8mb4_hr_0900_as_cs',
[300] = 'utf8mb4_vi_0900_as_cs',
[303] = 'utf8mb4_ja_0900_as_cs',
[304] = 'utf8mb4_ja_0900_as_cs_ks',
[305] = 'utf8mb4_0900_as_ci',
[306] = 'utf8mb4_ru_0900_ai_ci',
[307] = 'utf8mb4_ru_0900_as_cs',
[308] = 'utf8mb4_zh_0900_as_cs',
[309] = 'utf8mb4_0900_bin',
}
-- the following charset map is generated from the following mysql query: local collation_codes = index(collation_names)
-- SELECT CHARACTER_SET_NAME, ID
-- FROM information_schema.collations local default_collations = {
-- WHERE IS_DEFAULT = 'Yes' ORDER BY id; big5 = 'big5_chinese_ci',
local CHARSET_MAP = { dec8 = 'dec8_swedish_ci',
_default = 0, cp850 = 'cp850_general_ci',
big5 = 1, hp8 = 'hp8_english_ci',
dec8 = 3, koi8r = 'koi8r_general_ci',
cp850 = 4, latin1 = 'latin1_swedish_ci',
hp8 = 6, latin2 = 'latin2_general_ci',
koi8r = 7, swe7 = 'swe7_swedish_ci',
latin1 = 8, ascii = 'ascii_general_ci',
latin2 = 9, ujis = 'ujis_japanese_ci',
swe7 = 10, sjis = 'sjis_japanese_ci',
ascii = 11, hebrew = 'hebrew_general_ci',
ujis = 12, tis620 = 'tis620_thai_ci',
sjis = 13, euckr = 'euckr_korean_ci',
hebrew = 16, koi8u = 'koi8u_general_ci',
tis620 = 18, gb2312 = 'gb2312_chinese_ci',
euckr = 19, greek = 'greek_general_ci',
koi8u = 22, cp1250 = 'cp1250_general_ci',
gb2312 = 24, gbk = 'gbk_chinese_ci',
greek = 25, latin5 = 'latin5_turkish_ci',
cp1250 = 26, armscii8 = 'armscii8_general_ci',
gbk = 28, utf8 = 'utf8_general_ci',
latin5 = 30, ucs2 = 'ucs2_general_ci',
armscii8 = 32, cp866 = 'cp866_general_ci',
utf8 = 33, keybcs2 = 'keybcs2_general_ci',
ucs2 = 35, macce = 'macce_general_ci',
cp866 = 36, macroman = 'macroman_general_ci',
keybcs2 = 37, cp852 = 'cp852_general_ci',
macce = 38, latin7 = 'latin7_general_ci',
macroman = 39, cp1251 = 'cp1251_general_ci',
cp852 = 40, utf16 = 'utf16_general_ci',
latin7 = 41, utf16le = 'utf16le_general_ci',
utf8mb4 = 45, cp1256 = 'cp1256_general_ci',
cp1251 = 51, cp1257 = 'cp1257_general_ci',
utf16 = 54, utf32 = 'utf32_general_ci',
utf16le = 56, binary = 'binary',
cp1256 = 57, geostd8 = 'geostd8_general_ci',
cp1257 = 59, cp932 = 'cp932_japanese_ci',
utf32 = 60, eucjpms = 'eucjpms_japanese_ci',
binary = 63, gb18030 = 'gb18030_chinese_ci',
geostd8 = 92, utf8mb4 = 'utf8mb4_0900_ai_ci',
cp932 = 95, }
eucjpms = 97,
gb18030 = 248 local buffer_type_names = {
[ 0] = 'decimal',
[ 1] = 'tiny',
[ 2] = 'short',
[ 3] = 'long',
[ 4] = 'float',
[ 5] = 'double',
[ 6] = 'null',
[ 7] = 'timestamp',
[ 8] = 'longlong',
[ 9] = 'int24',
[ 10] = 'date',
[ 11] = 'time',
[ 12] = 'datetime',
[ 13] = 'year',
[ 15] = 'varchar',
[ 16] = 'bit',
[246] = 'newdecimal',
[247] = 'enum',
[248] = 'set',
[249] = 'tiny_blob',
[250] = 'medium_blob',
[251] = 'long_blob',
[252] = 'blob',
[253] = 'var_string',
[254] = 'string',
[255] = 'geometry',
}
local type_names = {
tiny = 'tinyint',
short = 'shortint',
long = 'int',
int24 = 'mediumint',
longlong = 'bigint',
newdecimal = 'decimal',
}
local bin_type_names = {
tiny_blob = 'tinyblob',
medium_blob = 'mediumblob',
long_blob = 'longblob',
blob = 'blob',
var_string = 'varbinary',
string = 'binary',
}
local text_type_names = {
tiny_blob = 'tinytext',
medium_blob = 'mediumtext',
long_blob = 'longtext',
blob = 'text',
var_string = 'varchar',
string = 'char',
} }
local conn = {} local conn = {}
local mt = {__index = conn} local mt = {__index = conn}
-- mysql field value type converters -- mysql field value type converters
local converters = {} local converters = {
tinyint = tonumber,
for i = 0x01, 0x05 do -- tiny, short, long, float, double shortint = tonumber,
converters[i] = tonumber mediumint = tonumber,
end int = tonumber,
converters[0x00] = tonumber -- decimal bigint = tonumber,
-- converters[0x08] = tonumber -- long long year = tonumber,
converters[0x09] = tonumber -- int24 float = tonumber,
converters[0x0d] = tonumber -- year double = tonumber,
converters[0xf6] = tonumber -- newdecimal }
local function _get_byte2(data, i) local function _get_byte2(data, i)
local a, b = strbyte(data, i, i + 1) local a, b = strbyte(data, i, i + 1)
@ -161,7 +480,7 @@ end
local function _from_cstring(data, i) local function _from_cstring(data, i)
local last = strfind(data, '\0', i, true) local last = data:find('\0', i, true)
if not last then if not last then
return nil, nil return nil, nil
end end
@ -234,29 +553,10 @@ local function _send_packet(self, req, size)
return sock:send(packet) return sock:send(packet)
end end
--static, auto-growing buffer allocation pattern (ctype must be vla).
local max, ceil, log = math.max, math.ceil, math.log
local function nextpow2(x)
return max(0, 2^(ceil(log(x) / log(2))))
end
local function grow_buffer(ctype)
local vla = ffi.typeof(ctype)
local buf, len = nil, -1
return function(minlen)
if minlen == false then
buf, len = nil, -1
elseif minlen > len then
len = nextpow2(minlen)
buf = vla(len)
end
return buf, len
end
end
local function _recv(self, sz) local function _recv(self, sz)
local buf = self.buf local buf = self.buf
if not buf then if not buf then
buf = grow_buffer'char[?]' buf = buffer'char[?]'
self.buf = buf self.buf = buf
end end
local buf = buf(sz) local buf = buf(sz)
@ -335,7 +635,7 @@ local function _from_length_coded_bin(data, pos)
end end
if first == 251 then if first == 251 then
return null, pos + 1 return nil, pos + 1
end end
if first == 252 then if first == 252 then
@ -360,8 +660,8 @@ end
local function _from_length_coded_str(data, pos) local function _from_length_coded_str(data, pos)
local len local len
len, pos = _from_length_coded_bin(data, pos) len, pos = _from_length_coded_bin(data, pos)
if not len or len == null then if not len then
return null, pos return nil, pos
end end
return sub(data, pos, pos + len - 1), pos + len return sub(data, pos, pos + len - 1), pos + len
end end
@ -376,6 +676,10 @@ local function _parse_ok_packet(packet)
res.insert_id, pos = _from_length_coded_bin(packet, pos) res.insert_id, pos = _from_length_coded_bin(packet, pos)
if res.insert_id == 0 then
res.insert_id = nil
end
--print('insert id: ', res.insert_id, ', pos:', pos) --print('insert id: ', res.insert_id, ', pos:', pos)
res.server_status, pos = _get_byte2(packet, pos) res.server_status, pos = _get_byte2(packet, pos)
@ -386,10 +690,7 @@ local function _parse_ok_packet(packet)
--print('warning count: ', res.warning_count, ', pos: ', pos) --print('warning count: ', res.warning_count, ', pos: ', pos)
local message = _from_length_coded_str(packet, pos) res.message = _from_length_coded_str(packet, pos)
if message and message ~= null then
res.message = message
end
--print('message: ', res.message, ', pos:', pos) --print('message: ', res.message, ', pos:', pos)
@ -419,6 +720,7 @@ local function _parse_err_packet(packet)
end end
local message = sub(packet, pos) local message = sub(packet, pos)
message = message:gsub('You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ', 'Syntax error: ')
return errno, message, sqlstate return errno, message, sqlstate
end end
@ -440,7 +742,7 @@ local AUTO_INCREMENT_FLAG = 512
local function _parse_field(data, pos) local function _parse_field(data, pos)
local s, pos = _from_length_coded_str(data, pos) local s, pos = _from_length_coded_str(data, pos)
s = s ~= null and s ~= '' and s:lower() or nil s = s and s ~= '' and s:lower() or nil
return s, pos return s, pos
end end
@ -450,38 +752,45 @@ local function _parse_field_packet(data)
col.catalog, pos = _parse_field(data, 1) col.catalog, pos = _parse_field(data, 1)
col.schema, pos = _parse_field(data, pos) col.schema, pos = _parse_field(data, pos)
col.table, pos = _parse_field(data, pos) col.table, pos = _parse_field(data, pos)
col.orig_table, pos = _parse_field(data, pos) col.origin_table, pos = _parse_field(data, pos)
col.name, pos = _parse_field(data, pos) col.name, pos = _parse_field(data, pos)
col.orig_name, pos = _parse_field(data, pos) col.origin_name, pos = _parse_field(data, pos)
pos = pos + 1 -- ignore the filler pos = pos + 1 -- ignore the filler
col.charsetnr, pos = _get_byte2(data, pos) local collation, pos = _get_byte2(data, pos)
col.length, pos = _get_byte4(data, pos) col.length, pos = _get_byte4(data, pos)
col.type = strbyte(data, pos) local buffer_type = buffer_type_names[strbyte(data, pos)]
local text_type_names = collation == 63 and bin_type_names or text_type_names
col.type = text_type_names[buffer_type]
if col.type then
col.collation = collation_names[collation]
col.charset = col.collation and col.collation:match'^[^_]+'
else
col.type = type_names[buffer_type] or buffer_type
end
pos = pos + 1 pos = pos + 1
col.flags, pos = _get_byte2(data, pos) local flags, pos = _get_byte2(data, pos)
col.not_null = band(col.flags, NOT_NULL_FLAG) ~= 0 or nil col.not_null = band(flags, NOT_NULL_FLAG ) ~= 0 or nil
col.pri_key = band(col.flags, PRI_KEY_FLAG) ~= 0 or nil col.pri_key = band(flags, PRI_KEY_FLAG ) ~= 0 or nil
col.unique_key = band(col.flags, UNIQUE_KEY_FLAG) ~= 0 or nil col.unique_key = band(flags, UNIQUE_KEY_FLAG ) ~= 0 or nil
col.unsigned = band(col.flags, UNSIGNED_FLAG) ~= 0 or nil col.unsigned = band(flags, UNSIGNED_FLAG ) ~= 0 or nil
col.auto_increment = band(col.flags, AUTO_INCREMENT_FLAG) ~= 0 or nil col.auto_increment = band(flags, AUTO_INCREMENT_FLAG) ~= 0 or nil
col.decimals = strbyte(data, pos) col.decimals = strbyte(data, pos)
pos = pos + 1 pos = pos + 1
local default = sub(data, pos + 2) col.default = repl(sub(data, pos + 2), '', nil)
if default and default ~= '' then
col.default = default
end
return col return col
end end
local function _parse_row_data_packet(data, cols, compact) local function _parse_row_data_packet(data, cols, compact, to_array, null_value)
local pos = 1 local pos = 1
local ncols = #cols local ncols = #cols
local row local row
if compact then if not to_array then
row = new_tab(ncols, 0) if compact then
else row = new_tab(ncols, 0)
row = new_tab(0, ncols) else
row = new_tab(0, ncols)
end
end end
for i = 1, ncols do for i = 1, ncols do
local value local value
@ -492,16 +801,22 @@ local function _parse_row_data_packet(data, cols, compact)
--print('row field value: ', value, ', type: ', typ) --print('row field value: ', value, ', type: ', typ)
if value ~= null then if value ~= nil then
local conv = converters[typ] local conv = converters[typ]
if conv then if conv then
value = conv(value) value = conv(value)
end end
else
value = null_value
end
if to_array then
return value
end end
if compact then if compact then
row[i] = value row[i] = value
elseif value ~= null then else
row[name] = value row[name] = value
end end
end end
@ -555,9 +870,12 @@ function conn:connect(opts)
local database = opts.database or '' local database = opts.database or ''
local user = opts.user or '' local user = opts.user or ''
local charset = CHARSET_MAP[opts.charset or '_default'] local collation = 0 --default
if not charset then if opts.collation then
return nil, 'charset \'' .. opts.charset .. '\' is not supported' collation = assert(collation_codes[opts.collation], 'invalid collation')
elseif opts.charset then
collation = assert(default_collations[opts.charset], 'invalid charset')
collation = assert(collation_codes[collation])
end end
local host = opts.host local host = opts.host
@ -652,7 +970,7 @@ function conn:connect(opts)
-- send a SSL Request Packet -- send a SSL Request Packet
local req = _set_byte4(bor(client_flags, CLIENT_SSL)) local req = _set_byte4(bor(client_flags, CLIENT_SSL))
.. _set_byte4(self._max_packet_size) .. _set_byte4(self._max_packet_size)
.. strchar(charset) .. strchar(collation)
.. strrep('\0', 23) .. strrep('\0', 23)
local packet_len = 4 + 4 + 1 + 23 local packet_len = 4 + 4 + 1 + 23
@ -675,7 +993,7 @@ function conn:connect(opts)
local req = _set_byte4(client_flags) local req = _set_byte4(client_flags)
.. _set_byte4(self._max_packet_size) .. _set_byte4(self._max_packet_size)
.. strchar(charset) .. strchar(collation)
.. strrep('\0', 23) .. strrep('\0', 23)
.. _to_cstring(user) .. _to_cstring(user)
.. _to_binary_coded_string(token) .. _to_binary_coded_string(token)
@ -712,7 +1030,7 @@ function conn:connect(opts)
return nil, 'bad packet type: ' .. typ return nil, 'bad packet type: ' .. typ
end end
self.state = STATE_CONNECTED self.state = 'ready'
return 1 return 1
end end
@ -735,7 +1053,7 @@ function conn:server_ver()
end end
function conn:send_query(query) function conn:send_query(query)
assert(self.state == STATE_CONNECTED) assert(self.state == 'ready')
local sock = assert(self.sock) local sock = assert(self.sock)
self.packet_no = -1 self.packet_no = -1
@ -748,30 +1066,24 @@ function conn:send_query(query)
return nil, err return nil, err
end end
self.state = STATE_COMMAND_SENT self.state = 'read'
--print('packet sent ', bytes, ' bytes') --print('packet sent ', bytes, ' bytes')
return bytes return bytes
end end
function conn:read_result(est_nrows, compact) function conn:read_result(opt)
assert(self.state == STATE_COMMAND_SENT) assert(self.state == 'read')
local sock = assert(self.sock) local sock = assert(self.sock)
compact = compact == 'compact'
if est_nrows == 'compact' then
est_nrows = null
compact = true
end
local packet, typ, err = _recv_packet(self) local packet, typ, err = _recv_packet(self)
if not packet then if not packet then
return nil, err return nil, err
end end
if typ == 'ERR' then if typ == 'ERR' then
self.state = STATE_CONNECTED self.state = 'ready'
local errno, msg, sqlstate = _parse_err_packet(packet) local errno, msg, sqlstate = _parse_err_packet(packet)
return nil, msg, errno, sqlstate return nil, msg, errno, sqlstate
@ -783,12 +1095,12 @@ function conn:read_result(est_nrows, compact)
return res, 'again' return res, 'again'
end end
self.state = STATE_CONNECTED self.state = 'ready'
return res return res
end end
if typ ~= 'DATA' then if typ ~= 'DATA' then
self.state = STATE_CONNECTED self.state = 'ready'
return nil, 'packet type ' .. typ .. ' not supported' return nil, 'packet type ' .. typ .. ' not supported'
end end
@ -808,7 +1120,9 @@ function conn:read_result(est_nrows, compact)
return nil, err, errno, sqlstate return nil, err, errno, sqlstate
end end
col.index = i
cols[i] = col cols[i] = col
cols[col.name] = col
end end
local packet, typ, err = _recv_packet(self) local packet, typ, err = _recv_packet(self)
@ -823,7 +1137,11 @@ function conn:read_result(est_nrows, compact)
-- typ == 'EOF' -- typ == 'EOF'
local rows = new_tab(est_nrows or 4, 0) local compact = opt and opt.compact
local to_array = opt and opt.to_array and #cols == 1
local null_value = opt and opt.null_value
local rows = new_tab(4, 0)
local i = 0 local i = 0
while true do while true do
--print('reading a row') --print('reading a row')
@ -851,20 +1169,21 @@ function conn:read_result(est_nrows, compact)
-- typ == 'DATA' -- typ == 'DATA'
local row = _parse_row_data_packet(packet, cols, compact) local row = _parse_row_data_packet(packet, cols, compact, to_array, null_value)
i = i + 1 i = i + 1
rows[i] = row rows[i] = row
end end
self.state = STATE_CONNECTED self.state = 'ready'
return rows, nil, cols return rows, nil, cols
end end
function conn:query(query, est_nrows) function conn:query(query, opt)
local bytes, err, errcode = self:send_query(query) local bytes, err, errcode = self:send_query(query)
if not bytes then return nil, err, errcode end if not bytes then return nil, err, errcode end
return self:read_result(est_nrows) return self:read_result(opt)
end end
local qmap = { local qmap = {

View File

@ -30,7 +30,7 @@ local res = assert(cn:query('create table cats '
.. 'name varchar(5))')) .. 'name varchar(5))'))
local res = assert(cn:query('insert into cats (name) ' local res = assert(cn:query('insert into cats (name) '
.. 'values (\'Bob\'),(\'\'),(null)')) .. "values ('Bob'),(''),(null)"))
print(res.affected_rows, ' rows inserted into table cats ', print(res.affected_rows, ' rows inserted into table cats ',
'(last insert id: ', res.insert_id, ')') '(last insert id: ', res.insert_id, ')')
@ -61,12 +61,8 @@ The `options` argument is a Lua table holding the following keys:
* `database`: the MySQL database name. * `database`: the MySQL database name.
* `user`: MySQL account name for login. * `user`: MySQL account name for login.
* `password`: MySQL account password for login (in clear text). * `password`: MySQL account password for login (in clear text).
* `charset`: the character set used for the connection, which can be one of: * `collation`: the collation used for the connection (`charset` is implied with this).
`big5`, `dec8`, `cp850`, `hp8`, `koi8r`, `latin1`, `latin2`, * `charset`: the character set used for the connection (the default collation for the charset is selected).
`swe7`, `ascii`, `ujis`, `sjis`, `hebrew`, `tis620`, `euckr`, `koi8u`, `gb2312`, `greek`,
`cp1250`, `gbk`, `latin5`, `armscii8`, `utf8`, `ucs2`, `cp866`, `keybcs2`, `macce`,
`macroman`, `cp852`, `latin7`, `utf8mb4`, `cp1251`, `utf16`, `utf16le`, `cp1256`,
`cp1257`, `utf32`, `binary`, `geostd8`, `cp932`, `eucjpms`, `gb18030`.
* `max_packet_size`: the upper limit for the reply packets sent from the server (default to 1MB). * `max_packet_size`: the upper limit for the reply packets sent from the server (default to 1MB).
* `ssl`: if `true`, then uses SSL to connect to MySQL (default to `false`). * `ssl`: if `true`, then uses SSL to connect to MySQL (default to `false`).
If the server does not have SSL support (or just disabled), the error string If the server does not have SSL support (or just disabled), the error string
@ -83,7 +79,7 @@ Sends the query to the remote MySQL server without waiting for its replies.
Returns the bytes successfully sent out. Use `read_result()` to read the replies. Returns the bytes successfully sent out. Use `read_result()` to read the replies.
### `cn:read_result([nrows,]['compact']) -> res,nil,cols | nil,err,errcode,sqlstate` ### `cn:read_result([options]) -> res,nil|'again',cols | nil,err,errcode,sqlstate`
Reads in one result returned from the server. Reads in one result returned from the server.
@ -91,23 +87,12 @@ It returns a Lua table (`res`) describing the MySQL `OK packet`
or `result set packet` for the query result. or `result set packet` for the query result.
For queries corresponding to a result set, it returns an array holding all the rows. For queries corresponding to a result set, it returns an array holding all the rows.
Each row holds key-value pairs for each data fields. For instance,
```lua The `options` arg can contain:
{
{ name = "Bob", age = 32, phone = mysql.null },
{ name = "Marry", age = 18, phone = "10666372" }
}
```
If `'compact'` given, it returns an array-of-arrays instead: * `compact = true` -- return an array of arrays instead of an array of `{column->value}` maps.
* `to_array = true` -- return an array of values for single-column results.
```lua * `null_value = val` -- value to use for `null` (defaults to `nil`).
{
{ "Bob", 32, null },
{ "Marry", 18, "10666372" }
}
```
For queries that do not correspond to a result set, it returns a Lua table like this: For queries that do not correspond to a result set, it returns a Lua table like this:
@ -126,7 +111,7 @@ will be given the string `again`. One should always check this (second) return
value and if it is `again`, then she should call this method again to retrieve value and if it is `again`, then she should call this method again to retrieve
more results. This usually happens when the original query contains multiple more results. This usually happens when the original query contains multiple
statements (separated by semicolon in the same query string) or calling a statements (separated by semicolon in the same query string) or calling a
MySQL procedure. See also [Multi-Resultset Support](#multi-resultset-support). MySQL procedure.
In case of errors, this method returns at most 4 values: `nil`, `err`, `errcode`, and `sqlstate`. In case of errors, this method returns at most 4 values: `nil`, `err`, `errcode`, and `sqlstate`.
The `err` return value contains a string describing the error, the `errcode` The `err` return value contains a string describing the error, the `errcode`
@ -135,18 +120,14 @@ the `sqlstate` return value contains the standard SQL error code that consists
of 5 characters. Note that, the `errcode` and `sqlstate` might be `nil` of 5 characters. Note that, the `errcode` and `sqlstate` might be `nil`
if MySQL does not return them. if MySQL does not return them.
The optional argument `nrows` can be used to specify an approximate number ### `cn:query(query, [options]) -> res,nil,cols | nil,err,errcode,sqlstate`
of rows for the result set. This value can be used to pre-allocate space
in the resulting Lua table for the result set. By default, it takes the value 4.
### `cn:query(query, [nrows]) -> res,nil,cols | nil,err,errcode,sqlstate`
This is a shortcut for combining the [send_query](#send_query) call This is a shortcut for combining the [send_query](#send_query) call
and the first [read_result](#read_result) call. and the first [read_result](#read_result) call.
You should always check if the `err` return value is `again` in case of You should always check if the `err` return value is `again` in case of
success because this method will only call [read_result](#read_result) success because this method will only call [read_result](#read_result)
only once for you. See also [Multi-Resultset Support](#multi-resultset-support). once for you.
### `cn:server_ver() -> s` ### `cn:server_ver() -> s`