├── mysql_connector.lua ├── mysql_connector.md ├── mysql_connector_print.lua ├── mysql_connector_test.lua └── mysql_h.lua /mysql_connector.lua: -------------------------------------------------------------------------------- 1 | 2 | --MySQL client library ffi binding. 3 | --Written by Cosmin Apreutesei. Public domain. 4 | 5 | --Supports MySQL Connector/C 6.1. 6 | --Based on MySQL 5.7 manual. 7 | 8 | if not ... then require'mysql_test'; return end 9 | 10 | local ffi = require'ffi' 11 | local bit = require'bit' 12 | require'mysql_h' 13 | 14 | local C 15 | local M = {} 16 | 17 | --select a mysql client library implementation. 18 | local function bind(lib) 19 | if not C then 20 | if not lib or lib == 'mysql' then 21 | C = ffi.load(ffi.abi'win' and 'libmysql' or 'mysqlclient') 22 | elseif lib == 'mariadb' then 23 | C = ffi.load'mariadb' 24 | elseif type(lib) == 'string' then 25 | C = ffi.load(lib) 26 | else 27 | C = lib 28 | end 29 | M.C = C 30 | end 31 | return M 32 | end 33 | 34 | M.bind = bind 35 | 36 | --we compare NULL pointers against NULL instead of nil for compatibility with luaffi. 37 | local NULL = ffi.cast('void*', nil) 38 | 39 | local function ptr(p) --convert NULLs to nil 40 | if p == NULL then return nil end 41 | return p 42 | end 43 | 44 | local function cstring(data) --convert null-term non-empty C strings to lua strings 45 | if data == NULL or data[0] == 0 then return nil end 46 | return ffi.string(data) 47 | end 48 | 49 | --error reporting 50 | 51 | local function myerror(mysql, stacklevel) 52 | local err = cstring(C.mysql_error(mysql)) 53 | if not err then return end 54 | error(string.format('mysql error: %s', err), stacklevel or 3) 55 | end 56 | 57 | local function checkz(mysql, ret) 58 | if ret == 0 then return end 59 | myerror(mysql, 4) 60 | end 61 | 62 | local function checkh(mysql, ret) 63 | if ret ~= NULL then return ret end 64 | myerror(mysql, 4) 65 | end 66 | 67 | local function enum(e, prefix) 68 | local v = type(e) == 'string' and (prefix and C[prefix..e] or C[e]) or e 69 | return assert(v, 'invalid enum value') 70 | end 71 | 72 | --client library info 73 | 74 | function M.thread_safe() 75 | bind() 76 | return C.mysql_thread_safe() == 1 77 | end 78 | 79 | function M.client_info() 80 | bind() 81 | return cstring(C.mysql_get_client_info()) 82 | end 83 | 84 | function M.client_version() 85 | bind() 86 | return tonumber(C.mysql_get_client_version()) 87 | end 88 | 89 | --connections 90 | 91 | local function bool_ptr(b) 92 | return ffi.new('my_bool[1]', b or false) 93 | end 94 | 95 | local function uint_bool_ptr(b) 96 | return ffi.new('uint32_t[1]', b or false) 97 | end 98 | 99 | local function uint_ptr(i) 100 | return ffi.new('uint32_t[1]', i) 101 | end 102 | 103 | local function proto_ptr(proto) --proto is 'MYSQL_PROTOCOL_*' or mysql.C.MYSQL_PROTOCOL_* 104 | return ffi.new('uint32_t[1]', enum(proto)) 105 | end 106 | 107 | local function ignore_arg() 108 | return nil 109 | end 110 | 111 | local option_encoders = { 112 | MYSQL_ENABLE_CLEARTEXT_PLUGIN = bool_ptr, 113 | MYSQL_OPT_LOCAL_INFILE = uint_bool_ptr, 114 | MYSQL_OPT_PROTOCOL = proto_ptr, 115 | MYSQL_OPT_READ_TIMEOUT = uint_ptr, 116 | MYSQL_OPT_WRITE_TIMEOUT = uint_ptr, 117 | MYSQL_OPT_USE_REMOTE_CONNECTION = ignore_arg, 118 | MYSQL_OPT_USE_EMBEDDED_CONNECTION = ignore_arg, 119 | MYSQL_OPT_GUESS_CONNECTION = ignore_arg, 120 | MYSQL_SECURE_AUTH = bool_ptr, 121 | MYSQL_REPORT_DATA_TRUNCATION = bool_ptr, 122 | MYSQL_OPT_RECONNECT = bool_ptr, 123 | MYSQL_OPT_SSL_VERIFY_SERVER_CERT = bool_ptr, 124 | MYSQL_ENABLE_CLEARTEXT_PLUGIN = bool_ptr, 125 | MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS = bool_ptr, 126 | } 127 | 128 | function M.connect(t, ...) 129 | bind() 130 | local host, user, pass, db, charset, port 131 | local unix_socket, flags, options, attrs 132 | local key, cert, ca, capath, cipher 133 | if type(t) == 'string' then 134 | host, user, pass, db, charset, port = t, ... 135 | else 136 | host, user, pass, db, charset, port = t.host, t.user, t.pass, t.db, t.charset, t.port 137 | unix_socket, flags, options, attrs = t.unix_socket, t.flags, t.options, t.attrs 138 | key, cert, ca, capath, cipher = t.key, t.cert, t.ca, t.capath, t.cipher 139 | end 140 | port = port or 0 141 | 142 | local client_flag = 0 143 | if type(flags) == 'number' then 144 | client_flag = flags 145 | elseif flags then 146 | for k,v in pairs(flags) do 147 | local flag = enum(k, 'MYSQL_') --'CLIENT_*' or mysql.C.MYSQL_CLIENT_* enum 148 | client_flag = v and bit.bor(client_flag, flag) or bit.band(client_flag, bit.bnot(flag)) 149 | end 150 | end 151 | 152 | local mysql = assert(C.mysql_init(nil)) 153 | ffi.gc(mysql, C.mysql_close) 154 | 155 | if options then 156 | for k,v in pairs(options) do 157 | local opt = enum(k) --'MYSQL_OPT_*' or mysql.C.MYSQL_OPT_* enum 158 | local encoder = option_encoders[k] 159 | if encoder then v = encoder(v) end 160 | assert(C.mysql_options(mysql, opt, ffi.cast('const void*', v)) == 0, 'invalid option') 161 | end 162 | end 163 | 164 | if attrs then 165 | for k,v in pairs(attrs) do 166 | assert(C.mysql_options4(mysql, C.MYSQL_OPT_CONNECT_ATTR_ADD, k, v) == 0) 167 | end 168 | end 169 | 170 | if key then 171 | checkz(mysql, C.mysql_ssl_set(mysql, key, cert, ca, capath, cipher)) 172 | end 173 | 174 | checkh(mysql, C.mysql_real_connect(mysql, host, user, pass, db, port, unix_socket, client_flag)) 175 | 176 | if charset then mysql:set_charset(charset) end 177 | 178 | return mysql 179 | end 180 | 181 | local conn = {} --connection methods 182 | 183 | function conn.close(mysql) 184 | C.mysql_close(mysql) 185 | ffi.gc(mysql, nil) 186 | end 187 | 188 | function conn.set_charset(mysql, charset) 189 | checkz(mysql, C.mysql_set_character_set(mysql, charset)) 190 | end 191 | 192 | function conn.select_db(mysql, db) 193 | checkz(mysql, C.mysql_select_db(mysql, db)) 194 | end 195 | 196 | function conn.change_user(mysql, user, pass, db) 197 | checkz(mysql, C.mysql_change_user(mysql, user, pass, db)) 198 | end 199 | 200 | function conn.set_multiple_statements(mysql, yes) 201 | checkz(mysql, C.mysql_set_server_option(mysql, yes and C.MYSQL_OPTION_MULTI_STATEMENTS_ON or 202 | C.MYSQL_OPTION_MULTI_STATEMENTS_OFF)) 203 | end 204 | 205 | --connection info 206 | 207 | function conn.charset(mysql) 208 | return cstring(C.mysql_character_set_name(mysql)) 209 | end 210 | 211 | function conn.charset_info(mysql) 212 | local info = ffi.new'MY_CHARSET_INFO' 213 | checkz(C.mysql_get_character_set_info(mysql, info)) 214 | assert(info.name ~= NULL) 215 | assert(info.csname ~= NULL) 216 | return { 217 | number = info.number, 218 | state = info.state, 219 | name = cstring(info.csname), --csname and name are inverted from the spec 220 | collation = cstring(info.name), 221 | comment = cstring(info.comment), 222 | dir = cstring(info.dir), 223 | mbminlen = info.mbminlen, 224 | mbmaxlen = info.mbmaxlen, 225 | } 226 | end 227 | 228 | function conn.ping(mysql) 229 | local ret = C.mysql_ping(mysql) 230 | if ret == 0 then 231 | return true 232 | elseif C.mysql_error(mysql) == C.MYSQL_CR_SERVER_GONE_ERROR then 233 | return false 234 | end 235 | myerror(mysql) 236 | end 237 | 238 | function conn.thread_id(mysql) 239 | return C.mysql_thread_id(mysql) --NOTE: result is cdata on x64! 240 | end 241 | 242 | function conn.stat(mysql) 243 | return cstring(checkh(mysql, C.mysql_stat(mysql))) 244 | end 245 | 246 | function conn.server_info(mysql) 247 | return cstring(checkh(mysql, C.mysql_get_server_info(mysql))) 248 | end 249 | 250 | function conn.host_info(mysql) 251 | return cstring(checkh(mysql, C.mysql_get_host_info(mysql))) 252 | end 253 | 254 | function conn.server_version(mysql) 255 | return tonumber(C.mysql_get_server_version(mysql)) 256 | end 257 | 258 | function conn.proto_info(...) 259 | return C.mysql_get_proto_info(...) 260 | end 261 | 262 | function conn.ssl_cipher(mysql) 263 | return cstring(C.mysql_get_ssl_cipher(mysql)) 264 | end 265 | 266 | --transactions 267 | 268 | function conn.commit(mysql) checkz(mysql, C.mysql_commit(mysql)) end 269 | function conn.rollback(mysql) checkz(mysql, C.mysql_rollback(mysql)) end 270 | function conn.set_autocommit(mysql, yes) 271 | checkz(mysql, C.mysql_autocommit(mysql, yes == nil or yes)) 272 | end 273 | 274 | --queries 275 | 276 | function conn.escape_tobuffer(mysql, data, size, buf, sz) 277 | size = size or #data 278 | assert(sz >= size * 2 + 1) 279 | return tonumber(C.mysql_real_escape_string(mysql, buf, data, size)) 280 | end 281 | 282 | function conn.escape(mysql, data, size) 283 | size = size or #data 284 | local sz = size * 2 + 1 285 | local buf = ffi.new('uint8_t[?]', sz) 286 | sz = conn.escape_tobuffer(mysql, data, size, buf, sz) 287 | return ffi.string(buf, sz) 288 | end 289 | 290 | function conn.query(mysql, data, size) 291 | checkz(mysql, C.mysql_real_query(mysql, data, size or #data)) 292 | end 293 | 294 | --query info 295 | 296 | function conn.field_count(...) 297 | return C.mysql_field_count(...) 298 | end 299 | 300 | local minus1_uint64 = ffi.cast('uint64_t', ffi.cast('int64_t', -1)) 301 | function conn.affected_rows(mysql) 302 | local n = C.mysql_affected_rows(mysql) 303 | if n == minus1_uint64 then myerror(mysql) end 304 | return tonumber(n) 305 | end 306 | 307 | function conn.insert_id(...) 308 | return C.mysql_insert_id(...) --NOTE: result is cdata on x64! 309 | end 310 | 311 | function conn.errno(conn) 312 | local err = C.mysql_errno(conn) 313 | if err == 0 then return end 314 | return err 315 | end 316 | 317 | function conn.sqlstate(mysql) 318 | return cstring(C.mysql_sqlstate(mysql)) 319 | end 320 | 321 | function conn.warning_count(...) 322 | return C.mysql_warning_count(...) 323 | end 324 | 325 | function conn.info(mysql) 326 | return cstring(C.mysql_info(mysql)) 327 | end 328 | 329 | --query results 330 | 331 | function conn.next_result(mysql) --multiple statement queries return multiple results 332 | local ret = C.mysql_next_result(mysql) 333 | if ret == 0 then return true end 334 | if ret == -1 then return false end 335 | myerror(mysql) 336 | end 337 | 338 | function conn.more_results(mysql) 339 | return C.mysql_more_results(mysql) == 1 340 | end 341 | 342 | local function result_function(func) 343 | return function(mysql) 344 | local res = checkh(mysql, C[func](mysql)) 345 | return ffi.gc(res, C.mysql_free_result) 346 | end 347 | end 348 | 349 | conn.store_result = result_function'mysql_store_result' 350 | conn.use_result = result_function'mysql_use_result' 351 | 352 | local res = {} --result methods 353 | 354 | function res.free(res) 355 | C.mysql_free_result(res) 356 | ffi.gc(res, nil) 357 | end 358 | 359 | function res.row_count(res) 360 | return tonumber(C.mysql_num_rows(res)) 361 | end 362 | 363 | function res.field_count(...) 364 | return C.mysql_num_fields(...) 365 | end 366 | 367 | function res.eof(res) 368 | return C.mysql_eof(res) ~= 0 369 | end 370 | 371 | --field info 372 | 373 | local field_type_names = { 374 | [ffi.C.MYSQL_TYPE_DECIMAL] = 'decimal', --DECIMAL or NUMERIC 375 | [ffi.C.MYSQL_TYPE_TINY] = 'tinyint', 376 | [ffi.C.MYSQL_TYPE_SHORT] = 'smallint', 377 | [ffi.C.MYSQL_TYPE_LONG] = 'int', 378 | [ffi.C.MYSQL_TYPE_FLOAT] = 'float', 379 | [ffi.C.MYSQL_TYPE_DOUBLE] = 'double', --DOUBLE or REAL 380 | [ffi.C.MYSQL_TYPE_NULL] = 'null', 381 | [ffi.C.MYSQL_TYPE_TIMESTAMP] = 'timestamp', 382 | [ffi.C.MYSQL_TYPE_LONGLONG] = 'bigint', 383 | [ffi.C.MYSQL_TYPE_INT24] = 'mediumint', 384 | [ffi.C.MYSQL_TYPE_DATE] = 'date', --pre mysql 5.0, storage = 4 bytes 385 | [ffi.C.MYSQL_TYPE_TIME] = 'time', 386 | [ffi.C.MYSQL_TYPE_DATETIME] = 'datetime', 387 | [ffi.C.MYSQL_TYPE_YEAR] = 'year', 388 | [ffi.C.MYSQL_TYPE_NEWDATE] = 'date', --mysql 5.0+, storage = 3 bytes 389 | [ffi.C.MYSQL_TYPE_VARCHAR] = 'varchar', 390 | [ffi.C.MYSQL_TYPE_BIT] = 'bit', 391 | [ffi.C.MYSQL_TYPE_TIMESTAMP2] = 'timestamp', --mysql 5.6+, can store fractional seconds 392 | [ffi.C.MYSQL_TYPE_DATETIME2] = 'datetime', --mysql 5.6+, can store fractional seconds 393 | [ffi.C.MYSQL_TYPE_TIME2] = 'time', --mysql 5.6+, can store fractional seconds 394 | [ffi.C.MYSQL_TYPE_NEWDECIMAL] = 'decimal', --mysql 5.0+, Precision math DECIMAL or NUMERIC 395 | [ffi.C.MYSQL_TYPE_ENUM] = 'enum', 396 | [ffi.C.MYSQL_TYPE_SET] = 'set', 397 | [ffi.C.MYSQL_TYPE_TINY_BLOB] = 'tinyblob', 398 | [ffi.C.MYSQL_TYPE_MEDIUM_BLOB] = 'mediumblob', 399 | [ffi.C.MYSQL_TYPE_LONG_BLOB] = 'longblob', 400 | [ffi.C.MYSQL_TYPE_BLOB] = 'text', --TEXT or BLOB 401 | [ffi.C.MYSQL_TYPE_VAR_STRING] = 'varchar', --VARCHAR or VARBINARY 402 | [ffi.C.MYSQL_TYPE_STRING] = 'char', --CHAR or BINARY 403 | [ffi.C.MYSQL_TYPE_GEOMETRY] = 'spatial', --Spatial field 404 | } 405 | 406 | local binary_field_type_names = { 407 | [ffi.C.MYSQL_TYPE_BLOB] = 'blob', 408 | [ffi.C.MYSQL_TYPE_VAR_STRING] = 'varbinary', 409 | [ffi.C.MYSQL_TYPE_STRING] = 'binary', 410 | } 411 | 412 | local field_flag_names = { 413 | [ffi.C.MYSQL_NOT_NULL_FLAG] = 'not_null', 414 | [ffi.C.MYSQL_PRI_KEY_FLAG] = 'pri_key', 415 | [ffi.C.MYSQL_UNIQUE_KEY_FLAG] = 'unique_key', 416 | [ffi.C.MYSQL_MULTIPLE_KEY_FLAG] = 'key', 417 | [ffi.C.MYSQL_BLOB_FLAG] = 'is_blob', 418 | [ffi.C.MYSQL_UNSIGNED_FLAG] = 'unsigned', 419 | [ffi.C.MYSQL_ZEROFILL_FLAG] = 'zerofill', 420 | [ffi.C.MYSQL_BINARY_FLAG] = 'is_binary', 421 | [ffi.C.MYSQL_ENUM_FLAG] = 'is_enum', 422 | [ffi.C.MYSQL_AUTO_INCREMENT_FLAG] = 'autoincrement', 423 | [ffi.C.MYSQL_TIMESTAMP_FLAG] = 'is_timestamp', 424 | [ffi.C.MYSQL_SET_FLAG] = 'is_set', 425 | [ffi.C.MYSQL_NO_DEFAULT_VALUE_FLAG] = 'no_default', 426 | [ffi.C.MYSQL_ON_UPDATE_NOW_FLAG] = 'on_update_now', 427 | [ffi.C.MYSQL_NUM_FLAG] = 'is_number', 428 | } 429 | 430 | local function field_type_name(info) 431 | local type_flag = tonumber(info.type) 432 | local field_type = field_type_names[type_flag] 433 | --charsetnr 63 changes CHAR into BINARY, VARCHAR into VARBYNARY, TEXT into BLOB 434 | field_type = info.charsetnr == 63 and binary_field_type_names[type_flag] or field_type 435 | return field_type 436 | end 437 | 438 | --convenience field type fetcher (less garbage) 439 | function res.field_type(res, i) 440 | assert(i >= 1 and i <= res:field_count(), 'index out of range') 441 | local info = C.mysql_fetch_field_direct(res, i-1) 442 | local unsigned = bit.bor(info.flags, C.MYSQL_UNSIGNED_FLAG) ~= 0 443 | return field_type_name(info), tonumber(info.length), unsigned, info.decimals 444 | end 445 | 446 | function res.field_info(res, i) 447 | assert(i >= 1 and i <= res:field_count(), 'index out of range') 448 | local info = C.mysql_fetch_field_direct(res, i-1) 449 | local t = { 450 | name = cstring(info.name, info.name_length), 451 | org_name = cstring(info.org_name, info.org_name_length), 452 | table = cstring(info.table, info.table_length), 453 | org_table = cstring(info.org_table, info.org_table_length), 454 | db = cstring(info.db, info.db_length), 455 | catalog = cstring(info.catalog, info.catalog_length), 456 | def = cstring(info.def, info.def_length), 457 | length = tonumber(info.length), 458 | max_length = tonumber(info.max_length), 459 | decimals = info.decimals, 460 | charsetnr = info.charsetnr, 461 | type_flag = tonumber(info.type), 462 | type = field_type_name(info), 463 | flags = info.flags, 464 | extension = ptr(info.extension), 465 | } 466 | for flag, name in pairs(field_flag_names) do 467 | t[name] = bit.band(flag, info.flags) ~= 0 468 | end 469 | return t 470 | end 471 | 472 | --convenience field name fetcher (less garbage) 473 | function res.field_name(res, i) 474 | assert(i >= 1 and i <= res:field_count(), 'index out of range') 475 | local info = C.mysql_fetch_field_direct(res, i-1) 476 | return cstring(info.name, info.name_length) 477 | end 478 | 479 | --convenience field iterator, shortcut for: for i=1,res:field_count() do local field = res:field_info(i) ... end 480 | function res.fields(res) 481 | local n = res:field_count() 482 | local i = 0 483 | return function() 484 | if i == n then return end 485 | i = i + 1 486 | return i, res:field_info(i) 487 | end 488 | end 489 | 490 | --row data fetching and parsing 491 | 492 | ffi.cdef('double strtod(const char*, char**);') 493 | local function parse_int(data, sz) --using strtod to avoid string creation 494 | return ffi.C.strtod(data, nil) 495 | end 496 | 497 | local function parse_float(data, sz) 498 | return tonumber(ffi.cast('float', ffi.C.strtod(data, nil))) --because windows is missing strtof() 499 | end 500 | 501 | local function parse_double(data, sz) 502 | return ffi.C.strtod(data, nil) 503 | end 504 | 505 | ffi.cdef('int64_t strtoll(const char*, char**, int) ' ..(ffi.os == 'Windows' and ' asm("_strtoi64")' or '') .. ';') 506 | local function parse_int64(data, sz) 507 | return ffi.C.strtoll(data, nil, 10) 508 | end 509 | 510 | ffi.cdef('uint64_t strtoull(const char*, char**, int) ' ..(ffi.os == 'Windows' and ' asm("_strtoui64")' or '') .. ';') 511 | local function parse_uint64(data, sz) 512 | return ffi.C.strtoull(data, nil, 10) 513 | end 514 | 515 | local function parse_bit(data, sz) 516 | data = ffi.cast('uint8_t*', data) --force unsigned 517 | local n = data[0] --this is the msb: bit fields always come in big endian byte order 518 | if sz > 6 then --we can cover up to 6 bytes with only Lua numbers 519 | n = ffi.new('uint64_t', n) 520 | end 521 | for i=1,sz-1 do 522 | n = n * 256 + data[i] 523 | end 524 | return n 525 | end 526 | 527 | local function parse_date_(data, sz) 528 | assert(sz >= 10) 529 | local z = ('0'):byte() 530 | local year = (data[0] - z) * 1000 + (data[1] - z) * 100 + (data[2] - z) * 10 + (data[3] - z) 531 | local month = (data[5] - z) * 10 + (data[6] - z) 532 | local day = (data[8] - z) * 10 + (data[9] - z) 533 | return year, month, day 534 | end 535 | 536 | local function parse_time_(data, sz) 537 | assert(sz >= 8) 538 | local z = ('0'):byte() 539 | local hour = (data[0] - z) * 10 + (data[1] - z) 540 | local min = (data[3] - z) * 10 + (data[4] - z) 541 | local sec = (data[6] - z) * 10 + (data[7] - z) 542 | local frac = 0 543 | for i = 9, sz-1 do 544 | frac = frac * 10 + (data[i] - z) 545 | end 546 | return hour, min, sec, frac 547 | end 548 | 549 | local function format_date(year, month, day) 550 | return string.format('%04d-%02d-%02d', year, month, day) 551 | end 552 | 553 | local function format_time(hour, min, sec, frac) 554 | if frac and frac ~= 0 then 555 | return string.format('%02d:%02d:%02d.%d', hour, min, sec, frac) 556 | else 557 | return string.format('%02d:%02d:%02d', hour, min, sec) 558 | end 559 | end 560 | 561 | local function datetime_tostring(t) 562 | local date, time 563 | if t.year then 564 | date = format_date(t.year, t.month, t.day) 565 | end 566 | if t.sec then 567 | time = format_time(t.hour, t.min, t.sec, t.frac) 568 | end 569 | if date and time then 570 | return date .. ' ' .. time 571 | else 572 | return assert(date or time) 573 | end 574 | end 575 | 576 | local datetime_meta = {__tostring = datetime_tostring} 577 | local function datetime(t) 578 | return setmetatable(t, datetime_meta) 579 | end 580 | 581 | local function parse_date(data, sz) 582 | local year, month, day = parse_date_(data, sz) 583 | return datetime{year = year, month = month, day = day} 584 | end 585 | 586 | local function parse_time(data, sz) 587 | local hour, min, sec, frac = parse_time_(data, sz) 588 | return datetime{hour = hour, min = min, sec = sec, frac = frac} 589 | end 590 | 591 | local function parse_datetime(data, sz) 592 | local year, month, day = parse_date_(data, sz) 593 | local hour, min, sec, frac = parse_time_(data + 11, sz - 11) 594 | return datetime{year = year, month = month, day = day, hour = hour, min = min, sec = sec, frac = frac} 595 | end 596 | 597 | local field_decoders = { --other field types not present here are returned as strings, unparsed 598 | [ffi.C.MYSQL_TYPE_TINY] = parse_int, 599 | [ffi.C.MYSQL_TYPE_SHORT] = parse_int, 600 | [ffi.C.MYSQL_TYPE_LONG] = parse_int, 601 | [ffi.C.MYSQL_TYPE_FLOAT] = parse_float, 602 | [ffi.C.MYSQL_TYPE_DOUBLE] = parse_double, 603 | [ffi.C.MYSQL_TYPE_TIMESTAMP] = parse_datetime, 604 | [ffi.C.MYSQL_TYPE_LONGLONG] = parse_int64, 605 | [ffi.C.MYSQL_TYPE_INT24] = parse_int, 606 | [ffi.C.MYSQL_TYPE_DATE] = parse_date, 607 | [ffi.C.MYSQL_TYPE_TIME] = parse_time, 608 | [ffi.C.MYSQL_TYPE_DATETIME] = parse_datetime, 609 | [ffi.C.MYSQL_TYPE_NEWDATE] = parse_date, 610 | [ffi.C.MYSQL_TYPE_TIMESTAMP2] = parse_datetime, 611 | [ffi.C.MYSQL_TYPE_DATETIME2] = parse_datetime, 612 | [ffi.C.MYSQL_TYPE_TIME2] = parse_time, 613 | [ffi.C.MYSQL_TYPE_YEAR] = parse_int, 614 | [ffi.C.MYSQL_TYPE_BIT] = parse_bit, 615 | } 616 | 617 | local unsigned_decoders = { 618 | [ffi.C.MYSQL_TYPE_LONGLONG] = parse_uint64, 619 | } 620 | 621 | local function mode_flags(mode) 622 | local assoc = mode and mode:find'a' 623 | local numeric = not mode or not assoc or mode:find'n' 624 | local decode = not mode or not mode:find's' 625 | local packed = mode and mode:find'[an]' 626 | local fetch_fields = assoc or decode --if assoc we need field_name, if decode we need field_type 627 | return numeric, assoc, decode, packed, fetch_fields 628 | end 629 | 630 | local function fetch_row(res, numeric, assoc, decode, field_count, fields, t) 631 | local values = C.mysql_fetch_row(res) 632 | if values == NULL then 633 | if res.conn ~= NULL then --buffered read: check for errors 634 | myerror(res.conn, 4) 635 | end 636 | return nil 637 | end 638 | local sizes = C.mysql_fetch_lengths(res) 639 | for i=0,field_count-1 do 640 | local v = values[i] 641 | if v ~= NULL then 642 | local decoder 643 | if decode then 644 | local ftype = tonumber(fields[i].type) 645 | local unsigned = bit.bor(fields[i].flags, C.MYSQL_UNSIGNED_FLAG) ~= 0 646 | decoder = unsigned and unsigned_decoders[ftype] or field_decoders[ftype] or ffi.string 647 | else 648 | decoder = ffi.string 649 | end 650 | v = decoder(values[i], tonumber(sizes[i])) 651 | if numeric then 652 | t[i+1] = v 653 | end 654 | if assoc then 655 | local k = ffi.string(fields[i].name, fields[i].name_length) 656 | t[k] = v 657 | end 658 | end 659 | end 660 | return t 661 | end 662 | 663 | function res.fetch(res, mode, t) 664 | local numeric, assoc, decode, packed, fetch_fields = mode_flags(mode) 665 | local field_count = C.mysql_num_fields(res) 666 | local fields = fetch_fields and C.mysql_fetch_fields(res) 667 | local row = fetch_row(res, numeric, assoc, decode, field_count, fields, t or {}) 668 | if not row then return nil end 669 | if packed then 670 | return row 671 | else 672 | return true, unpack(row) 673 | end 674 | end 675 | 676 | function res.rows(res, mode, t) 677 | local numeric, assoc, decode, packed, fetch_fields = mode_flags(mode) 678 | local field_count = C.mysql_num_fields(res) 679 | local fields = fetch_fields and C.mysql_fetch_fields(res) 680 | local i = 0 681 | res:seek(1) 682 | return function() 683 | local row = fetch_row(res, numeric, assoc, decode, field_count, fields, t or {}) 684 | if not row then return nil end 685 | i = i + 1 686 | if packed then 687 | return i, row 688 | else 689 | return i, unpack(row) 690 | end 691 | end 692 | end 693 | 694 | function res.tell(...) 695 | return C.mysql_row_tell(...) 696 | end 697 | 698 | function res.seek(res, where) --use in conjunction with res:row_count() 699 | if type(where) == 'number' then 700 | C.mysql_data_seek(res, where-1) 701 | else 702 | C.mysql_row_seek(res, where) 703 | end 704 | end 705 | 706 | --reflection 707 | 708 | local function list_function(func) 709 | return function(mysql, wild) 710 | local res = checkh(mysql, C[func](mysql, wild)) 711 | return ffi.gc(res, C.mysql_free_result) 712 | end 713 | end 714 | 715 | conn.list_dbs = list_function'mysql_list_dbs' 716 | conn.list_tables = list_function'mysql_list_tables' 717 | conn.list_processes = result_function'mysql_list_processes' 718 | 719 | --remote control 720 | 721 | function conn.kill(mysql, pid) 722 | checkz(mysql, C.mysql_kill(mysql, pid)) 723 | end 724 | 725 | function conn.shutdown(mysql, level) 726 | checkz(mysql, C.mysql_shutdown(mysql, enum(level or C.MYSQL_SHUTDOWN_DEFAULT, 'MYSQL_'))) 727 | end 728 | 729 | function conn.refresh(mysql, t) --options are 'REFRESH_*' or mysql.C.MYSQL_REFRESH_* enums 730 | local options = 0 731 | if type(t) == 'number' then 732 | options = t 733 | else 734 | for k,v in pairs(t) do 735 | if v then 736 | options = bit.bor(options, enum(k, 'MYSQL_')) 737 | end 738 | end 739 | end 740 | checkz(mysql, C.mysql_refresh(mysql, options)) 741 | end 742 | 743 | function conn.dump_debug_info(mysql) 744 | checkz(mysql, C.mysql_dump_debug_info(mysql)) 745 | end 746 | 747 | --prepared statements 748 | 749 | local function sterror(stmt, stacklevel) 750 | local err = cstring(C.mysql_stmt_error(stmt)) 751 | if not err then return end 752 | error(string.format('mysql error: %s', err), stacklevel or 3) 753 | end 754 | 755 | local function stcheckz(stmt, ret) 756 | if ret == 0 then return end 757 | sterror(stmt, 4) 758 | end 759 | 760 | local function stcheckbool(stmt, ret) 761 | if ret == 1 then return end 762 | sterror(stmt, 4) 763 | end 764 | 765 | local function stcheckh(stmt, ret) 766 | if ret ~= NULL then return ret end 767 | sterror(stmt, 4) 768 | end 769 | 770 | function conn.prepare(mysql, query) 771 | local stmt = checkh(mysql, C.mysql_stmt_init(mysql)) 772 | ffi.gc(stmt, C.mysql_stmt_close) 773 | stcheckz(stmt, C.mysql_stmt_prepare(stmt, query, #query)) 774 | return stmt 775 | end 776 | 777 | local stmt = {} --statement methods 778 | 779 | function stmt.close(stmt) 780 | stcheckbool(stmt, C.mysql_stmt_close(stmt)) 781 | ffi.gc(stmt, nil) 782 | end 783 | 784 | function stmt.exec(stmt) 785 | stcheckz(stmt, C.mysql_stmt_execute(stmt)) 786 | end 787 | 788 | function stmt.next_result(stmt) 789 | local ret = C.mysql_stmt_next_result(stmt) 790 | if ret == 0 then return true end 791 | if ret == -1 then return false end 792 | sterror(stmt) 793 | end 794 | 795 | function stmt.store_result(stmt) 796 | stcheckz(stmt, C.mysql_stmt_store_result(stmt)) 797 | end 798 | 799 | function stmt.free_result(stmt) 800 | stcheckbool(stmt, C.mysql_stmt_free_result(stmt)) 801 | end 802 | 803 | function stmt.row_count(stmt) 804 | return tonumber(C.mysql_stmt_num_rows(stmt)) 805 | end 806 | 807 | function stmt.affected_rows(stmt) 808 | local n = C.mysql_stmt_affected_rows(stmt) 809 | if n == minus1_uint64 then sterror(stmt) end 810 | return tonumber(n) 811 | end 812 | 813 | function stmt.insert_id(...) 814 | return C.mysql_stmt_insert_id(...) 815 | end 816 | 817 | function stmt.field_count(stmt) 818 | return tonumber(C.mysql_stmt_field_count(stmt)) 819 | end 820 | 821 | function stmt.param_count(stmt) 822 | return tonumber(C.mysql_stmt_param_count(stmt)) 823 | end 824 | 825 | function stmt.errno(stmt) 826 | local err = C.mysql_stmt_errno(stmt) 827 | if err == 0 then return end 828 | return err 829 | end 830 | 831 | function stmt.sqlstate(stmt) 832 | return cstring(C.mysql_stmt_sqlstate(stmt)) 833 | end 834 | 835 | function stmt.result_metadata(stmt) 836 | local res = stcheckh(stmt, C.mysql_stmt_result_metadata(stmt)) 837 | return res and ffi.gc(res, C.mysql_free_result) 838 | end 839 | 840 | function stmt.fields(stmt) 841 | local res = stmt:result_metadata() 842 | if not res then return nil end 843 | local fields = res:fields() 844 | return function() 845 | local i, info = fields() 846 | if not i then 847 | res:free() 848 | end 849 | return i, info 850 | end 851 | end 852 | 853 | function stmt.fetch(stmt) 854 | local ret = C.mysql_stmt_fetch(stmt) 855 | if ret == 0 then return true end 856 | if ret == C.MYSQL_NO_DATA then return false end 857 | if ret == C.MYSQL_DATA_TRUNCATED then return true, 'truncated' end 858 | sterror(stmt) 859 | end 860 | 861 | function stmt.reset(stmt) 862 | stcheckz(stmt, C.mysql_stmt_reset(stmt)) 863 | end 864 | 865 | function stmt.tell(...) 866 | return C.mysql_stmt_row_tell(...) 867 | end 868 | 869 | function stmt.seek(stmt, where) --use in conjunction with stmt:row_count() 870 | if type(where) == 'number' then 871 | C.mysql_stmt_data_seek(stmt, where-1) 872 | else 873 | C.mysql_stmt_row_seek(stmt, where) 874 | end 875 | end 876 | 877 | function stmt.write(stmt, param_number, data, size) 878 | stcheckz(stmt, C.mysql_stmt_send_long_data(stmt, param_number, data, size or #data)) 879 | end 880 | 881 | function stmt.update_max_length(stmt) 882 | local attr = ffi.new'my_bool[1]' 883 | stcheckz(stmt, C.mysql_stmt_attr_get(stmt, C.STMT_ATTR_UPDATE_MAX_LENGTH, attr)) 884 | return attr[0] == 1 885 | end 886 | 887 | function stmt.set_update_max_length(stmt, yes) 888 | local attr = ffi.new('my_bool[1]', yes == nil or yes) 889 | stcheckz(stmt, C.mysql_stmt_attr_set(stmt, C.STMT_ATTR_CURSOR_TYPE, attr)) 890 | end 891 | 892 | function stmt.cursor_type(stmt) 893 | local attr = ffi.new'uint32_t[1]' 894 | stcheckz(stmt, C.mysql_stmt_attr_get(stmt, C.STMT_ATTR_CURSOR_TYPE, attr)) 895 | return attr[0] 896 | end 897 | 898 | function stmt.set_cursor_type(stmt, cursor_type) 899 | local attr = ffi.new('uint32_t[1]', enum(cursor_type, 'MYSQL_')) 900 | stcheckz(stmt, C.mysql_stmt_attr_set(stmt, C.STMT_ATTR_CURSOR_TYPE, attr)) 901 | end 902 | 903 | function stmt.prefetch_rows(stmt) 904 | local attr = ffi.new'uint32_t[1]' 905 | stcheckz(stmt, C.mysql_stmt_attr_get(stmt, C.STMT_ATTR_PREFETCH_ROWS, attr)) 906 | return attr[0] 907 | end 908 | 909 | function stmt.set_prefetch_rows(stmt, n) 910 | local attr = ffi.new('uint32_t[1]', n) 911 | stcheckz(stmt, C.mysql_stmt_attr_set(stmt, C.STMT_ATTR_PREFETCH_ROWS, attr)) 912 | end 913 | 914 | --prepared statements / bind buffers 915 | 916 | --see http://dev.mysql.com/doc/refman/5.7/en/c-api-prepared-statement-type-codes.html 917 | local bb_types_input = { 918 | --conversion-free types 919 | tinyint = ffi.C.MYSQL_TYPE_TINY, 920 | smallint = ffi.C.MYSQL_TYPE_SHORT, 921 | int = ffi.C.MYSQL_TYPE_LONG, 922 | integer = ffi.C.MYSQL_TYPE_LONG, --alias of int 923 | bigint = ffi.C.MYSQL_TYPE_LONGLONG, 924 | float = ffi.C.MYSQL_TYPE_FLOAT, 925 | double = ffi.C.MYSQL_TYPE_DOUBLE, 926 | time = ffi.C.MYSQL_TYPE_TIME, 927 | date = ffi.C.MYSQL_TYPE_DATE, 928 | datetime = ffi.C.MYSQL_TYPE_DATETIME, 929 | timestamp = ffi.C.MYSQL_TYPE_TIMESTAMP, 930 | text = ffi.C.MYSQL_TYPE_STRING, 931 | char = ffi.C.MYSQL_TYPE_STRING, 932 | varchar = ffi.C.MYSQL_TYPE_STRING, 933 | blob = ffi.C.MYSQL_TYPE_BLOB, 934 | binary = ffi.C.MYSQL_TYPE_BLOB, 935 | varbinary = ffi.C.MYSQL_TYPE_BLOB, 936 | null = ffi.C.MYSQL_TYPE_NULL, 937 | --conversion types (can only use one of the above C types) 938 | mediumint = ffi.C.MYSQL_TYPE_LONG, 939 | real = ffi.C.MYSQL_TYPE_DOUBLE, 940 | decimal = ffi.C.MYSQL_TYPE_BLOB, 941 | numeric = ffi.C.MYSQL_TYPE_BLOB, 942 | year = ffi.C.MYSQL_TYPE_SHORT, 943 | tinyblob = ffi.C.MYSQL_TYPE_BLOB, 944 | tinytext = ffi.C.MYSQL_TYPE_BLOB, 945 | mediumblob = ffi.C.MYSQL_TYPE_BLOB, 946 | mediumtext = ffi.C.MYSQL_TYPE_BLOB, 947 | longblob = ffi.C.MYSQL_TYPE_BLOB, 948 | longtext = ffi.C.MYSQL_TYPE_BLOB, 949 | bit = ffi.C.MYSQL_TYPE_LONGLONG, --MYSQL_TYPE_BIT is not available for input params 950 | set = ffi.C.MYSQL_TYPE_BLOB, 951 | enum = ffi.C.MYSQL_TYPE_BLOB, 952 | } 953 | 954 | local bb_types_output = { 955 | --conversion-free types 956 | tinyint = ffi.C.MYSQL_TYPE_TINY, 957 | smallint = ffi.C.MYSQL_TYPE_SHORT, 958 | mediumint = ffi.C.MYSQL_TYPE_INT24, --int32 959 | int = ffi.C.MYSQL_TYPE_LONG, 960 | integer = ffi.C.MYSQL_TYPE_LONG, --alias of int 961 | bigint = ffi.C.MYSQL_TYPE_LONGLONG, 962 | float = ffi.C.MYSQL_TYPE_FLOAT, 963 | double = ffi.C.MYSQL_TYPE_DOUBLE, 964 | real = ffi.C.MYSQL_TYPE_DOUBLE, 965 | decimal = ffi.C.MYSQL_TYPE_NEWDECIMAL, --char[] 966 | numeric = ffi.C.MYSQL_TYPE_NEWDECIMAL, --char[] 967 | year = ffi.C.MYSQL_TYPE_SHORT, 968 | time = ffi.C.MYSQL_TYPE_TIME, 969 | date = ffi.C.MYSQL_TYPE_DATE, 970 | datetime = ffi.C.MYSQL_TYPE_DATETIME, 971 | timestamp = ffi.C.MYSQL_TYPE_TIMESTAMP, 972 | char = ffi.C.MYSQL_TYPE_STRING, 973 | binary = ffi.C.MYSQL_TYPE_STRING, 974 | varchar = ffi.C.MYSQL_TYPE_VAR_STRING, 975 | varbinary = ffi.C.MYSQL_TYPE_VAR_STRING, 976 | tinyblob = ffi.C.MYSQL_TYPE_TINY_BLOB, 977 | tinytext = ffi.C.MYSQL_TYPE_TINY_BLOB, 978 | blob = ffi.C.MYSQL_TYPE_BLOB, 979 | text = ffi.C.MYSQL_TYPE_BLOB, 980 | mediumblob = ffi.C.MYSQL_TYPE_MEDIUM_BLOB, 981 | mediumtext = ffi.C.MYSQL_TYPE_MEDIUM_BLOB, 982 | longblob = ffi.C.MYSQL_TYPE_LONG_BLOB, 983 | longtext = ffi.C.MYSQL_TYPE_LONG_BLOB, 984 | bit = ffi.C.MYSQL_TYPE_BIT, 985 | --conversion types (can only use one of the above C types) 986 | null = ffi.C.MYSQL_TYPE_TINY, 987 | set = ffi.C.MYSQL_TYPE_BLOB, 988 | enum = ffi.C.MYSQL_TYPE_BLOB, 989 | } 990 | 991 | local number_types = { 992 | [ffi.C.MYSQL_TYPE_TINY] = 'int8_t[1]', 993 | [ffi.C.MYSQL_TYPE_SHORT] = 'int16_t[1]', 994 | [ffi.C.MYSQL_TYPE_LONG] = 'int32_t[1]', 995 | [ffi.C.MYSQL_TYPE_INT24] = 'int32_t[1]', 996 | [ffi.C.MYSQL_TYPE_LONGLONG] = 'int64_t[1]', 997 | [ffi.C.MYSQL_TYPE_FLOAT] = 'float[1]', 998 | [ffi.C.MYSQL_TYPE_DOUBLE] = 'double[1]', 999 | } 1000 | 1001 | local uint_types = { 1002 | [ffi.C.MYSQL_TYPE_TINY] = 'uint8_t[1]', 1003 | [ffi.C.MYSQL_TYPE_SHORT] = 'uint16_t[1]', 1004 | [ffi.C.MYSQL_TYPE_LONG] = 'uint32_t[1]', 1005 | [ffi.C.MYSQL_TYPE_INT24] = 'uint32_t[1]', 1006 | [ffi.C.MYSQL_TYPE_LONGLONG] = 'uint64_t[1]', 1007 | } 1008 | 1009 | local time_types = { 1010 | [ffi.C.MYSQL_TYPE_TIME] = true, 1011 | [ffi.C.MYSQL_TYPE_DATE] = true, 1012 | [ffi.C.MYSQL_TYPE_DATETIME] = true, 1013 | [ffi.C.MYSQL_TYPE_TIMESTAMP] = true, 1014 | } 1015 | 1016 | local time_struct_types = { 1017 | [ffi.C.MYSQL_TYPE_TIME] = ffi.C.MYSQL_TIMESTAMP_TIME, 1018 | [ffi.C.MYSQL_TYPE_DATE] = ffi.C.MYSQL_TIMESTAMP_DATE, 1019 | [ffi.C.MYSQL_TYPE_DATETIME] = ffi.C.MYSQL_TIMESTAMP_DATETIME, 1020 | [ffi.C.MYSQL_TYPE_TIMESTAMP] = ffi.C.MYSQL_TIMESTAMP_DATETIME, 1021 | } 1022 | 1023 | local params = {} --params bind buffer methods 1024 | local params_meta = {__index = params} 1025 | local fields = {} --params bind buffer methods 1026 | local fields_meta = {__index = fields} 1027 | 1028 | -- "varchar(200)" -> "varchar", 200; "decimal(10,4)" -> "decimal", 12; "int unsigned" -> "int", nil, true 1029 | local function parse_type(s) 1030 | s = s:lower() 1031 | local unsigned = false 1032 | local rest = s:match'(.-)%s+unsigned$' 1033 | if rest then s, unsigned = rest, true end 1034 | local rest, sz = s:match'^%s*([^%(]+)%s*%(%s*(%d+)[^%)]*%)%s*$' 1035 | if rest then 1036 | s, sz = rest, assert(tonumber(sz), 'invalid type') 1037 | if s == 'decimal' or s == 'numeric' then --make room for the dot and the minus sign 1038 | sz = sz + 2 1039 | end 1040 | end 1041 | return s, sz, unsigned 1042 | end 1043 | 1044 | local function bind_buffer(bb_types, meta, types) 1045 | local self = setmetatable({}, meta) 1046 | 1047 | self.count = #types 1048 | self.buffer = ffi.new('MYSQL_BIND[?]', #types) 1049 | self.data = {} --data buffers, one for each field 1050 | self.lengths = ffi.new('unsigned long[?]', #types) --length buffers, one for each field 1051 | self.null_flags = ffi.new('my_bool[?]', #types) --null flag buffers, one for each field 1052 | self.error_flags = ffi.new('my_bool[?]', #types) --error (truncation) flag buffers, one for each field 1053 | 1054 | for i,typedef in ipairs(types) do 1055 | local stype, size, unsigned = parse_type(typedef) 1056 | local btype = assert(bb_types[stype], 'invalid type') 1057 | local data 1058 | if stype == 'bit' then 1059 | if btype == C.MYSQL_TYPE_LONGLONG then --for input: use unsigned int64 and ignore size 1060 | data = ffi.new'uint64_t[1]' 1061 | self.buffer[i-1].is_unsigned = 1 1062 | size = 0 1063 | elseif btype == C.MYSQL_TYPE_BIT then --for output: use mysql conversion-free type 1064 | size = size or 64 --if missing size, assume maximum 1065 | size = math.ceil(size / 8) 1066 | assert(size >= 1 and size <= 8, 'invalid size') 1067 | data = ffi.new('uint8_t[?]', size) 1068 | end 1069 | elseif number_types[btype] then 1070 | assert(not size, 'fixed size type') 1071 | data = ffi.new(unsigned and uint_types[btype] or number_types[btype]) 1072 | self.buffer[i-1].is_unsigned = unsigned 1073 | size = ffi.sizeof(data) 1074 | elseif time_types[btype] then 1075 | assert(not size, 'fixed size type') 1076 | data = ffi.new'MYSQL_TIME' 1077 | data.time_type = time_struct_types[btype] 1078 | size = 0 1079 | elseif btype == C.MYSQL_TYPE_NULL then 1080 | assert(not size, 'fixed size type') 1081 | size = 0 1082 | else 1083 | assert(size, 'missing size') 1084 | data = size > 0 and ffi.new('uint8_t[?]', size) or nil 1085 | end 1086 | self.null_flags[i-1] = true 1087 | self.data[i] = data 1088 | self.lengths[i-1] = 0 1089 | self.buffer[i-1].buffer_type = btype 1090 | self.buffer[i-1].buffer = data 1091 | self.buffer[i-1].buffer_length = size 1092 | self.buffer[i-1].is_null = self.null_flags + (i - 1) 1093 | self.buffer[i-1].error = self.error_flags + (i - 1) 1094 | self.buffer[i-1].length = self.lengths + (i - 1) 1095 | end 1096 | return self 1097 | end 1098 | 1099 | local function params_bind_buffer(types) 1100 | return bind_buffer(bb_types_input, params_meta, types) 1101 | end 1102 | 1103 | local function fields_bind_buffer(types) 1104 | return bind_buffer(bb_types_output, fields_meta, types) 1105 | end 1106 | 1107 | local function bind_check_range(self, i) 1108 | assert(i >= 1 and i <= self.count, 'index out of bounds') 1109 | end 1110 | 1111 | --realloc a buffer using supplied size. only for varsize fields. 1112 | function params:realloc(i, size) 1113 | bind_check_range(self, i) 1114 | assert(ffi.istype(self.data[i], 'uint8_t[?]'), 'attempt to realloc a fixed size field') 1115 | local data = size > 0 and ffi.new('uint8_t[?]', size) or nil 1116 | self.null_flags[i-1] = true 1117 | self.data[i] = data 1118 | self.lengths[i-1] = 0 1119 | self.buffer[i-1].buffer = data 1120 | self.buffer[i-1].buffer_length = size 1121 | end 1122 | 1123 | fields.realloc = params.realloc 1124 | 1125 | function fields:get_date(i) 1126 | bind_check_range(self, i) 1127 | local btype = tonumber(self.buffer[i-1].buffer_type) 1128 | local date = btype == C.MYSQL_TYPE_DATE or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP 1129 | local time = btype == C.MYSQL_TYPE_TIME or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP 1130 | assert(date or time, 'not a date/time type') 1131 | if self.null_flags[i-1] == 1 then return nil end 1132 | local tm = self.data[i] 1133 | return 1134 | date and tm.year or nil, 1135 | date and tm.month or nil, 1136 | date and tm.day or nil, 1137 | time and tm.hour or nil, 1138 | time and tm.minute or nil, 1139 | time and tm.second or nil, 1140 | time and tonumber(tm.second_part) or nil 1141 | end 1142 | 1143 | function params:set_date(i, year, month, day, hour, min, sec, frac) 1144 | bind_check_range(self, i) 1145 | local tm = self.data[i] 1146 | local btype = tonumber(self.buffer[i-1].buffer_type) 1147 | local date = btype == C.MYSQL_TYPE_DATE or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP 1148 | local time = btype == C.MYSQL_TYPE_TIME or btype == C.MYSQL_TYPE_DATETIME or btype == C.MYSQL_TYPE_TIMESTAMP 1149 | assert(date or time, 'not a date/time type') 1150 | local tm = self.data[i] 1151 | tm.year = date and math.max(0, math.min(year or 0, 9999)) or 0 1152 | tm.month = date and math.max(1, math.min(month or 0, 12)) or 0 1153 | tm.day = date and math.max(1, math.min(day or 0, 31)) or 0 1154 | tm.hour = time and math.max(0, math.min(hour or 0, 59)) or 0 1155 | tm.minute = time and math.max(0, math.min(min or 0, 59)) or 0 1156 | tm.second = time and math.max(0, math.min(sec or 0, 59)) or 0 1157 | tm.second_part = time and math.max(0, math.min(frac or 0, 999999)) or 0 1158 | self.null_flags[i-1] = false 1159 | end 1160 | 1161 | function params:set(i, v, size) 1162 | bind_check_range(self, i) 1163 | v = ptr(v) 1164 | if v == nil then 1165 | self.null_flags[i-1] = true 1166 | return 1167 | end 1168 | local btype = tonumber(self.buffer[i-1].buffer_type) 1169 | if btype == C.MYSQL_TYPE_NULL then 1170 | error('attempt to set a null type param') 1171 | elseif number_types[btype] then --this includes bit type which is LONGLONG 1172 | self.data[i][0] = v 1173 | self.null_flags[i-1] = false 1174 | elseif time_types[btype] then 1175 | self:set_date(i, v.year, v.month, v.day, v.hour, v.min, v.sec, v.frac) 1176 | else --var-sized types and raw bit blobs 1177 | size = size or #v 1178 | local bsize = tonumber(self.buffer[i-1].buffer_length) 1179 | assert(bsize >= size, 'string too long') 1180 | ffi.copy(self.data[i], v, size) 1181 | self.lengths[i-1] = size 1182 | self.null_flags[i-1] = false 1183 | end 1184 | end 1185 | 1186 | function fields:get(i) 1187 | bind_check_range(self, i) 1188 | local btype = tonumber(self.buffer[i-1].buffer_type) 1189 | if btype == C.MYSQL_TYPE_NULL or self.null_flags[i-1] == 1 then 1190 | return nil 1191 | end 1192 | if number_types[btype] then 1193 | return self.data[i][0] --ffi converts this to a number or int64 type, which maches result:fetch() decoding 1194 | elseif time_types[btype] then 1195 | local t = self.data[i] 1196 | if t.time_type == C.MYSQL_TIMESTAMP_TIME then 1197 | return datetime{hour = t.hour, min = t.minute, sec = t.second, frac = tonumber(t.second_part)} 1198 | elseif t.time_type == C.MYSQL_TIMESTAMP_DATE then 1199 | return datetime{year = t.year, month = t.month, day = t.day} 1200 | elseif t.time_type == C.MYSQL_TIMESTAMP_DATETIME then 1201 | return datetime{year = t.year, month = t.month, day = t.day, 1202 | hour = t.hour, min = t.minute, sec = t.second, frac = tonumber(t.second_part)} 1203 | else 1204 | error'invalid time' 1205 | end 1206 | else 1207 | local sz = math.min(tonumber(self.buffer[i-1].buffer_length), tonumber(self.lengths[i-1])) 1208 | if btype == C.MYSQL_TYPE_BIT then 1209 | return parse_bit(self.data[i], sz) 1210 | else 1211 | return ffi.string(self.data[i], sz) 1212 | end 1213 | end 1214 | end 1215 | 1216 | function fields:is_null(i) --returns true if the field is null 1217 | bind_check_range(self, i) 1218 | local btype = self.buffer[i-1].buffer_type 1219 | return btype == C.MYSQL_TYPE_NULL or self.null_flags[i-1] == 1 1220 | end 1221 | 1222 | function fields:is_truncated(i) --returns true if the field value was truncated 1223 | bind_check_range(self, i) 1224 | return self.error_flags[i-1] == 1 1225 | end 1226 | 1227 | local varsize_types = { 1228 | char = true, 1229 | binary = true, 1230 | varchar = true, 1231 | varbinary = true, 1232 | tinyblob = true, 1233 | tinytext = true, 1234 | blob = true, 1235 | text = true, 1236 | mediumblob = true, 1237 | mediumtext = true, 1238 | longblob = true, 1239 | longtext = true, 1240 | bit = true, 1241 | set = true, 1242 | enum = true, 1243 | } 1244 | 1245 | function stmt.bind_result_types(stmt, maxsize) 1246 | local types = {} 1247 | local field_count = stmt:field_count() 1248 | local res = stmt:result_metadata() 1249 | if not res then return nil end 1250 | for i=1,field_count do 1251 | local ftype, size, unsigned, decimals = res:field_type(i) 1252 | if ftype == 'decimal' then 1253 | ftype = string.format('%s(%d,%d)', ftype, size-2, decimals) 1254 | elseif varsize_types[ftype] then 1255 | size = math.min(size, maxsize or 65535) 1256 | ftype = string.format('%s(%d)', ftype, size) 1257 | end 1258 | ftype = unsigned and ftype..' unsigned' or ftype 1259 | types[i] = ftype 1260 | end 1261 | res:free() 1262 | return types 1263 | end 1264 | 1265 | function stmt.bind_params(stmt, ...) 1266 | local types = type((...)) == 'string' and {...} or ... or {} 1267 | assert(stmt:param_count() == #types, 'wrong number of param types') 1268 | local bb = params_bind_buffer(types) 1269 | stcheckz(stmt, C.mysql_stmt_bind_param(stmt, bb.buffer)) 1270 | return bb 1271 | end 1272 | 1273 | function stmt.bind_result(stmt, arg1, ...) 1274 | local types 1275 | if type(arg1) == 'string' then 1276 | types = {arg1, ...} 1277 | elseif type(arg1) == 'number' then 1278 | types = stmt:bind_result_types(arg1) 1279 | elseif arg1 then 1280 | types = arg1 1281 | else 1282 | types = stmt:bind_result_types() 1283 | end 1284 | assert(stmt:field_count() == #types, 'wrong number of field types') 1285 | local bb = fields_bind_buffer(types) 1286 | stcheckz(stmt, C.mysql_stmt_bind_result(stmt, bb.buffer)) 1287 | return bb 1288 | end 1289 | 1290 | --publish methods 1291 | 1292 | ffi.metatype('MYSQL', {__index = conn}) 1293 | ffi.metatype('MYSQL_RES', {__index = res}) 1294 | ffi.metatype('MYSQL_STMT', {__index = stmt}) 1295 | 1296 | --publish classes (for introspection, not extending) 1297 | 1298 | M.conn = conn 1299 | M.res = res 1300 | M.stmt = stmt 1301 | M.params = params 1302 | M.fields = fields 1303 | 1304 | return M 1305 | -------------------------------------------------------------------------------- /mysql_connector.md: -------------------------------------------------------------------------------- 1 | 2 | ## `local mysql = require'mysql'` 3 | 4 | A complete, lightweight ffi binding of the mysql client library. 5 | 6 | Works with both libmysql and [libmariadb]. 7 | 8 | ## Summary 9 | 10 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 11 | **[Initialization]** 12 | `mysql.bind(['mysql'|'mariadb'|libname|clib]) -> mysql` 13 | **[Connections]** 14 | `mysql.connect(host, [user], [pass], [db], [charset], [port]) -> conn` connect to a mysql server 15 | `mysql.connect(options_t) -> conn` connect to a mysql server 16 | `conn:close()` close the connection 17 | **[Queries]** 18 | `conn:query(s)` execute a query 19 | `conn:escape(s) -> s` escape an SQL string 20 | **[Fetching results]** 21 | `conn:store_result() -> result` get a cursor for buffered read ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-store-result.html)) 22 | `conn:use_result() -> result` get a cursor for unbuffered read ([manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-use-result.html)) 23 | `result:fetch([mode[, row_t]]) -> true, v1, v2, ... | row_t | nil` fetch the next row from the result 24 | `result:rows([mode[, row_t]]) -> iterator() -> row_num, val1, val2, ...` row iterator 25 | `result:rows([mode[, row_t]]) -> iterator() -> row_num, row_t` row iterator 26 | `result:free()` free the cursor 27 | `result:row_count() -> n` number of rows 28 | `result:eof() -> true | false` check if no more rows 29 | `result:seek(row_number)` seek to row number 30 | **[Query info]** 31 | `conn:field_count() -> n` number of result fields in the executed query 32 | `conn:affected_rows() -> n` number of affected rows in the executed query 33 | `conn:insert_id() -> n` the id of the autoincrement column in the executed query 34 | `conn:errno() -> n` mysql error code (0 if no error) from the executed query 35 | `conn:sqlstate() -> s` 36 | `conn:warning_count() -> n` number of errors, warnings, and notes from executed query 37 | `conn:info() -> s` 38 | **[Field info]** 39 | `result:field_count() -> n` number of fields in the result 40 | `result:field_name(field_number) -> s` field name given field index 41 | `result:field_type(field_number) -> type, length, unsigned, decimals` field type given field index 42 | `result:field_info(field_number) -> info_t` field info table 43 | `result:fields() -> iterator() -> i, info_t` field info iterator 44 | **[Result bookmarks]** 45 | `result:tell() -> bookmark` bookmark the current row for later seek 46 | `result:seek(bookmark)` seek to a row bookmark 47 | **[Multiple statement queries]** 48 | `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)) 49 | `conn:more_results() -> true | false` are there more result sets? 50 | **[Prepared statements]** 51 | `conn:prepare(query) -> stmt` prepare a query for multiple executions 52 | `stmt:param_count() -> n` number of params 53 | `stmt:exec()` execute a prepared statement 54 | `stmt:store_result()` store all the resulted rows to the client 55 | `stmt:fetch() -> true | false | true, 'truncated'` fetch the next row 56 | `stmt:free_result()` free the current result buffers 57 | `stmt:close()` close the statement 58 | `stmt:next_result()` skip to the next result set 59 | `stmt:row_count() -> n` number of rows in the result, if the result was stored 60 | `stmt:affected_rows() -> n` number of affected rows after execution 61 | `stmt:insert_id() -> n` the id of the autoincrement column after execution 62 | `stmt:field_count() -> n` number of fields in the result after execution 63 | `stmt:errno() -> n` mysql error code, if any, from the executed statement 64 | `stmt:sqlstate() -> s` 65 | `stmt:result_metadata() -> result` get a result for accessing the field info 66 | `stmt:fields() -> iterator() -> i, info_t` iterate the result fields info 67 | `stmt:reset()` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-reset.html) 68 | `stmt:seek(row_number)` seek to row number 69 | `stmt:tell() -> bookmark` get a bookmark in the current result 70 | `stmt:seek(bookmark)` seek to a row bookmark in the current result 71 | **[Prepared statements I/O]** 72 | `stmt:bind_params(type1, ... | types_t) -> params` bind query parameters based on type definitions 73 | `params:set(i, number | int64_t | uint64_t | true | false)` set an integer, float or bit param 74 | `params:set(i, s[, size])` set a variable sized param 75 | `params:set(i, cdata, size)` set a variable sized param 76 | `params:set(i, {year=, month=, ...})` set a time/date param 77 | `params:set_date(i, [year], [month], [day], [hour], [min], [sec], [frac])` set a time/date param 78 | `stmt:write(param_number, data[, size])` send a long param in chunks 79 | `stmt:bind_result([type1, ... | types_t | maxsize]) -> fields` bind query result fields based on type definitions 80 | `fields:get(i) -> value` get the current row value of a field 81 | `fields:get_datetime(i) -> year, month, day, hour, min, sec, frac` get the value of a date/time field directly 82 | `fields:is_null(i) -> true | false` is field null? 83 | `fields:is_truncated(i) -> true | false` was field value truncated? 84 | **[Prepared statements settings]** 85 | `stmt:update_max_length() -> true | false` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) 86 | `stmt:set_update_max_length(true | false)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) 87 | `stmt:cursor_type() -> mysql.C.MYSQL_CURSOR_TYPE_*` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) 88 | `stmt:set_cursor_type('CURSOR_TYPE_...')` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) 89 | `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) 90 | `stmt:prefetch_rows() -> n` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) 91 | `stmt:set_prefetch_rows(stmt, n)` see [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) 92 | **[Connection info]** 93 | `conn:set_charset(charset)` change the current charset 94 | `conn:select_db(dbname)` change the current database 95 | `conn:change_user(user, [pass], [db])` change the current user (and database) 96 | `conn:set_multiple_statements(true | false)` enable/disable support for multiple statements 97 | `conn:charset() -> s` get current charset's name 98 | `conn:charset_info() -> info_t` get info about the current charset 99 | `conn:ping() -> true | false` check if the connection is still alive 100 | `conn:thread_id() -> id` 101 | `conn:stat() -> s` 102 | `conn:server_info() -> s` 103 | `conn:host_info() -> s` 104 | `conn:server_version() -> n` 105 | `conn:proto_info() -> n` 106 | `conn:ssl_cipher() -> s` 107 | **[Transactions]** 108 | `conn:commit()` commit the current transaction 109 | `conn:rollback()` rollback the current transaction 110 | `conn:set_autocommit([true | false])` enable/disable autocommit on the current connection 111 | **[Reflection]** 112 | `conn:list_dbs([wildcard]) -> result` return info about databases as a result object 113 | `conn:list_tables([wildcard]) -> result` return info about tables as a result object 114 | `conn:list_processes() -> result` return info about processes as a result object 115 | **[Remote control]** 116 | `conn:kill(pid)` kill a connection based on process id 117 | `conn:shutdown([level])` shutdown the server 118 | `conn:refresh(options)` flush tables or caches 119 | `conn:dump_debug_info()` dump debug info in the log file 120 | **[Client library info]** 121 | `mysql.thread_safe() -> true | false` was the client library compiled as thread-safe? 122 | `mysql.client_info() -> s` 123 | `mysql.client_version() -> n` 124 | -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- 125 | 126 | ## Features 127 | 128 | * covers all of the functionality provided by the mysql C API 129 | * all data types are supported with options for conversion 130 | * prepared statements, avoiding dynamic allocations and format conversions when fetching rows 131 | * all C calls are checked for errors and Lua errors are raised 132 | * all C objects are tied to Lua's garbage collector 133 | * lightweight OOP-style API using only `ffi.metatype` 134 | * no external dependencies 135 | 136 | ## Example 137 | 138 | ~~~{.lua} 139 | function print_help(search) 140 | local mysql = require'mysql' 141 | 142 | local conn = mysql.connect('localhost', 'root', nil, 'mysql', 'utf8') 143 | conn:query("select name, description, example from help_topic where name like '" .. 144 | conn:escape(search) .. "'") 145 | local result = conn:store_result() 146 | 147 | print('Found:') 148 | for i,name in result:rows() do 149 | print(' ' .. name) 150 | end 151 | 152 | print() 153 | for i, name, description, example in result:rows() do 154 | print(name) 155 | print'-------------------------------------------' 156 | print(description) 157 | print'Example:' 158 | print'-------------------------------------------' 159 | print(example) 160 | print() 161 | end 162 | 163 | result:free() 164 | conn:close() 165 | end 166 | 167 | print_help'CONCAT%' 168 | ~~~ 169 | 170 | ## Initialization 171 | 172 | ### `mysql.config(['mysql'|'mariadb'|libname|clib]) -> mysql` 173 | 174 | Load the mysql client library to use (default is 'mysql'). 175 | This function is called on every module-level function. 176 | Calling this function again is a no-op. 177 | 178 | ## Connections 179 | 180 | ### `mysql.connect(host, [user], [pass], [db], [charset], [port]) -> conn` 181 | ### `mysql.connect(options_t) -> conn` 182 | 183 | Connect to a mysql server, optionally selecting a working database and charset. 184 | 185 | In the second form, `options_t` is a table that besides `host`, `user`, `pass`, `db`, `charset`, `port` 186 | can have the following fields: 187 | 188 | * `unix_socket`: specify a unix socket filename to connect to 189 | * `flags`: bit field corresponding to mysql [client_flag](http://dev.mysql.com/doc/refman/5.7/en/mysql-real-connect.html) parameter 190 | * can be a table of form `{CLIENT_... = true | false, ...}`, or 191 | * a number of form `bit.bor(mysql.C.CLIENT_..., ...)` 192 | * `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) 193 | * `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) 194 | * `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) 195 | 196 | ### `conn:close()` 197 | 198 | Close a mysql connection freeing all associated resources (otherwise called when `conn` is garbage collected). 199 | 200 | ## Queries 201 | 202 | ### `conn:query(s)` 203 | 204 | Execute a query. If the query string contains multiple statements, only the first statement is executed 205 | (see the section on multiple statements). 206 | 207 | ### `conn:escape(s) -> s` 208 | 209 | Escape a value to be safely embedded in SQL queries. Assumes the current charset. 210 | 211 | ## Fetching results 212 | 213 | ### `conn:store_result() -> result` 214 | 215 | Fetch all the rows in the current result set from the server and return a result object to read them one by one. 216 | 217 | ### `conn:use_result() -> result` 218 | 219 | Return a result object that will fetch the rows in the current result set from the server on demand. 220 | 221 | ### `result:fetch([mode[, row_t]]) -> true, v1, v2, ... | row_t | nil` 222 | 223 | Fetch and return the next row of values from the current result set. Returns nil if there are no more rows to fetch. 224 | 225 | * the `mode` arg can contain any combination of the following letters: 226 | * `"n"` - return values in a table with numeric indices as keys. 227 | * `"a"` - return values in a table with field names as keys. 228 | * `"s"` - do not convert numeric and time values to Lua types. 229 | * the `row_t` arg is an optional table to store the row values in, instead of creating a new one on each fetch. 230 | * options "a" and "n" can be combined to get a table with both numeric and field name indices. 231 | * if `mode` is missing or if neither "a" nor "n" is specified, the values 232 | are returned to the caller unpacked, after a first value that is always 233 | true, to make it easy to distinguish between a valid `NULL` value in the 234 | first column and eof. 235 | * 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()`. 236 | * in "a" mode, for fields with duplicate names only the last field will be present. 237 | * if `mode` does not specify `"s"`, the following conversions are applied on the returned values: 238 | * integer types are returned as Lua numbers, except bigint which is returned as an `int64_t` cdata (or `uint64` if unsigned). 239 | * date/time types are returned as tables in the usual `os.date"*t"` format (date fields are missing for time-only types and viceversa). 240 | * decimal/numeric types are returned as Lua strings. 241 | * bit types are returned as Lua numbers, and as `uint64_t` for bit types larger than 48 bits. 242 | * enum and set types are always returned as strings. 243 | 244 | ### `result:rows([mode[, row_t]]) -> iterator() -> row_num, val1, val2, ...` 245 | ### `result:rows([mode[, row_t]]) -> iterator() -> row_num, row_t` 246 | 247 | Convenience iterator for fetching (or refetching) all the rows from the current result set. The `mode` arg 248 | is the same as for `result:fetch()`, with the exception that in unpacked mode, the first `true` value is not present. 249 | 250 | ### `result:free()` 251 | 252 | Free the result buffer (otherwise called when `result` is garbage collected). 253 | 254 | ### `result:row_count() -> n` 255 | 256 | Return the number of rows in the current result set . This value is only correct if `result:store_result()` was 257 | previously called or if all the rows were fetched, in other words if `result:eof()` is true. 258 | 259 | ### `result:eof() -> true | false` 260 | 261 | Check if there are no more rows to fetch. If `result:store_result()` was previously called, then all rows were 262 | already fetched, so `result:eof()` always returns `true` in this case. 263 | 264 | ### `result:seek(row_number)` 265 | 266 | Seek back to a particular row number to refetch the rows from there. 267 | 268 | ## Query info 269 | 270 | ### `conn:field_count() -> n` 271 | ### `conn:affected_rows() -> n` 272 | ### `conn:insert_id() -> n` 273 | ### `conn:errno() -> n` 274 | ### `conn:sqlstate() -> s` 275 | ### `conn:warning_count() -> n` 276 | ### `conn:info() -> s` 277 | 278 | Return various pieces of information about the previously executed query. 279 | 280 | ## Field info 281 | 282 | ### `result:field_count() -> n` 283 | ### `result:field_name(field_number) -> s` 284 | ### `result:field_type(field_number) -> type, length, decimals, unsigned` 285 | ### `result:field_info(field_number) -> info_t` 286 | ### `result:fields() -> iterator() -> i, info_t` 287 | 288 | Return information about the fields (columns) in the current result set. 289 | 290 | ## Result bookmarks 291 | 292 | ### `result:tell() -> bookmark` 293 | 294 | Get a bookmark to the current row to be later seeked into with `seek()`. 295 | 296 | ### `result:seek(bookmark)` 297 | 298 | Seek to a previous saved row bookmark, or to a specific row number, fetching more rows as needed. 299 | 300 | ## Multiple statement queries 301 | 302 | ### `conn:next_result() -> true | false` 303 | 304 | Skip over to the next result set in a multiple statement query, and make that the current result set. 305 | Return true if there more result sets after this one. 306 | 307 | ### `conn:more_results() -> true | false` 308 | 309 | Check if there are more result sets after this one. 310 | 311 | ## Prepared statements 312 | 313 | Prepared statements are a way to run queries and retrieve results more efficiently from the database, in particular: 314 | 315 | * parametrized queries allow sending query parameters in their native format, avoiding having to convert values into strings and escaping those strings. 316 | * 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. 317 | * fetching the result rows in preallocated buffers avoids dynamic allocation on each row fetch. 318 | 319 | The flow for prepared statements is like this: 320 | 321 | * call `conn:prepare()` to prepare a query and get a statement object. 322 | * call `stmt:bind_params()` and `stmt:bind_result()` to get the buffer objects for setting params and getting row values. 323 | * run the query multiple times; each time: 324 | * call `params:set()` for each param to set param values. 325 | * call `stmt:exec()` to run the query. 326 | * fetch the resulting rows one by one; for each row: 327 | * call `stmt:fetch()` to get the next row (it returns false if it was the last row). 328 | * call `fields:get()` to read the values of the fetched row. 329 | * call `stmt:close()` to free the statement object and all the associated resources from the server and client. 330 | 331 | ### `conn:prepare(query) -> stmt, params` 332 | 333 | Prepare a query for multiple execution and return a statement object. 334 | 335 | ### `stmt:param_count() -> n` 336 | 337 | Number of parameters. 338 | 339 | ### `stmt:exec()` 340 | 341 | Execute a prepared statement. 342 | 343 | ### `stmt:store_result()` 344 | 345 | Fetch all the rows in the current result set from the server, otherwise the rows are fetched on demand. 346 | 347 | ### `stmt:fetch() -> true | false | true, 'truncated'` 348 | 349 | Fetch the next row from the current result set. Use a binding buffer (see prepared statements I/O section) 350 | to get the row values. If present, second value indicates that at least one of the rows were truncated because 351 | the receiving buffer was too small for it. 352 | 353 | ### `stmt:free_result()` 354 | 355 | Free the current result and all associated resources (otherwise the result is closed when the statement is closed). 356 | 357 | ### `stmt:close()` 358 | 359 | Close a prepared statement and free all associated resources (otherwise the statement is closed when garbage collected). 360 | 361 | ### `stmt:next_result()` 362 | 363 | Skip over to the next result set in a multiple statement query. 364 | 365 | ### `stmt:row_count() -> n` 366 | ### `stmt:affected_rows() -> n` 367 | ### `stmt:insert_id() -> n` 368 | ### `stmt:field_count() -> n` 369 | ### `stmt:errno() -> n` 370 | ### `stmt:sqlstate() -> s` 371 | ### `stmt:result_metadata() -> result` 372 | ### `stmt:fields() -> iterator() -> i, info_t` 373 | 374 | Return various pieces of information on the executed statement. 375 | 376 | ### `stmt:reset()` 377 | 378 | See [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-reset.html). 379 | 380 | ### `stmt:seek(row_number)` 381 | ### `stmt:tell() -> bookmark` 382 | ### `stmt:seek(bookmark)` 383 | 384 | Seek into the current result set. 385 | 386 | ## Prepared statements I/O 387 | 388 | ### `stmt:bind_params(type1, ... | types_t) -> params` 389 | 390 | Bind query parameters according to a list of type definitions (which can be given either packed or unpacked). 391 | Return a binding buffer object to be used for setting parameters. 392 | 393 | The types must be valid, fully specified SQL types, eg. 394 | 395 | * `smallint unsigned` specifies a 16bit unsigned integer 396 | * `bit(32)` specifies a 32bit bit field 397 | * `varchar(200)` specifies a 200 byte varchar. 398 | 399 | ### `params:set(i, number | int64_t | uint64_t | true | false)` 400 | ### `params:set(i, s[, size])` 401 | ### `params:set(i, cdata, size)` 402 | ### `params:set(i, {year=, month=, ...})` 403 | ### `params:set_date(i, [year], [month], [day], [hour], [min], [sec], [frac])` 404 | 405 | Set a parameter value. 406 | 407 | * the first form is for setting integers and bit fields. 408 | * the second and third forms are for setting variable-sized fields and decimal/numeric fields. 409 | * the last forms are for setting date/time/datetime/timestamp fields. 410 | * the null type cannot be set (raises an error if attempted). 411 | 412 | ### `stmt:write(param_number, data[, size])` 413 | 414 | Send a parameter value in chunks (for long, var-sized values). 415 | 416 | ### `stmt:bind_result([type1, ... | types_t | maxsize]) -> fields` 417 | 418 | Bind result fields according to a list of type definitions (same as for params). 419 | Return a binding buffer object to be used for getting row values. 420 | If no types are specified, appropriate type definitions will be created automatically as to minimize type conversions. 421 | Variable-sized fields will get a buffer sized according to data type's maximum allowed size 422 | and `maxsize` (which defaults to 64k). 423 | 424 | ### `fields:get(i) -> value` 425 | ### `fields:get_datetime(i) -> year, month, day, hour, min, sec, frac` 426 | 427 | Get a row value from the last fetched row. The same type conversions as for `result:fetch()` apply. 428 | 429 | ### `fields:is_null(i) -> true | false` 430 | 431 | Check if a value is null without having to get it if it's not. 432 | 433 | ### `fields:is_truncated(i) -> true | false` 434 | 435 | Check if a value was truncated due to insufficient buffer space. 436 | 437 | ### `stmt:bind_result_types([maxsize]) -> types_t` 438 | 439 | Return the list of type definitions that describe the result of a prepared statement. 440 | 441 | ## Prepared statements settings 442 | 443 | ### `stmt:update_max_length() -> true | false` 444 | ### `stmt:set_update_max_length(true | false)` 445 | ### `stmt:cursor_type() -> mysql.C.MYSQL_CURSOR_TYPE_*` 446 | ### `stmt:set_cursor_type('CURSOR_TYPE_...')` 447 | ### `stmt:set_cursor_type(mysql.C.MYSQL_CURSOR_TYPE_...)` 448 | ### `stmt:prefetch_rows() -> n` 449 | ### `stmt:set_prefetch_rows(stmt, n)` 450 | 451 | See [manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-attr-set.html) for these. 452 | 453 | ## Connection info 454 | 455 | ### `conn:set_charset(charset)` 456 | 457 | Change the current charset. 458 | 459 | ### `conn:select_db(dbname)` 460 | 461 | Change the current database. 462 | 463 | ### `conn:change_user(user, [pass], [db])` 464 | 465 | Change the current user and optionally select a database. 466 | 467 | ### `conn:set_multiple_statements(true | false)` 468 | 469 | Enable or disable support for query strings containing multiple statements separated by a semi-colon. 470 | 471 | ### `conn:charset() -> s` 472 | 473 | Get the current charset. 474 | 475 | ### `conn:charset_info() -> info_t` 476 | 477 | Return a table of information about the current charset. 478 | 479 | ### `conn:ping() -> true | false` 480 | 481 | Check if the connection to the server is still alive. 482 | 483 | ### `conn:thread_id() -> id` 484 | ### `conn:stat() -> s` 485 | ### `conn:server_info() -> s` 486 | ### `conn:host_info() -> s` 487 | ### `conn:server_version() -> n` 488 | ### `conn:proto_info() -> n` 489 | ### `conn:ssl_cipher() -> s` 490 | 491 | Return various pieces of information about the connection and server. 492 | 493 | ## Transactions 494 | 495 | ### `conn:commit()` 496 | ### `conn:rollback()` 497 | 498 | Commit/rollback the current transaction. 499 | 500 | ### `conn:set_autocommit([true | false])` 501 | 502 | Set autocommit on the connection (set to true if no argument is given). 503 | 504 | ## Reflection 505 | 506 | ### `conn:list_dbs([wildcard]) -> result` 507 | ### `conn:list_tables([wildcard]) -> result` 508 | ### `conn:list_processes() -> result` 509 | 510 | Return information about databases, tables and proceses as a stored result object that can be iterated etc. 511 | using the methods of result objects. The optional `wild` parameter may contain the wildcard characters 512 | `"%"` or `"_"`, similar to executing the query `SHOW DATABASES [LIKE wild]`. 513 | 514 | ## Remote control 515 | 516 | ### `conn:kill(pid)` 517 | 518 | Kill a connection with a specific `pid`. 519 | 520 | ### `conn:shutdown([level])` 521 | 522 | Shutdown the server. `SHUTDOWN` priviledge needed. The level argument is reserved for future versions of mysql. 523 | 524 | ### `conn:refresh(options)` 525 | 526 | Flush tables or caches, or resets replication server information. `RELOAD` priviledge needed. Options are either 527 | a table of form `{REFRESH_... = true | false, ...}` or a number of form `bit.bor(mysql.C.MYSQL_REFRESH_*, ...)` and 528 | they are as described in the [mysql manual](http://dev.mysql.com/doc/refman/5.7/en/mysql-refresh.html). 529 | 530 | ### `conn:dump_debug_info()` 531 | 532 | Instruct the server to dump debug info in the log file. `SUPER` priviledge needed. 533 | 534 | ## Client library info 535 | 536 | ### `mysql.thread_safe() -> true | false` 537 | ### `mysql.client_info() -> s` 538 | ### `mysql.client_version() -> n` 539 | 540 | ---- 541 | 542 | ## TODO 543 | 544 | * reader function for getting large blobs in chunks using 545 | mysql_stmt_fetch_column: `stmt:chunks(i[, bufsize])` or `stmt:read()` ? 546 | -------------------------------------------------------------------------------- /mysql_connector_print.lua: -------------------------------------------------------------------------------- 1 | --mysql table pretty printing 2 | 3 | if not ... then require'mysql_test'; return end 4 | 5 | local function ellipsis(s,n) 6 | return #s > n and (s:sub(1,n-3) .. '...') or s 7 | end 8 | 9 | local align = {} 10 | 11 | function align.left(s,n) 12 | s = s..(' '):rep(n - #s) 13 | return ellipsis(s,n) 14 | end 15 | 16 | function align.right(s,n) 17 | s = (' '):rep(n - #s)..s 18 | return ellipsis(s,n) 19 | end 20 | 21 | function align.center(s,n) 22 | local total = n - #s 23 | local left = math.floor(total / 2) 24 | local right = math.ceil(total / 2) 25 | s = (' '):rep(left)..s..(' '):rep(right) 26 | return ellipsis(s,n) 27 | end 28 | 29 | local function fit(s,n,al) 30 | return align[al or 'left'](s,n) 31 | end 32 | 33 | local function print_table(fields, rows, aligns, minsize, print) 34 | print = print or _G.print 35 | minsize = minsize or 0 36 | local max_sizes = {} 37 | for i=1,#rows do 38 | for j=1,#fields do 39 | max_sizes[j] = math.max(max_sizes[j] or minsize, #rows[i][j]) 40 | end 41 | end 42 | 43 | local totalsize = 0 44 | for j=1,#fields do 45 | max_sizes[j] = math.max(max_sizes[j] or minsize, #fields[j]) 46 | totalsize = totalsize + max_sizes[j] + 3 47 | end 48 | 49 | print() 50 | local s, ps = '', '' 51 | for j=1,#fields do 52 | s = s .. fit(fields[j], max_sizes[j], 'center') .. ' | ' 53 | ps = ps .. ('-'):rep(max_sizes[j]) .. ' + ' 54 | end 55 | print(s) 56 | print(ps) 57 | 58 | for i=1,#rows do 59 | local s = '' 60 | for j=1,#fields do 61 | local val = rows[i][j] 62 | s = s .. fit(val, max_sizes[j], aligns and aligns[j]) .. ' | ' 63 | end 64 | print(s) 65 | end 66 | print() 67 | end 68 | 69 | local function invert_table(fields, rows, minsize) 70 | local ft, rt = {'field'}, {} 71 | for i=1,#rows do 72 | ft[i+1] = tostring(i) 73 | end 74 | for j=1,#fields do 75 | local row = {fields[j]} 76 | for i=1,#rows do 77 | row[i+1] = rows[i][j] 78 | end 79 | rt[j] = row 80 | end 81 | return ft, rt 82 | end 83 | 84 | local function format_cell(v) 85 | if v == nil then 86 | return 'NULL' 87 | else 88 | return tostring(v) 89 | end 90 | end 91 | 92 | local function cell_align(current_align, cell_value) 93 | if current_align == 'left' then return 'left' end 94 | if type(cell_value) == 'number' or type(cell_value) == 'cdata' then return 'right' end 95 | return 'left' 96 | end 97 | 98 | local function print_result(res, minsize, print) 99 | local fields = {} 100 | for i,field in res:fields() do 101 | fields[i] = field.name 102 | end 103 | local rows = {} 104 | local aligns = {} --deduced from values 105 | for i,row in res:rows'n' do 106 | local t = {} 107 | for j=1,#fields do 108 | t[j] = format_cell(row[j]) 109 | aligns[j] = cell_align(aligns[j], row[j]) 110 | end 111 | rows[i] = t 112 | end 113 | print_table(fields, rows, aligns, minsize, print) 114 | end 115 | 116 | local function print_statement(stmt, minsize, print) 117 | local res = stmt:bind_result() 118 | local fields = {} 119 | for i,field in stmt:fields() do 120 | fields[i] = field.name 121 | end 122 | local rows = {} 123 | local aligns = {} 124 | while stmt:fetch() do 125 | local row = {} 126 | for i=1,#fields do 127 | local v = res:get(i) 128 | row[i] = format_cell(v) 129 | aligns[i] = cell_align(aligns[i], v) 130 | end 131 | rows[#rows+1] = row 132 | end 133 | stmt:close() 134 | print_table(fields, rows, aligns, minsize, print) 135 | end 136 | 137 | return { 138 | fit = fit, 139 | format_cell = format_cell, 140 | cell_align = cell_align, 141 | table = print_table, 142 | result = print_result, 143 | statement = print_statement, 144 | } 145 | 146 | -------------------------------------------------------------------------------- /mysql_connector_test.lua: -------------------------------------------------------------------------------- 1 | --mysql test unit (see comments for problems with libmariadb) 2 | --NOTE: create a database called 'test' first to run these tests! 3 | local mysql = require'mysql_connector' 4 | local glue = require'glue' 5 | local pp = require'pp' 6 | local myprint = require'mysql_connector_print' 7 | local ffi = require'ffi' 8 | 9 | mysql.bind'mariadb' 10 | 11 | --helpers 12 | 13 | local print_table = myprint.table 14 | local print_result = myprint.result 15 | local fit = myprint.fit 16 | 17 | local function assert_deepequal(t1, t2) --assert the equality of two values 18 | assert(type(t1) == type(t2), type(t1)..' ~= '..type(t2)) 19 | if type(t1) == 'table' then 20 | for k,v in pairs(t1) do assert_deepequal(t2[k], v) end 21 | for k,v in pairs(t2) do assert_deepequal(t1[k], v) end 22 | else 23 | assert(t1 == t2, pp.format(t1) .. ' ~= ' .. pp.format(t2)) 24 | end 25 | end 26 | 27 | local function print_fields(fields_iter) 28 | local fields = {'name', 'type', 'type_flag', 'length', 'max_length', 'decimals', 'charsetnr', 29 | 'org_name', 'table', 'org_table', 'db', 'catalog', 'def', 'extension'} 30 | local rows = {} 31 | local aligns = {} 32 | for i,field in fields_iter do 33 | rows[i] = {} 34 | for j=1,#fields do 35 | local v = field[fields[j]] 36 | rows[i][j] = tostring(v) 37 | aligns[j] = type(v) == 'number' and 'right' or 'left' 38 | end 39 | end 40 | print_table(fields, rows, aligns) 41 | end 42 | 43 | --client library 44 | 45 | print('mysql.thread_safe() ', '->', pp.format(mysql.thread_safe())) 46 | print('mysql.client_info() ', '->', pp.format(mysql.client_info())) 47 | print('mysql.client_version()', '->', pp.format(mysql.client_version())) 48 | 49 | --connections 50 | 51 | local t = { 52 | host = 'localhost', 53 | user = 'root', 54 | db = 'test', 55 | options = { 56 | MYSQL_SECURE_AUTH = false, --not supported by libmariadb 57 | MYSQL_OPT_READ_TIMEOUT = 1, 58 | }, 59 | flags = { 60 | CLIENT_LONG_PASSWORD = true, 61 | }, 62 | } 63 | local conn = mysql.connect(t) 64 | print('mysql.connect ', pp.format(t, ' '), '->', conn) 65 | print('conn:change_user( ', pp.format(t.user), ')', conn:change_user(t.user)) 66 | print('conn:select_db( ', pp.format(t.db), ')', conn:select_db(t.db)) 67 | print('conn:set_multiple_statements(', pp.format(true), ')', conn:set_multiple_statements(true)) 68 | print('conn:set_charset( ', pp.format('utf8'), ')', conn:set_charset('utf8')) 69 | 70 | --conn info 71 | 72 | print('conn:charset_name() ', '->', pp.format(conn:charset())); assert(conn:charset() == 'utf8') 73 | print('conn:charset_info() ', '->', pp.format(conn:charset_info(), ' ')) --crashes libmariadb 74 | print('conn:ping() ', '->', pp.format(conn:ping())) 75 | print('conn:thread_id() ', '->', pp.format(conn:thread_id())) 76 | print('conn:stat() ', '->', pp.format(conn:stat())) 77 | print('conn:server_info() ', '->', pp.format(conn:server_info())) 78 | print('conn:host_info() ', '->', pp.format(conn:host_info())) 79 | print('conn:server_version() ', '->', pp.format(conn:server_version())) 80 | print('conn:proto_info() ', '->', pp.format(conn:proto_info())) 81 | print('conn:ssl_cipher() ', '->', pp.format(conn:ssl_cipher())) 82 | 83 | --transactions 84 | 85 | print('conn:commit() ', conn:commit()) 86 | print('conn:rollback() ', conn:rollback()) 87 | print('conn:set_autocommit() ', conn:set_autocommit(true)) 88 | 89 | --test types and values 90 | 91 | local test_fields = { 92 | 'fdecimal', 93 | 'fnumeric', 94 | 'ftinyint', 95 | 'futinyint', 96 | 'fsmallint', 97 | 'fusmallint', 98 | 'finteger', 99 | 'fuinteger', 100 | 'ffloat', 101 | 'fdouble', 102 | 'fdouble2', 103 | 'fdouble3', 104 | 'fdouble4', 105 | 'freal', 106 | 'fbigint', 107 | 'fubigint', 108 | 'fmediumint', 109 | 'fumediumint', 110 | 'fdate', 111 | 'ftime', 112 | 'ftime2', 113 | 'fdatetime', 114 | 'fdatetime2', 115 | 'ftimestamp', 116 | 'ftimestamp2', 117 | 'fyear', 118 | 'fbit2', 119 | 'fbit22', 120 | 'fbit64', 121 | 'fenum', 122 | 'fset', 123 | 'ftinyblob', 124 | 'fmediumblob', 125 | 'flongblob', 126 | 'ftext', 127 | 'fblob', 128 | 'fvarchar', 129 | 'fvarbinary', 130 | 'fchar', 131 | 'fbinary', 132 | 'fnull', 133 | } 134 | 135 | local field_indices = glue.index(test_fields) 136 | 137 | local field_types = { 138 | fdecimal = 'decimal(8,2)', 139 | fnumeric = 'numeric(6,4)', 140 | ftinyint = 'tinyint', 141 | futinyint = 'tinyint unsigned', 142 | fsmallint = 'smallint', 143 | fusmallint = 'smallint unsigned', 144 | finteger = 'int', 145 | fuinteger = 'int unsigned', 146 | ffloat = 'float', 147 | fdouble = 'double', 148 | fdouble2 = 'double', 149 | fdouble3 = 'double', 150 | fdouble4 = 'double', 151 | freal = 'real', 152 | fbigint = 'bigint', 153 | fubigint = 'bigint unsigned', 154 | fmediumint = 'mediumint', 155 | fumediumint = 'mediumint unsigned', 156 | fdate = 'date', 157 | ftime = 'time(0)', 158 | ftime2 = 'time(6)', 159 | fdatetime = 'datetime(0)', 160 | fdatetime2 = 'datetime(6)', 161 | ftimestamp = 'timestamp(0) null', 162 | ftimestamp2 = 'timestamp(6) null', 163 | fyear = 'year', 164 | fbit2 = 'bit(2)', 165 | fbit22 = 'bit(22)', 166 | fbit64 = 'bit(64)', 167 | fenum = "enum('yes', 'no')", 168 | fset = "set('e1', 'e2', 'e3')", 169 | ftinyblob = 'tinyblob', 170 | fmediumblob = 'mediumblob', 171 | flongblob = 'longblob', 172 | ftext = 'text', 173 | fblob = 'blob', 174 | fvarchar = 'varchar(200)', 175 | fvarbinary = 'varbinary(200)', 176 | fchar = 'char(200)', 177 | fbinary = 'binary(20)', 178 | fnull = 'int' 179 | } 180 | 181 | local test_values = { 182 | fdecimal = '42.12', 183 | fnumeric = '42.1234', 184 | ftinyint = 42, 185 | futinyint = 255, 186 | fsmallint = 42, 187 | fusmallint = 65535, 188 | finteger = 42, 189 | fuinteger = 2^32-1, 190 | ffloat = tonumber(ffi.cast('float', 42.33)), 191 | fdouble = 42.33, 192 | fdouble2 = nil, --null from mysql 5.1.24+ 193 | fdouble3 = nil, --null from mysql 5.1.24+ 194 | fdouble4 = nil, --null from mysql 5.1.24+ 195 | freal = 42.33, 196 | fbigint = 420LL, 197 | fubigint = 0ULL - 1, 198 | fmediumint = 440, 199 | fumediumint = 2^24-1, 200 | fdate = {year = 2013, month = 10, day = 05}, 201 | ftime = {hour = 21, min = 30, sec = 15, frac = 0}, 202 | ftime2 = {hour = 21, min = 30, sec = 16, frac = 123456}, 203 | fdatetime = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 17, frac = 0}, 204 | fdatetime2 = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 18, frac = 123456}, 205 | ftimestamp = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 19, frac = 0}, 206 | ftimestamp2 = {year = 2013, month = 10, day = 05, hour = 21, min = 30, sec = 20, frac = 123456}, 207 | fyear = 2013, 208 | fbit2 = 2, 209 | fbit22 = 2 * 2^8 + 2, 210 | fbit64 = 2ULL * 2^(64-8) + 2 * 2^8 + 2, 211 | fenum = 'yes', 212 | fset = 'e2,e3', 213 | ftinyblob = 'tiny tiny blob', 214 | fmediumblob = 'medium blob', 215 | flongblob = 'loong blob', 216 | ftext = 'just a text', 217 | fblob = 'bloob', 218 | fvarchar = 'just a varchar', 219 | fvarbinary = 'a varbinary', 220 | fchar = 'a char', 221 | fbinary = 'a binary char\0\0\0\0\0\0\0', 222 | fnull = nil, 223 | } 224 | 225 | local set_values = { 226 | fdecimal = "'42.12'", 227 | fnumeric = "42.1234", 228 | ftinyint = "'42'", 229 | futinyint = "'255'", 230 | fsmallint = "42", 231 | fusmallint = "65535", 232 | finteger = "'42'", 233 | fuinteger = tostring(2^32-1), 234 | ffloat = "42.33", 235 | fdouble = "'42.33'", 236 | fdouble2 = "0/0", 237 | fdouble3 = "1/0", 238 | fdouble4 = "-1/0", 239 | freal = "42.33", 240 | fbigint = "'420'", 241 | fubigint = tostring(0ULL-1):sub(1,-4), --remove 'ULL' 242 | fmediumint = "440", 243 | fumediumint = tostring(2^24-1), 244 | fdate = "'2013-10-05'", 245 | ftime = "'21:30:15'", 246 | ftime2 = "'21:30:16.123456'", 247 | fdatetime = "'2013-10-05 21:30:17'", 248 | fdatetime2 = "'2013-10-05 21:30:18.123456'", 249 | ftimestamp = "'2013-10-05 21:30:19'", 250 | ftimestamp2 = "'2013-10-05 21:30:20.123456'", 251 | fyear = "2013", 252 | fbit2 = "b'10'", 253 | fbit22 = "b'1000000010'", 254 | fbit64 = "b'0000001000000000000000000000000000000000000000000000001000000010'", 255 | fenum = "'yes'", 256 | fset = "('e3,e2')", 257 | ftinyblob = "'tiny tiny blob'", 258 | fmediumblob = "'medium blob'", 259 | flongblob = "'loong blob'", 260 | ftext = "'just a text'", 261 | fblob = "'bloob'", 262 | fvarchar = "'just a varchar'", 263 | fvarbinary = "'a varbinary'", 264 | fchar = "'a char'", 265 | fbinary = "'a binary char'", 266 | fnull = "null" 267 | } 268 | 269 | local bind_types = { 270 | fdecimal = 'decimal(20)', --TODO: truncation 271 | fnumeric = 'numeric(20)', 272 | ftinyint = 'tinyint', 273 | futinyint = 'tinyint unsigned', 274 | fsmallint = 'smallint', 275 | fusmallint = 'smallint unsigned', 276 | finteger = 'int', 277 | fuinteger = 'int unsigned', 278 | ffloat = 'float', 279 | fdouble = 'double', 280 | fdouble2 = 'double', 281 | fdouble3 = 'double', 282 | fdouble4 = 'double', 283 | freal = 'real', 284 | fbigint = 'bigint', 285 | fubigint = 'bigint unsigned', 286 | fmediumint = 'mediumint', 287 | fumediumint = 'mediumint unsigned', 288 | fdate = 'date', 289 | ftime = 'time', 290 | ftime2 = 'time', 291 | fdatetime = 'datetime', 292 | fdatetime2 = 'datetime', 293 | ftimestamp = 'timestamp', 294 | ftimestamp2 = 'timestamp', 295 | fyear = 'year', 296 | fbit2 = 'bit(2)', 297 | fbit22 = 'bit(22)', 298 | fbit64 = 'bit(64)', 299 | fenum = 'enum(200)', 300 | fset = 'set(200)', 301 | ftinyblob = 'tinyblob(200)', 302 | fmediumblob = 'mediumblob(200)', 303 | flongblob = 'longblob(200)', 304 | ftext = 'text(200)', 305 | fblob = 'blob(200)', 306 | fvarchar = 'varchar(200)', 307 | fvarbinary = 'varbinary(200)', 308 | fchar = 'char(200)', 309 | fbinary = 'binary(200)', 310 | fnull = 'int', 311 | } 312 | 313 | --queries 314 | 315 | local esc = "'escape me'" 316 | print('conn:escape( ', pp.format(esc), ')', '->', pp.format(conn:escape(esc))) 317 | local q1 = 'drop table if exists binding_test' 318 | print('conn:query( ', pp.format(q1), ')', conn:query(q1)) 319 | 320 | local field_defs = '' 321 | for i,field in ipairs(test_fields) do 322 | field_defs = field_defs .. field .. ' ' .. field_types[field] .. (i == #test_fields and '' or ', ') 323 | end 324 | 325 | local field_sets = '' 326 | for i,field in ipairs(test_fields) do 327 | field_sets = field_sets .. field .. ' = ' .. set_values[field] .. (i == #test_fields and '' or ', ') 328 | end 329 | 330 | conn:query([[ 331 | create table binding_test ( ]] .. field_defs .. [[ ); 332 | 333 | insert into binding_test set ]] .. field_sets .. [[ ; 334 | 335 | insert into binding_test values (); 336 | 337 | select * from binding_test; 338 | ]]) 339 | 340 | --query info 341 | 342 | print('conn:field_count() ', '->', pp.format(conn:field_count())) 343 | print('conn:affected_rows() ', '->', pp.format(conn:affected_rows())) 344 | print('conn:insert_id() ', '->', conn:insert_id()) 345 | print('conn:errno() ', '->', pp.format(conn:errno())) 346 | print('conn:sqlstate() ', '->', pp.format(conn:sqlstate())) 347 | print('conn:warning_count() ', '->', pp.format(conn:warning_count())) 348 | print('conn:info() ', '->', pp.format(conn:info())) 349 | for i=1,3 do 350 | print('conn:more_results() ', '->', pp.format(conn:more_results())); assert(conn:more_results()) 351 | print('conn:next_result() ', '->', pp.format(conn:next_result())) 352 | end 353 | assert(not conn:more_results()) 354 | 355 | --query results 356 | 357 | local res = conn:store_result() --TODO: local res = conn:use_result() 358 | print('conn:store_result() ', '->', res) 359 | print('res:row_count() ', '->', pp.format(res:row_count())); assert(res:row_count() == 2) 360 | print('res:field_count() ', '->', pp.format(res:field_count())); assert(res:field_count() == #test_fields) 361 | print('res:eof() ', '->', pp.format(res:eof())); assert(res:eof() == true) 362 | print('res:fields() ', '->') print_fields(res:fields()) 363 | print('res:field_info(1) ', '->', pp.format(res:field_info(1))) 364 | 365 | --first row: fetch as array and test values 366 | local row = assert(res:fetch'n') 367 | print("res:fetch'n' ", '->', pp.format(row)) 368 | for i,field in res:fields() do 369 | assert_deepequal(row[i], test_values[field.name]) 370 | end 371 | 372 | --first row again: fetch as assoc. array and test values 373 | print('res:seek(1) ', '->', res:seek(1)) 374 | local row = assert(res:fetch'a') 375 | print("res:fetch'a' ", '->', pp.format(row)) 376 | for i,field in res:fields() do 377 | assert_deepequal(row[field.name], test_values[field.name]) 378 | end 379 | 380 | --first row again: fetch unpacked and test values 381 | print('res:seek(1) ', '->', res:seek(1)) 382 | local function pack(_, ...) 383 | local t = {} 384 | for i=1,select('#', ...) do 385 | t[i] = select(i, ...) 386 | end 387 | return t 388 | end 389 | local row = pack(res:fetch()) 390 | print("res:fetch() ", '-> packed: ', pp.format(row)) 391 | for i,field in res:fields() do 392 | assert_deepequal(row[i], test_values[field.name]) 393 | end 394 | 395 | --first row again: print its values parsed and unparsed for comparison 396 | res:seek(1) 397 | local row = assert(res:fetch'n') 398 | res:seek(1) 399 | local row_s = assert(res:fetch'ns') 400 | print() 401 | print(fit('', 4, 'right') .. ' ' .. fit('field', 20) .. fit('unparsed', 40) .. ' ' .. 'parsed') 402 | print(('-'):rep(4 + 2 + 20 + 40 + 40)) 403 | for i,field in res:fields() do 404 | print(fit(tostring(i), 4, 'right') .. ' ' .. fit(field.name, 20) .. fit(pp.format(row_s[i]), 40) .. ' ' .. pp.format(row[i])) 405 | end 406 | print() 407 | 408 | --second row: all nulls 409 | local row = assert(res:fetch'n') 410 | print("res:fetch'n' ", '->', pp.format(row)) 411 | assert(#row == 0) 412 | for i=1,res:field_count() do 413 | assert(row[i] == nil) 414 | end 415 | assert(not res:fetch'n') 416 | 417 | --all rows again: test iterator 418 | res:seek(1) 419 | local n = 0 420 | for i,row in res:rows'nas' do 421 | n = n + 1 422 | assert(i == n) 423 | end 424 | print("for i,row in res:rows'nas' do ", '->', n); assert(n == 2) 425 | 426 | print('res:free() ', res:free()) 427 | 428 | --reflection 429 | 430 | print('res:list_dbs() ', '->'); print_result(conn:list_dbs()) 431 | print('res:list_tables() ', '->'); print_result(conn:list_tables()) 432 | print('res:list_processes() ', '->'); print_result(conn:list_processes()) 433 | 434 | --prepared statements 435 | 436 | local query = 'select '.. table.concat(test_fields, ', ')..' from binding_test' 437 | local stmt = conn:prepare(query) 438 | 439 | print('conn:prepare( ', pp.format(query), ')', '->', stmt) 440 | print('stmt:field_count() ', '->', pp.format(stmt:field_count())); assert(stmt:field_count() == #test_fields) 441 | --we can get the fields and their types before execution so we can create create our bind structures. 442 | --max. length is not computed though, but length is, so we can use that. 443 | print('stmt:fields() ', '->'); print_fields(stmt:fields()) 444 | 445 | --binding phase 446 | 447 | local btypes = {} 448 | for i,field in ipairs(test_fields) do 449 | btypes[i] = bind_types[field] 450 | end 451 | local bind = stmt:bind_result(btypes) 452 | print('stmt:bind_result( ', pp.format(btypes), ')', '->', pp.format(bind)) 453 | 454 | --execution and loading 455 | 456 | print('stmt:exec() ', stmt:exec()) 457 | print('stmt:store_result() ', stmt:store_result()) 458 | 459 | --result info 460 | 461 | print('stmt:row_count() ', '->', pp.format(stmt:row_count())) 462 | print('stmt:affected_rows() ', '->', pp.format(stmt:affected_rows())) 463 | print('stmt:insert_id() ', '->', pp.format(stmt:insert_id())) 464 | print('stmt:sqlstate() ', '->', pp.format(stmt:sqlstate())) 465 | 466 | --result data (different API since we don't get a result object) 467 | 468 | print('stmt:fetch() ', stmt:fetch()) 469 | 470 | print('stmt:fields() ', '->'); print_fields(stmt:fields()) 471 | 472 | print('bind:is_truncated(1) ', '->', pp.format(bind:is_truncated(1))); assert(bind:is_truncated(1) == false) 473 | print('bind:is_null(1) ', '->', pp.format(bind:is_null(1))); assert(bind:is_null(1) == false) 474 | print('bind:get(1) ', '->', pp.format(bind:get(1))); assert(bind:get(1) == test_values.fdecimal) 475 | local i = field_indices.fdate 476 | print('bind:get_date( ', i, ')', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5}) 477 | local i = field_indices.ftime 478 | print('bind:get_date( ', i, ')', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {nil, nil, nil, 21, 30, 15, 0}) 479 | local i = field_indices.fdatetime 480 | print('bind:get_date( ', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5, 21, 30, 17, 0}) 481 | local i = field_indices.ftimestamp 482 | print('bind:get_date( ', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5, 21, 30, 19, 0}) 483 | local i = field_indices.ftimestamp2 484 | print('bind:get_date( ', '->', bind:get_date(i)); assert_deepequal({bind:get_date(i)}, {2013, 10, 5, 21, 30, 20, 123456}) 485 | print('for i=1,bind.field_count do bind:get(i)', '->') 486 | 487 | local function print_bind_buffer(bind) 488 | print() 489 | for i,field in ipairs(test_fields) do 490 | local v = bind:get(i) 491 | assert_deepequal(v, test_values[field]) 492 | assert(bind:is_truncated(i) == false) 493 | assert(bind:is_null(i) == (test_values[field] == nil)) 494 | print(fit(tostring(i), 4, 'right') .. ' ' .. fit(field, 20) .. pp.format(v)) 495 | end 496 | print() 497 | end 498 | print_bind_buffer(bind) 499 | 500 | print('stmt:free_result() ', stmt:free_result()) 501 | --local next_result = stmt:next_result() 502 | --print('stmt:next_result() ', '->', pp.format(next_result)); assert(next_result == false) 503 | 504 | print('stmt:reset() ', stmt:reset()) 505 | print('stmt:close() ', stmt:close()) 506 | 507 | --prepared statements with parameters 508 | 509 | for i,field in ipairs(test_fields) do 510 | local query = 'select * from binding_test where '..field..' = ?' 511 | local stmt = conn:prepare(query) 512 | print('conn:prepare( ', pp.format(query), ')') 513 | local param_bind_def = {bind_types[field]} 514 | 515 | local bind = stmt:bind_params(param_bind_def) 516 | print('stmt:bind_params ', pp.format(param_bind_def)) 517 | 518 | local function exec() 519 | print('stmt:exec() ', stmt:exec()) 520 | print('stmt:store_result() ', stmt:store_result()) 521 | print('stmt:row_count() ', '->', stmt:row_count()) 522 | assert(stmt:row_count() == 1) --libmariadb() returns 0 523 | end 524 | 525 | local v = test_values[field] 526 | if v ~= nil then 527 | print('bind:set( ', 1, pp.format(v), ')'); bind:set(1, v); exec() 528 | 529 | if field:find'date' or field:find'time' then 530 | print('bind:set_date( ', 1, v.year, v.month, v.day, v.hour, v.min, v.sec, v.frac, ')') 531 | bind:set_date(1, v.year, v.month, v.day, v.hour, v.min, v.sec, v.frac) 532 | exec() --libmariadb crashes the server 533 | end 534 | end 535 | print('stmt:close() ', stmt:close()) 536 | end 537 | 538 | --prepared statements with auto-allocated result bind buffers. 539 | 540 | local query = 'select * from binding_test' 541 | local stmt = conn:prepare(query) 542 | local bind = stmt:bind_result() 543 | --pp(stmt:bind_result_types()) 544 | stmt:exec() 545 | stmt:store_result() 546 | stmt:fetch() 547 | print_bind_buffer(bind) 548 | stmt:close() 549 | 550 | local q = 'drop table binding_test' 551 | print('conn:query( ', pp.format(q), ')', conn:query(q)) 552 | print('conn:commit() ', conn:commit()) 553 | print('conn:close() ', conn:close()) 554 | 555 | -------------------------------------------------------------------------------- /mysql_h.lua: -------------------------------------------------------------------------------- 1 | 2 | --result of `cpp mysql.h` with lots of cleanup and defines from other headers. 3 | --Written by Cosmin Apreutesei. MySQL Connector/C 6.1. 4 | 5 | --NOTE: MySQL Connector/C is GPL software. Is this "derived work" then? 6 | 7 | local ffi = require'ffi' 8 | 9 | ffi.cdef[[ 10 | 11 | typedef char my_bool; 12 | typedef unsigned long long my_ulonglong; 13 | 14 | enum { 15 | MYSQL_PORT = 3306, 16 | MYSQL_ERRMSG_SIZE = 512, 17 | 18 | // status return codes 19 | MYSQL_NO_DATA = 100, 20 | MYSQL_DATA_TRUNCATED = 101 21 | }; 22 | 23 | // ----------------------------------------------------------- error constants 24 | 25 | // NOTE: added MYSQL_ prefix to these. 26 | enum mysql_error_code { 27 | MYSQL_CR_UNKNOWN_ERROR = 2000, 28 | MYSQL_CR_SOCKET_CREATE_ERROR = 2001, 29 | MYSQL_CR_CONNECTION_ERROR = 2002, 30 | MYSQL_CR_CONN_HOST_ERROR = 2003, 31 | MYSQL_CR_IPSOCK_ERROR = 2004, 32 | MYSQL_CR_UNKNOWN_HOST = 2005, 33 | MYSQL_CR_SERVER_GONE_ERROR = 2006, 34 | MYSQL_CR_VERSION_ERROR = 2007, 35 | MYSQL_CR_OUT_OF_MEMORY = 2008, 36 | MYSQL_CR_WRONG_HOST_INFO = 2009, 37 | MYSQL_CR_LOCALHOST_CONNECTION = 2010, 38 | MYSQL_CR_TCP_CONNECTION = 2011, 39 | MYSQL_CR_SERVER_HANDSHAKE_ERR = 2012, 40 | MYSQL_CR_SERVER_LOST = 2013, 41 | MYSQL_CR_COMMANDS_OUT_OF_SYNC = 2014, 42 | MYSQL_CR_NAMEDPIPE_CONNECTION = 2015, 43 | MYSQL_CR_NAMEDPIPEWAIT_ERROR = 2016, 44 | MYSQL_CR_NAMEDPIPEOPEN_ERROR = 2017, 45 | MYSQL_CR_NAMEDPIPESETSTATE_ERROR = 2018, 46 | MYSQL_CR_CANT_READ_CHARSET = 2019, 47 | MYSQL_CR_NET_PACKET_TOO_LARGE = 2020, 48 | MYSQL_CR_EMBEDDED_CONNECTION = 2021, 49 | MYSQL_CR_PROBE_SLAVE_STATUS = 2022, 50 | MYSQL_CR_PROBE_SLAVE_HOSTS = 2023, 51 | MYSQL_CR_PROBE_SLAVE_CONNECT = 2024, 52 | MYSQL_CR_PROBE_MASTER_CONNECT = 2025, 53 | MYSQL_CR_SSL_CONNECTION_ERROR = 2026, 54 | MYSQL_CR_MALFORMED_PACKET = 2027, 55 | MYSQL_CR_WRONG_LICENSE = 2028, 56 | 57 | /* new 4.1 error codes */ 58 | MYSQL_CR_NULL_POINTER = 2029, 59 | MYSQL_CR_NO_PREPARE_STMT = 2030, 60 | MYSQL_CR_PARAMS_NOT_BOUND = 2031, 61 | MYSQL_CR_DATA_TRUNCATED = 2032, 62 | MYSQL_CR_NO_PARAMETERS_EXISTS = 2033, 63 | MYSQL_CR_INVALID_PARAMETER_NO = 2034, 64 | MYSQL_CR_INVALID_BUFFER_USE = 2035, 65 | MYSQL_CR_UNSUPPORTED_PARAM_TYPE = 2036, 66 | 67 | MYSQL_CR_SHARED_MEMORY_CONNECTION = 2037, 68 | MYSQL_CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR = 2038, 69 | MYSQL_CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR = 2039, 70 | MYSQL_CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR = 2040, 71 | MYSQL_CR_SHARED_MEMORY_CONNECT_MAP_ERROR = 2041, 72 | MYSQL_CR_SHARED_MEMORY_FILE_MAP_ERROR = 2042, 73 | MYSQL_CR_SHARED_MEMORY_MAP_ERROR = 2043, 74 | MYSQL_CR_SHARED_MEMORY_EVENT_ERROR = 2044, 75 | MYSQL_CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR = 2045, 76 | MYSQL_CR_SHARED_MEMORY_CONNECT_SET_ERROR = 2046, 77 | MYSQL_CR_CONN_UNKNOW_PROTOCOL = 2047, 78 | MYSQL_CR_INVALID_CONN_HANDLE = 2048, 79 | MYSQL_CR_SECURE_AUTH = 2049, 80 | MYSQL_CR_FETCH_CANCELED = 2050, 81 | MYSQL_CR_NO_DATA = 2051, 82 | MYSQL_CR_NO_STMT_METADATA = 2052, 83 | MYSQL_CR_NO_RESULT_SET = 2053, 84 | MYSQL_CR_NOT_IMPLEMENTED = 2054, 85 | MYSQL_CR_SERVER_LOST_EXTENDED = 2055, 86 | MYSQL_CR_STMT_CLOSED = 2056, 87 | MYSQL_CR_NEW_STMT_METADATA = 2057, 88 | MYSQL_CR_ALREADY_CONNECTED = 2058, 89 | MYSQL_CR_AUTH_PLUGIN_CANNOT_LOAD = 2059, 90 | MYSQL_CR_DUPLICATE_CONNECTION_ATTR = 2060, 91 | MYSQL_CR_AUTH_PLUGIN_ERR = 2061 92 | }; 93 | 94 | // ------------------------------------------------------------ client library 95 | 96 | unsigned int mysql_thread_safe(void); // is the client library thread safe? 97 | const char *mysql_get_client_info(void); 98 | unsigned long mysql_get_client_version(void); 99 | 100 | // --------------------------------------------------------------- connections 101 | 102 | typedef struct MYSQL_ MYSQL; 103 | 104 | MYSQL * mysql_init(MYSQL *mysql); 105 | 106 | enum mysql_protocol_type 107 | { 108 | MYSQL_PROTOCOL_DEFAULT, MYSQL_PROTOCOL_TCP, MYSQL_PROTOCOL_SOCKET, 109 | MYSQL_PROTOCOL_PIPE, MYSQL_PROTOCOL_MEMORY 110 | }; 111 | enum mysql_option 112 | { 113 | MYSQL_OPT_CONNECT_TIMEOUT, MYSQL_OPT_COMPRESS, MYSQL_OPT_NAMED_PIPE, 114 | MYSQL_INIT_COMMAND, MYSQL_READ_DEFAULT_FILE, MYSQL_READ_DEFAULT_GROUP, 115 | MYSQL_SET_CHARSET_DIR, MYSQL_SET_CHARSET_NAME, MYSQL_OPT_LOCAL_INFILE, 116 | MYSQL_OPT_PROTOCOL, MYSQL_SHARED_MEMORY_BASE_NAME, MYSQL_OPT_READ_TIMEOUT, 117 | MYSQL_OPT_WRITE_TIMEOUT, MYSQL_OPT_USE_RESULT, 118 | MYSQL_OPT_USE_REMOTE_CONNECTION, MYSQL_OPT_USE_EMBEDDED_CONNECTION, 119 | MYSQL_OPT_GUESS_CONNECTION, MYSQL_SET_CLIENT_IP, MYSQL_SECURE_AUTH, 120 | MYSQL_REPORT_DATA_TRUNCATION, MYSQL_OPT_RECONNECT, 121 | MYSQL_OPT_SSL_VERIFY_SERVER_CERT, MYSQL_PLUGIN_DIR, MYSQL_DEFAULT_AUTH, 122 | MYSQL_OPT_BIND, 123 | MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT, 124 | MYSQL_OPT_SSL_CA, MYSQL_OPT_SSL_CAPATH, MYSQL_OPT_SSL_CIPHER, 125 | MYSQL_OPT_SSL_CRL, MYSQL_OPT_SSL_CRLPATH, 126 | MYSQL_OPT_CONNECT_ATTR_RESET, MYSQL_OPT_CONNECT_ATTR_ADD, 127 | MYSQL_OPT_CONNECT_ATTR_DELETE, 128 | MYSQL_SERVER_PUBLIC_KEY, 129 | MYSQL_ENABLE_CLEARTEXT_PLUGIN, 130 | MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS 131 | }; 132 | int mysql_options(MYSQL *mysql, enum mysql_option option, const void *arg); 133 | int mysql_options4(MYSQL *mysql, enum mysql_option option, const void *arg1, const void *arg2); 134 | 135 | // NOTE: added MYSQL_ prefix to these. Also, these are bit flags not exclusive enum values. 136 | enum { 137 | MYSQL_CLIENT_LONG_PASSWORD = 1, /* new more secure passwords */ 138 | MYSQL_CLIENT_FOUND_ROWS = 2, /* Found instead of affected rows */ 139 | MYSQL_CLIENT_LONG_FLAG = 4, /* Get all column flags */ 140 | MYSQL_CLIENT_CONNECT_WITH_DB = 8, /* One can specify db on connect */ 141 | MYSQL_CLIENT_NO_SCHEMA = 16, /* Don't allow database.table.column */ 142 | MYSQL_CLIENT_COMPRESS = 32, /* Can use compression protocol */ 143 | MYSQL_CLIENT_ODBC = 64, /* ODBC client */ 144 | MYSQL_CLIENT_LOCAL_FILES = 128, /* Can use LOAD DATA LOCAL */ 145 | MYSQL_CLIENT_IGNORE_SPACE = 256, /* Ignore spaces before '(' */ 146 | MYSQL_CLIENT_PROTOCOL_41 = 512, /* New 4.1 protocol */ 147 | MYSQL_CLIENT_INTERACTIVE = 1024, /* This is an interactive client */ 148 | MYSQL_CLIENT_SSL = 2048, /* Switch to SSL after handshake */ 149 | MYSQL_CLIENT_IGNORE_SIGPIPE = 4096, /* IGNORE sigpipes */ 150 | MYSQL_CLIENT_TRANSACTIONS = 8192, /* Client knows about transactions */ 151 | MYSQL_CLIENT_RESERVED = 16384, /* Old flag for 4.1 protocol */ 152 | MYSQL_CLIENT_SECURE_CONNECTION = (1U << 15), /* New 4.1 authentication */ 153 | MYSQL_CLIENT_MULTI_STATEMENTS = (1U << 16), /* Enable/disable multi-stmt support */ 154 | MYSQL_CLIENT_MULTI_RESULTS = (1U << 17), /* Enable/disable multi-results */ 155 | MYSQL_CLIENT_PS_MULTI_RESULTS = (1U << 18), /* Multi-results in PS-protocol */ 156 | MYSQL_CLIENT_PLUGIN_AUTH = (1U << 19), /* Client supports plugin authentication */ 157 | MYSQL_CLIENT_CONNECT_ATTRS = (1U << 20), /* Client supports connection attributes */ 158 | 159 | /* Enable authentication response packet to be larger than 255 bytes. */ 160 | MYSQL_CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = (1U << 21), 161 | 162 | /* Don't close the connection for a connection with expired password. */ 163 | MYSQL_CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = (1U << 22), 164 | 165 | MYSQL_CLIENT_SSL_VERIFY_SERVER_CERT = (1U << 30), 166 | MYSQL_CLIENT_REMEMBER_OPTIONS = (1U << 31) 167 | }; 168 | MYSQL * mysql_real_connect(MYSQL *mysql, const char *host, 169 | const char *user, 170 | const char *passwd, 171 | const char *db, 172 | unsigned int port, 173 | const char *unix_socket, 174 | unsigned long clientflag); 175 | 176 | void mysql_close(MYSQL *sock); 177 | 178 | int mysql_set_character_set(MYSQL *mysql, const char *csname); 179 | 180 | int mysql_select_db(MYSQL *mysql, const char *db); 181 | 182 | my_bool mysql_change_user(MYSQL *mysql, const char *user, const char *passwd, 183 | const char *db); 184 | 185 | my_bool mysql_ssl_set(MYSQL *mysql, const char *key, 186 | const char *cert, const char *ca, 187 | const char *capath, const char *cipher); 188 | 189 | enum enum_mysql_set_option 190 | { 191 | MYSQL_OPTION_MULTI_STATEMENTS_ON, 192 | MYSQL_OPTION_MULTI_STATEMENTS_OFF 193 | }; 194 | int mysql_set_server_option(MYSQL *mysql, enum enum_mysql_set_option option); 195 | 196 | // ----------------------------------------------------------- connection info 197 | 198 | const char * mysql_character_set_name(MYSQL *mysql); 199 | 200 | typedef struct character_set 201 | { 202 | unsigned int number; 203 | unsigned int state; 204 | const char *csname; 205 | const char *name; 206 | const char *comment; 207 | const char *dir; 208 | unsigned int mbminlen; 209 | unsigned int mbmaxlen; 210 | } MY_CHARSET_INFO; 211 | void mysql_get_character_set_info(MYSQL *mysql, MY_CHARSET_INFO *charset); 212 | 213 | int mysql_ping(MYSQL *mysql); 214 | unsigned long mysql_thread_id(MYSQL *mysql); 215 | const char * mysql_stat(MYSQL *mysql); 216 | const char * mysql_get_server_info(MYSQL *mysql); 217 | const char * mysql_get_host_info(MYSQL *mysql); 218 | unsigned long mysql_get_server_version(MYSQL *mysql); 219 | unsigned int mysql_get_proto_info(MYSQL *mysql); 220 | const char * mysql_get_ssl_cipher(MYSQL *mysql); 221 | 222 | // -------------------------------------------------------------- transactions 223 | 224 | my_bool mysql_commit(MYSQL * mysql); 225 | my_bool mysql_rollback(MYSQL * mysql); 226 | my_bool mysql_autocommit(MYSQL * mysql, my_bool auto_mode); 227 | 228 | // ------------------------------------------------------------------- queries 229 | 230 | unsigned long mysql_real_escape_string(MYSQL *mysql, char *to, 231 | const char *from, unsigned long length); 232 | int mysql_real_query(MYSQL *mysql, const char *q, unsigned long length); 233 | 234 | // ---------------------------------------------------------------- query info 235 | 236 | unsigned int mysql_field_count(MYSQL *mysql); 237 | my_ulonglong mysql_affected_rows(MYSQL *mysql); 238 | my_ulonglong mysql_insert_id(MYSQL *mysql); 239 | unsigned int mysql_errno(MYSQL *mysql); 240 | const char * mysql_error(MYSQL *mysql); 241 | const char * mysql_sqlstate(MYSQL *mysql); 242 | unsigned int mysql_warning_count(MYSQL *mysql); 243 | const char * mysql_info(MYSQL *mysql); 244 | 245 | // ------------------------------------------------------------- query results 246 | 247 | int mysql_next_result(MYSQL *mysql); 248 | my_bool mysql_more_results(MYSQL *mysql); 249 | 250 | // NOTE: normally we would've made this an opaque handle, but we need to expose 251 | // the connection handle from it so we can report errors for unbuffered reads. 252 | typedef struct st_mysql_res { 253 | my_ulonglong __row_count; 254 | void *__fields; 255 | void *__data; 256 | void *__data_cursor; 257 | void *__lengths; 258 | MYSQL *conn; /* for unbuffered reads */ 259 | } MYSQL_RES; 260 | 261 | MYSQL_RES *mysql_store_result(MYSQL *mysql); 262 | MYSQL_RES *mysql_use_result(MYSQL *mysql); 263 | void mysql_free_result(MYSQL_RES *result); 264 | 265 | my_ulonglong mysql_num_rows(MYSQL_RES *res); 266 | unsigned int mysql_num_fields(MYSQL_RES *res); 267 | my_bool mysql_eof(MYSQL_RES *res); 268 | 269 | unsigned long * mysql_fetch_lengths(MYSQL_RES *result); 270 | typedef char **MYSQL_ROW; 271 | MYSQL_ROW mysql_fetch_row(MYSQL_RES *result); 272 | 273 | void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset); 274 | 275 | typedef struct MYSQL_ROWS_ MYSQL_ROWS; 276 | typedef MYSQL_ROWS *MYSQL_ROW_OFFSET; 277 | MYSQL_ROW_OFFSET mysql_row_tell(MYSQL_RES *res); 278 | MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset); 279 | 280 | // ---------------------------------------------------------- query field info 281 | 282 | enum enum_field_types { 283 | MYSQL_TYPE_DECIMAL, MYSQL_TYPE_TINY, 284 | MYSQL_TYPE_SHORT, MYSQL_TYPE_LONG, 285 | MYSQL_TYPE_FLOAT, MYSQL_TYPE_DOUBLE, 286 | MYSQL_TYPE_NULL, MYSQL_TYPE_TIMESTAMP, 287 | MYSQL_TYPE_LONGLONG,MYSQL_TYPE_INT24, 288 | MYSQL_TYPE_DATE, MYSQL_TYPE_TIME, 289 | MYSQL_TYPE_DATETIME, MYSQL_TYPE_YEAR, 290 | MYSQL_TYPE_NEWDATE, MYSQL_TYPE_VARCHAR, 291 | MYSQL_TYPE_BIT, 292 | MYSQL_TYPE_TIMESTAMP2, 293 | MYSQL_TYPE_DATETIME2, 294 | MYSQL_TYPE_TIME2, 295 | MYSQL_TYPE_NEWDECIMAL=246, 296 | MYSQL_TYPE_ENUM=247, 297 | MYSQL_TYPE_SET=248, 298 | MYSQL_TYPE_TINY_BLOB=249, 299 | MYSQL_TYPE_MEDIUM_BLOB=250, 300 | MYSQL_TYPE_LONG_BLOB=251, 301 | MYSQL_TYPE_BLOB=252, 302 | MYSQL_TYPE_VAR_STRING=253, 303 | MYSQL_TYPE_STRING=254, 304 | MYSQL_TYPE_GEOMETRY=255 305 | }; 306 | 307 | // NOTE: added MYSQL_ prefix to these. Also, these are bit flags, not exclusive enum values. 308 | enum { 309 | MYSQL_NOT_NULL_FLAG = 1, /* Field can't be NULL */ 310 | MYSQL_PRI_KEY_FLAG = 2, /* Field is part of a primary key */ 311 | MYSQL_UNIQUE_KEY_FLAG = 4, /* Field is part of a unique key */ 312 | MYSQL_MULTIPLE_KEY_FLAG = 8, /* Field is part of a key */ 313 | MYSQL_BLOB_FLAG = 16, /* Field is a blob */ 314 | MYSQL_UNSIGNED_FLAG = 32, /* Field is unsigned */ 315 | MYSQL_ZEROFILL_FLAG = 64, /* Field is zerofill */ 316 | MYSQL_BINARY_FLAG = 128, /* Field is binary */ 317 | 318 | /* The following are only sent to new clients */ 319 | MYSQL_ENUM_FLAG = 256, /* field is an enum */ 320 | MYSQL_AUTO_INCREMENT_FLAG = 512, /* field is a autoincrement field */ 321 | MYSQL_TIMESTAMP_FLAG = 1024, /* Field is a timestamp */ 322 | MYSQL_SET_FLAG = 2048, /* field is a set */ 323 | MYSQL_NO_DEFAULT_VALUE_FLAG = 4096, /* Field doesn't have default value */ 324 | MYSQL_ON_UPDATE_NOW_FLAG = 8192, /* Field is set to NOW on UPDATE */ 325 | MYSQL_NUM_FLAG = 32768, /* Field is num (for clients) */ 326 | MYSQL_PART_KEY_FLAG = 16384, /* Intern; Part of some key */ 327 | MYSQL_GROUP_FLAG = 32768, /* Intern: Group field */ 328 | MYSQL_UNIQUE_FLAG = 65536, /* Intern: Used by sql_yacc */ 329 | MYSQL_BINCMP_FLAG = 131072, /* Intern: Used by sql_yacc */ 330 | MYSQL_GET_FIXED_FIELDS_FLAG = (1 << 18), /* Used to get fields in item tree */ 331 | MYSQL_FIELD_IN_PART_FUNC_FLAG = (1 << 19) /* Field part of partition func */ 332 | }; 333 | 334 | typedef struct st_mysql_field { 335 | char *name; 336 | char *org_name; 337 | char *table; 338 | char *org_table; 339 | char *db; 340 | char *catalog; 341 | char *def; 342 | unsigned long length; 343 | unsigned long max_length; 344 | unsigned int name_length; 345 | unsigned int org_name_length; 346 | unsigned int table_length; 347 | unsigned int org_table_length; 348 | unsigned int db_length; 349 | unsigned int catalog_length; 350 | unsigned int def_length; 351 | unsigned int flags; 352 | unsigned int decimals; 353 | unsigned int charsetnr; 354 | enum enum_field_types type; 355 | void *extension; 356 | } MYSQL_FIELD; 357 | 358 | MYSQL_FIELD *mysql_fetch_field_direct(MYSQL_RES *res, unsigned int fieldnr); 359 | 360 | // ---------------------------------------------------------------- reflection 361 | 362 | MYSQL_RES *mysql_list_dbs(MYSQL *mysql, const char *wild); 363 | MYSQL_RES *mysql_list_tables(MYSQL *mysql, const char *wild); 364 | MYSQL_RES *mysql_list_processes(MYSQL *mysql); 365 | 366 | // ------------------------------------------------------------ remote control 367 | 368 | int mysql_kill(MYSQL *mysql, unsigned long pid); 369 | 370 | // NOTE: added MYSQL_ prefix. 371 | enum mysql_enum_shutdown_level { 372 | MYSQL_SHUTDOWN_DEFAULT = 0, 373 | MYSQL_SHUTDOWN_WAIT_CONNECTIONS = 1, 374 | MYSQL_SHUTDOWN_WAIT_TRANSACTIONS = 2, 375 | MYSQL_SHUTDOWN_WAIT_UPDATES = 8, 376 | MYSQL_SHUTDOWN_WAIT_ALL_BUFFERS = 16, 377 | MYSQL_SHUTDOWN_WAIT_CRITICAL_BUFFERS = 17, 378 | MYSQL_KILL_QUERY = 254, 379 | MYSQL_KILL_CONNECTION = 255 380 | }; 381 | int mysql_shutdown(MYSQL *mysql, enum mysql_enum_shutdown_level shutdown_level); // needs SHUTDOWN priviledge 382 | 383 | // NOTE: added MYSQL_ prefix. not really enum values either, just bit flags. 384 | enum { 385 | MYSQL_REFRESH_GRANT = 1, /* Refresh grant tables */ 386 | MYSQL_REFRESH_LOG = 2, /* Start on new log file */ 387 | MYSQL_REFRESH_TABLES = 4, /* close all tables */ 388 | MYSQL_REFRESH_HOSTS = 8, /* Flush host cache */ 389 | MYSQL_REFRESH_STATUS = 16, /* Flush status variables */ 390 | MYSQL_REFRESH_THREADS = 32, /* Flush thread cache */ 391 | MYSQL_REFRESH_SLAVE = 64, /* Reset master info and restart slave thread */ 392 | MYSQL_REFRESH_MASTER = 128, /* Remove all bin logs in the index and truncate the index */ 393 | MYSQL_REFRESH_ERROR_LOG = 256, /* Rotate only the erorr log */ 394 | MYSQL_REFRESH_ENGINE_LOG = 512, /* Flush all storage engine logs */ 395 | MYSQL_REFRESH_BINARY_LOG = 1024, /* Flush the binary log */ 396 | MYSQL_REFRESH_RELAY_LOG = 2048, /* Flush the relay log */ 397 | MYSQL_REFRESH_GENERAL_LOG = 4096, /* Flush the general log */ 398 | MYSQL_REFRESH_SLOW_LOG = 8192, /* Flush the slow query log */ 399 | 400 | /* The following can't be set with mysql_refresh() */ 401 | MYSQL_REFRESH_READ_LOCK = 16384, /* Lock tables for read */ 402 | MYSQL_REFRESH_FAST = 32768, /* Intern flag */ 403 | 404 | /* RESET (remove all queries) from query cache */ 405 | MYSQL_REFRESH_QUERY_CACHE = 65536, 406 | MYSQL_REFRESH_QUERY_CACHE_FREE = 0x20000, /* pack query cache */ 407 | MYSQL_REFRESH_DES_KEY_FILE = 0x40000, 408 | MYSQL_REFRESH_USER_RESOURCES = 0x80000, 409 | MYSQL_REFRESH_FOR_EXPORT = 0x100000, /* FLUSH TABLES ... FOR EXPORT */ 410 | }; 411 | int mysql_refresh(MYSQL *mysql, unsigned int refresh_options); // needs RELOAD priviledge 412 | int mysql_dump_debug_info(MYSQL *mysql); // needs SUPER priviledge 413 | 414 | // ------------------------------------------------------- prepared statements 415 | 416 | typedef struct MYSQL_STMT_ MYSQL_STMT; 417 | 418 | MYSQL_STMT * mysql_stmt_init(MYSQL *mysql); 419 | my_bool mysql_stmt_close(MYSQL_STMT * stmt); 420 | 421 | int mysql_stmt_prepare(MYSQL_STMT *stmt, const char *query, unsigned long length); 422 | int mysql_stmt_execute(MYSQL_STMT *stmt); 423 | 424 | int mysql_stmt_next_result(MYSQL_STMT *stmt); 425 | int mysql_stmt_store_result(MYSQL_STMT *stmt); 426 | my_bool mysql_stmt_free_result(MYSQL_STMT *stmt); 427 | 428 | MYSQL_RES *mysql_stmt_result_metadata(MYSQL_STMT *stmt); 429 | my_ulonglong mysql_stmt_num_rows(MYSQL_STMT *stmt); 430 | my_ulonglong mysql_stmt_affected_rows(MYSQL_STMT *stmt); 431 | my_ulonglong mysql_stmt_insert_id(MYSQL_STMT *stmt); 432 | unsigned int mysql_stmt_field_count(MYSQL_STMT *stmt); 433 | 434 | unsigned int mysql_stmt_errno(MYSQL_STMT * stmt); 435 | const char *mysql_stmt_error(MYSQL_STMT * stmt); 436 | const char *mysql_stmt_sqlstate(MYSQL_STMT * stmt); 437 | 438 | int mysql_stmt_fetch(MYSQL_STMT *stmt); 439 | my_bool mysql_stmt_reset(MYSQL_STMT * stmt); 440 | 441 | void mysql_stmt_data_seek(MYSQL_STMT *stmt, my_ulonglong offset); 442 | 443 | MYSQL_ROW_OFFSET mysql_stmt_row_tell(MYSQL_STMT *stmt); 444 | MYSQL_ROW_OFFSET mysql_stmt_row_seek(MYSQL_STMT *stmt, MYSQL_ROW_OFFSET offset); 445 | 446 | // NOTE: added MYSQL_ prefix to these. 447 | enum enum_cursor_type 448 | { 449 | MYSQL_CURSOR_TYPE_NO_CURSOR= 0, 450 | MYSQL_CURSOR_TYPE_READ_ONLY= 1, 451 | MYSQL_CURSOR_TYPE_FOR_UPDATE= 2, 452 | MYSQL_CURSOR_TYPE_SCROLLABLE= 4 453 | }; 454 | 455 | enum enum_stmt_attr_type 456 | { 457 | STMT_ATTR_UPDATE_MAX_LENGTH, 458 | STMT_ATTR_CURSOR_TYPE, 459 | STMT_ATTR_PREFETCH_ROWS 460 | }; 461 | my_bool mysql_stmt_attr_set(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr); 462 | my_bool mysql_stmt_attr_get(MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, void *attr); 463 | 464 | my_bool mysql_stmt_send_long_data(MYSQL_STMT *stmt, 465 | unsigned int param_number, 466 | const char *data, 467 | unsigned long length); 468 | 469 | // -------------------------------------------- prepared statements / bindings 470 | 471 | enum enum_mysql_timestamp_type 472 | { 473 | MYSQL_TIMESTAMP_NONE= -2, MYSQL_TIMESTAMP_ERROR= -1, 474 | MYSQL_TIMESTAMP_DATE= 0, MYSQL_TIMESTAMP_DATETIME= 1, MYSQL_TIMESTAMP_TIME= 2 475 | }; 476 | typedef struct st_mysql_time 477 | { 478 | unsigned int year, month, day, hour, minute, second; 479 | unsigned long second_part; /**< microseconds */ 480 | my_bool neg; 481 | enum enum_mysql_timestamp_type time_type; 482 | } MYSQL_TIME; 483 | 484 | unsigned long mysql_stmt_param_count(MYSQL_STMT * stmt); 485 | 486 | typedef struct NET_ NET; 487 | typedef struct st_mysql_bind 488 | { 489 | unsigned long *length; 490 | my_bool *is_null; 491 | void *buffer; 492 | my_bool *error; 493 | unsigned char *row_ptr; 494 | void (*store_param_func)(NET *net, struct st_mysql_bind *param); 495 | void (*fetch_result)(struct st_mysql_bind *, MYSQL_FIELD *, 496 | unsigned char **row); 497 | void (*skip_result)(struct st_mysql_bind *, MYSQL_FIELD *, 498 | unsigned char **row); 499 | unsigned long buffer_length; 500 | unsigned long offset; 501 | unsigned long length_value; 502 | unsigned int param_number; 503 | unsigned int pack_length; 504 | enum enum_field_types buffer_type; 505 | my_bool error_value; 506 | my_bool is_unsigned; 507 | my_bool long_data_used; 508 | my_bool is_null_value; 509 | void *extension; 510 | } MYSQL_BIND; 511 | 512 | my_bool mysql_stmt_bind_param(MYSQL_STMT * stmt, MYSQL_BIND * bnd); 513 | my_bool mysql_stmt_bind_result(MYSQL_STMT * stmt, MYSQL_BIND * bnd); 514 | 515 | int mysql_stmt_fetch_column(MYSQL_STMT *stmt, MYSQL_BIND *bind_arg, 516 | unsigned int column, 517 | unsigned long offset); 518 | 519 | // ---------------------------------------------- LOAD DATA LOCAL INFILE hooks 520 | 521 | void mysql_set_local_infile_handler(MYSQL *mysql, 522 | int (*local_infile_init)(void **, const char *, void *), 523 | int (*local_infile_read)(void *, char *, unsigned int), 524 | void (*local_infile_end)(void *), 525 | int (*local_infile_error)(void *, char*, unsigned int), 526 | void *); 527 | void mysql_set_local_infile_default(MYSQL *mysql); 528 | 529 | // ----------------------------------------------------- mysql proxy scripting 530 | 531 | my_bool mysql_read_query_result(MYSQL *mysql); 532 | 533 | // ----------------------------------------------------------------- debugging 534 | 535 | void mysql_debug(const char *debug); 536 | 537 | // ------------------------------------------------ present but not documented 538 | 539 | int mysql_server_init(int argc, char **argv, char **groups); 540 | void mysql_server_end(void); 541 | char *get_tty_password(const char *opt_message); 542 | void myodbc_remove_escape(MYSQL *mysql, char *name); 543 | my_bool mysql_embedded(void); 544 | int mysql_send_query(MYSQL *mysql, const char *q, unsigned long length); 545 | 546 | // ------------------------------------------------------- redundant functions 547 | 548 | my_bool mysql_thread_init(void); // called anyway 549 | void mysql_thread_end(void); // called anyway 550 | const char *mysql_errno_to_sqlstate(unsigned int mysql_errno); // use mysql_sqlstate 551 | unsigned long mysql_hex_string(char *to, const char *from, 552 | unsigned long from_length); // bad taste 553 | 554 | // redundant ways to get field info. 555 | // we use use mysql_field_count and mysql_fetch_field_direct instead. 556 | MYSQL_FIELD *mysql_fetch_field(MYSQL_RES *result); 557 | MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res); 558 | typedef unsigned int MYSQL_FIELD_OFFSET; 559 | MYSQL_FIELD_OFFSET mysql_field_tell(MYSQL_RES *res); 560 | MYSQL_FIELD_OFFSET mysql_field_seek(MYSQL_RES *result, MYSQL_FIELD_OFFSET offset); 561 | MYSQL_RES *mysql_stmt_param_metadata(MYSQL_STMT *stmt); 562 | 563 | // ------------------------------------------------------ deprecated functions 564 | 565 | unsigned long mysql_escape_string(char *to, const char *from, 566 | unsigned long from_length); // use mysql_real_escape_string 567 | int mysql_query(MYSQL *mysql, const char *q); // use mysql_real_query 568 | MYSQL_RES *mysql_list_fields(MYSQL *mysql, const char *table, 569 | const char *wild); // use "SHOW COLUMNS FROM table" 570 | 571 | ]] 572 | --------------------------------------------------------------------------------