diff --git a/README.markdown b/README.markdown index 7369b78..45a611c 100644 --- a/README.markdown +++ b/README.markdown @@ -286,6 +286,8 @@ send_query Sends the query to the remote MySQL server without waiting for its replies. +The argument query can be a string or a (nested) Lua table holding string fragments. + Returns the bytes successfully sent out in success and otherwise returns `nil` and a string describing the error. You should use the [read_result](#read_result) method to read the MySQL replies afterwards. diff --git a/lib/resty/mysql.lua b/lib/resty/mysql.lua index ae4ed8b..642634b 100644 --- a/lib/resty/mysql.lua +++ b/lib/resty/mysql.lua @@ -120,6 +120,11 @@ converters[0x09] = tonumber -- int24 converters[0x0d] = tonumber -- year converters[0xf6] = tonumber -- newdecimal +-- avoid extra allocation of temp tables by sharing preallocated tables +local packet = new_tab(3, 0) +local query_packet = new_tab(2, 0) +query_packet[1] = strchar(COM_QUERY) + local function _get_byte2(data, i) local a, b = strbyte(data, i, i + 1) @@ -230,6 +235,29 @@ local function _compute_token(password, scramble) end +local function _compute_size(a) + -- compute total size of a string or a (nested) Lua table holding string fragments + if type(a) == "string" then + return #a + end + local n = 0 + for i, v in ipairs(a) do + n = n + _compute_size(v) + end + return n +end + + +local function _concat_rec(t, sep) + for i, v in ipairs(t) do + if type(v) == "table" then + t[i] = _concat_rec(v, sep) + end + end + return concat(t, sep) +end + + local function _send_packet(self, req, size) local sock = self.sock @@ -237,11 +265,13 @@ local function _send_packet(self, req, size) -- print("packet no: ", self.packet_no) - local packet = _set_byte3(size) .. strchar(band(self.packet_no, 255)) .. req + packet[1] = _set_byte3(size) + packet[2] = strchar(band(self.packet_no, 255)) + packet[3] = req - -- print("sending packet: ", _dump(packet)) + -- print("sending packet: ", _dump(_concat_rec(packet))) - -- print("sending packet... of size " .. #packet) + -- print("sending packet... of size " .. _compute_size(packet)) return sock:send(packet) end @@ -805,10 +835,9 @@ local function send_query(self, query) self.packet_no = -1 - local cmd_packet = strchar(COM_QUERY) .. query - local packet_len = 1 + #query - - local bytes, err = _send_packet(self, cmd_packet, packet_len) + local packet_len = 1 + _compute_size(query) + query_packet[2] = query + local bytes, err = _send_packet(self, query_packet, packet_len) if not bytes then return nil, err end diff --git a/t/sanity.t b/t/sanity.t index 6252530..fb6df52 100644 --- a/t/sanity.t +++ b/t/sanity.t @@ -1321,3 +1321,81 @@ success --- no_error_log [error] --- timeout: 20 + + + +=== TEST 20: query with a table +--- http_config eval: $::HttpConfig +--- config + location /t { + content_by_lua ' + local ljson = require "ljson" + + local mysql = require "resty.mysql" + local db = mysql:new() + + db:set_timeout(2000) -- 2 sec + + local ok, err, errno, sqlstate = db:connect({ + host = "$TEST_NGINX_MYSQL_HOST", + port = $TEST_NGINX_MYSQL_PORT, + database = "ngx_test", + user = "ngx_test", + password = "ngx_test"}) + + if not ok then + ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) + return + end + + ngx.say("connected to mysql.") + + local res, err, errno, sqlstate = db:query("drop table if exists cats") + if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return + end + + ngx.say("table cats dropped.") + + res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") + if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return + end + + ngx.say("table cats created.") + + res, err, errno, sqlstate = db:query({"insert into ", "cats (name) value ", {"(\'Bob\'),", "(\'\'),", "(null)"}}) + if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return + end + + ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") + + res, err, errno, sqlstate = db:query({"select * from ", "cats", " order by id asc"}) + if not res then + ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") + return + end + + ngx.say("result: ", ljson.encode(res)) + + local ok, err = db:close() + if not ok then + ngx.say("failed to close: ", err) + return + end + '; + } +--- request +GET /t +--- response_body +connected to mysql. +table cats dropped. +table cats created. +3 rows inserted into table cats (last id: 1) +result: [{"id":"1","name":"Bob"},{"id":"2","name":""},{"id":"3","name":null}] +--- no_error_log +[error]