diff --git a/mysql_client.lua b/mysql_client.lua index 0f63969..ff07a93 100644 --- a/mysql_client.lua +++ b/mysql_client.lua @@ -1,4 +1,5 @@ --- Copyright (C) Yichun Zhang (agentzh) + +-- Copyright (C) Yichun Zhang (agentzh). BSD license. local tcp = require'sock'.tcp local sha1 = require'sha1' diff --git a/mysql_client.md b/mysql_client.md new file mode 100644 index 0000000..4c192d0 --- /dev/null +++ b/mysql_client.md @@ -0,0 +1,246 @@ + +## `local mysql = require'mysql_client'` + +MySQL client protocol + +## Status + +This library is considered production ready. + +## Example + +```lua +local mysql = require "resty.mysql" +local db, err = mysql:new() +if not db then + ngx.say("failed to instantiate mysql: ", err) + return +end + +db:set_timeout(1000) -- 1 sec + +-- or connect to a unix domain socket file listened +-- by a mysql server: +-- local ok, err, errcode, sqlstate = +-- db:connect{ +-- path = "/path/to/mysql.sock", +-- database = "ngx_test", +-- user = "ngx_test", +-- password = "ngx_test" } + +local ok, err, errcode, sqlstate = db:connect{ + host = "127.0.0.1", + port = 3306, + database = "ngx_test", + user = "ngx_test", + password = "ngx_test", + charset = "utf8", + max_packet_size = 1024 * 1024, +} + +if not ok then + ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate) + return +end + +ngx.say("connected to mysql.") + +local res, err, errcode, sqlstate = + db:query("drop table if exists cats") +if not res then + ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") + return +end + +res, err, errcode, sqlstate = + db:query("create table cats " + .. "(id serial primary key, " + .. "name varchar(5))") +if not res then + ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") + return +end + +ngx.say("table cats created.") + +res, err, errcode, sqlstate = + db:query("insert into cats (name) " + .. "values (\'Bob\'),(\'\'),(null)") +if not res then + ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") + return +end + +ngx.say(res.affected_rows, " rows inserted into table cats ", + "(last insert id: ", res.insert_id, ")") + +-- run a select query, expected about 10 rows in +-- the result set: +res, err, errcode, sqlstate = + db:query("select * from cats order by id asc", 10) +if not res then + ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") + return +end + +local cjson = require "cjson" +ngx.say("result: ", cjson.encode(res)) + +-- put it into the connection pool of size 100, +-- with 10 seconds max idle timeout +local ok, err = db:set_keepalive(10000, 100) +if not ok then + ngx.say("failed to set keepalive: ", err) + return +end + +-- or just close the connection right away: +-- local ok, err = db:close() +-- if not ok then +-- ngx.say("failed to close: ", err) +-- return +-- end +``` + +## API + +### `mysql:new() -> db | nil,err` + +Creates a MySQL connection object. + +### `db:connect(options) -> ok | nil,err,errcode,sqlstate` + +Connect to a MySQL server. + +The `options` argument is a Lua table holding the following keys: + + * `host`: the host name for the MySQL server. + * `port`: the port that the MySQL server is listening on. Default to 3306. + * `path`: the path of the unix socket file listened by the MySQL server. + * `database`: the MySQL database name. + * `user`: MySQL account name for login. + * `password`: MySQL account password for login (in clear text). + * `charset`: the character set used for the connection, which can be one of: + `big5`, `dec8`, `cp850`, `hp8`, `koi8r`, `latin1`, `latin2`, + `swe7`, `ascii`, `ujis`, `sjis`, `hebrew`, `tis620`, `euckr`, `koi8u`, `gb2312`, `greek`, + `cp1250`, `gbk`, `latin5`, `armscii8`, `utf8`, `ucs2`, `cp866`, `keybcs2`, `macce`, + `macroman`, `cp852`, `latin7`, `utf8mb4`, `cp1251`, `utf16`, `utf16le`, `cp1256`, + `cp1257`, `utf32`, `binary`, `geostd8`, `cp932`, `eucjpms`, `gb18030`. + * `max_packet_size`: the upper limit for the reply packets sent from the server (default to 1MB). + * `ssl`: if `true`, then uses SSL to connect to MySQL (default to `false`). + If the server does not have SSL support (or just disabled), the error string + "ssl disabled on server" will be returned. + * `ssl_verify`: if `true`, then verifies the validity of the server SSL certificate (default to `false`). + * `compact_arrays`: `true` to use array-of-arrays structure for the result set, + rather than the default array-of-hashes structure. + +### `db:set_timeout(time)` + +Sets the timeout (in ms) protection for subsequent operations, +including the `connect` method. + +### `db:close() -> 1 | nil,err` + +Closes the current mysql connection and returns the status. + +### `db:send_query(query) -> bytes | nil,err` + +Sends the query to the remote MySQL server without waiting for its replies. + +Returns the bytes successfully sent out. Use `read_result()` to read the replies. + +### `db:read_result([nrows]) -> res | nil,err,errcode,sqlstate` + +Reads in one result returned from the server. + +It returns a Lua table (`res`) describing the MySQL `OK packet` +or `result set packet` for the query result. + +For queries corresponding to a result set, it returns an array holding all the rows. +Each row holds key-value pairs for each data fields. For instance, + +```lua + { + { name = "Bob", age = 32, phone = ngx.null }, + { name = "Marry", age = 18, phone = "10666372"} + } +``` + +For queries that do not correspond to a result set, it returns a Lua table like this: + +```lua + { + insert_id = 0, + server_status = 2, + warning_count = 1, + affected_rows = 32, + message = nil + } +``` + +If more results are following the current result, a second `err` return value +will be given the string `again`. One should always check this (second) return +value and if it is `again`, then she should call this method again to retrieve +more results. This usually happens when the original query contains multiple +statements (separated by semicolon in the same query string) or calling a +MySQL procedure. See also [Multi-Resultset Support](#multi-resultset-support). + +In case of errors, this method returns at most 4 values: `nil`, `err`, `errcode`, and `sqlstate`. +The `err` return value contains a string describing the error, the `errcode` +return value holds the MySQL error code (a numerical value), and finally, +the `sqlstate` return value contains the standard SQL error code that consists +of 5 characters. Note that, the `errcode` and `sqlstate` might be `nil` +if MySQL does not return them. + +The optional argument `nrows` can be used to specify an approximate number +of rows for the result set. This value can be used to pre-allocate space +in the resulting Lua table for the result set. By default, it takes the value 4. + +### `db:query(query, [nrows]) -> res, err, errcode, sqlstate` + +This is a shortcut for combining the [send_query](#send_query) call +and the first [read_result](#read_result) call. + +You should always check if the `err` return value is `again` in case of +success because this method will only call [read_result](#read_result) +only once for you. See also [Multi-Resultset Support](#multi-resultset-support). + +### `db:server_ver() -> s` + +Returns the MySQL server version string, like `"5.1.64"`. + +You should only call this method after successfully connecting to a MySQL server, +otherwise `nil` will be returned. + +### `db:set_compact_arrays(true|false)` + +Sets whether to use the "compact-arrays" structure for the resultsets returned +by subsequent queries. See the `compact_arrays` option for the `connect` +method for more details. + +### `mysql.quote(s) -> s` + +Quote literal string to be used in queries. + +### Multiple result set support + +For a SQL query that produces multiple result-sets, it is always your duty to +check the 'again' error message returned by the query, and keep pulling more +result sets by calling the `read_result()` until no 'again' error message +returned (or some other errors happen). + +## Limitations + +### Authentication + +By default, of all authentication methods, only +[Old Password Authentication(mysql_old_password)](https://dev.mysql.com/doc/internals/en/old-password-authentication.html) +and [Secure Password Authentication(mysql_native_password)](https://dev.mysql.com/doc/internals/en/secure-password-authentication.html) +are suppored. + +## TODO + +* implement the MySQL binary row data packets. +* implement MySQL server prepare and execute packets. +* implement the data compression support in the protocol. +