├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── .travis └── initializedb.sh ├── Makefile ├── README.markdown ├── dist.ini ├── lib └── resty │ └── mysql.lua ├── server.crt ├── server.key ├── t ├── Test.pm ├── auth.t ├── big.t ├── bug.t ├── charset.t ├── compact_arrays.t ├── data │ ├── test-sha1.crt │ ├── test-sha1.key │ ├── test-sha1.pub │ ├── test-sha256.crt │ ├── test-sha256.key │ └── world.sql.gz ├── lib │ ├── create_sql_by_columns.lua │ └── ljson.lua ├── sanity.t ├── ssl.t ├── version.t └── world.t └── valgrind.suppress /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | This place is for bug reports and development discussions only. For general questions and 2 | discussions, please join the openresty-en mailing list instead: https://openresty.org/en/community.html 3 | 4 | Ensure you have provided the following details while reporting a problem: 5 | 6 | * The exact version of the related software, including but not limited to the OpenResty version 7 | (if any), the NGINX core version, ngx_lua module version, this Lua library''s version, 8 | and operating system version. 9 | 10 | * A minimal and standalone test case that others can easily run on their side and 11 | reproduce the issue you are seeing. 12 | 13 | * Do not simply say "something is broken" or "something does not work". Always provide 14 | as much details as possible. Always describe the symptoms and your expected results. 15 | 16 | Please, do not use Chinese here. This place is considered English only. If you 17 | really want to use Chinese, please join and post to the openresty (Chinese) 18 | mailing list instead. Please see https://openresty.org/en/community.html Thanks for 19 | your cooperation. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | *~ 4 | go 5 | t/servroot/ 6 | reindex 7 | tags 8 | *.t_ 9 | src/ 10 | mysqld.cnf 11 | download-cache/ 12 | t/data/test.* 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: focal 2 | 3 | branches: 4 | only: 5 | - "master" 6 | 7 | os: linux 8 | 9 | services: 10 | - docker 11 | 12 | language: c 13 | 14 | compiler: 15 | - gcc 16 | 17 | addons: 18 | apt: 19 | packages: 20 | - ack 21 | - axel 22 | - cpanminus 23 | - libtest-base-perl 24 | - libtext-diff-perl 25 | - liburi-perl 26 | - libwww-perl 27 | - libtest-longstring-perl 28 | - liblist-moreutils-perl 29 | - libgd-dev 30 | - time 31 | - cmake 32 | - curl 33 | 34 | before_cache: 35 | - docker save -o docker_images/images.tar $(docker images -a -q) 36 | 37 | cache: 38 | apt: true 39 | directories: 40 | - download-cache 41 | - docker_images 42 | 43 | env: 44 | global: 45 | - JOBS=3 46 | - NGX_BUILD_JOBS=$JOBS 47 | - LUAJIT_PREFIX=/opt/luajit21 48 | - LUAJIT_LIB=$LUAJIT_PREFIX/lib 49 | - LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1 50 | - LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH 51 | - TEST_NGINX_MYSQL_PATH=/var/run/mysqld/mysqld.sock 52 | - TEST_NGINX_SLEEP=0.006 53 | - OPENSSL_PREFIX=/opt/ssl 54 | - OPENSSL_LIB=$OPENSSL_PREFIX/lib 55 | - OPENSSL_INC=$OPENSSL_PREFIX/include 56 | - OPENSSL_VER=1.1.1w 57 | jobs: 58 | - NGINX_VERSION=1.27.1 DB_VERSION=mysql:5.7 59 | - NGINX_VERSION=1.27.1 DB_VERSION=mysql:8.0 60 | - NGINX_VERSION=1.27.1 DB_VERSION=mariadb:5.5 61 | - NGINX_VERSION=1.27.1 DB_VERSION=mariadb:10.0 62 | - NGINX_VERSION=1.27.1 DB_VERSION=mariadb:10.1 63 | - NGINX_VERSION=1.27.1 DB_VERSION=mariadb:10.2 64 | - NGINX_VERSION=1.27.1 DB_VERSION=mariadb:10.3 65 | 66 | install: 67 | - if [ ! -d download-cache ]; then mkdir download-cache; fi 68 | - if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -O download-cache/openssl-$OPENSSL_VER.tar.gz https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi 69 | - git clone https://github.com/openresty/test-nginx.git 70 | - git clone https://github.com/openresty/openresty.git ../openresty 71 | - git clone https://github.com/openresty/nginx-devel-utils.git 72 | - git clone https://github.com/openresty/lua-cjson.git 73 | - git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module 74 | - git clone https://github.com/openresty/stream-lua-nginx-module.git ../stream-lua-nginx-module 75 | - git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core 76 | - git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache 77 | - git clone https://github.com/openresty/lua-resty-string.git ../lua-resty-string 78 | - git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx 79 | - git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git luajit2 80 | - git clone https://github.com/spacewander/lua-resty-rsa.git ../lua-resty-rsa 81 | 82 | before_script: 83 | - docker load -i docker_images/images.tar || true 84 | - sudo systemctl stop mysql 85 | - ./.travis/initializedb.sh 86 | - cd luajit2/ 87 | - make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT -msse4.2' > build.log 2>&1 || (cat build.log && exit 1) 88 | - sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1) 89 | - cd .. 90 | 91 | script: 92 | - tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz 93 | - cd openssl-$OPENSSL_VER/ 94 | - ./config shared --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1) 95 | - make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1) 96 | - sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1) && cd .. 97 | - (cpanm --notest ./test-nginx > build.log 2>&1 || (cat build.log && exit 1)) 98 | - cd lua-cjson && (LUA_INCLUDE_DIR=$LUAJIT_INC make && sudo PATH=$PATH make install) || (cat build.log && exit 1) && cd .. 99 | - export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH 100 | - ngx-build $NGINX_VERSION --with-ipv6 --with-http_realip_module --with-http_ssl_module --with-cc-opt="-I$OPENSSL_INC" --with-ld-opt="-L$OPENSSL_LIB -Wl,-rpath,$OPENSSL_LIB" --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --add-module=../lua-nginx-module --add-module=../stream-lua-nginx-module --with-debug > build.log 2>&1 || (cat build.log && exit 1) 101 | - TEST_SUBSYSTEM=http prove -I. -I./test-nginx/inc -I./test-nginx/lib -r t 102 | - TEST_SUBSYSTEM=stream prove -I. -I./test-nginx/inc -I./test-nginx/lib -r t 103 | -------------------------------------------------------------------------------- /.travis/initializedb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | if [ -z "$DB_VERSION" ]; then 6 | echo "DB_VERSION is required" 7 | exit 1 8 | fi 9 | 10 | if [ "$DB_VERSION" = 'mysql:8.0' ] || [ "$DB_VERSION" = 'mariadb:10.3' ]; then 11 | sudo cp t/data/test-sha256.crt t/data/test.crt 12 | sudo cp t/data/test-sha256.key t/data/test.key 13 | else 14 | sudo cp t/data/test-sha1.crt t/data/test.crt 15 | sudo cp t/data/test-sha1.key t/data/test.key 16 | sudo cp t/data/test-sha1.pub t/data/test.pub 17 | fi 18 | 19 | cat << EOF >mysqld.cnf 20 | [mysqld] 21 | ssl-ca=/etc/mysql/ssl/test.crt 22 | ssl-cert=/etc/mysql/ssl/test.crt 23 | ssl-key=/etc/mysql/ssl/test.key 24 | socket=/var/run/mysqld/mysqld.sock 25 | EOF 26 | 27 | if [ "$DB_VERSION" != 'mysql:8.0' ] && [ "$DB_VERSION" != 'mysql:5.7' ]; then 28 | cat << EOF >>mysqld.cnf 29 | secure-auth=0 30 | EOF 31 | fi 32 | 33 | if [ "$DB_VERSION" = 'mysql:5.6' ]; then 34 | cat << EOF >>mysqld.cnf 35 | sha256_password_private_key_path=/etc/mysql/ssl/test.key 36 | sha256_password_public_key_path=/etc/mysql/ssl/test.pub 37 | EOF 38 | fi 39 | 40 | cat << EOF >>mysqld.cnf 41 | 42 | [client] 43 | socket=/var/run/mysqld/mysqld.sock 44 | EOF 45 | 46 | path=`pwd` 47 | container_name=mysqld 48 | 49 | if [ "$(sudo docker ps -a --filter "name=^/$container_name$" --format '{{.Names}}')" = "$container_name" ]; then 50 | sudo docker stop "$container_name" 51 | sudo docker rm "$container_name" 52 | fi 53 | 54 | sudo mkdir -p /var/run/mysqld 55 | sudo chmod -R 777 /var/run/mysqld 56 | sudo docker pull "${DB_VERSION}" 57 | sudo docker run \ 58 | -itd \ 59 | --privileged \ 60 | --name="$container_name" \ 61 | --pid=host \ 62 | --net=host \ 63 | --ipc=host \ 64 | -e MYSQL_ALLOW_EMPTY_PASSWORD=yes \ 65 | -e MYSQL_TCP_PORT="${TEST_NGINX_MYSQL_PORT:-3306}" \ 66 | --volume=/var/run/mysqld:/var/run/mysqld \ 67 | --volume="$path/mysqld.cnf":/etc/mysql/conf.d/mysqld.cnf \ 68 | --volume="$path/t/data/test.crt":/etc/mysql/ssl/test.crt \ 69 | --volume="$path/t/data/test.key":/etc/mysql/ssl/test.key \ 70 | --volume="$path/t/data/test.pub":/etc/mysql/ssl/test.pub \ 71 | ${DB_VERSION} 72 | 73 | mysql() { 74 | sudo docker exec mysqld mysql "${@}" 75 | } 76 | for i in {1..100} 77 | do 78 | sleep 3 79 | mysql --protocol=tcp -e 'select version()' && break 80 | done 81 | sudo docker logs mysqld 82 | 83 | sudo docker cp $path/t/data/world.sql.gz mysqld:/tmp/world.sql.gz 84 | sudo docker exec mysqld /bin/sh -c "zcat /tmp/world.sql.gz | mysql -uroot" 85 | 86 | mysql -uroot -e 'create database ngx_test;' 87 | mysql -uroot -e 'create user "ngx_test"@"%" identified by "ngx_test";' 88 | mysql -uroot -e 'grant all on ngx_test.* to "ngx_test"@"%";' 89 | 90 | if [ "$DB_VERSION" = 'mysql:8.0' -o "$DB_VERSION" = 'mysql:5.7' ]; then # sha256_password, mysql_native_password 91 | mysql -uroot -e 'create user "user_sha256"@"%" identified with "sha256_password" by "pass_sha256";' 92 | mysql -uroot -e 'grant all on ngx_test.* to "user_sha256"@"%";' 93 | mysql -uroot -e 'create user "nopass_sha256"@"%" identified with "sha256_password";' 94 | mysql -uroot -e 'grant all on ngx_test.* to "nopass_sha256"@"%";' 95 | 96 | if [ "$DB_VERSION" != 'mysql:5.7' ]; then # mysql:8.0 caching_sha2_password 97 | mysql -uroot -e 'create user "user_caching_sha2"@"%" identified with "caching_sha2_password" by "pass_caching_sha2";' 98 | mysql -uroot -e 'grant all on ngx_test.* to "user_caching_sha2"@"%";' 99 | mysql -uroot -e 'create user "nopass_caching_sha2"@"%" identified with "caching_sha2_password";' 100 | mysql -uroot -e 'grant all on ngx_test.* to "nopass_caching_sha2"@"%";' 101 | fi 102 | 103 | mysql -uroot -e 'create user "user_native"@"%" identified with "mysql_native_password" by "pass_native";' 104 | else # other: mysql_native_password, mysql_old_password 105 | if [ "$DB_VERSION" = 'mysql:5.6' ]; then # mysql:5.6 sha256_password 106 | mysql -uroot -e 'create user "user_sha256"@"%" identified with "sha256_password";' 107 | mysql -uroot -e 'set old_passwords = 2;set password for "user_sha256"@"%" = PASSWORD("pass_sha256");' 108 | mysql -uroot -e 'grant all on ngx_test.* to "user_sha256"@"%";' 109 | mysql -uroot -e 'create user "nopass_sha256"@"%" identified with "sha256_password";' 110 | mysql -uroot -e 'grant all on ngx_test.* to "nopass_sha256"@"%";' 111 | fi 112 | 113 | mysql -uroot -e 'create user "user_old"@"%" identified with mysql_old_password;' 114 | mysql -uroot -e 'set old_passwords = 1;set password for "user_old"@"%" = PASSWORD("pass_old");' 115 | mysql -uroot -e 'grant all on ngx_test.* to "user_old"@"%";' 116 | mysql -uroot -e 'create user "nopass_old"@"%" identified with mysql_old_password;' 117 | mysql -uroot -e 'set password for "nopass_old"@"%" = "";' 118 | mysql -uroot -e 'grant all on ngx_test.* to "nopass_old"@"%";' 119 | 120 | mysql -uroot -e 'create user "user_native"@"%" identified with mysql_native_password;' 121 | mysql -uroot -e 'set old_passwords = 0;set password for "user_native"@"%" = PASSWORD("pass_native");' 122 | fi 123 | 124 | mysql -uroot -e 'grant all on ngx_test.* to "user_native"@"%";' 125 | mysql -uroot -e 'create user "nopass_native"@"%" identified with mysql_native_password;' 126 | mysql -uroot -e 'grant all on ngx_test.* to "nopass_native"@"%";' 127 | 128 | mysql -uroot -e 'select * from information_schema.plugins where plugin_type="AUTHENTICATION"\G'; 129 | mysql -uroot -e 'select User, plugin from mysql.user\G'; 130 | 131 | mysql -uroot -e 'grant all on world.* to "ngx_test"@"%"; flush privileges;' 132 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OPENRESTY_PREFIX=/usr/local/openresty-debug 2 | 3 | PREFIX ?= /usr/local 4 | LUA_INCLUDE_DIR ?= $(PREFIX)/include 5 | LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION) 6 | INSTALL ?= install 7 | 8 | .PHONY: all test install 9 | 10 | all: ; 11 | 12 | install: all 13 | $(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty 14 | $(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty 15 | 16 | test: all 17 | PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t 18 | 19 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | lua-resty-mysql - Lua MySQL client driver for ngx_lua based on the cosocket API 5 | 6 | Table of Contents 7 | ================= 8 | 9 | * [Name](#name) 10 | * [Status](#status) 11 | * [Description](#description) 12 | * [Synopsis](#synopsis) 13 | * [Methods](#methods) 14 | * [new](#new) 15 | * [connect](#connect) 16 | * [set_timeout](#set_timeout) 17 | * [set_keepalive](#set_keepalive) 18 | * [get_reused_times](#get_reused_times) 19 | * [close](#close) 20 | * [send_query](#send_query) 21 | * [read_result](#read_result) 22 | * [query](#query) 23 | * [server_ver](#server_ver) 24 | * [set_compact_arrays](#set_compact_arrays) 25 | * [SQL Literal Quoting](#sql-literal-quoting) 26 | * [Multi-Resultset Support](#multi-resultset-support) 27 | * [Debugging](#debugging) 28 | * [Automatic Error Logging](#automatic-error-logging) 29 | * [Limitations](#limitations) 30 | * [More Authentication Method Support](#more-authentication-method-support) 31 | * [Installation](#installation) 32 | * [Community](#community) 33 | * [English Mailing List](#english-mailing-list) 34 | * [Chinese Mailing List](#chinese-mailing-list) 35 | * [Bugs and Patches](#bugs-and-patches) 36 | * [TODO](#todo) 37 | * [Author](#author) 38 | * [Copyright and License](#copyright-and-license) 39 | * [See Also](#see-also) 40 | 41 | Status 42 | ====== 43 | 44 | This library is considered production ready. 45 | 46 | Description 47 | =========== 48 | 49 | This Lua library is a MySQL client driver for the ngx_lua nginx module: 50 | 51 | https://github.com/openresty/lua-nginx-module 52 | 53 | This Lua library takes advantage of ngx_lua's cosocket API, which ensures 54 | 100% nonblocking behavior. 55 | 56 | Note that at least [ngx_lua 0.9.11](https://github.com/chaoslawful/lua-nginx-module/tags) or [ngx_openresty 1.7.4.1](http://openresty.org/#Download) is required. 57 | 58 | Also, the [bit library](http://bitop.luajit.org/) is also required. If you're using LuaJIT 2 with ngx_lua, then the `bit` library is already available by default. 59 | 60 | Synopsis 61 | ======== 62 | 63 | ```lua 64 | 65 | # you do not need the following line if you are using 66 | # the ngx_openresty bundle: 67 | lua_package_path "/path/to/lua-resty-mysql/lib/?.lua;;"; 68 | 69 | server { 70 | location /test { 71 | content_by_lua ' 72 | local mysql = require "resty.mysql" 73 | local db, err = mysql:new() 74 | if not db then 75 | ngx.say("failed to instantiate mysql: ", err) 76 | return 77 | end 78 | 79 | db:set_timeout(1000) -- 1 sec 80 | 81 | -- or connect to a unix domain socket file listened 82 | -- by a mysql server: 83 | -- local ok, err, errcode, sqlstate = 84 | -- db:connect{ 85 | -- path = "/path/to/mysql.sock", 86 | -- database = "ngx_test", 87 | -- user = "ngx_test", 88 | -- password = "ngx_test" } 89 | 90 | local ok, err, errcode, sqlstate = db:connect{ 91 | host = "127.0.0.1", 92 | port = 3306, 93 | database = "ngx_test", 94 | user = "ngx_test", 95 | password = "ngx_test", 96 | charset = "utf8", 97 | max_packet_size = 1024 * 1024, 98 | } 99 | 100 | if not ok then 101 | ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate) 102 | db:close() 103 | return 104 | end 105 | 106 | ngx.say("connected to mysql.") 107 | 108 | local res, err, errcode, sqlstate = 109 | db:query("drop table if exists cats") 110 | if not res then 111 | ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") 112 | db:close() 113 | return 114 | end 115 | 116 | res, err, errcode, sqlstate = 117 | db:query("create table cats " 118 | .. "(id serial primary key, " 119 | .. "name varchar(5))") 120 | if not res then 121 | ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") 122 | db:close() 123 | return 124 | end 125 | 126 | ngx.say("table cats created.") 127 | 128 | res, err, errcode, sqlstate = 129 | db:query("insert into cats (name) " 130 | .. "values (\'Bob\'),(\'\'),(null)") 131 | if not res then 132 | ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") 133 | db:close() 134 | return 135 | end 136 | 137 | ngx.say(res.affected_rows, " rows inserted into table cats ", 138 | "(last insert id: ", res.insert_id, ")") 139 | 140 | -- run a select query, expected about 10 rows in 141 | -- the result set: 142 | res, err, errcode, sqlstate = 143 | db:query("select * from cats order by id asc", 10) 144 | if not res then 145 | ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".") 146 | db:close() 147 | return 148 | end 149 | 150 | local cjson = require "cjson" 151 | ngx.say("result: ", cjson.encode(res)) 152 | 153 | -- put it into the connection pool of size 100, 154 | -- with 10 seconds max idle timeout 155 | local ok, err = db:set_keepalive(10000, 100) 156 | if not ok then 157 | ngx.say("failed to set keepalive: ", err) 158 | db:close() 159 | return 160 | end 161 | 162 | -- or just close the connection right away: 163 | -- local ok, err = db:close() 164 | -- if not ok then 165 | -- ngx.say("failed to close: ", err) 166 | -- return 167 | -- end 168 | '; 169 | } 170 | } 171 | ``` 172 | 173 | [Back to TOC](#table-of-contents) 174 | 175 | Methods 176 | ======= 177 | 178 | [Back to TOC](#table-of-contents) 179 | 180 | new 181 | --- 182 | `syntax: db, err = mysql:new()` 183 | 184 | Creates a MySQL connection object. In case of failures, returns `nil` and a string describing the error. 185 | 186 | [Back to TOC](#table-of-contents) 187 | 188 | connect 189 | ------- 190 | `syntax: ok, err, errcode, sqlstate = db:connect(options)` 191 | 192 | Attempts to connect to the remote MySQL server. 193 | 194 | The `options` argument is a Lua table holding the following keys: 195 | 196 | * `host` 197 | 198 | the host name for the MySQL server. 199 | * `port` 200 | 201 | the port that the MySQL server is listening on. Default to 3306. 202 | * `path` 203 | 204 | the path of the unix socket file listened by the MySQL server. 205 | * `database` 206 | 207 | the MySQL database name. 208 | * `user` 209 | 210 | MySQL account name for login. 211 | * `password` 212 | 213 | MySQL account password for login (in clear text). 214 | * `charset` 215 | 216 | the character set used on the MySQL connection, which can be different from the default charset setting. 217 | The following values are accepted: `big5`, `dec8`, `cp850`, `hp8`, `koi8r`, `latin1`, `latin2`, 218 | `swe7`, `ascii`, `ujis`, `sjis`, `hebrew`, `tis620`, `euckr`, `koi8u`, `gb2312`, `greek`, 219 | `cp1250`, `gbk`, `latin5`, `armscii8`, `utf8`, `ucs2`, `cp866`, `keybcs2`, `macce`, 220 | `macroman`, `cp852`, `latin7`, `utf8mb4`, `cp1251`, `utf16`, `utf16le`, `cp1256`, 221 | `cp1257`, `utf32`, `binary`, `geostd8`, `cp932`, `eucjpms`, `gb18030`. 222 | * `max_packet_size` 223 | 224 | the upper limit for the reply packets sent from the MySQL server (default to 1MB). 225 | * `ssl` 226 | 227 | If set to `true`, then uses SSL to connect to MySQL (default to `false`). If the MySQL 228 | server does not have SSL support 229 | (or just disabled), the error string "ssl disabled on server" will be returned. 230 | * `ssl_verify` 231 | 232 | If set to `true`, then verifies the validity of the server SSL certificate (default to `false`). 233 | Note that you need to configure the [lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate) 234 | to specify the CA (or server) certificate used by your MySQL server. You may also 235 | need to configure [lua_ssl_verify_depth](https://github.com/openresty/lua-nginx-module#lua_ssl_verify_depth) 236 | accordingly. 237 | * `pool` 238 | 239 | the name for the MySQL connection pool. if omitted, an ambiguous pool name will be generated automatically with the string template `user:database:host:port` or `user:database:path`. (this option was first introduced in `v0.08`.) 240 | 241 | * `pool_size` 242 | 243 | Specifies the size of the connection pool. If omitted and no `backlog` option was provided, no pool will be created. If omitted but `backlog` was provided, the pool will be created with a default size equal to the value of the [lua_socket_pool_size](https://github.com/openresty/lua-nginx-module#lua_socket_pool_size) directive. The connection pool holds up to `pool_size` alive connections ready to be reused by subsequent calls to [connect](#connect), but note that there is no upper limit to the total number of opened connections outside of the pool. If you need to restrict the total number of opened connections, specify the `backlog` option. When the connection pool would exceed its size limit, the least recently used (kept-alive) connection already in the pool will be closed to make room for the current connection. Note that the cosocket connection pool is per Nginx worker process rather than per Nginx server instance, so the size limit specified here also applies to every single Nginx worker process. Also note that the size of the connection pool cannot be changed once it has been created. Note that at least [ngx_lua 0.10.14](https://github.com/openresty/lua-nginx-module/tags) is required to use this options. 244 | 245 | * `backlog` 246 | 247 | If specified, this module will limit the total number of opened connections for this pool. No more connections than `pool_size` can be opened for this pool at any time. If the connection pool is full, subsequent connect operations will be queued into a queue equal to this option's value (the "backlog" queue). If the number of queued connect operations is equal to `backlog`, subsequent connect operations will fail and return nil plus the error string `"too many waiting connect operations"`. The queued connect operations will be resumed once the number of connections in the pool is less than `pool_size`. The queued connect operation will abort once they have been queued for more than `connect_timeout`, controlled by [set_timeout](#set_timeout), and will return nil plus the error string "timeout". Note that at least [ngx_lua 0.10.14](https://github.com/openresty/lua-nginx-module/tags) is required to use this options. 248 | 249 | * `compact_arrays` 250 | 251 | when this option is set to true, then the [query](#query) and [read_result](#read_result) methods will return the array-of-arrays structure for the resultset, rather than the default array-of-hashes structure. 252 | 253 | Before actually resolving the host name and connecting to the remote backend, this method will always look up the connection pool for matched idle connections created by previous calls of this method. 254 | 255 | [Back to TOC](#table-of-contents) 256 | 257 | set_timeout 258 | ---------- 259 | `syntax: db:set_timeout(time)` 260 | 261 | Sets the timeout (in ms) protection for subsequent operations, including the `connect` method. 262 | 263 | [Back to TOC](#table-of-contents) 264 | 265 | set_keepalive 266 | ------------ 267 | `syntax: ok, err = db:set_keepalive(max_idle_timeout, pool_size)` 268 | 269 | Puts the current MySQL connection immediately into the ngx_lua cosocket connection pool. 270 | 271 | You can specify the max idle timeout (in ms) when the connection is in the pool and the maximal size of the pool every nginx worker process. 272 | 273 | In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. 274 | 275 | Only call this method in the place you would have called the `close` method instead. Calling this method will immediately turn the current `resty.mysql` object into the `closed` state. Any subsequent operations other than `connect()` on the current objet will return the `closed` error. 276 | 277 | [Back to TOC](#table-of-contents) 278 | 279 | get_reused_times 280 | ---------------- 281 | `syntax: times, err = db:get_reused_times()` 282 | 283 | This method returns the (successfully) reused times for the current connection. In case of error, it returns `nil` and a string describing the error. 284 | 285 | If the current connection does not come from the built-in connection pool, then this method always returns `0`, that is, the connection has never been reused (yet). If the connection comes from the connection pool, then the return value is always non-zero. So this method can also be used to determine if the current connection comes from the pool. 286 | 287 | [Back to TOC](#table-of-contents) 288 | 289 | close 290 | ----- 291 | `syntax: ok, err = db:close()` 292 | 293 | Closes the current mysql connection and returns the status. 294 | 295 | In case of success, returns `1`. In case of errors, returns `nil` with a string describing the error. 296 | 297 | [Back to TOC](#table-of-contents) 298 | 299 | send_query 300 | ---------- 301 | `syntax: bytes, err = db:send_query(query)` 302 | 303 | Sends the query to the remote MySQL server without waiting for its replies. 304 | 305 | Returns the bytes successfully sent out in success and otherwise returns `nil` and a string describing the error. 306 | 307 | You should use the [read_result](#read_result) method to read the MySQL replies afterwards. 308 | 309 | [Back to TOC](#table-of-contents) 310 | 311 | read_result 312 | ----------- 313 | `syntax: res, err, errcode, sqlstate = db:read_result()` 314 | 315 | `syntax: res, err, errcode, sqlstate = db:read_result(nrows)` 316 | 317 | Reads in one result returned from the MySQL server. 318 | 319 | It returns a Lua table (`res`) describing the MySQL `OK packet` or `result set packet` for the query result. 320 | 321 | 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, 322 | 323 | ```lua 324 | { 325 | { name = "Bob", age = 32, phone = ngx.null }, 326 | { name = "Marry", age = 18, phone = "10666372"} 327 | } 328 | ``` 329 | 330 | For queries that do not correspond to a result set, it returns a Lua table like this: 331 | 332 | ```lua 333 | { 334 | insert_id = 0, 335 | server_status = 2, 336 | warning_count = 1, 337 | affected_rows = 32, 338 | message = nil 339 | } 340 | ``` 341 | 342 | 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). 343 | 344 | 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. 345 | 346 | The optional argument `nrows` can be used to specify an approximate number of rows for the result set. This value can be used 347 | to pre-allocate space in the resulting Lua table for the result set. By default, it takes the value 4. 348 | 349 | [Back to TOC](#table-of-contents) 350 | 351 | query 352 | ----- 353 | `syntax: res, err, errcode, sqlstate = db:query(query)` 354 | 355 | `syntax: res, err, errcode, sqlstate = db:query(query, nrows)` 356 | 357 | This is a shortcut for combining the [send_query](#send_query) call and the first [read_result](#read_result) call. 358 | 359 | 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). 360 | 361 | [Back to TOC](#table-of-contents) 362 | 363 | server_ver 364 | ---------- 365 | `syntax: str = db:server_ver()` 366 | 367 | Returns the MySQL server version string, like `"5.1.64"`. 368 | 369 | You should only call this method after successfully connecting to a MySQL server, otherwise `nil` will be returned. 370 | 371 | [Back to TOC](#table-of-contents) 372 | 373 | set_compact_arrays 374 | ------------------ 375 | `syntax: db:set_compact_arrays(boolean)` 376 | 377 | 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. 378 | 379 | This method was first introduced in the `v0.09` release. 380 | 381 | [Back to TOC](#table-of-contents) 382 | 383 | SQL Literal Quoting 384 | =================== 385 | 386 | It is always important to quote SQL literals properly to prevent SQL injection attacks. You can use the 387 | [ngx.quote_sql_str](https://github.com/openresty/lua-nginx-module#ngxquote_sql_str) function provided by ngx_lua to quote values. 388 | Here is an example: 389 | 390 | ```lua 391 | local name = ngx.unescape_uri(ngx.var.arg_name) 392 | local quoted_name = ngx.quote_sql_str(name) 393 | local sql = "select * from users where name = " .. quoted_name 394 | ``` 395 | 396 | [Back to TOC](#table-of-contents) 397 | 398 | Multi-Resultset Support 399 | ======================= 400 | 401 | For a SQL query that produces multiple result-sets, it is always your duty to check the "again" error message returned by the [query](#query) or [read_result](#read_result) method calls, and keep pulling more result sets by calling the [read_result](#read_result) method until no "again" error message returned (or some other errors happen). 402 | 403 | Below is a trivial example for this: 404 | 405 | ```lua 406 | local cjson = require "cjson" 407 | local mysql = require "resty.mysql" 408 | 409 | local db = mysql:new() 410 | local ok, err, errcode, sqlstate = db:connect({ 411 | host = "127.0.0.1", 412 | port = 3306, 413 | database = "world", 414 | user = "monty", 415 | password = "pass"}) 416 | 417 | if not ok then 418 | ngx.log(ngx.ERR, "failed to connect: ", err, ": ", errcode, " ", sqlstate) 419 | return ngx.exit(500) 420 | end 421 | 422 | res, err, errcode, sqlstate = db:query("select 1; select 2; select 3;") 423 | if not res then 424 | ngx.log(ngx.ERR, "bad result #1: ", err, ": ", errcode, ": ", sqlstate, ".") 425 | db:close() 426 | return ngx.exit(500) 427 | end 428 | 429 | ngx.say("result #1: ", cjson.encode(res)) 430 | 431 | local i = 2 432 | while err == "again" do 433 | res, err, errcode, sqlstate = db:read_result() 434 | if not res then 435 | ngx.log(ngx.ERR, "bad result #", i, ": ", err, ": ", errcode, ": ", sqlstate, ".") 436 | db:close() 437 | return ngx.exit(500) 438 | end 439 | 440 | ngx.say("result #", i, ": ", cjson.encode(res)) 441 | i = i + 1 442 | end 443 | 444 | local ok, err = db:set_keepalive(10000, 50) 445 | if not ok then 446 | ngx.log(ngx.ERR, "failed to set keepalive: ", err) 447 | db:close() 448 | ngx.exit(500) 449 | end 450 | ``` 451 | 452 | This code snippet will produce the following response body data: 453 | 454 | result #1: [{"1":"1"}] 455 | result #2: [{"2":"2"}] 456 | result #3: [{"3":"3"}] 457 | 458 | [Back to TOC](#table-of-contents) 459 | 460 | Debugging 461 | ========= 462 | 463 | It is usually convenient to use the [lua-cjson](http://www.kyne.com.au/~mark/software/lua-cjson.php) library to encode the return values of the MySQL query methods to JSON. For example, 464 | 465 | ```lua 466 | local cjson = require "cjson" 467 | ... 468 | local res, err, errcode, sqlstate = db:query("select * from cats") 469 | if res then 470 | print("res: ", cjson.encode(res)) 471 | end 472 | ``` 473 | 474 | [Back to TOC](#table-of-contents) 475 | 476 | Automatic Error Logging 477 | ======================= 478 | 479 | By default the underlying [ngx_lua](https://github.com/openresty/lua-nginx-module) module 480 | does error logging when socket errors happen. If you are already doing proper error 481 | handling in your own Lua code, then you are recommended to disable this automatic error logging by turning off [ngx_lua](https://github.com/openresty/lua-nginx-module)'s [lua_socket_log_errors](https://github.com/openresty/lua-nginx-module#lua_socket_log_errors) directive, that is, 482 | 483 | ```nginx 484 | lua_socket_log_errors off; 485 | ``` 486 | 487 | [Back to TOC](#table-of-contents) 488 | 489 | Limitations 490 | =========== 491 | 492 | * This library cannot be used in code contexts like init_by_lua*, set_by_lua*, log_by_lua*, and 493 | header_filter_by_lua* where the ngx_lua cosocket API is not available. 494 | * The `resty.mysql` object instance cannot be stored in a Lua variable at the Lua module level, 495 | because it will then be shared by all the concurrent requests handled by the same nginx 496 | worker process (see 497 | https://github.com/openresty/lua-nginx-module#data-sharing-within-an-nginx-worker ) and 498 | result in bad race conditions when concurrent requests are trying to use the same `resty.mysql` instance. 499 | You should always initiate `resty.mysql` objects in function local 500 | variables or in the `ngx.ctx` table. These places all have their own data copies for 501 | each request. 502 | 503 | [Back to TOC](#table-of-contents) 504 | 505 | More Authentication Method Support 506 | ========= 507 | 508 | By default, Of all authentication method, 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. If the server requires [sha256_password](https://dev.mysql.com/doc/internals/en/sha256.html) or cache_sha2_password, an error like `auth plugin caching_sha2_password or sha256_password are not supported because resty.rsa is not installed` may be returned. 509 | 510 | Need [lua-resty-rsa](https://github.com/spacewander/lua-resty-rsa) when using the `sha256_password` and `cache_sha2_password`. 511 | 512 | [Back to TOC](#table-of-contents) 513 | 514 | Installation 515 | ============ 516 | 517 | If you are using the ngx_openresty bundle (http://openresty.org ), then 518 | you do not need to do anything because it already includes and enables 519 | lua-resty-mysql by default. And you can just use it in your Lua code, 520 | as in 521 | 522 | ```lua 523 | local mysql = require "resty.mysql" 524 | ... 525 | ``` 526 | 527 | If you are using your own nginx + ngx_lua build, then you need to configure 528 | the lua_package_path directive to add the path of your lua-resty-mysql source 529 | tree to ngx_lua's LUA_PATH search path, as in 530 | 531 | ```nginx 532 | # nginx.conf 533 | http { 534 | lua_package_path "/path/to/lua-resty-mysql/lib/?.lua;;"; 535 | ... 536 | } 537 | ``` 538 | 539 | Ensure that the system account running your Nginx ''worker'' proceses have 540 | enough permission to read the `.lua` file. 541 | 542 | [Back to TOC](#table-of-contents) 543 | 544 | Community 545 | ========= 546 | 547 | [Back to TOC](#table-of-contents) 548 | 549 | English Mailing List 550 | -------------------- 551 | 552 | The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers. 553 | 554 | [Back to TOC](#table-of-contents) 555 | 556 | Chinese Mailing List 557 | -------------------- 558 | 559 | The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers. 560 | 561 | [Back to TOC](#table-of-contents) 562 | 563 | Bugs and Patches 564 | ================ 565 | 566 | Please submit bug reports, wishlists, or patches by 567 | 568 | 1. creating a ticket on the [GitHub Issue Tracker](http://github.com/agentzh/lua-resty-mysql/issues), 569 | 1. or posting to the [OpenResty community](https://github.com/openresty/lua-nginx-module#community). 570 | 571 | [Back to TOC](#table-of-contents) 572 | 573 | TODO 574 | ==== 575 | 576 | * improve the MySQL connection pool support. 577 | * implement the MySQL binary row data packets. 578 | * implement MySQL server prepare and execute packets. 579 | * implement the data compression support in the protocol. 580 | 581 | [Back to TOC](#table-of-contents) 582 | 583 | Author 584 | ====== 585 | 586 | Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 587 | 588 | [Back to TOC](#table-of-contents) 589 | 590 | Copyright and License 591 | ===================== 592 | 593 | This module is licensed under the BSD license. 594 | 595 | Copyright (C) 2012-2018, by Yichun "agentzh" Zhang (章亦春) , OpenResty Inc. 596 | 597 | All rights reserved. 598 | 599 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 600 | 601 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 602 | 603 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 604 | 605 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 606 | 607 | [Back to TOC](#table-of-contents) 608 | 609 | See Also 610 | ======== 611 | * the ngx_lua module: https://github.com/openresty/lua-nginx-module 612 | * the MySQL wired protocol specification: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol 613 | * the [lua-resty-memcached](https://github.com/agentzh/lua-resty-memcached) library 614 | * the [lua-resty-redis](https://github.com/agentzh/lua-resty-redis) library 615 | * the ngx_drizzle module: https://github.com/openresty/drizzle-nginx-module 616 | 617 | [Back to TOC](#table-of-contents) 618 | 619 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name=lua-resty-mysql 2 | abstract=Lua MySQL client driver for ngx_lua based on the cosocket API 3 | author=Yichun "agentzh" Zhang (agentzh) 4 | is_original=yes 5 | license=2bsd 6 | lib_dir=lib 7 | doc_dir=lib 8 | repo_link=https://github.com/openresty/lua-resty-mysql 9 | main_module=lib/resty/mysql.lua 10 | requires = luajit >= 2.1.0, ngx_http_lua >= 0.10.6 11 | -------------------------------------------------------------------------------- /lib/resty/mysql.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local bit = require "bit" 5 | local resty_sha256 = require "resty.sha256" 6 | local sub = string.sub 7 | local tcp = ngx.socket.tcp 8 | local strbyte = string.byte 9 | local strchar = string.char 10 | local strfind = string.find 11 | local format = string.format 12 | local strrep = string.rep 13 | local null = ngx.null 14 | local band = bit.band 15 | local bxor = bit.bxor 16 | local bor = bit.bor 17 | local lshift = bit.lshift 18 | local rshift = bit.rshift 19 | local tohex = bit.tohex 20 | local sha1 = ngx.sha1_bin 21 | local concat = table.concat 22 | local setmetatable = setmetatable 23 | local error = error 24 | local tonumber = tonumber 25 | local to_int = math.floor 26 | 27 | local has_rsa, resty_rsa = pcall(require, "resty.rsa") 28 | 29 | 30 | if not ngx.config then 31 | error("ngx_lua 0.9.11+ or ngx_stream_lua required") 32 | end 33 | 34 | if (not ngx.config.subsystem 35 | or ngx.config.subsystem == "http") -- subsystem is http 36 | and (not ngx.config.ngx_lua_version 37 | or ngx.config.ngx_lua_version < 9011) -- old version 38 | then 39 | error("ngx_lua 0.9.11+ required") 40 | end 41 | 42 | 43 | local ok, new_tab = pcall(require, "table.new") 44 | if not ok then 45 | new_tab = function (narr, nrec) return {} end 46 | end 47 | 48 | 49 | local _M = { _VERSION = '0.27' } 50 | 51 | 52 | -- constants 53 | 54 | local STATE_CONNECTED = 1 55 | local STATE_COMMAND_SENT = 2 56 | 57 | local COM_QUIT = 0x01 58 | local COM_QUERY = 0x03 59 | 60 | -- refer to https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags 61 | -- CLIENT_LONG_PASSWORD | CLIENT_FOUND_ROWS | CLIENT_LONG_FLAG 62 | -- | CLIENT_CONNECT_WITH_DB | CLIENT_ODBC | CLIENT_LOCAL_FILES 63 | -- | CLIENT_IGNORE_SPACE | CLIENT_PROTOCOL_41 | CLIENT_INTERACTIVE 64 | -- | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS | CLIENT_RESERVED 65 | -- | CLIENT_SECURE_CONNECTION | CLIENT_MULTI_STATEMENTS | CLIENT_MULTI_RESULTS 66 | local DEFAULT_CLIENT_FLAGS = 0x3f7cf 67 | local CLIENT_SSL = 0x00000800 68 | local CLIENT_PLUGIN_AUTH = 0x00080000 69 | local CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA = 0x00200000 70 | local DEFAULT_AUTH_PLUGIN = "mysql_native_password" 71 | 72 | local SERVER_MORE_RESULTS_EXISTS = 8 73 | 74 | local RESP_OK = "OK" 75 | local RESP_AUTHMOREDATA = "AUTHMOREDATA" 76 | local RESP_LOCALINFILE = "LOCALINFILE" 77 | local RESP_EOF = "EOF" 78 | local RESP_ERR = "ERR" 79 | local RESP_DATA = "DATA" 80 | 81 | local MY_RND_MAX_VAL = 0x3FFFFFFF 82 | local MIN_PROTOCOL_VER = 10 83 | 84 | local LEN_NATIVE_SCRAMBLE = 20 85 | local LEN_OLD_SCRAMBLE = 8 86 | 87 | -- 16MB - 1, the default max allowed packet size used by libmysqlclient 88 | local FULL_PACKET_SIZE = 16777215 89 | 90 | -- the following charset map is generated from the following mysql query: 91 | -- SELECT CHARACTER_SET_NAME, ID 92 | -- FROM information_schema.collations 93 | -- WHERE IS_DEFAULT = 'Yes' ORDER BY id; 94 | local CHARSET_MAP = { 95 | _default = 0, 96 | big5 = 1, 97 | dec8 = 3, 98 | cp850 = 4, 99 | hp8 = 6, 100 | koi8r = 7, 101 | latin1 = 8, 102 | latin2 = 9, 103 | swe7 = 10, 104 | ascii = 11, 105 | ujis = 12, 106 | sjis = 13, 107 | hebrew = 16, 108 | tis620 = 18, 109 | euckr = 19, 110 | koi8u = 22, 111 | gb2312 = 24, 112 | greek = 25, 113 | cp1250 = 26, 114 | gbk = 28, 115 | latin5 = 30, 116 | armscii8 = 32, 117 | utf8 = 33, 118 | ucs2 = 35, 119 | cp866 = 36, 120 | keybcs2 = 37, 121 | macce = 38, 122 | macroman = 39, 123 | cp852 = 40, 124 | latin7 = 41, 125 | utf8mb4 = 45, 126 | cp1251 = 51, 127 | utf16 = 54, 128 | utf16le = 56, 129 | cp1256 = 57, 130 | cp1257 = 59, 131 | utf32 = 60, 132 | binary = 63, 133 | geostd8 = 92, 134 | cp932 = 95, 135 | eucjpms = 97, 136 | gb18030 = 248 137 | } 138 | 139 | local mt = { __index = _M } 140 | 141 | 142 | -- mysql field value type converters 143 | local converters = new_tab(0, 9) 144 | 145 | for i = 0x01, 0x05 do 146 | -- tiny, short, long, float, double 147 | converters[i] = tonumber 148 | end 149 | converters[0x00] = tonumber -- decimal 150 | -- converters[0x08] = tonumber -- long long 151 | converters[0x09] = tonumber -- int24 152 | converters[0x0d] = tonumber -- year 153 | converters[0xf6] = tonumber -- newdecimal 154 | 155 | 156 | local function _get_byte2(data, i) 157 | local a, b = strbyte(data, i, i + 1) 158 | return bor(a, lshift(b, 8)), i + 2 159 | end 160 | 161 | 162 | local function _get_byte3(data, i) 163 | local a, b, c = strbyte(data, i, i + 2) 164 | return bor(a, lshift(b, 8), lshift(c, 16)), i + 3 165 | end 166 | 167 | 168 | local function _get_byte4(data, i) 169 | local a, b, c, d = strbyte(data, i, i + 3) 170 | return bor(a, lshift(b, 8), lshift(c, 16), lshift(d, 24)), i + 4 171 | end 172 | 173 | 174 | local function _get_byte8(data, i) 175 | local a, b, c, d, e, f, g, h = strbyte(data, i, i + 7) 176 | 177 | -- XXX workaround for the lack of 64-bit support in bitop: 178 | -- XXX return results in the range of signed 32 bit numbers 179 | local lo = bor(a, lshift(b, 8), lshift(c, 16)) 180 | local hi = bor(e, lshift(f, 8), lshift(g, 16), lshift(h, 24)) 181 | return lo + 16777216 * d + hi * 4294967296, i + 8 182 | 183 | -- return bor(a, lshift(b, 8), lshift(c, 16), lshift(d, 24), lshift(e, 32), 184 | -- lshift(f, 40), lshift(g, 48), lshift(h, 56)), i + 8 185 | end 186 | 187 | 188 | local function _set_byte2(n) 189 | return strchar(band(n, 0xff), band(rshift(n, 8), 0xff)) 190 | end 191 | 192 | 193 | local function _set_byte3(n) 194 | return strchar(band(n, 0xff), 195 | band(rshift(n, 8), 0xff), 196 | band(rshift(n, 16), 0xff)) 197 | end 198 | 199 | 200 | local function _set_byte4(n) 201 | return strchar(band(n, 0xff), 202 | band(rshift(n, 8), 0xff), 203 | band(rshift(n, 16), 0xff), 204 | band(rshift(n, 24), 0xff)) 205 | end 206 | 207 | 208 | local function _from_cstring(data, i) 209 | local last = strfind(data, "\0", i, true) 210 | if not last then 211 | return nil, nil 212 | end 213 | 214 | return sub(data, i, last - 1), last + 1 215 | end 216 | 217 | 218 | local function _to_cstring(data) 219 | return data .. "\0" 220 | end 221 | 222 | 223 | local function _dump(data) 224 | local len = #data 225 | local bytes = new_tab(len, 0) 226 | for i = 1, len do 227 | bytes[i] = format("%x", strbyte(data, i)) 228 | end 229 | return concat(bytes, " ") 230 | end 231 | 232 | 233 | local function _dumphex(data) 234 | local len = #data 235 | local bytes = new_tab(len, 0) 236 | for i = 1, len do 237 | bytes[i] = tohex(strbyte(data, i), 2) 238 | end 239 | return concat(bytes, " ") 240 | end 241 | 242 | 243 | local function _pwd_hash(password) 244 | local add = 7 245 | 246 | local hash1 = 1345345333 247 | local hash2 = 0x12345671 248 | 249 | local len = #password 250 | for i = 1, len do 251 | -- skip spaces and tabs in password 252 | local byte = strbyte(password, i) 253 | if byte ~= 32 and byte ~= 9 then -- not ' ' or '\t' 254 | hash1 = bxor(hash1, (band(hash1, 63) + add) * byte 255 | + lshift(hash1, 8)) 256 | 257 | hash2 = bxor(lshift(hash2, 8), hash1) + hash2 258 | 259 | add = add + byte 260 | end 261 | end 262 | 263 | -- remove sign bit (1<<31)-1) 264 | return band(hash1, 0x7FFFFFFF), band(hash2, 0x7FFFFFFF) 265 | end 266 | 267 | 268 | local function _random_byte(seed1, seed2) 269 | seed1 = (seed1 * 3 + seed2) % MY_RND_MAX_VAL 270 | seed2 = (seed1 + seed2 + 33) % MY_RND_MAX_VAL 271 | 272 | return to_int(seed1 * 31 / MY_RND_MAX_VAL), seed1, seed2 273 | end 274 | 275 | 276 | local function _compute_old_token(password, scramble) 277 | if password == "" then 278 | return "" 279 | end 280 | 281 | scramble = sub(scramble, 1, LEN_OLD_SCRAMBLE) 282 | 283 | local hash_pw1, hash_pw2 = _pwd_hash(password) 284 | local hash_sc1, hash_sc2 = _pwd_hash(scramble) 285 | 286 | local seed1 = bxor(hash_pw1, hash_sc1) % MY_RND_MAX_VAL 287 | local seed2 = bxor(hash_pw2, hash_sc2) % MY_RND_MAX_VAL 288 | local rand_byte 289 | 290 | local bytes = new_tab(LEN_OLD_SCRAMBLE, 0) 291 | for i = 1, LEN_OLD_SCRAMBLE do 292 | rand_byte, seed1, seed2 = _random_byte(seed1, seed2) 293 | bytes[i] = rand_byte + 64 294 | end 295 | 296 | rand_byte = _random_byte(seed1, seed2) 297 | for i = 1, LEN_OLD_SCRAMBLE do 298 | bytes[i] = strchar(bxor(bytes[i], rand_byte)) 299 | end 300 | 301 | return _to_cstring(concat(bytes)) 302 | end 303 | 304 | 305 | local function _compute_sha256_token(password, scramble) 306 | if password == "" then 307 | return "" 308 | end 309 | 310 | local sha256 = resty_sha256:new() 311 | if not sha256 then 312 | return nil, "failed to create the sha256 object" 313 | end 314 | 315 | if not sha256:update(password) then 316 | return nil, "failed to update string to sha256" 317 | end 318 | 319 | local message1 = sha256:final() 320 | 321 | sha256:reset() 322 | 323 | if not sha256:update(message1) then 324 | return nil, "failed to update string to sha256" 325 | end 326 | 327 | local message1_hash = sha256:final() 328 | 329 | sha256:reset() 330 | 331 | if not sha256:update(message1_hash) then 332 | return nil, "failed to update string to sha256" 333 | end 334 | 335 | if not sha256:update(scramble) then 336 | return nil, "failed to update string to sha256" 337 | end 338 | 339 | local message2 = sha256:final() 340 | 341 | local n = #message2 342 | local bytes = new_tab(n, 0) 343 | for i = 1, n do 344 | bytes[i] = strchar(bxor(strbyte(message1, i), strbyte(message2, i))) 345 | end 346 | 347 | return concat(bytes) 348 | end 349 | 350 | 351 | local function _compute_token(password, scramble) 352 | if password == "" then 353 | return "" 354 | end 355 | 356 | scramble = sub(scramble, 1, LEN_NATIVE_SCRAMBLE) 357 | 358 | local stage1 = sha1(password) 359 | local stage2 = sha1(stage1) 360 | local stage3 = sha1(scramble .. stage2) 361 | local n = #stage1 362 | local bytes = new_tab(n, 0) 363 | for i = 1, n do 364 | bytes[i] = strchar(bxor(strbyte(stage3, i), strbyte(stage1, i))) 365 | end 366 | 367 | return concat(bytes) 368 | end 369 | 370 | 371 | local function _send_packet(self, req, size) 372 | local sock = self.sock 373 | 374 | if self.packet_no == nil then 375 | self.packet_no = 0 376 | end 377 | 378 | self.packet_no = self.packet_no + 1 379 | 380 | -- print("packet no: ", self.packet_no) 381 | 382 | local packet = _set_byte3(size) .. strchar(band(self.packet_no, 255)) .. req 383 | 384 | -- print("sending packet: ", _dump(packet)) 385 | 386 | -- print("sending packet... of size " .. #packet) 387 | 388 | return sock:send(packet) 389 | end 390 | 391 | 392 | local function _recv_packet(self) 393 | local sock = self.sock 394 | 395 | local data, err = sock:receive(4) -- packet header 396 | if not data then 397 | return nil, nil, "failed to receive packet header: " .. err 398 | end 399 | 400 | --print("packet header: ", _dump(data)) 401 | 402 | local len, pos = _get_byte3(data, 1) 403 | 404 | --print("packet length: ", len) 405 | 406 | if len == 0 then 407 | return nil, nil, "empty packet" 408 | end 409 | 410 | if len > self._max_packet_size then 411 | return nil, nil, "packet size too big: " .. len 412 | end 413 | 414 | local num = strbyte(data, pos) 415 | 416 | --print("recv packet: packet no: ", num) 417 | 418 | self.packet_no = num 419 | 420 | data, err = sock:receive(len) 421 | 422 | --print("receive returned") 423 | 424 | if not data then 425 | return nil, nil, "failed to read packet content: " .. err 426 | end 427 | 428 | --print("packet content: ", _dump(data)) 429 | --print("packet content (ascii): ", data) 430 | 431 | local field_count = strbyte(data, 1) 432 | 433 | local typ 434 | if field_count == 0x00 then 435 | typ = RESP_OK 436 | elseif field_count == 0x01 then 437 | typ = RESP_AUTHMOREDATA 438 | elseif field_count == 0xfb then 439 | typ = RESP_LOCALINFILE 440 | elseif field_count == 0xfe then 441 | typ = RESP_EOF 442 | elseif field_count == 0xff then 443 | typ = RESP_ERR 444 | else 445 | typ = RESP_DATA 446 | end 447 | 448 | return data, typ 449 | end 450 | 451 | 452 | local function _from_length_coded_bin(data, pos) 453 | local first = strbyte(data, pos) 454 | 455 | --print("LCB: first: ", first) 456 | 457 | if not first then 458 | return nil, pos 459 | end 460 | 461 | if first >= 0 and first <= 250 then 462 | return first, pos + 1 463 | end 464 | 465 | if first == 251 then 466 | return null, pos + 1 467 | end 468 | 469 | if first == 252 then 470 | pos = pos + 1 471 | return _get_byte2(data, pos) 472 | end 473 | 474 | if first == 253 then 475 | pos = pos + 1 476 | return _get_byte3(data, pos) 477 | end 478 | 479 | if first == 254 then 480 | pos = pos + 1 481 | return _get_byte8(data, pos) 482 | end 483 | 484 | return nil, pos + 1 485 | end 486 | 487 | 488 | local function _from_length_coded_str(data, pos) 489 | local len 490 | len, pos = _from_length_coded_bin(data, pos) 491 | if not len or len == null then 492 | return null, pos 493 | end 494 | 495 | return sub(data, pos, pos + len - 1), pos + len 496 | end 497 | 498 | 499 | local function _parse_ok_packet(packet) 500 | local res = new_tab(0, 5) 501 | local pos 502 | 503 | res.affected_rows, pos = _from_length_coded_bin(packet, 2) 504 | 505 | --print("affected rows: ", res.affected_rows, ", pos:", pos) 506 | 507 | res.insert_id, pos = _from_length_coded_bin(packet, pos) 508 | 509 | --print("insert id: ", res.insert_id, ", pos:", pos) 510 | 511 | res.server_status, pos = _get_byte2(packet, pos) 512 | 513 | --print("server status: ", res.server_status, ", pos:", pos) 514 | 515 | res.warning_count, pos = _get_byte2(packet, pos) 516 | 517 | --print("warning count: ", res.warning_count, ", pos: ", pos) 518 | 519 | local message = _from_length_coded_str(packet, pos) 520 | if message and message ~= null then 521 | res.message = message 522 | end 523 | 524 | --print("message: ", res.message, ", pos:", pos) 525 | 526 | return res 527 | end 528 | 529 | 530 | local function _parse_eof_packet(packet) 531 | local pos = 2 532 | 533 | local warning_count, pos = _get_byte2(packet, pos) 534 | local status_flags = _get_byte2(packet, pos) 535 | 536 | return warning_count, status_flags 537 | end 538 | 539 | 540 | local function _parse_err_packet(packet) 541 | local errno, pos = _get_byte2(packet, 2) 542 | local marker = sub(packet, pos, pos) 543 | local sqlstate 544 | if marker == '#' then 545 | -- with sqlstate 546 | pos = pos + 1 547 | sqlstate = sub(packet, pos, pos + 5 - 1) 548 | pos = pos + 5 549 | end 550 | 551 | local message = sub(packet, pos) 552 | return errno, message, sqlstate 553 | end 554 | 555 | 556 | local function _parse_result_set_header_packet(packet) 557 | local field_count, pos = _from_length_coded_bin(packet, 1) 558 | 559 | local extra 560 | extra = _from_length_coded_bin(packet, pos) 561 | 562 | return field_count, extra 563 | end 564 | 565 | 566 | local function _parse_field_packet(data) 567 | local col = new_tab(0, 2) 568 | local catalog, db, table, orig_table, orig_name, charsetnr, length 569 | local pos 570 | catalog, pos = _from_length_coded_str(data, 1) 571 | 572 | --print("catalog: ", col.catalog, ", pos:", pos) 573 | 574 | db, pos = _from_length_coded_str(data, pos) 575 | table, pos = _from_length_coded_str(data, pos) 576 | orig_table, pos = _from_length_coded_str(data, pos) 577 | col.name, pos = _from_length_coded_str(data, pos) 578 | 579 | orig_name, pos = _from_length_coded_str(data, pos) 580 | 581 | pos = pos + 1 -- ignore the filler 582 | 583 | charsetnr, pos = _get_byte2(data, pos) 584 | 585 | length, pos = _get_byte4(data, pos) 586 | 587 | col.type = strbyte(data, pos) 588 | 589 | --[[ 590 | pos = pos + 1 591 | 592 | col.flags, pos = _get_byte2(data, pos) 593 | 594 | col.decimals = strbyte(data, pos) 595 | pos = pos + 1 596 | 597 | local default = sub(data, pos + 2) 598 | if default and default ~= "" then 599 | col.default = default 600 | end 601 | --]] 602 | 603 | return col 604 | end 605 | 606 | 607 | local function _parse_row_data_packet(data, cols, compact) 608 | local pos = 1 609 | local ncols = #cols 610 | local row 611 | if compact then 612 | row = new_tab(ncols, 0) 613 | else 614 | row = new_tab(0, ncols) 615 | end 616 | for i = 1, ncols do 617 | local value 618 | value, pos = _from_length_coded_str(data, pos) 619 | local col = cols[i] 620 | local typ = col.type 621 | local name = col.name 622 | 623 | --print("row field value: ", value, ", type: ", typ) 624 | 625 | if value ~= null then 626 | local conv = converters[typ] 627 | if conv then 628 | value = conv(value) 629 | end 630 | end 631 | 632 | if compact then 633 | row[i] = value 634 | 635 | else 636 | row[name] = value 637 | end 638 | end 639 | 640 | return row 641 | end 642 | 643 | 644 | local function _recv_field_packet(self) 645 | local packet, typ, err = _recv_packet(self) 646 | if not packet then 647 | return nil, err 648 | end 649 | 650 | if typ == RESP_ERR then 651 | local errno, msg, sqlstate = _parse_err_packet(packet) 652 | return nil, msg, errno, sqlstate 653 | end 654 | 655 | if typ ~= RESP_DATA then 656 | return nil, "bad field packet type: " .. typ 657 | end 658 | 659 | -- typ == RESP_DATA 660 | 661 | return _parse_field_packet(packet) 662 | end 663 | 664 | 665 | -- refer to https://dev.mysql.com/doc/internals/en/connection-phase-packets.html 666 | local function _read_hand_shake_packet(self) 667 | local packet, typ, err = _recv_packet(self) 668 | if not packet then 669 | return nil, nil, err 670 | end 671 | 672 | if typ == RESP_ERR then 673 | local errno, msg, sqlstate = _parse_err_packet(packet) 674 | return nil, nil, msg, errno, sqlstate 675 | end 676 | 677 | local protocol_ver = tonumber(strbyte(packet)) 678 | if not protocol_ver then 679 | return nil, nil, 680 | "bad handshake initialization packet: bad protocol version" 681 | end 682 | 683 | if protocol_ver < MIN_PROTOCOL_VER then 684 | return nil, nil, "unsupported protocol version " .. protocol_ver 685 | .. ", version " .. MIN_PROTOCOL_VER 686 | .. " or higher is required" 687 | end 688 | 689 | self.protocol_ver = protocol_ver 690 | 691 | local server_ver, pos = _from_cstring(packet, 2) 692 | if not server_ver then 693 | return nil, nil, 694 | "bad handshake initialization packet: bad server version" 695 | end 696 | 697 | self._server_ver = server_ver 698 | 699 | local thread_id, pos = _get_byte4(packet, pos) 700 | 701 | local scramble = sub(packet, pos, pos + 8 - 1) 702 | if not scramble then 703 | return nil, nil, "1st part of scramble not found" 704 | end 705 | 706 | pos = pos + 9 -- skip filler(8 + 1) 707 | 708 | -- two lower bytes 709 | local capabilities -- server capabilities 710 | capabilities, pos = _get_byte2(packet, pos) 711 | 712 | self._server_lang = strbyte(packet, pos) 713 | pos = pos + 1 714 | 715 | self._server_status, pos = _get_byte2(packet, pos) 716 | 717 | local more_capabilities 718 | more_capabilities, pos = _get_byte2(packet, pos) 719 | 720 | self.capabilities = bor(capabilities, lshift(more_capabilities, 16)) 721 | 722 | pos = pos + 11 -- skip length of auth-plugin-data(1) and reserved(10) 723 | 724 | -- follow official Python library uses the fixed length 12 725 | -- and the 13th byte is "\0 byte 726 | local scramble_part2 = sub(packet, pos, pos + 12 - 1) 727 | if not scramble_part2 then 728 | return nil, nil, "2nd part of scramble not found" 729 | end 730 | 731 | pos = pos + 13 732 | 733 | local plugin, _ 734 | if band(self.capabilities, CLIENT_PLUGIN_AUTH) > 0 then 735 | plugin, _ = _from_cstring(packet, pos) 736 | if not plugin then 737 | -- EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2) 738 | -- \NUL otherwise 739 | plugin = sub(packet, pos) 740 | end 741 | 742 | else 743 | plugin = DEFAULT_AUTH_PLUGIN 744 | end 745 | 746 | return scramble .. scramble_part2, plugin 747 | end 748 | 749 | 750 | local function _append_auth_length(self, data) 751 | local n = #data 752 | 753 | if n <= 250 then 754 | data = strchar(n) .. data 755 | return data, 1 + n 756 | end 757 | 758 | self.DEFAULT_CLIENT_FLAGS = bor(self.DEFAULT_CLIENT_FLAGS, 759 | CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA) 760 | 761 | if n <= 0xffff then 762 | data = strchar(0xfc, band(n, 0xff), band(rshift(n, 8), 0xff)) .. data 763 | return data, 3 + n 764 | end 765 | 766 | if n <= 0xffffff then 767 | data = strchar(0xfd, 768 | band(n, 0xff), 769 | band(rshift(n, 8), 0xff), 770 | band(rshift(n, 16), 0xff)) 771 | .. data 772 | return data, 4 + n 773 | end 774 | 775 | data = strchar(0xfe, 776 | band(n, 0xff), 777 | band(rshift(n, 8), 0xff), 778 | band(rshift(n, 16), 0xff), 779 | band(rshift(n, 24), 0xff), 780 | band(rshift(n, 32), 0xff), 781 | band(rshift(n, 40), 0xff), 782 | band(rshift(n, 48), 0xff), 783 | band(rshift(n, 56), 0xff)) 784 | .. data 785 | return data, 9 + n 786 | end 787 | 788 | 789 | local function _write_hand_shake_response(self, auth_resp, plugin) 790 | local append_auth, len = _append_auth_length(self, auth_resp) 791 | 792 | if self.use_ssl then 793 | if band(self.capabilities, CLIENT_SSL) == 0 then 794 | return "ssl disabled on server" 795 | end 796 | 797 | -- send a SSL Request Packet 798 | local req = _set_byte4(bor(self.DEFAULT_CLIENT_FLAGS, CLIENT_SSL)) 799 | .. _set_byte4(self._max_packet_size) 800 | .. strchar(self.charset) 801 | .. strrep("\0", 23) 802 | 803 | local packet_len = 4 + 4 + 1 + 23 804 | local bytes, err = _send_packet(self, req, packet_len) 805 | if not bytes then 806 | return "failed to send client authentication packet: " .. err 807 | end 808 | 809 | local sock = self.sock 810 | 811 | local ok, err = sock:sslhandshake(false, nil, self.ssl_verify) 812 | if not ok then 813 | return "failed to do ssl handshake: " .. (err or "") 814 | end 815 | end 816 | 817 | local req = _set_byte4(self.DEFAULT_CLIENT_FLAGS) 818 | .. _set_byte4(self._max_packet_size) 819 | .. strchar(self.charset) 820 | .. strrep("\0", 23) 821 | .. _to_cstring(self.user) 822 | .. append_auth 823 | .. _to_cstring(self.database) 824 | .. _to_cstring(plugin) 825 | 826 | local packet_len = 4 + 4 + 1 + 23 + #self.user + 1 827 | + len + #self.database + 1 + #plugin + 1 828 | 829 | local bytes, err = _send_packet(self, req, packet_len) 830 | if not bytes then 831 | return "failed to send client authentication packet: " .. err 832 | end 833 | 834 | return nil 835 | end 836 | 837 | 838 | local function _read_auth_result(self, old_auth_data, plugin) 839 | local packet, typ, err = _recv_packet(self) 840 | if not packet then 841 | return nil, nil, "failed to receive the result packet: " .. err 842 | end 843 | 844 | if typ == RESP_OK then 845 | return RESP_OK, "" 846 | end 847 | 848 | if typ == RESP_AUTHMOREDATA then 849 | return sub(packet, 2), "" 850 | end 851 | 852 | if typ == RESP_EOF then 853 | if #packet == 1 then -- old pre-4.1 authentication protocol 854 | return nil, "mysql_old_password" 855 | end 856 | 857 | local pos 858 | 859 | plugin, pos = _from_cstring(packet, 2) 860 | if not plugin then 861 | return nil, nil, "malformed packet" 862 | end 863 | 864 | return sub(packet, pos), plugin 865 | end 866 | 867 | if typ == RESP_ERR then 868 | local errno, msg, sqlstate = _parse_err_packet(packet) 869 | return errno, sqlstate, msg 870 | end 871 | 872 | return nil, nil, "bad packet type: " .. typ 873 | end 874 | 875 | 876 | local function _read_ok_result(self) 877 | local packet, typ, err = _recv_packet(self) 878 | if not packet then 879 | return "failed to receive the result packet: " .. err 880 | end 881 | 882 | if typ == RESP_ERR then 883 | local errno, msg, sqlstate = _parse_err_packet(packet) 884 | return msg, errno, sqlstate 885 | end 886 | 887 | if typ ~= RESP_OK then 888 | return "bad packet type: " .. typ 889 | end 890 | end 891 | 892 | 893 | local function _encrypt_password(self, auth_data, public_key) 894 | if not has_rsa then 895 | error("auth plugin caching_sha2_password or sha256_password are not" .. 896 | " supported because resty.rsa is not installed", 2) 897 | end 898 | 899 | local password = _to_cstring(self.password) 900 | local n = #password 901 | local l = #auth_data 902 | local bytes = new_tab(n, 0) 903 | 904 | for i = 1, n do 905 | local j = i % l 906 | bytes[i] = strchar(bxor(strbyte(password, i), strbyte(auth_data, j))) 907 | end 908 | 909 | local pub, err = resty_rsa:new({ 910 | public_key = public_key, 911 | key_type = resty_rsa.KEY_TYPE.PKCS8, 912 | padding = resty_rsa.PADDING.RSA_PKCS1_OAEP_PADDING, 913 | algorithm = "sha1", 914 | }) 915 | if not pub then 916 | return nil, "new rsa err: " .. err 917 | end 918 | 919 | local enc, err = pub:encrypt(concat(bytes)) 920 | if not enc then 921 | return nil, "encode password packet: " .. err 922 | end 923 | 924 | return enc 925 | end 926 | 927 | 928 | local function _write_encode_password(self, auth_data, public_key) 929 | local enc, err = _encrypt_password(self, auth_data, public_key) 930 | 931 | local bytes, err = _send_packet(self, enc, #enc) 932 | if not bytes then 933 | return "failed to send encode password packet: " .. err 934 | end 935 | end 936 | 937 | 938 | local function _auth(self, auth_data, plugin) 939 | local password = self.password 940 | 941 | if plugin == "caching_sha2_password" then 942 | local auth_resp, err = _compute_sha256_token(password, auth_data) 943 | if err then 944 | return nil, "failed to compute sha256 token: " .. err 945 | end 946 | 947 | return auth_resp 948 | end 949 | 950 | if plugin == "mysql_old_password" then 951 | return _compute_old_token(password, auth_data) 952 | end 953 | 954 | if plugin == "mysql_clear_password" then 955 | return _to_cstring(password) 956 | end 957 | 958 | if plugin == "mysql_native_password" then 959 | return _compute_token(password, auth_data) 960 | end 961 | 962 | if plugin == "sha256_password" then 963 | if self.is_unix or self.use_ssl or #password == 0 then 964 | return _to_cstring(password) 965 | end 966 | 967 | local public_key = self.public_key 968 | if public_key then 969 | return _encrypt_password(self, auth_data, public_key) 970 | end 971 | 972 | return "\1" -- request public key from server 973 | end 974 | 975 | return nil, "unknown plugin: " .. plugin 976 | end 977 | 978 | 979 | local function _handle_auth_result(self, old_auth_data, plugin) 980 | local auth_data, new_plugin, err = _read_auth_result(self, old_auth_data, 981 | plugin) 982 | 983 | if err ~= nil then 984 | local errno, sqlstate = auth_data, new_plugin 985 | return err, errno, sqlstate 986 | end 987 | 988 | if auth_data == RESP_OK then 989 | return 990 | end 991 | 992 | if new_plugin ~= "" then 993 | if not auth_data then 994 | auth_data = old_auth_data 995 | else 996 | old_auth_data = auth_data 997 | end 998 | 999 | plugin = new_plugin 1000 | 1001 | local auth_resp, err = _auth(self, auth_data, plugin) 1002 | if not auth_resp then 1003 | return err 1004 | end 1005 | 1006 | local bytes, err = _send_packet(self, auth_resp, #auth_resp) 1007 | if not bytes then 1008 | return "failed to send client authentication packet: " .. err 1009 | end 1010 | 1011 | auth_data, new_plugin, err = _read_auth_result(self, old_auth_data, 1012 | plugin) 1013 | 1014 | if err ~= nil then 1015 | local errno, sqlstate = auth_data, new_plugin 1016 | return err, errno, sqlstate 1017 | end 1018 | 1019 | if auth_data == RESP_OK then 1020 | return 1021 | end 1022 | 1023 | if new_plugin ~= "" then 1024 | return "malformed packet" 1025 | end 1026 | end 1027 | 1028 | if plugin == "caching_sha2_password" then 1029 | local len = #auth_data 1030 | if len == 0 then 1031 | return 1032 | end 1033 | 1034 | if len == 1 then 1035 | local status = strbyte(auth_data) 1036 | -- caching_sha2_password fast auth success 1037 | if status == 3 then 1038 | return _read_ok_result(self) 1039 | end 1040 | 1041 | -- caching_sha2_password perform full authentication 1042 | if status == 4 then 1043 | if self.is_unix or self.use_ssl then 1044 | local bytes, err = _send_packet(self, 1045 | _to_cstring(self.password), 1046 | #self.password + 1) 1047 | 1048 | if not bytes then 1049 | return "failed to send cleartext auth packet: " 1050 | .. err 1051 | end 1052 | 1053 | else 1054 | local public_key = self.public_key 1055 | if not public_key then 1056 | -- caching_sha2_password request public_key 1057 | local bytes, err = _send_packet(self, "\2", 1) 1058 | if not bytes then 1059 | return "failed to send password request packet: " 1060 | .. err 1061 | end 1062 | 1063 | local packet, _, err = _recv_packet(self) 1064 | if not packet then 1065 | return "failed to receive the result packet: " 1066 | .. err 1067 | end 1068 | 1069 | public_key = sub(packet, 2) 1070 | end 1071 | 1072 | err = _write_encode_password(self, old_auth_data, 1073 | public_key) 1074 | 1075 | if err then 1076 | return err 1077 | end 1078 | 1079 | self.public_key = public_key 1080 | end 1081 | 1082 | return _read_ok_result(self) 1083 | end 1084 | end 1085 | 1086 | return "malformed packet" 1087 | end 1088 | 1089 | if plugin == "sha256_password" then 1090 | if #auth_data ~= 0 then 1091 | local enc, err = _write_encode_password(self, old_auth_data, 1092 | auth_data) 1093 | 1094 | if err then 1095 | return err 1096 | end 1097 | 1098 | return _read_ok_result(self) 1099 | end 1100 | end 1101 | end 1102 | 1103 | 1104 | function _M.new() 1105 | local sock, err = tcp() 1106 | if not sock then 1107 | return nil, err 1108 | end 1109 | return setmetatable({ sock = sock }, mt) 1110 | end 1111 | 1112 | 1113 | function _M.set_timeout(self, timeout) 1114 | local sock = self.sock 1115 | if not sock then 1116 | return nil, "not initialized" 1117 | end 1118 | 1119 | return sock:settimeout(timeout) 1120 | end 1121 | 1122 | 1123 | function _M.connect(self, opts) 1124 | local sock = self.sock 1125 | if not sock then 1126 | return nil, "not initialized" 1127 | end 1128 | 1129 | local max_packet_size = opts.max_packet_size 1130 | if not max_packet_size then 1131 | max_packet_size = 1024 * 1024 -- default 1 MB 1132 | end 1133 | self._max_packet_size = max_packet_size 1134 | 1135 | local ok, err 1136 | 1137 | self.compact = opts.compact_arrays 1138 | 1139 | self.database = opts.database or "" 1140 | self.user = opts.user or "" 1141 | 1142 | self.charset = CHARSET_MAP[opts.charset or "_default"] 1143 | if not self.charset then 1144 | return nil, "charset '" .. opts.charset .. "' is not supported" 1145 | end 1146 | 1147 | local pool = opts.pool 1148 | 1149 | self.ssl_verify = opts.ssl_verify 1150 | self.use_ssl = opts.ssl or opts.ssl_verify 1151 | 1152 | self.password = opts.password or "" 1153 | 1154 | local host = opts.host 1155 | if host then 1156 | local port = opts.port or 3306 1157 | if not pool then 1158 | pool = self.user .. ":" .. self.database .. ":" .. host .. ":" 1159 | .. port 1160 | end 1161 | 1162 | ok, err = sock:connect(host, port, { pool = pool, 1163 | pool_size = opts.pool_size, 1164 | backlog = opts.backlog }) 1165 | 1166 | else 1167 | local path = opts.path 1168 | if not path then 1169 | return nil, 'neither "host" nor "path" options are specified' 1170 | end 1171 | 1172 | if not pool then 1173 | pool = self.user .. ":" .. self.database .. ":" .. path 1174 | end 1175 | 1176 | self.is_unix = true 1177 | ok, err = sock:connect("unix:" .. path, { pool = pool, 1178 | pool_size = opts.pool_size, 1179 | backlog = opts.backlog }) 1180 | end 1181 | 1182 | if not ok then 1183 | return nil, 'failed to connect: ' .. err 1184 | end 1185 | 1186 | local reused = sock:getreusedtimes() 1187 | 1188 | if reused and reused > 0 then 1189 | self.state = STATE_CONNECTED 1190 | return 1 1191 | end 1192 | 1193 | self.DEFAULT_CLIENT_FLAGS = bor(DEFAULT_CLIENT_FLAGS, CLIENT_PLUGIN_AUTH) 1194 | 1195 | local auth_data, plugin, err, errno, sqlstate 1196 | = _read_hand_shake_packet(self) 1197 | 1198 | if err ~= nil then 1199 | return nil, err 1200 | end 1201 | 1202 | local auth_resp, err = _auth(self, auth_data, plugin) 1203 | if not auth_resp then 1204 | return nil, err 1205 | end 1206 | 1207 | err = _write_hand_shake_response(self, auth_resp, plugin) 1208 | if err ~= nil then 1209 | return nil, err 1210 | end 1211 | 1212 | local err, errno, sqlstate = _handle_auth_result(self, auth_data, plugin) 1213 | if err ~= nil then 1214 | return nil, err, errno, sqlstate 1215 | end 1216 | 1217 | self.state = STATE_CONNECTED 1218 | 1219 | return 1 1220 | end 1221 | 1222 | 1223 | function _M.set_keepalive(self, ...) 1224 | local sock = self.sock 1225 | if not sock then 1226 | return nil, "not initialized" 1227 | end 1228 | 1229 | if self.state ~= STATE_CONNECTED then 1230 | return nil, "cannot be reused in the current connection state: " 1231 | .. (self.state or "nil") 1232 | end 1233 | 1234 | self.state = nil 1235 | return sock:setkeepalive(...) 1236 | end 1237 | 1238 | 1239 | function _M.get_reused_times(self) 1240 | local sock = self.sock 1241 | if not sock then 1242 | return nil, "not initialized" 1243 | end 1244 | 1245 | return sock:getreusedtimes() 1246 | end 1247 | 1248 | 1249 | function _M.close(self) 1250 | local sock = self.sock 1251 | if not sock then 1252 | return nil, "not initialized" 1253 | end 1254 | 1255 | self.state = nil 1256 | 1257 | local bytes, err = _send_packet(self, strchar(COM_QUIT), 1) 1258 | if not bytes then 1259 | return nil, err 1260 | end 1261 | 1262 | return sock:close() 1263 | end 1264 | 1265 | 1266 | function _M.server_ver(self) 1267 | return self._server_ver 1268 | end 1269 | 1270 | 1271 | local function send_query(self, query) 1272 | if self.state ~= STATE_CONNECTED then 1273 | return nil, "cannot send query in the current context: " 1274 | .. (self.state or "nil") 1275 | end 1276 | 1277 | local sock = self.sock 1278 | if not sock then 1279 | return nil, "not initialized" 1280 | end 1281 | 1282 | self.packet_no = -1 1283 | 1284 | local cmd_packet = strchar(COM_QUERY) .. query 1285 | local packet_len = 1 + #query 1286 | 1287 | local bytes, err = _send_packet(self, cmd_packet, packet_len) 1288 | if not bytes then 1289 | return nil, err 1290 | end 1291 | 1292 | self.state = STATE_COMMAND_SENT 1293 | 1294 | --print("packet sent ", bytes, " bytes") 1295 | 1296 | return bytes 1297 | end 1298 | _M.send_query = send_query 1299 | 1300 | 1301 | local function read_result(self, est_nrows) 1302 | if self.state ~= STATE_COMMAND_SENT then 1303 | return nil, "cannot read result in the current context: " 1304 | .. (self.state or "nil") 1305 | end 1306 | 1307 | local sock = self.sock 1308 | if not sock then 1309 | return nil, "not initialized" 1310 | end 1311 | 1312 | local packet, typ, err = _recv_packet(self) 1313 | if not packet then 1314 | return nil, err 1315 | end 1316 | 1317 | if typ == RESP_ERR then 1318 | self.state = STATE_CONNECTED 1319 | 1320 | local errno, msg, sqlstate = _parse_err_packet(packet) 1321 | return nil, msg, errno, sqlstate 1322 | end 1323 | 1324 | if typ == RESP_OK then 1325 | local res = _parse_ok_packet(packet) 1326 | if res and band(res.server_status, SERVER_MORE_RESULTS_EXISTS) ~= 0 then 1327 | return res, "again" 1328 | end 1329 | 1330 | self.state = STATE_CONNECTED 1331 | return res 1332 | end 1333 | 1334 | if typ == RESP_LOCALINFILE then 1335 | self.state = STATE_CONNECTED 1336 | 1337 | return nil, "packet type " .. typ .. " not supported" 1338 | end 1339 | 1340 | -- typ == RESP_DATA or RESP_AUTHMOREDATA(also mean RESP_DATA here) 1341 | 1342 | --print("read the result set header packet") 1343 | 1344 | local field_count, extra = _parse_result_set_header_packet(packet) 1345 | 1346 | --print("field count: ", field_count) 1347 | 1348 | local cols = new_tab(field_count, 0) 1349 | for i = 1, field_count do 1350 | local col, err, errno, sqlstate = _recv_field_packet(self) 1351 | if not col then 1352 | return nil, err, errno, sqlstate 1353 | end 1354 | 1355 | cols[i] = col 1356 | end 1357 | 1358 | local packet, typ, err = _recv_packet(self) 1359 | if not packet then 1360 | return nil, err 1361 | end 1362 | 1363 | if typ ~= RESP_EOF then 1364 | return nil, "unexpected packet type " .. typ .. " while eof packet is " 1365 | .. "expected" 1366 | end 1367 | 1368 | -- typ == RESP_EOF 1369 | 1370 | local compact = self.compact 1371 | 1372 | local rows = new_tab(est_nrows or 4, 0) 1373 | local i = 0 1374 | while true do 1375 | --print("reading a row") 1376 | 1377 | packet, typ, err = _recv_packet(self) 1378 | if not packet then 1379 | return nil, err 1380 | end 1381 | 1382 | if typ == RESP_EOF then 1383 | local warning_count, status_flags = _parse_eof_packet(packet) 1384 | 1385 | --print("status flags: ", status_flags) 1386 | 1387 | if band(status_flags, SERVER_MORE_RESULTS_EXISTS) ~= 0 then 1388 | return rows, "again" 1389 | end 1390 | 1391 | break 1392 | end 1393 | 1394 | if typ == RESP_ERR then 1395 | self.state = STATE_CONNECTED 1396 | local errno, msg, sqlstate = _parse_err_packet(packet) 1397 | return nil, msg, errno, sqlstate 1398 | end 1399 | 1400 | local row = _parse_row_data_packet(packet, cols, compact) 1401 | i = i + 1 1402 | rows[i] = row 1403 | end 1404 | 1405 | self.state = STATE_CONNECTED 1406 | 1407 | return rows 1408 | end 1409 | _M.read_result = read_result 1410 | 1411 | 1412 | function _M.query(self, query, est_nrows) 1413 | local bytes, err = send_query(self, query) 1414 | if not bytes then 1415 | return nil, "failed to send query: " .. err 1416 | end 1417 | 1418 | return read_result(self, est_nrows) 1419 | end 1420 | 1421 | 1422 | function _M.set_compact_arrays(self, value) 1423 | self.compact = value 1424 | end 1425 | 1426 | 1427 | return _M 1428 | -------------------------------------------------------------------------------- /server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqTCCAhICCQCCQYMy4RG07jANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x 4 | EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQD 5 | DAh0ZXN0LmNvbTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20wIBcN 6 | MjAwNjAzMTI1NzIzWhgPMjEyMDA1MTAxMjU3MjNaMIGXMQswCQYDVQQGEwJVUzET 7 | MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAG 8 | A1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRl 9 | c3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNvbTCBnzANBgkq 10 | hkiG9w0BAQEFAAOBjQAwgYkCgYEAnETL3u1VPLIHHdqbUiCdYCDfzEiI7YxCEa97 11 | 7PQ/hw3BuykC0udJf9K2ALmhumnyqZ7maG62ecvihdYZk+PWvph7CPXT5hLdJbI+ 12 | 3BEwO/7DL88kk+r/dUv4DbvQHobewu0DueiSrs82Wb1yzaHPU0ljmM+7L8q6kWbn 13 | QKwOC5kCAwEAATANBgkqhkiG9w0BAQsFAAOBgQApArlYvWgHBefd25XLrYV5Rvhx 14 | DkmnieLQ1PGJL+iyvaQE7WFNPHrgGFWc3zF3zjD5b9UxKPJropxZ2OjDLEB5wt1a 15 | MAAlsex31X1sOYRPXuvFw82NyhOu1BrSCAiA5AW8LufIQ/j2T7pw/yHc++/CPREz 16 | m5XC/p+0cyRhn0d3CA== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQCcRMve7VU8sgcd2ptSIJ1gIN/MSIjtjEIRr3vs9D+HDcG7KQLS 3 | 50l/0rYAuaG6afKpnuZobrZ5y+KF1hmT49a+mHsI9dPmEt0lsj7cETA7/sMvzyST 4 | 6v91S/gNu9Aeht7C7QO56JKuzzZZvXLNoc9TSWOYz7svyrqRZudArA4LmQIDAQAB 5 | AoGBAIPwukmhDYTPs56LQvUWwIm3d/4diC85a1dPWUz59OFsxhPmTpIP4kjgefcm 6 | xNyGM/LvTQ8D22H+uWXTHYl2p4q3pwd+QD6ITHl8nU/wkIYV7kFtmIc0E0FKxZzP 7 | 5knwPUI1M98v4mhoSyaw3EuD+VMMFlDAC1qz4Jr/HPWPW1sBAkEAyF8e9U+dttqh 8 | XUI7/otbp5VUaf0pvz98h1elhwKHbpu5I4mwCuOvTvhQBegNjKvJ4yWMAdDDAtMT 9 | IVe06I7skQJBAMenLvU9GLH5T6/rrj+0GoVLRASzsbrj4D0O43wjSpSBGGnpkjoh 10 | lam8AJyvfenxQHFngiXh78Jdb5kRf/SbUokCQQCmV8l0r9bnwZMqv9zckcbSVjdR 11 | EjoBKmxwU4P4lFHosmC9rwW11JVqQtz0OlRdTxR4fWhA7ZWnl6GPEaf/VP/xAkEA 12 | qsBvRsqT/lKNNpXIly7/p5Rxfdv5Wy9dLps3L4o3VL78FEjxCMqEZ4AkvdzRQW1V 13 | gifWhXOwTHkA4ta2qorUkQJAC54nj44IVHedd3kGvf0M1HzGOXhkGrGBBLG45rtl 14 | y0NNp7u5JfCkxP0a+vJqMujeyvJJjaYvG3J8XZYptPfdSg== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /t/Test.pm: -------------------------------------------------------------------------------- 1 | package t::Test; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Test::Nginx::Socket::Lua::Stream -Base; 7 | use Cwd qw(cwd); 8 | 9 | my $pwd = cwd(); 10 | 11 | my $default_config = qq{ 12 | resolver \$TEST_NGINX_RESOLVER; 13 | lua_package_path "$pwd/t/servroot/html/?.lua;$pwd/lib/?.lua;$pwd/t/lib/?.lua;$pwd/../lua-resty-rsa/lib/?.lua;$pwd/../lua-resty-string/lib/?.lua;;"; 14 | lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;"; 15 | }; 16 | 17 | $ENV{TEST_NGINX_RESOLVER} = '8.8.8.8'; 18 | $ENV{TEST_NGINX_MYSQL_PORT} ||= 3306; 19 | $ENV{TEST_NGINX_MYSQL_HOST} ||= '127.0.0.1'; 20 | $ENV{TEST_NGINX_MYSQL_PATH} ||= '/var/run/mysql/mysql.sock'; 21 | 22 | no_long_string(); 23 | 24 | add_block_preprocessor(sub { 25 | my $block = shift; 26 | 27 | if (defined($ENV{TEST_SUBSYSTEM}) && $ENV{TEST_SUBSYSTEM} eq "stream") { 28 | if (!defined $block->stream_config) { 29 | $block->set_value("stream_config", $default_config); 30 | } 31 | if (!defined $block->stream_server_config) { 32 | $block->set_value("stream_server_config", $block->server_config); 33 | } 34 | } else { 35 | if (!defined $block->http_config) { 36 | $block->set_value("http_config", $default_config); 37 | } 38 | if (!defined $block->request) { 39 | $block->set_value("request", "GET /t\n"); 40 | } 41 | if (!defined $block->config) { 42 | $block->set_value("config", "location /t {\n" . $block->server_config . "\n}"); 43 | } 44 | } 45 | }); 46 | 47 | 1; 48 | -------------------------------------------------------------------------------- /t/auth.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(5); 6 | 7 | plan tests => repeat_each() * (2 * blocks()); 8 | 9 | #log_level 'warn'; 10 | 11 | no_long_string(); 12 | no_shuffle(); 13 | check_accum_error_log(); 14 | 15 | add_block_preprocessor(sub { 16 | my $block = shift; 17 | 18 | if (!defined $block->user_files) { 19 | $block->set_value("user_files", <<'_EOC_'); 20 | >>> test_suit.lua 21 | local _M = {} 22 | 23 | local ljson = require "ljson" 24 | local mysql = require "resty.mysql" 25 | 26 | function _M.prepare() 27 | local db = mysql:new() 28 | db:set_timeout(2000) -- 2 sec 29 | 30 | local ok, err, errno, sqlstate = db:connect({ 31 | host = "$TEST_NGINX_MYSQL_HOST", 32 | port = $TEST_NGINX_MYSQL_PORT, 33 | database = "ngx_test", 34 | user = "ngx_test", 35 | password = "ngx_test"}) 36 | 37 | if not ok then 38 | ngx.log(ngx.ERR, "failed to connect: ", err, ": ", errno, " ", sqlstate) 39 | return 40 | end 41 | 42 | ngx.say("connected to mysql.") 43 | 44 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 45 | if not res then 46 | ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".") 47 | return 48 | end 49 | 50 | ngx.say("table cats dropped.") 51 | 52 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 53 | if not res then 54 | ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".") 55 | return 56 | end 57 | 58 | ngx.say("table cats created.") 59 | 60 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'Bob\'),(\'\'),(null)") 61 | if not res then 62 | ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".") 63 | return 64 | end 65 | 66 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 67 | 68 | local ok, err = db:close() 69 | if not ok then 70 | ngx.log(ngx.ERR, "failed to close: ", err) 71 | return 72 | end 73 | end 74 | 75 | function _M.run(user, password, ssl) 76 | local db = mysql:new() 77 | db:set_timeout(2000) -- 2 sec 78 | 79 | local ok, err, errno, sqlstate = db:connect({ 80 | host = "$TEST_NGINX_MYSQL_HOST", 81 | port = $TEST_NGINX_MYSQL_PORT, 82 | database = "ngx_test", 83 | user = user, 84 | password = password, 85 | ssl = ssl, 86 | }) 87 | 88 | if not ok then 89 | ngx.log(ngx.ERR, "failed to connect: ", err, ": ", errno, " ", sqlstate) 90 | return 91 | end 92 | 93 | ngx.say("mysql auth successful.") 94 | 95 | local res, err, errno, sqlstate = db:query("select * from cats order by id asc") 96 | if not res then 97 | ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".") 98 | return 99 | end 100 | 101 | ngx.say("result: ", ljson.encode(res)) 102 | 103 | res, err, errno, sqlstate = db:query("select * from cats order by id desc") 104 | if not res then 105 | ngx.log(ngx.ERR, "bad result: ", err, ": ", errno, ": ", sqlstate, ".") 106 | return 107 | end 108 | 109 | ngx.say("result: ", ljson.encode(res)) 110 | 111 | local ok, err = db:close() 112 | if not ok then 113 | ngx.log(ngx.ERR, "failed to close: ", err) 114 | return 115 | end 116 | 117 | return true 118 | end 119 | 120 | return _M 121 | _EOC_ 122 | } 123 | }); 124 | 125 | run_tests(); 126 | 127 | __DATA__ 128 | 129 | === TEST 1: test different auth plugin 130 | --- main_config 131 | env DB_VERSION; 132 | --- server_config 133 | content_by_lua_block { 134 | local test_suit = require "test_suit" 135 | test_suit.prepare() 136 | local version = os.getenv("DB_VERSION") 137 | if not version then 138 | ngx.log(ngx.ERR, "please add the environment value \"DB_VERSION\"") 139 | end 140 | 141 | local version_plugin_mapping = { 142 | ["mysql:5.5"] = { 143 | "mysql_native_password", 144 | "mysql_old_password", 145 | }, 146 | ["mysql:5.6"] = { 147 | "mysql_native_password", 148 | "mysql_old_password", 149 | "sha256_password", 150 | }, 151 | ["mysql:5.7"] = { 152 | "mysql_native_password", 153 | "sha256_password", 154 | }, 155 | ["mysql:8.0"] = { 156 | "mysql_native_password", 157 | "sha256_password", 158 | "caching_sha2_password", 159 | }, 160 | ["mariadb:5.5"] = { 161 | "mysql_native_password", 162 | "mysql_old_password", 163 | }, 164 | ["mariadb:10.0"] = { 165 | "mysql_native_password", 166 | "mysql_old_password", 167 | }, 168 | ["mariadb:10.1"] = { 169 | "mysql_native_password", 170 | "mysql_old_password", 171 | }, 172 | ["mariadb:10.2"] = { 173 | "mysql_native_password", 174 | "mysql_old_password", 175 | }, 176 | ["mariadb:10.3"] = { 177 | "mysql_native_password", 178 | "mysql_old_password", 179 | }, 180 | } 181 | 182 | local plugin_user_mapping = { 183 | ["mysql_native_password"] = { 184 | { 185 | user = "user_native", 186 | password = "pass_native", 187 | }, 188 | { 189 | user = "nopass_native", 190 | }, 191 | }, 192 | ["mysql_old_password"] = { 193 | { 194 | user = "user_old", 195 | password = "pass_old", 196 | }, 197 | { 198 | user = "nopass_old", 199 | }, 200 | }, 201 | ["sha256_password"] = { 202 | { 203 | user = "user_sha256", 204 | password = "pass_sha256", 205 | }, 206 | { 207 | user = "nopass_sha256", 208 | }, 209 | }, 210 | ["caching_sha2_password"] = { 211 | { 212 | user = "user_caching_sha2", 213 | password = "pass_caching_sha2", 214 | }, 215 | { 216 | user = "nopass_caching_sha2", 217 | }, 218 | }, 219 | } 220 | 221 | local plugin_list = version_plugin_mapping[version] 222 | if not plugin_list then 223 | ngx.log(ngx.ERR, "unknown version: ", version) 224 | end 225 | 226 | for _, p in ipairs(plugin_list) do 227 | local user_infos = plugin_user_mapping[p] 228 | for _, u in ipairs(user_infos) do 229 | if not test_suit.run(u.user, u.password) then 230 | ngx.log(ngx.ERR, "testing with plugin ", p, 231 | " failed, and the user is ", u.user, 232 | ", password is ", u.password or "null") 233 | end 234 | 235 | if not test_suit.run(u.user, u.password, true) then -- tls 236 | ngx.log(ngx.ERR, "testing(by tls) with plugin ", p, 237 | " failed, and the user is ", u.user, 238 | ", password is ", u.password or "null") 239 | end 240 | end 241 | end 242 | } 243 | --- no_error_log 244 | [error] 245 | -------------------------------------------------------------------------------- /t/big.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | my @skip; 4 | BEGIN { 5 | if ($ENV{LD_PRELOAD} =~ /\bmockeagain\.so\b/) { 6 | @skip = (skip_all => 'too slow in mockeagain mode') 7 | } 8 | } 9 | 10 | use t::Test @skip; 11 | 12 | repeat_each(50); 13 | #repeat_each(10); 14 | 15 | plan tests => repeat_each() * (3 * blocks()); 16 | 17 | log_level 'warn'; 18 | 19 | #no_long_string(); 20 | #no_diff(); 21 | no_shuffle(); 22 | 23 | run_tests(); 24 | 25 | __DATA__ 26 | 27 | === TEST 1: big field value exceeding 256 28 | --- server_config 29 | content_by_lua ' 30 | local ljson = require "ljson" 31 | 32 | local mysql = require "resty.mysql" 33 | local db = mysql:new() 34 | 35 | db:set_timeout(2000) -- 2 sec 36 | 37 | local ok, err, errno, sqlstate = db:connect({ 38 | host = "$TEST_NGINX_MYSQL_HOST", 39 | port = $TEST_NGINX_MYSQL_PORT, 40 | database = "ngx_test", 41 | user = "ngx_test", 42 | password = "ngx_test"}) 43 | 44 | if not ok then 45 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 46 | return 47 | end 48 | 49 | ngx.say("connected to mysql.") 50 | 51 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 52 | if not res then 53 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 54 | return 55 | end 56 | 57 | ngx.say("table cats dropped.") 58 | 59 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(1024))") 60 | if not res then 61 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 62 | return 63 | end 64 | 65 | ngx.say("table cats created.") 66 | 67 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'" 68 | .. string.rep("B", 1024) 69 | .. "\')") 70 | 71 | if not res then 72 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 73 | return 74 | end 75 | 76 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 77 | 78 | res, err, errno, sqlstate = db:query("select * from cats order by id asc") 79 | if not res then 80 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 81 | return 82 | end 83 | 84 | ngx.say("result: ", ljson.encode(res)) 85 | 86 | res, err, errno, sqlstate = db:query("select * from cats order by id desc") 87 | if not res then 88 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 89 | return 90 | end 91 | 92 | ngx.say("result: ", ljson.encode(res)) 93 | 94 | local ok, err = db:close() 95 | if not ok then 96 | ngx.say("failed to close: ", err) 97 | return 98 | end 99 | '; 100 | --- response_body eval 101 | 'connected to mysql. 102 | table cats dropped. 103 | table cats created. 104 | 1 rows inserted into table cats (last id: 1) 105 | result: [{"id":"1","name":"' . ('B' x 1024) 106 | . '"}]' . "\n" . 107 | 'result: [{"id":"1","name":"' . ('B' x 1024) 108 | . '"}]' . "\n" 109 | --- no_error_log 110 | [error] 111 | 112 | 113 | 114 | === TEST 2: big field value exceeding max packet size 115 | --- server_config 116 | content_by_lua ' 117 | local ljson = require "ljson" 118 | 119 | local mysql = require "resty.mysql" 120 | local db = mysql:new() 121 | 122 | db:set_timeout(2000) -- 2 sec 123 | 124 | local ok, err, errno, sqlstate = db:connect({ 125 | host = "$TEST_NGINX_MYSQL_HOST", 126 | port = $TEST_NGINX_MYSQL_PORT, 127 | database = "ngx_test", 128 | user = "ngx_test", 129 | password = "ngx_test", 130 | max_packet_size = 1024 }) 131 | 132 | if not ok then 133 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 134 | return 135 | end 136 | 137 | ngx.say("connected to mysql.") 138 | 139 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 140 | if not res then 141 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 142 | return 143 | end 144 | 145 | ngx.say("table cats dropped.") 146 | 147 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(1024))") 148 | if not res then 149 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 150 | return 151 | end 152 | 153 | ngx.say("table cats created.") 154 | 155 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'" 156 | .. string.rep("B", 1024) 157 | .. "\')") 158 | 159 | if not res then 160 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 161 | return 162 | end 163 | 164 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 165 | 166 | res, err, errno, sqlstate = 167 | db:query("select * from cats order by id asc") 168 | if not res then 169 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 170 | return 171 | end 172 | 173 | ngx.say("result: ", ljson.encode(res)) 174 | 175 | res, err, errno, sqlstate = 176 | db:query("select * from cats order by id desc") 177 | if not res then 178 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 179 | return 180 | end 181 | 182 | ngx.say("result: ", ljson.encode(res)) 183 | 184 | local ok, err = db:close() 185 | if not ok then 186 | ngx.say("failed to close: ", err) 187 | return 188 | end 189 | '; 190 | --- response_body eval 191 | 'connected to mysql. 192 | table cats dropped. 193 | table cats created. 194 | 1 rows inserted into table cats (last id: 1) 195 | bad result: packet size too big: 1029: nil: nil. 196 | ' 197 | --- no_error_log 198 | [error] 199 | 200 | 201 | 202 | === TEST 3: big field value exceeding 256 (first field in rows) 203 | --- server_config 204 | content_by_lua ' 205 | local ljson = require "ljson" 206 | 207 | local mysql = require "resty.mysql" 208 | local db = mysql:new() 209 | 210 | db:set_timeout(2000) -- 2 sec 211 | 212 | local ok, err, errno, sqlstate = db:connect({ 213 | host = "$TEST_NGINX_MYSQL_HOST", 214 | port = $TEST_NGINX_MYSQL_PORT, 215 | database = "ngx_test", 216 | user = "ngx_test", 217 | password = "ngx_test"}) 218 | 219 | if not ok then 220 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 221 | return 222 | end 223 | 224 | ngx.say("connected to mysql.") 225 | 226 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 227 | if not res then 228 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 229 | return 230 | end 231 | 232 | ngx.say("table cats dropped.") 233 | 234 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(1024))") 235 | if not res then 236 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 237 | return 238 | end 239 | 240 | ngx.say("table cats created.") 241 | 242 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'" 243 | .. string.rep("B", 1024) 244 | .. "\')") 245 | 246 | if not res then 247 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 248 | return 249 | end 250 | 251 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 252 | 253 | res, err, errno, sqlstate = db:query("select name from cats order by id asc") 254 | if not res then 255 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 256 | return 257 | end 258 | 259 | ngx.say("result: ", ljson.encode(res)) 260 | 261 | res, err, errno, sqlstate = db:query("select name from cats order by id desc") 262 | if not res then 263 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 264 | return 265 | end 266 | 267 | ngx.say("result: ", ljson.encode(res)) 268 | 269 | local ok, err = db:close() 270 | if not ok then 271 | ngx.say("failed to close: ", err) 272 | return 273 | end 274 | '; 275 | --- response_body eval 276 | 'connected to mysql. 277 | table cats dropped. 278 | table cats created. 279 | 1 rows inserted into table cats (last id: 1) 280 | result: [{"name":"' . ('B' x 1024) 281 | . '"}]' . "\n" . 282 | 'result: [{"name":"' . ('B' x 1024) 283 | . '"}]' . "\n" 284 | --- no_error_log 285 | [error] 286 | 287 | 288 | 289 | === TEST 4: big field value exceeding 65536 (first field in rows) 290 | --- server_config 291 | content_by_lua ' 292 | local ljson = require "ljson" 293 | 294 | local mysql = require "resty.mysql" 295 | local db = mysql:new() 296 | 297 | db:set_timeout(2000) -- 2 sec 298 | 299 | local ok, err, errno, sqlstate = db:connect({ 300 | host = "$TEST_NGINX_MYSQL_HOST", 301 | port = $TEST_NGINX_MYSQL_PORT, 302 | database = "ngx_test", 303 | user = "ngx_test", 304 | password = "ngx_test"}) 305 | 306 | if not ok then 307 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 308 | return 309 | end 310 | 311 | ngx.say("connected to mysql.") 312 | 313 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 314 | if not res then 315 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 316 | return 317 | end 318 | 319 | ngx.say("table cats dropped.") 320 | 321 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name text(65540))") 322 | if not res then 323 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 324 | return 325 | end 326 | 327 | ngx.say("table cats created.") 328 | 329 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'" 330 | .. string.rep("B", 65540) 331 | .. "\')") 332 | 333 | if not res then 334 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 335 | return 336 | end 337 | 338 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 339 | 340 | res, err, errno, sqlstate = db:query("select name from cats order by id asc") 341 | if not res then 342 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 343 | return 344 | end 345 | 346 | ngx.say("result: ", ljson.encode(res)) 347 | 348 | res, err, errno, sqlstate = db:query("select name from cats order by id desc") 349 | if not res then 350 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 351 | return 352 | end 353 | 354 | ngx.say("result: ", ljson.encode(res)) 355 | 356 | local ok, err = db:close() 357 | if not ok then 358 | ngx.say("failed to close: ", err) 359 | return 360 | end 361 | '; 362 | --- response_body eval 363 | 'connected to mysql. 364 | table cats dropped. 365 | table cats created. 366 | 1 rows inserted into table cats (last id: 1) 367 | result: [{"name":"' . ('B' x 65540) 368 | . '"}]' . "\n" . 369 | 'result: [{"name":"' . ('B' x 65540) 370 | . '"}]' . "\n" 371 | --- no_error_log 372 | [error] 373 | --- timeout: 10 374 | -------------------------------------------------------------------------------- /t/bug.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | #log_level 'warn'; 10 | 11 | no_long_string(); 12 | no_shuffle(); 13 | check_accum_error_log(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: message in ok packet (github #61) 20 | --- server_config 21 | content_by_lua_block { 22 | local mysql_driver = require "resty.mysql" 23 | 24 | local connect_table = { 25 | host = "$TEST_NGINX_MYSQL_HOST", 26 | port = $TEST_NGINX_MYSQL_PORT, 27 | database = "ngx_test", 28 | user = 'ngx_test', 29 | password = 'ngx_test', 30 | } 31 | 32 | local connect_timeout = 1000 33 | local idle_timeout = 10000 34 | local pool_size = 50 35 | 36 | local function query(statement, compact, rows) 37 | local db, res, ok, err, errno, sqlstate 38 | db, err = mysql_driver:new() 39 | if not db then 40 | return nil, err 41 | end 42 | db:set_timeout(connect_timeout) 43 | res, err, errno, sqlstate = db:connect(connect_table) 44 | if not res then 45 | return nil, err, errno, sqlstate 46 | end 47 | db.compact = compact 48 | res, err, errno, sqlstate = db:query(statement, rows) 49 | if res ~= nil then 50 | ok, err = db:set_keepalive(idle_timeout, pool_size) 51 | if not ok then 52 | return nil, 'fail to set_keepalive:'..err 53 | end 54 | end 55 | return res, err, errno, sqlstate 56 | end 57 | 58 | local statements = { 59 | 'drop table if exists test_usr', 60 | 'create table test_usr (name varchar(10))', 61 | 'insert into test_usr values ("name1")', 62 | 'update test_usr set name="foo"', 63 | } 64 | 65 | local res, err 66 | for i, stm in ipairs(statements) do 67 | res, err = query(stm) 68 | if not res then 69 | return ngx.say(err) 70 | end 71 | if res.message then 72 | ngx.say(res.message) 73 | end 74 | end 75 | } 76 | --- response_body 77 | Rows matched: 1 Changed: 1 Warnings: 0 78 | --- no_error_log 79 | [error] 80 | 81 | 82 | 83 | === TEST 2: ensure packet_no is not nil before proceeding (gitub #141) 84 | --- server_config 85 | content_by_lua_block { 86 | local mysql = require "resty.mysql" 87 | 88 | local ok, err = mysql:close() 89 | ngx.say("mysql connection closed") 90 | } 91 | --- response_body_like 92 | mysql connection closed 93 | --- no_error_log 94 | [error] 95 | -------------------------------------------------------------------------------- /t/charset.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | #log_level 'warn'; 10 | 11 | #no_long_string(); 12 | no_shuffle(); 13 | check_accum_error_log(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: connect db using charset option (utf8) 20 | --- server_config 21 | content_by_lua_block { 22 | local ljson = require "ljson" 23 | local mysql = require "resty.mysql" 24 | local db = mysql:new() 25 | 26 | db:set_timeout(1000) -- 1 sec 27 | 28 | local ok, err, errno, sqlstate = db:connect({ 29 | path = "$TEST_NGINX_MYSQL_PATH", 30 | database = "ngx_test", 31 | user = "ngx_test", 32 | password = "ngx_test", 33 | charset = "utf8", 34 | pool = "my_pool"}) 35 | 36 | if not ok then 37 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 38 | return 39 | end 40 | 41 | -- generate test data 42 | local res, err, errno, sqlstate = db:query("DROP TABLE IF EXISTS cats") 43 | if not res then 44 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 45 | return 46 | end 47 | 48 | res, err, errno, sqlstate = db:query("CREATE TABLE cats (id serial PRIMARY KEY, name VARCHAR(128)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") 49 | if not res then 50 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 51 | return 52 | end 53 | 54 | -- add new record with '愛麗絲' by utf8 encoded. 55 | res, err, errno, sqlstate = db:query("INSERT INTO cats(name) VALUES (0xe6849be9ba97e7b5b2)") 56 | if not res then 57 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 58 | return 59 | end 60 | 61 | res, err, errno, sqlstate = db:query("SELECT * FROM cats") 62 | if not res then 63 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 64 | return 65 | end 66 | 67 | db:close() 68 | 69 | ngx.say(ljson.encode(res)) 70 | } 71 | --- response_body 72 | [{"id":"1","name":"愛麗絲"}] 73 | --- no_error_log 74 | [error] 75 | 76 | 77 | 78 | === TEST 2: connect db using charset option (big5) 79 | --- server_config 80 | content_by_lua_block { 81 | local ljson = require "ljson" 82 | local mysql = require "resty.mysql" 83 | local db = mysql:new() 84 | 85 | db:set_timeout(1000) -- 1 sec 86 | 87 | local ok, err, errno, sqlstate = db:connect({ 88 | path = "$TEST_NGINX_MYSQL_PATH", 89 | database = "ngx_test", 90 | user = "ngx_test", 91 | password = "ngx_test", 92 | charset = "big5", 93 | pool = "my_pool"}) 94 | 95 | if not ok then 96 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 97 | return 98 | end 99 | 100 | -- generate test data 101 | local res, err, errno, sqlstate = db:query("DROP TABLE IF EXISTS cats") 102 | if not res then 103 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 104 | return 105 | end 106 | 107 | res, err, errno, sqlstate = db:query("CREATE TABLE cats (id serial PRIMARY KEY, name VARCHAR(128)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") 108 | if not res then 109 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 110 | return 111 | end 112 | 113 | -- add new record with '愛麗絲' by utf8 encoded. 114 | res, err, errno, sqlstate = db:query("INSERT INTO cats(name) VALUES (0xe6849be9ba97e7b5b2)") 115 | if not res then 116 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 117 | return 118 | end 119 | 120 | res, err, errno, sqlstate = db:query("SELECT * FROM cats") 121 | if not res then 122 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 123 | return 124 | end 125 | 126 | db:close() 127 | 128 | ngx.say(ljson.encode(res)) 129 | } 130 | --- response_body eval 131 | qq/[{"id":"1","name":"\x{b7}R\x{c4}R\x{b5}\x{b7}"}]\n/ 132 | --- no_error_log 133 | [error] 134 | 135 | 136 | 137 | === TEST 3: connect db using charset option (gbk) 138 | --- server_config 139 | content_by_lua_block { 140 | local ljson = require "ljson" 141 | local mysql = require "resty.mysql" 142 | local db = mysql:new() 143 | 144 | db:set_timeout(1000) -- 1 sec 145 | 146 | local ok, err, errno, sqlstate = db:connect({ 147 | path = "$TEST_NGINX_MYSQL_PATH", 148 | database = "ngx_test", 149 | user = "ngx_test", 150 | password = "ngx_test", 151 | charset = "gbk", 152 | pool = "my_pool"}) 153 | 154 | if not ok then 155 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 156 | return 157 | end 158 | 159 | -- generate test data 160 | local res, err, errno, sqlstate = db:query("DROP TABLE IF EXISTS cats") 161 | if not res then 162 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 163 | return 164 | end 165 | 166 | res, err, errno, sqlstate = db:query("CREATE TABLE cats (id serial PRIMARY KEY, name VARCHAR(128)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;") 167 | if not res then 168 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 169 | return 170 | end 171 | 172 | -- add new record with '愛麗絲' by utf8 encoded. 173 | res, err, errno, sqlstate = db:query("INSERT INTO cats(name) VALUES (0xe6849be9ba97e7b5b2)") 174 | if not res then 175 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 176 | return 177 | end 178 | 179 | res, err, errno, sqlstate = db:query("SELECT * FROM cats") 180 | if not res then 181 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 182 | return 183 | end 184 | 185 | db:close() 186 | 187 | ngx.say(ljson.encode(res)) 188 | } 189 | --- response_body eval 190 | qq/[{"id":"1","name":"\x{90}\x{db}\x{fb}\x{90}\x{bd}z"}]\n/ 191 | --- no_error_log 192 | [error] 193 | -------------------------------------------------------------------------------- /t/compact_arrays.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | #log_level 'warn'; 10 | 11 | no_long_string(); 12 | no_shuffle(); 13 | 14 | run_tests(); 15 | 16 | __DATA__ 17 | 18 | === TEST 1: send query w/o result set 19 | --- server_config 20 | content_by_lua ' 21 | local mysql = require "resty.mysql" 22 | local db = mysql:new() 23 | 24 | db:set_timeout(2000) -- 2 sec 25 | 26 | local ok, err, errno, sqlstate = db:connect{ 27 | compact_arrays = true, 28 | host = "$TEST_NGINX_MYSQL_HOST", 29 | port = $TEST_NGINX_MYSQL_PORT, 30 | database = "ngx_test", 31 | user = "ngx_test", 32 | password = "ngx_test"} 33 | 34 | if not ok then 35 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 36 | return 37 | end 38 | 39 | ngx.say("connected to mysql ", db:server_ver(), ".") 40 | 41 | local bytes, err = db:send_query("drop table if exists cats") 42 | if not bytes then 43 | ngx.say("failed to send query: ", err) 44 | end 45 | 46 | ngx.say("sent ", bytes, " bytes.") 47 | 48 | local res, err, errno, sqlstate = db:read_result() 49 | if not res then 50 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 51 | end 52 | 53 | local ljson = require "ljson" 54 | ngx.say("result: ", ljson.encode(res)) 55 | 56 | local ok, err = db:close() 57 | if not ok then 58 | ngx.say("failed to close: ", err) 59 | return 60 | end 61 | '; 62 | --- response_body_like chop 63 | ^connected to mysql \d\.[^\s\x00]+\. 64 | sent 30 bytes\. 65 | result: (?:{"insert_id":0,"server_status":2,"warning_count":[01],"affected_rows":0}|{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]})$ 66 | --- no_error_log 67 | [error] 68 | 69 | 70 | 71 | === TEST 2: select query with an non-empty result set 72 | --- server_config 73 | content_by_lua ' 74 | local ljson = require "ljson" 75 | 76 | local mysql = require "resty.mysql" 77 | local db = mysql:new() 78 | 79 | db:set_timeout(2000) -- 2 sec 80 | 81 | local ok, err, errno, sqlstate = db:connect{ 82 | compact_arrays = true, 83 | host = "$TEST_NGINX_MYSQL_HOST", 84 | port = $TEST_NGINX_MYSQL_PORT, 85 | database = "ngx_test", 86 | user = "ngx_test", 87 | password = "ngx_test"} 88 | 89 | if not ok then 90 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 91 | return 92 | end 93 | 94 | ngx.say("connected to mysql.") 95 | 96 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 97 | if not res then 98 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 99 | return 100 | end 101 | 102 | ngx.say("table cats dropped.") 103 | 104 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 105 | if not res then 106 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 107 | return 108 | end 109 | 110 | ngx.say("table cats created.") 111 | 112 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'Bob\'),(\'\'),(null)") 113 | if not res then 114 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 115 | return 116 | end 117 | 118 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 119 | 120 | res, err, errno, sqlstate = db:query("select name, id from cats order by id asc") 121 | if not res then 122 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 123 | return 124 | end 125 | 126 | ngx.say("result: ", ljson.encode(res)) 127 | 128 | res, err, errno, sqlstate = db:query("select name, id from cats order by id desc") 129 | if not res then 130 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 131 | return 132 | end 133 | 134 | ngx.say("result: ", ljson.encode(res)) 135 | 136 | local ok, err = db:close() 137 | if not ok then 138 | ngx.say("failed to close: ", err) 139 | return 140 | end 141 | '; 142 | --- response_body 143 | connected to mysql. 144 | table cats dropped. 145 | table cats created. 146 | 3 rows inserted into table cats (last id: 1) 147 | result: [["Bob","1"],["","2"],[null,"3"]] 148 | result: [[null,"3"],["","2"],["Bob","1"]] 149 | --- no_error_log 150 | [error] 151 | 152 | 153 | 154 | === TEST 3: select query with an empty result set 155 | --- server_config 156 | content_by_lua ' 157 | local ljson = require "ljson" 158 | 159 | local mysql = require "resty.mysql" 160 | local db = mysql:new() 161 | 162 | db:set_timeout(1000) -- 1 sec 163 | 164 | local ok, err, errno, sqlstate = db:connect{ 165 | compact_arrays = true, 166 | host = "$TEST_NGINX_MYSQL_HOST", 167 | port = $TEST_NGINX_MYSQL_PORT, 168 | database = "ngx_test", 169 | user = "ngx_test", 170 | password = "ngx_test"} 171 | 172 | if not ok then 173 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 174 | return 175 | end 176 | 177 | ngx.say("connected to mysql.") 178 | 179 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 180 | if not res then 181 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 182 | return 183 | end 184 | 185 | ngx.say("table cats dropped.") 186 | 187 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 188 | if not res then 189 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 190 | return 191 | end 192 | 193 | ngx.say("table cats created.") 194 | 195 | res, err, errno, sqlstate = db:query("select * from cats order by id asc") 196 | if not res then 197 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 198 | return 199 | end 200 | 201 | ngx.say("result: ", ljson.encode(res)) 202 | 203 | res, err, errno, sqlstate = db:query("select * from cats order by id desc") 204 | if not res then 205 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 206 | return 207 | end 208 | 209 | ngx.say("result: ", ljson.encode(res)) 210 | 211 | local ok, err = db:close() 212 | if not ok then 213 | ngx.say("failed to close: ", err) 214 | return 215 | end 216 | '; 217 | --- response_body 218 | connected to mysql. 219 | table cats dropped. 220 | table cats created. 221 | result: [] 222 | result: [] 223 | --- no_error_log 224 | [error] 225 | 226 | 227 | 228 | === TEST 4: select query with an non-empty result set - set_compact_arrays 229 | --- server_config 230 | content_by_lua ' 231 | local ljson = require "ljson" 232 | 233 | local mysql = require "resty.mysql" 234 | local db = mysql:new() 235 | 236 | db:set_timeout(1000) -- 1 sec 237 | 238 | local ok, err, errno, sqlstate = db:connect{ 239 | host = "$TEST_NGINX_MYSQL_HOST", 240 | port = $TEST_NGINX_MYSQL_PORT, 241 | database = "ngx_test", 242 | user = "ngx_test", 243 | password = "ngx_test"} 244 | 245 | if not ok then 246 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 247 | return 248 | end 249 | 250 | ngx.say("connected to mysql.") 251 | 252 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 253 | if not res then 254 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 255 | return 256 | end 257 | 258 | ngx.say("table cats dropped.") 259 | 260 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 261 | if not res then 262 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 263 | return 264 | end 265 | 266 | ngx.say("table cats created.") 267 | 268 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'Bob\'),(\'\'),(null)") 269 | if not res then 270 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 271 | return 272 | end 273 | 274 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 275 | 276 | db:set_compact_arrays(true) 277 | 278 | res, err, errno, sqlstate = db:query("select name, id from cats order by id asc") 279 | if not res then 280 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 281 | return 282 | end 283 | 284 | ngx.say("result: ", ljson.encode(res)) 285 | 286 | db:set_compact_arrays(false) 287 | 288 | res, err, errno, sqlstate = db:query("select name, id from cats order by id desc") 289 | if not res then 290 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 291 | return 292 | end 293 | 294 | ngx.say("result: ", ljson.encode(res)) 295 | 296 | local ok, err = db:close() 297 | if not ok then 298 | ngx.say("failed to close: ", err) 299 | return 300 | end 301 | '; 302 | --- response_body 303 | connected to mysql. 304 | table cats dropped. 305 | table cats created. 306 | 3 rows inserted into table cats (last id: 1) 307 | result: [["Bob","1"],["","2"],[null,"3"]] 308 | result: [{"id":"3","name":null},{"id":"2","name":""},{"id":"1","name":"Bob"}] 309 | --- no_error_log 310 | [error] 311 | -------------------------------------------------------------------------------- /t/data/test-sha1.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICqTCCAhICCQClDm1WkreW4jANBgkqhkiG9w0BAQUFADCBlzELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x 4 | EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQD 5 | DAh0ZXN0LmNvbTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20wIBcN 6 | MTQwNzIxMDMyMzQ3WhgPMjE1MTA2MTMwMzIzNDdaMIGXMQswCQYDVQQGEwJVUzET 7 | MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAG 8 | A1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRl 9 | c3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNvbTCBnzANBgkq 10 | hkiG9w0BAQEFAAOBjQAwgYkCgYEA6P18zUvtmaKQK2xePy8ZbFwSyTLw+jW6t9eZ 11 | aiTec8X3ibN9WemrxHzkTRikxP3cAQoITRuZiQvF4Q7DO6wMkz/b0zwfgX5uedGq 12 | 047AJP6n/mwlDOjGSNomBLoXQzo7tVe60ikEm3ZyDUqnJPJMt3hImO5XSop4MPMu 13 | Za9WhFcCAwEAATANBgkqhkiG9w0BAQUFAAOBgQA4OBb9bOyWB1//93nSXX1mdENZ 14 | IQeyTK0Dd6My76lnZxnZ4hTWrvvd0b17KLDU6JnS2N5ee3ATVkojPidRLWLIhnh5 15 | 0eXrcKalbO2Ce6nShoFvQCQKXN2Txmq2vO/Mud2bHAWwJALg+qi1Iih/gVYB9sct 16 | FLg8zFOzRlYiU+6Mmw== 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /t/data/test-sha1.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDo/XzNS+2ZopArbF4/LxlsXBLJMvD6Nbq315lqJN5zxfeJs31Z 3 | 6avEfORNGKTE/dwBCghNG5mJC8XhDsM7rAyTP9vTPB+Bfm550arTjsAk/qf+bCUM 4 | 6MZI2iYEuhdDOju1V7rSKQSbdnINSqck8ky3eEiY7ldKingw8y5lr1aEVwIDAQAB 5 | AoGBANgB66sKMga2SKN5nQdHS3LDCkevCutu1OWM5ZcbB4Kej5kC57xsf+tzPtab 6 | emeIVGhCPOAALqB4YcT+QtMX967oM1MjcFbtH7si5oq6UYyp3i0G9Si6jIoVHz3+ 7 | 8yOUaqwKbK+bRX8VS0YsHZmBsPK5ryN50iUwsU08nemoA94BAkEA9GS9Q5OPeFkM 8 | tFxsIQ1f2FSsZAuN/1cpZgJqY+YaAN7MSPGTWyfd7nWG/Zgk3GO9/2ihh4gww+7B 9 | To09GkmW4QJBAPQOHC2V+t2TA98+6Lj6+TYwcGEkhOENfVpH25mQ+kXgF/1Bd6rA 10 | nosT1bdAY+SnmWXbSw6Kv5C20Em+bEX8WjcCQCSRRjhsRdVODbaW9Z7kb2jhEoJN 11 | sEt6cTlQNzcHYPCsZYisjM3g4zYg47fiIfHQAsfKkhDDcfh/KvFj9LaQOEECQQCH 12 | eBWYEDpSJ7rsfqT7mQQgWj7nDThdG/nK1TxGP71McBmg0Gg2dfkLRhVJRQqt74Is 13 | kc9V4Rp4n6F6baL4Lh19AkEA6pZZer0kg3Kv9hjhaITIKUYdfIp9vYnDRWbQlBmR 14 | atV8V9u9q2ETZvqfHpN+9Lu6NYR4yXIEIRf1bnIZ/mr9eQ== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /t/data/test-sha1.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo/XzNS+2ZopArbF4/LxlsXBLJ 3 | MvD6Nbq315lqJN5zxfeJs31Z6avEfORNGKTE/dwBCghNG5mJC8XhDsM7rAyTP9vT 4 | PB+Bfm550arTjsAk/qf+bCUM6MZI2iYEuhdDOju1V7rSKQSbdnINSqck8ky3eEiY 5 | 7ldKingw8y5lr1aEVwIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /t/data/test-sha256.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDrjCCApYCCQCcdjRMo/UMDjANBgkqhkiG9w0BAQsFADCBlzELMAkGA1UEBhMC 3 | VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x 4 | EjAQBgNVBAoMCU9wZW5SZXN0eTESMBAGA1UECwwJT3BlblJlc3R5MREwDwYDVQQD 5 | DAh0ZXN0LmNvbTEgMB4GCSqGSIb3DQEJARYRYWdlbnR6aEBnbWFpbC5jb20wIBcN 6 | MjAwNjAzMTI0ODQzWhgPMjEyMDA1MTAxMjQ4NDNaMIGXMQswCQYDVQQGEwJVUzET 7 | MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzESMBAG 8 | A1UECgwJT3BlblJlc3R5MRIwEAYDVQQLDAlPcGVuUmVzdHkxETAPBgNVBAMMCHRl 9 | c3QuY29tMSAwHgYJKoZIhvcNAQkBFhFhZ2VudHpoQGdtYWlsLmNvbTCCASIwDQYJ 10 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ1BGBbWH+9GbtrSoD21zfkHjH1nEVRy 11 | Jq/kORPD/ADzNRqOPaxDZBPqTRjmc/cUglET6uASC5GYa+aiuKdHgf+B/ocCZkl1 12 | h3VRWsV7X8blKgKZv1uvCd77QtLuhgA99k4+vlJnHduV5vuK45BjXNPENYX8eqbk 13 | oxaFSKktF2qsmGbJsycy14yF/ZFE0BvX50NIXlTG3l79r6Kh2CIWk97lmYnyU3Jw 14 | bHzr6gZToB0UbpIRZrD0dXAgY7v1vuvG1n+3e6CsqzgqqTTtuHa9Afx1mnntp5Bk 15 | KICK36IUbsD+ORhUlNtjYJGIHhN3U7XRReHb58kCvaqOKhsV7stcSG0CAwEAATAN 16 | BgkqhkiG9w0BAQsFAAOCAQEAWWWcU5/OvgK8hDy3YTcuyip2zzH7197ls7abwXTg 17 | Hhq+YVRrf0CfGd4cdBCD/MjWOWeQgmnZC0PfRUqYrBKgujhwHTTD6SfrTlgW43lt 18 | FvHq6M2euoy/LwyngQBukeqgH6LGbMlxydU8Z0LxyFzPy7apB0LkUZyZDZzjcIiA 19 | kzsDAKqzcq0Vhp+vUHWpEkNH9Y+QaNO2yZrtdBEw3nuy8pDQsvWdZPcZJy9EoMm8 20 | FyQqBXHz1ampbIaYrDRliRrpD8w9QKH/vwOzRoeKzhmQfXoSt1PO1KO2gZkSYZSz 21 | vXiQH+kKyOlxT6uU52U67Y8xBVGoqVpGv+1M4YlwTHqqVA== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /t/data/test-sha256.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAnUEYFtYf70Zu2tKgPbXN+QeMfWcRVHImr+Q5E8P8APM1Go49 3 | rENkE+pNGOZz9xSCURPq4BILkZhr5qK4p0eB/4H+hwJmSXWHdVFaxXtfxuUqApm/ 4 | W68J3vtC0u6GAD32Tj6+Umcd25Xm+4rjkGNc08Q1hfx6puSjFoVIqS0XaqyYZsmz 5 | JzLXjIX9kUTQG9fnQ0heVMbeXv2voqHYIhaT3uWZifJTcnBsfOvqBlOgHRRukhFm 6 | sPR1cCBju/W+68bWf7d7oKyrOCqpNO24dr0B/HWaee2nkGQogIrfohRuwP45GFSU 7 | 22NgkYgeE3dTtdFF4dvnyQK9qo4qGxXuy1xIbQIDAQABAoIBAD9zA1A7h1kZFyNt 8 | tsAPehMP66yxiHmFMXzo3erOugB8ISIc4F+ANYIfmIoCeinoeYhTPAK65TYSxP6j 9 | C1RbcuVwQw2gdzsOk+BPOgvSctuinWe9N0u7/YSd5uGK4kB4v8fkYxCGkLKH8lHu 10 | QVGB9fNwCJSOQcLTH8a3cpDoI7mjVjBr5xLTDfB7noZx8I+cHbQLIq4WUuG8KnP0 11 | aFASNp5PnqJnd5rtBAqRMLFuaLtFnmXDt2T1rjV9Q7a0qR2CUwXnyfuaWKgV4FDz 12 | RWfS769azTz+AsWnvpRfv9Znb39s7BiU7CtaWsxSPhQPp5CoEynzkPs1qr4jn083 13 | K4nhQgECgYEA0T1MpAyQpKyEhSkbNzhfPTa35Nh2fHTNr/2rt817MZBVPpf3rDim 14 | iy/Boc4YBVyVgYPLmQPj+E8Wf+LfCYCMqqGsjM+hAnggjM8DjLETLsr2zTPVdXCi 15 | pmfAT1oSxs43sNEP4K2OUC82ETJ0BqKyAdNEyVaJrZ4E2JxGpcvlle0CgYEAwGWx 16 | 7FnmJMiex7R5ktUeBCGQfyCpepdzlM0tsZyU5BQMZC5NO9kE27grUz2Ta80bZZLE 17 | u2ug4dw9S5UVll6eOK7bvPRg9S3WqUBncTlhJK1ckDnkz21sVKls23HTnPsbYg1Y 18 | dy9O9WLmpyLi6VEL7K8UBEps2HEoxedXuYWjLIECgYEAo5Ss6kP4gQVuxTwRzuX/ 19 | bzfsJLkmSL54X/KN4TB/84vHWRwtwTK44XAAjaM2HFr5dzu2XHYyL9qE2I5hmGgr 20 | kOq8n4ljcy6/I9ZkDAYT3S1ILb0sUJgyUK4kJuVgcJSf7VhEdX66F+4Q8vjixJ1F 21 | VS/6Qv2ovVcgqsfiY5Q4dTECgYAYmSftWvUUwN2kFMvyXPMQfiYWStvAQ7rQTIdK 22 | DQXwPyvyZwUS4MVBPkOzycyeXMPi8afbeoBAQENJ9Y6kAgbiomCPwQLgdfLbAtP2 23 | 4uoroUqTWgOKDahrDppPWDdA+83ddG1aF59VukZZGhm+0NDgeuXpQoa2CYm3+8L4 24 | gkCBgQKBgQDBwIl59plr2FU5NoGDEEGz0s7a2hbmnjDXsFtzKFLnYvhl9z9y8R/U 25 | T96MtfbMS4bLcDFA6w/LAmHwInK3mLXbnQlnZpQ1AoSXk5nIUJhP9JJOEx1IeD/c 26 | P8tauzGwEJWfrRu4G26s0/hWInDN+sbRZa/xkJt38KW+oxw2UegPEg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /t/data/world.sql.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openresty/lua-resty-mysql/cc1d693bd5ebe8e7225c6ba5c9273648ee347530/t/data/world.sql.gz -------------------------------------------------------------------------------- /t/lib/create_sql_by_columns.lua: -------------------------------------------------------------------------------- 1 | local type = type 2 | local pairs = pairs 3 | local ipairs = ipairs 4 | local tostring = tostring 5 | local concat = table.concat 6 | 7 | local ok, new_tab = pcall(require, "table.new") 8 | if not ok then 9 | new_tab = function(narr, nrec) return {} end 10 | end 11 | 12 | local _M = {} 13 | 14 | function _M.create(num, engine) 15 | local bits = new_tab(num + 1, 0) 16 | bits[1] = "CREATE TABLE test" .. num .. " (id int(10) AUTO_INCREMENT, " 17 | for i = 2, num do 18 | bits[i] = "t" .. i - 1 .. " int(1) DEFAULT 0, " 19 | end 20 | 21 | if not engine then 22 | engine = "InnoDB" 23 | end 24 | bits[num + 1] = "PRIMARY KEY (id)) ENGINE=" .. engine 25 | 26 | return concat(bits, "") 27 | end 28 | 29 | function _M.drop(num) 30 | return "drop table if exists test" .. num 31 | end 32 | 33 | function _M.insert(num) 34 | return "insert into test" .. num .. " (t1) values(1)" 35 | end 36 | 37 | function _M.query(num) 38 | return "select * from test" .. num 39 | end 40 | 41 | return _M 42 | -------------------------------------------------------------------------------- /t/lib/ljson.lua: -------------------------------------------------------------------------------- 1 | local ngx_null = ngx.null 2 | local tostring = tostring 3 | local byte = string.byte 4 | local gsub = string.gsub 5 | local sort = table.sort 6 | local pairs = pairs 7 | local ipairs = ipairs 8 | local concat = table.concat 9 | 10 | local ok, new_tab = pcall(require, "table.new") 11 | if not ok then 12 | new_tab = function (narr, nrec) return {} end 13 | end 14 | 15 | local _M = {} 16 | 17 | local metachars = { 18 | ['\t'] = '\\t', 19 | ["\\"] = "\\\\", 20 | ['"'] = '\\"', 21 | ['\r'] = '\\r', 22 | ['\n'] = '\\n', 23 | } 24 | 25 | local function encode_str(s) 26 | -- XXX we will rewrite this when string.buffer is implemented 27 | -- in LuaJIT 2.1 because string.gsub cannot be JIT compiled. 28 | return gsub(s, '["\\\r\n\t]', metachars) 29 | end 30 | 31 | local function is_arr(t) 32 | local exp = 1 33 | for k, _ in pairs(t) do 34 | if k ~= exp then 35 | return nil 36 | end 37 | exp = exp + 1 38 | end 39 | return exp - 1 40 | end 41 | 42 | local encode 43 | 44 | encode = function (v) 45 | if v == nil or v == ngx_null then 46 | return "null" 47 | end 48 | 49 | local typ = type(v) 50 | if typ == 'string' then 51 | return '"' .. encode_str(v) .. '"' 52 | end 53 | 54 | if typ == 'number' or typ == 'boolean' then 55 | return tostring(v) 56 | end 57 | 58 | if typ == 'table' then 59 | local n = is_arr(v) 60 | if n then 61 | local bits = new_tab(n, 0) 62 | for i, elem in ipairs(v) do 63 | bits[i] = encode(elem) 64 | end 65 | return "[" .. concat(bits, ",") .. "]" 66 | end 67 | 68 | local keys = {} 69 | local i = 0 70 | for key, _ in pairs(v) do 71 | i = i + 1 72 | keys[i] = key 73 | end 74 | sort(keys) 75 | 76 | local bits = new_tab(0, i) 77 | i = 0 78 | for _, key in ipairs(keys) do 79 | i = i + 1 80 | bits[i] = encode(key) .. ":" .. encode(v[key]) 81 | end 82 | return "{" .. concat(bits, ",") .. "}" 83 | end 84 | 85 | return '"<' .. typ .. '>"' 86 | end 87 | _M.encode = encode 88 | 89 | return _M 90 | -------------------------------------------------------------------------------- /t/sanity.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks() + 4); 8 | 9 | #log_level 'warn'; 10 | 11 | no_long_string(); 12 | no_shuffle(); 13 | check_accum_error_log(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: bad user 20 | --- server_config 21 | content_by_lua ' 22 | local mysql = require "resty.mysql" 23 | local db = mysql:new() 24 | 25 | db:set_timeout(1000) -- 1 sec 26 | 27 | local ok, err, errno, sqlstate = db:connect({ 28 | host = "$TEST_NGINX_MYSQL_HOST", 29 | port = $TEST_NGINX_MYSQL_PORT, 30 | database = "ngx_test", 31 | user = "user_not_found", 32 | password = "ngx_test"}) 33 | 34 | if not ok then 35 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 36 | return 37 | end 38 | 39 | db:close() 40 | '; 41 | --- response_body_like 42 | failed to connect: Access denied for user 'user_not_found'@'[^\s]+' \(using password: YES\): 1045 28000 43 | --- no_error_log 44 | [error] 45 | 46 | 47 | 48 | === TEST 2: bad host 49 | --- server_config 50 | content_by_lua ' 51 | local mysql = require "resty.mysql" 52 | local db = mysql:new() 53 | 54 | db:set_timeout(1000) -- 1 sec 55 | 56 | local ok, err, errno, sqlstate = db:connect({ 57 | host = "host-not-found.org", 58 | port = $TEST_NGINX_MYSQL_PORT, 59 | database = "ngx_test", 60 | user = "ngx_test", 61 | password = "ngx_test"}) 62 | 63 | if not ok then 64 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 65 | return 66 | end 67 | 68 | db:close() 69 | '; 70 | --- response_body_like chop 71 | ^failed to connect: failed to connect: host-not-found.org could not be resolved(?: \(3: Host not found\))?: nil nil$ 72 | --- no_error_log 73 | [error] 74 | --- timeout: 7 75 | 76 | 77 | 78 | === TEST 3: connected 79 | --- server_config 80 | content_by_lua ' 81 | local mysql = require "resty.mysql" 82 | local db = mysql:new() 83 | 84 | db:set_timeout(1000) -- 1 sec 85 | 86 | local ok, err, errno, sqlstate = db:connect({ 87 | host = "$TEST_NGINX_MYSQL_HOST", 88 | port = $TEST_NGINX_MYSQL_PORT, 89 | database = "ngx_test", 90 | user = "ngx_test", 91 | password = "ngx_test"}) 92 | 93 | if not ok then 94 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 95 | return 96 | end 97 | 98 | ngx.say("connected to mysql ", db:server_ver()) 99 | 100 | db:close() 101 | '; 102 | --- response_body_like 103 | connected to mysql \d\.[^\s\x00]+ 104 | --- no_error_log 105 | [error] 106 | 107 | 108 | 109 | === TEST 4: send query w/o result set 110 | --- server_config 111 | content_by_lua ' 112 | local mysql = require "resty.mysql" 113 | local db = mysql:new() 114 | 115 | db:set_timeout(1000) -- 1 sec 116 | 117 | local ok, err, errno, sqlstate = db:connect({ 118 | host = "$TEST_NGINX_MYSQL_HOST", 119 | port = $TEST_NGINX_MYSQL_PORT, 120 | database = "ngx_test", 121 | user = "ngx_test", 122 | password = "ngx_test"}) 123 | 124 | if not ok then 125 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 126 | return 127 | end 128 | 129 | ngx.say("connected to mysql ", db:server_ver(), ".") 130 | 131 | local bytes, err = db:send_query("drop table if exists cats") 132 | if not bytes then 133 | ngx.say("failed to send query: ", err) 134 | end 135 | 136 | ngx.say("sent ", bytes, " bytes.") 137 | 138 | local res, err, errno, sqlstate = db:read_result() 139 | if not res then 140 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 141 | end 142 | 143 | local ljson = require "ljson" 144 | ngx.say("result: ", ljson.encode(res)) 145 | 146 | local ok, err = db:close() 147 | if not ok then 148 | ngx.say("failed to close: ", err) 149 | return 150 | end 151 | '; 152 | --- response_body_like chop 153 | ^connected to mysql \d\.[^\s\x00]+\. 154 | sent 30 bytes\. 155 | result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$ 156 | --- no_error_log 157 | [error] 158 | 159 | 160 | 161 | === TEST 5: send bad query 162 | --- server_config 163 | content_by_lua ' 164 | local mysql = require "resty.mysql" 165 | local db = mysql:new() 166 | 167 | db:set_timeout(1000) -- 1 sec 168 | 169 | local ok, err, errno, sqlstate = db:connect({ 170 | host = "$TEST_NGINX_MYSQL_HOST", 171 | port = $TEST_NGINX_MYSQL_PORT, 172 | database = "ngx_test", 173 | user = "ngx_test", 174 | password = "ngx_test"}) 175 | 176 | if not ok then 177 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 178 | return 179 | end 180 | 181 | ngx.say("connected to mysql ", db:server_ver(), ".") 182 | 183 | local bytes, err = db:send_query("bad SQL") 184 | if not bytes then 185 | ngx.say("failed to send query: ", err) 186 | end 187 | 188 | ngx.say("sent ", bytes, " bytes.") 189 | 190 | local res, err, errno, sqlstate = db:read_result() 191 | if not res then 192 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 193 | return 194 | end 195 | 196 | local ljson = require "ljson" 197 | ngx.say("result: ", ljson.encode(res)) 198 | 199 | local ok, err = db:close() 200 | if not ok then 201 | ngx.say("failed to close: ", err) 202 | return 203 | end 204 | '; 205 | --- response_body_like chop 206 | ^connected to mysql \d\.[^\s\x00]+\. 207 | sent 12 bytes\. 208 | bad result: You have an error in your SQL syntax; check the manual that corresponds to your (?:MySQL|MariaDB) server version for the right syntax to use near 'bad SQL' at line 1: 1064: 42000\.$ 209 | --- no_error_log 210 | [error] 211 | 212 | 213 | 214 | === TEST 6: select query with an non-empty result set 215 | --- server_config 216 | content_by_lua ' 217 | local ljson = require "ljson" 218 | 219 | local mysql = require "resty.mysql" 220 | local db = mysql:new() 221 | 222 | db:set_timeout(2000) -- 2 sec 223 | 224 | local ok, err, errno, sqlstate = db:connect({ 225 | host = "$TEST_NGINX_MYSQL_HOST", 226 | port = $TEST_NGINX_MYSQL_PORT, 227 | database = "ngx_test", 228 | user = "ngx_test", 229 | password = "ngx_test"}) 230 | 231 | if not ok then 232 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 233 | return 234 | end 235 | 236 | ngx.say("connected to mysql.") 237 | 238 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 239 | if not res then 240 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 241 | return 242 | end 243 | 244 | ngx.say("table cats dropped.") 245 | 246 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 247 | if not res then 248 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 249 | return 250 | end 251 | 252 | ngx.say("table cats created.") 253 | 254 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'Bob\'),(\'\'),(null)") 255 | if not res then 256 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 257 | return 258 | end 259 | 260 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 261 | 262 | res, err, errno, sqlstate = db:query("select * from cats order by id asc") 263 | if not res then 264 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 265 | return 266 | end 267 | 268 | ngx.say("result: ", ljson.encode(res)) 269 | 270 | res, err, errno, sqlstate = db:query("select * from cats order by id desc") 271 | if not res then 272 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 273 | return 274 | end 275 | 276 | ngx.say("result: ", ljson.encode(res)) 277 | 278 | local ok, err = db:close() 279 | if not ok then 280 | ngx.say("failed to close: ", err) 281 | return 282 | end 283 | '; 284 | --- response_body 285 | connected to mysql. 286 | table cats dropped. 287 | table cats created. 288 | 3 rows inserted into table cats (last id: 1) 289 | result: [{"id":"1","name":"Bob"},{"id":"2","name":""},{"id":"3","name":null}] 290 | result: [{"id":"3","name":null},{"id":"2","name":""},{"id":"1","name":"Bob"}] 291 | --- no_error_log 292 | [error] 293 | 294 | 295 | 296 | === TEST 7: select query with an empty result set 297 | --- server_config 298 | content_by_lua ' 299 | local ljson = require "ljson" 300 | 301 | local mysql = require "resty.mysql" 302 | local db = mysql:new() 303 | 304 | db:set_timeout(2000) -- 2 sec 305 | 306 | local ok, err, errno, sqlstate = db:connect({ 307 | host = "$TEST_NGINX_MYSQL_HOST", 308 | port = $TEST_NGINX_MYSQL_PORT, 309 | database = "ngx_test", 310 | user = "ngx_test", 311 | password = "ngx_test"}) 312 | 313 | if not ok then 314 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 315 | return 316 | end 317 | 318 | ngx.say("connected to mysql.") 319 | 320 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 321 | if not res then 322 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 323 | return 324 | end 325 | 326 | ngx.say("table cats dropped.") 327 | 328 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 329 | if not res then 330 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 331 | return 332 | end 333 | 334 | ngx.say("table cats created.") 335 | 336 | res, err, errno, sqlstate = db:query("select * from cats order by id asc") 337 | if not res then 338 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 339 | return 340 | end 341 | 342 | ngx.say("result: ", ljson.encode(res)) 343 | 344 | res, err, errno, sqlstate = db:query("select * from cats order by id desc") 345 | if not res then 346 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 347 | return 348 | end 349 | 350 | ngx.say("result: ", ljson.encode(res)) 351 | 352 | local ok, err = db:close() 353 | if not ok then 354 | ngx.say("failed to close: ", err) 355 | return 356 | end 357 | '; 358 | --- response_body 359 | connected to mysql. 360 | table cats dropped. 361 | table cats created. 362 | result: [] 363 | result: [] 364 | --- no_error_log 365 | [error] 366 | 367 | 368 | 369 | === TEST 8: numerical types 370 | --- server_config 371 | content_by_lua ' 372 | local ljson = require "ljson" 373 | 374 | local mysql = require "resty.mysql" 375 | local db = mysql:new() 376 | 377 | db:set_timeout(1000) -- 1 sec 378 | 379 | local ok, err, errno, sqlstate = db:connect({ 380 | host = "$TEST_NGINX_MYSQL_HOST", 381 | port = $TEST_NGINX_MYSQL_PORT, 382 | database = "ngx_test", 383 | user = "ngx_test", 384 | password = "ngx_test"}) 385 | 386 | if not ok then 387 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 388 | return 389 | end 390 | 391 | ngx.say("connected to mysql.") 392 | 393 | local res, err, errno, sqlstate = db:query("drop table if exists foo") 394 | if not res then 395 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 396 | return 397 | end 398 | 399 | ngx.say("table foo dropped.") 400 | 401 | res, err, errno, sqlstate = db:query("create table foo (id serial primary key, bar tinyint, baz smallint, bah float, blah double, kah bigint, hah mediumint, haha year, lah int)") 402 | if not res then 403 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 404 | return 405 | end 406 | 407 | ngx.say("table foo created.") 408 | 409 | res, err, errno, sqlstate = db:query("insert into foo (bar, baz, bah, blah, kah, hah, haha, lah) value (3, 4, 3.14, 5.16, 65535, 256, 1998, 579),(null, null, null, null, null, null, null, null)") 410 | if not res then 411 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 412 | return 413 | end 414 | 415 | ngx.say(res.affected_rows, " rows inserted into table foo (last id: ", res.insert_id, ")") 416 | 417 | res, err, errno, sqlstate = db:query("select * from foo order by id asc") 418 | if not res then 419 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 420 | return 421 | end 422 | 423 | ngx.say("result: ", ljson.encode(res)) 424 | 425 | res, err, errno, sqlstate = db:query("select * from foo order by id desc") 426 | if not res then 427 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 428 | return 429 | end 430 | 431 | ngx.say("result: ", ljson.encode(res)) 432 | 433 | local ok, err = db:close() 434 | if not ok then 435 | ngx.say("failed to close: ", err) 436 | return 437 | end 438 | '; 439 | --- response_body 440 | connected to mysql. 441 | table foo dropped. 442 | table foo created. 443 | 2 rows inserted into table foo (last id: 1) 444 | result: [{"bah":3.14,"bar":3,"baz":4,"blah":5.16,"hah":256,"haha":1998,"id":"1","kah":"65535","lah":579},{"bah":null,"bar":null,"baz":null,"blah":null,"hah":null,"haha":null,"id":"2","kah":null,"lah":null}] 445 | result: [{"bah":null,"bar":null,"baz":null,"blah":null,"hah":null,"haha":null,"id":"2","kah":null,"lah":null},{"bah":3.14,"bar":3,"baz":4,"blah":5.16,"hah":256,"haha":1998,"id":"1","kah":"65535","lah":579}] 446 | --- no_error_log 447 | [error] 448 | --- timeout: 5 449 | 450 | 451 | 452 | === TEST 9: multiple DDL statements 453 | --- server_config 454 | content_by_lua ' 455 | local ljson = require "ljson" 456 | 457 | local mysql = require "resty.mysql" 458 | local db = mysql:new() 459 | 460 | db:set_timeout(1000) -- 1 sec 461 | 462 | local ok, err, errno, sqlstate = db:connect({ 463 | host = "$TEST_NGINX_MYSQL_HOST", 464 | port = $TEST_NGINX_MYSQL_PORT, 465 | database = "ngx_test", 466 | user = "ngx_test", 467 | password = "ngx_test"}) 468 | 469 | if not ok then 470 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 471 | return 472 | end 473 | 474 | ngx.say("connected to mysql.") 475 | 476 | local res, err, errno, sqlstate = 477 | db:query("drop table if exists foo; " 478 | .. "create table foo (id serial primary key, name text);") 479 | if not res then 480 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 481 | return 482 | end 483 | 484 | ngx.say("result: ", ljson.encode(res), ", err:", err) 485 | 486 | res, err, errno, sqlstate = 487 | db:query("select * from foo order by id asc") 488 | if not res then 489 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 490 | return 491 | else 492 | ngx.say("result: ", ljson.encode(res), ", err:", err) 493 | end 494 | 495 | res, err, errno, sqlstate = db:read_result() 496 | if not res then 497 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 498 | return 499 | else 500 | ngx.say("result: ", ljson.encode(res), ", err:", err) 501 | end 502 | 503 | res, err, errno, sqlstate = 504 | db:query("select * from foo order by id asc") 505 | if not res then 506 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 507 | return 508 | else 509 | ngx.say("result: ", ljson.encode(res), ", err:", err) 510 | end 511 | 512 | local ok, err = db:close() 513 | if not ok then 514 | ngx.say("failed to close: ", err) 515 | return 516 | end 517 | '; 518 | --- response_body 519 | connected to mysql. 520 | result: {"affected_rows":0,"insert_id":0,"server_status":10,"warning_count":0}, err:again 521 | bad result: failed to send query: cannot send query in the current context: 2: nil: nil. 522 | --- no_error_log 523 | [error] 524 | 525 | 526 | 527 | === TEST 10: multiple select queries 528 | --- server_config 529 | content_by_lua ' 530 | local ljson = require "ljson" 531 | 532 | local mysql = require "resty.mysql" 533 | local db = mysql:new() 534 | 535 | db:set_timeout(1000) -- 1 sec 536 | 537 | local ok, err, errno, sqlstate = db:connect({ 538 | host = "$TEST_NGINX_MYSQL_HOST", 539 | port = $TEST_NGINX_MYSQL_PORT, 540 | database = "ngx_test", 541 | user = "ngx_test", 542 | password = "ngx_test"}) 543 | 544 | if not ok then 545 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 546 | return 547 | end 548 | 549 | ngx.say("connected to mysql.") 550 | 551 | local res, err, errno, sqlstate = 552 | db:query("drop table if exists cats") 553 | if not res then 554 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 555 | return 556 | end 557 | 558 | ngx.say("table cats dropped.") 559 | 560 | res, err, errno, sqlstate = 561 | db:query("create table cats " 562 | .. "(id serial primary key, name varchar(5))") 563 | if not res then 564 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 565 | return 566 | end 567 | 568 | ngx.say("table cats created.") 569 | 570 | res, err, errno, sqlstate = 571 | db:query("insert into cats (name) " 572 | .. "values (\'Bob\'),(\'\'),(null)") 573 | if not res then 574 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 575 | return 576 | end 577 | 578 | ngx.say(res.affected_rows .. " rows inserted into table cats (last id: ", res.insert_id, ")") 579 | 580 | res, err, errno, sqlstate = 581 | db:query("select * from cats order by id asc; " 582 | .. "select * from cats order by id desc") 583 | if not res then 584 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 585 | return 586 | end 587 | 588 | ngx.say("result: ", ljson.encode(res), ", err:", err) 589 | 590 | res, err, errno, sqlstate = db:read_result() 591 | if not res then 592 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 593 | return 594 | end 595 | 596 | ngx.say("result: ", ljson.encode(res), ", err:", err) 597 | 598 | local ok, err = db:close() 599 | if not ok then 600 | ngx.say("failed to close: ", err) 601 | return 602 | end 603 | '; 604 | --- response_body 605 | connected to mysql. 606 | table cats dropped. 607 | table cats created. 608 | 3 rows inserted into table cats (last id: 1) 609 | result: [{"id":"1","name":"Bob"},{"id":"2","name":""},{"id":"3","name":null}], err:again 610 | result: [{"id":"3","name":null},{"id":"2","name":""},{"id":"1","name":"Bob"}], err:nil 611 | --- no_error_log 612 | [error] 613 | 614 | 615 | 616 | === TEST 11: set_keepalive in the wrong state 617 | --- server_config 618 | content_by_lua ' 619 | local ljson = require "ljson" 620 | 621 | local mysql = require "resty.mysql" 622 | local db = mysql:new() 623 | 624 | db:set_timeout(1000) -- 1 sec 625 | 626 | local ok, err, errno, sqlstate = db:connect({ 627 | host = "$TEST_NGINX_MYSQL_HOST", 628 | port = $TEST_NGINX_MYSQL_PORT, 629 | database = "ngx_test", 630 | user = "ngx_test", 631 | password = "ngx_test"}) 632 | 633 | if not ok then 634 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 635 | return 636 | end 637 | 638 | ngx.say("connected to mysql.") 639 | 640 | local res, err, errno, sqlstate = 641 | db:query("drop table if exists cats") 642 | if not res then 643 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 644 | return 645 | end 646 | 647 | ngx.say("table cats dropped.") 648 | 649 | res, err, errno, sqlstate = 650 | db:query("create table cats " 651 | .. "(id serial primary key, name varchar(5))") 652 | if not res then 653 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 654 | return 655 | end 656 | 657 | ngx.say("table cats created.") 658 | 659 | res, err, errno, sqlstate = 660 | db:query("insert into cats (name) " 661 | .. "values (\'Bob\'),(\'\'),(null)") 662 | if not res then 663 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 664 | return 665 | end 666 | 667 | ngx.say(res.affected_rows .. " rows inserted into table cats (last id: ", res.insert_id, ")") 668 | 669 | res, err, errno, sqlstate = 670 | db:query("select * from cats order by id asc; " 671 | .. "select * from cats order by id desc") 672 | if not res then 673 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 674 | return 675 | end 676 | 677 | ngx.say("result: ", ljson.encode(res), ", err:", err) 678 | 679 | local ok, err = db:set_keepalive() 680 | if not ok then 681 | ngx.say("failed to set keepalive: ", err) 682 | return 683 | end 684 | '; 685 | --- response_body 686 | connected to mysql. 687 | table cats dropped. 688 | table cats created. 689 | 3 rows inserted into table cats (last id: 1) 690 | result: [{"id":"1","name":"Bob"},{"id":"2","name":""},{"id":"3","name":null}], err:again 691 | failed to set keepalive: cannot be reused in the current connection state: 2 692 | --- no_error_log 693 | [error] 694 | 695 | 696 | 697 | === TEST 12: set keepalive (tcp) 698 | --- server_config 699 | content_by_lua ' 700 | local ljson = require "ljson" 701 | 702 | local mysql = require "resty.mysql" 703 | local db = mysql:new() 704 | 705 | db:set_timeout(1000) -- 1 sec 706 | 707 | local ok, err, errno, sqlstate = db:connect({ 708 | host = "$TEST_NGINX_MYSQL_HOST", 709 | port = $TEST_NGINX_MYSQL_PORT, 710 | database = "ngx_test", 711 | user = "ngx_test", 712 | password = "ngx_test"}) 713 | 714 | if not ok then 715 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 716 | return 717 | end 718 | 719 | ngx.say("connected to mysql: ", db:get_reused_times()) 720 | 721 | local ok, err = db:set_keepalive() 722 | if not ok then 723 | ngx.say("failed to set keepalive: ", err) 724 | return 725 | end 726 | 727 | ok, err, errno, sqlstate = db:connect({ 728 | host = "$TEST_NGINX_MYSQL_HOST", 729 | port = $TEST_NGINX_MYSQL_PORT, 730 | database = "ngx_test", 731 | user = "ngx_test", 732 | password = "ngx_test"}) 733 | 734 | ngx.say("connected to mysql: ", db:get_reused_times()) 735 | 736 | local res, err, errno, sqlstate = 737 | db:query("select * from cats order by id asc;") 738 | if not res then 739 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 740 | return 741 | end 742 | 743 | ngx.say("result: ", ljson.encode(res), ", err:", err) 744 | 745 | local ok, err = db:set_keepalive() 746 | if not ok then 747 | ngx.say("failed to set keepalive: ", err) 748 | return 749 | end 750 | '; 751 | --- response_body_like chop 752 | ^connected to mysql: [02] 753 | connected to mysql: [13] 754 | result: \[\{"id":"1","name":"Bob"\},\{"id":"2","name":""\},\{"id":"3","name":null\}\], err:nil$ 755 | --- no_error_log 756 | [error] 757 | --- error_log eval 758 | qr/lua tcp socket keepalive create connection pool for key "ngx_test:ngx_test:[^\s:]+:\d+"/ 759 | --- log_level: debug 760 | --- wait: 0.3 761 | 762 | 763 | 764 | === TEST 13: send query w/o result set (unix domain socket) 765 | --- server_config 766 | content_by_lua ' 767 | local mysql = require "resty.mysql" 768 | local db = mysql:new() 769 | 770 | db:set_timeout(1000) -- 1 sec 771 | 772 | local ok, err, errno, sqlstate = db:connect({ 773 | path = "$TEST_NGINX_MYSQL_PATH", 774 | database = "ngx_test", 775 | user = "ngx_test", 776 | password = "ngx_test"}) 777 | 778 | if not ok then 779 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 780 | return 781 | end 782 | 783 | ngx.say("connected to mysql ", db:server_ver(), ".") 784 | 785 | local bytes, err = db:send_query("drop table if exists cats") 786 | if not bytes then 787 | ngx.say("failed to send query: ", err) 788 | end 789 | 790 | ngx.say("sent ", bytes, " bytes.") 791 | 792 | local res, err, errno, sqlstate = db:read_result() 793 | if not res then 794 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 795 | end 796 | 797 | local ljson = require "ljson" 798 | ngx.say("result: ", ljson.encode(res)) 799 | 800 | local ok, err = db:close() 801 | if not ok then 802 | ngx.say("failed to close: ", err) 803 | return 804 | end 805 | '; 806 | --- response_body_like chop 807 | ^connected to mysql \d\.[^\s\x00]+\. 808 | sent 30 bytes\. 809 | result: (?:\{"insert_id":0,"server_status":2,"warning_count":1,"affected_rows":0}|{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\})$ 810 | --- no_error_log 811 | [error] 812 | 813 | 814 | 815 | === TEST 14: null at the beginning of a row 816 | --- server_config 817 | content_by_lua ' 818 | local ljson = require "ljson" 819 | 820 | local mysql = require "resty.mysql" 821 | local db = mysql:new() 822 | 823 | db:set_timeout(1000) -- 1 sec 824 | 825 | local ok, err, errno, sqlstate = db:connect({ 826 | host = "$TEST_NGINX_MYSQL_HOST", 827 | port = $TEST_NGINX_MYSQL_PORT, 828 | database = "ngx_test", 829 | user = "ngx_test", 830 | password = "ngx_test"}) 831 | 832 | if not ok then 833 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 834 | return 835 | end 836 | 837 | ngx.say("connected to mysql.") 838 | 839 | local res 840 | res, err, errno, sqlstate = db:query("drop table if exists cats") 841 | if not res then 842 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 843 | return 844 | end 845 | 846 | ngx.say("table cats dropped.") 847 | 848 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 849 | if not res then 850 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 851 | return 852 | end 853 | 854 | ngx.say("table cats created.") 855 | 856 | res, err, errno, sqlstate = db:query("insert into cats (name) value (\'Bob\'),(\'\'),(null)") 857 | if not res then 858 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 859 | return 860 | end 861 | 862 | ngx.say(res.affected_rows, " rows inserted into table cats (last id: ", res.insert_id, ")") 863 | 864 | res, err, errno, sqlstate = db:query("select name from cats order by id asc") 865 | if not res then 866 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 867 | return 868 | end 869 | 870 | ngx.say("result: ", ljson.encode(res)) 871 | 872 | res, err, errno, sqlstate = db:query("select name from cats order by id desc") 873 | if not res then 874 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 875 | return 876 | end 877 | 878 | ngx.say("result: ", ljson.encode(res)) 879 | 880 | local ok, err = db:close() 881 | if not ok then 882 | ngx.say("failed to close: ", err) 883 | return 884 | end 885 | '; 886 | --- response_body 887 | connected to mysql. 888 | table cats dropped. 889 | table cats created. 890 | 3 rows inserted into table cats (last id: 1) 891 | result: [{"name":"Bob"},{"name":""},{"name":null}] 892 | result: [{"name":null},{"name":""},{"name":"Bob"}] 893 | --- no_error_log 894 | [error] 895 | 896 | 897 | 898 | === TEST 15: set keepalive (uds) 899 | --- server_config 900 | content_by_lua ' 901 | local ljson = require "ljson" 902 | 903 | local mysql = require "resty.mysql" 904 | local db = mysql:new() 905 | 906 | db:set_timeout(1000) -- 1 sec 907 | 908 | local ok, err, errno, sqlstate = db:connect({ 909 | path = "$TEST_NGINX_MYSQL_PATH", 910 | database = "ngx_test", 911 | user = "ngx_test", 912 | password = "ngx_test"}) 913 | 914 | if not ok then 915 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 916 | return 917 | end 918 | 919 | ngx.say("connected to mysql: ", db:get_reused_times()) 920 | 921 | local ok, err = db:set_keepalive() 922 | if not ok then 923 | ngx.say("failed to set keepalive: ", err) 924 | return 925 | end 926 | 927 | ok, err, errno, sqlstate = db:connect({ 928 | path = "$TEST_NGINX_MYSQL_PATH", 929 | database = "ngx_test", 930 | user = "ngx_test", 931 | password = "ngx_test"}) 932 | 933 | ngx.say("connected to mysql: ", db:get_reused_times()) 934 | 935 | local res 936 | res, err, errno, sqlstate = 937 | db:query("select * from cats order by id asc;") 938 | if not res then 939 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 940 | return 941 | end 942 | 943 | ngx.say("result: ", ljson.encode(res), ", err:", err) 944 | 945 | local ok, err = db:set_keepalive() 946 | if not ok then 947 | ngx.say("failed to set keepalive: ", err) 948 | return 949 | end 950 | '; 951 | --- response_body_like chop 952 | ^connected to mysql: [02] 953 | connected to mysql: [13] 954 | result: \[\{"id":"1","name":"Bob"\},\{"id":"2","name":""\},\{"id":"3","name":null\}\], err:nil$ 955 | --- no_error_log 956 | [error] 957 | --- error_log eval 958 | qr/lua tcp socket keepalive create connection pool for key "ngx_test:ngx_test:[^\s:]+"/ 959 | --- log_level: debug 960 | --- wait: 0.3 961 | 962 | 963 | 964 | === TEST 16: set keepalive (explicit pool name) 965 | --- server_config 966 | content_by_lua ' 967 | local ljson = require "ljson" 968 | 969 | local mysql = require "resty.mysql" 970 | local db = mysql:new() 971 | 972 | db:set_timeout(1000) -- 1 sec 973 | 974 | local ok, err, errno, sqlstate = db:connect({ 975 | path = "$TEST_NGINX_MYSQL_PATH", 976 | database = "ngx_test", 977 | user = "ngx_test", 978 | password = "ngx_test", 979 | pool = "my_pool"}) 980 | 981 | if not ok then 982 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 983 | return 984 | end 985 | 986 | ngx.say("connected to mysql: ", db:get_reused_times()) 987 | 988 | local ok, err = db:set_keepalive() 989 | if not ok then 990 | ngx.say("failed to set keepalive: ", err) 991 | return 992 | end 993 | 994 | ok, err, errno, sqlstate = db:connect({ 995 | host = "$TEST_NGINX_MYSQL_HOST", 996 | port = $TEST_NGINX_MYSQL_PORT, 997 | database = "ngx_test", 998 | user = "ngx_test", 999 | password = "ngx_test", 1000 | pool = "my_pool"}) 1001 | 1002 | ngx.say("connected to mysql: ", db:get_reused_times()) 1003 | 1004 | local res 1005 | res, err, errno, sqlstate = 1006 | db:query("select * from cats order by id asc;") 1007 | if not res then 1008 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1009 | return 1010 | end 1011 | 1012 | ngx.say("result: ", ljson.encode(res), ", err:", err) 1013 | 1014 | local ok, err = db:set_keepalive() 1015 | if not ok then 1016 | ngx.say("failed to set keepalive: ", err) 1017 | return 1018 | end 1019 | '; 1020 | --- response_body_like chop 1021 | ^connected to mysql: [02] 1022 | connected to mysql: [13] 1023 | result: \[\{"id":"1","name":"Bob"\},\{"id":"2","name":""\},\{"id":"3","name":null\}\], err:nil$ 1024 | --- no_error_log 1025 | [error] 1026 | --- error_log eval 1027 | qr/lua tcp socket keepalive create connection pool for key "my_pool"/ 1028 | --- log_level: debug 1029 | --- wait: 0.3 1030 | 1031 | 1032 | 1033 | === TEST 17: the mysql newdecimal type 1034 | --- server_config 1035 | content_by_lua ' 1036 | local ljson = require "ljson" 1037 | 1038 | local mysql = require "resty.mysql" 1039 | local db = mysql:new() 1040 | 1041 | db:set_timeout(1000) -- 1 sec 1042 | 1043 | local ok, err, errno, sqlstate = db:connect({ 1044 | path = "$TEST_NGINX_MYSQL_PATH", 1045 | database = "ngx_test", 1046 | user = "ngx_test", 1047 | password = "ngx_test", 1048 | pool = "my_pool"}) 1049 | 1050 | if not ok then 1051 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1052 | return 1053 | end 1054 | 1055 | ngx.say("connected to mysql: ", db:get_reused_times()) 1056 | 1057 | ok, err = db:set_keepalive() 1058 | if not ok then 1059 | ngx.say("failed to set keepalive: ", err) 1060 | return 1061 | end 1062 | 1063 | ok, err, errno, sqlstate = db:connect({ 1064 | host = "$TEST_NGINX_MYSQL_HOST", 1065 | port = $TEST_NGINX_MYSQL_PORT, 1066 | database = "ngx_test", 1067 | user = "ngx_test", 1068 | password = "ngx_test", 1069 | pool = "my_pool"}) 1070 | 1071 | ngx.say("connected to mysql: ", db:get_reused_times()) 1072 | 1073 | local res 1074 | res, err, errno, sqlstate = 1075 | db:query("select sum(id) from cats") 1076 | if not res then 1077 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1078 | return 1079 | end 1080 | 1081 | ngx.say("result: ", ljson.encode(res), ", err:", err) 1082 | 1083 | local ok, err = db:set_keepalive() 1084 | if not ok then 1085 | ngx.say("failed to set keepalive: ", err) 1086 | return 1087 | end 1088 | '; 1089 | --- response_body_like chop 1090 | ^connected to mysql: [02] 1091 | connected to mysql: [13] 1092 | result: \[\{"sum\(id\)":6\}\], err:nil$ 1093 | --- no_error_log 1094 | [error] 1095 | --- error_log eval 1096 | qr/lua tcp socket keepalive create connection pool for key "my_pool"/ 1097 | --- log_level: debug 1098 | --- wait: 0.3 1099 | 1100 | 1101 | 1102 | === TEST 18: large insert_id exceeding a 32-bit integer value 1103 | --- server_config 1104 | content_by_lua ' 1105 | local mysql = require("resty.mysql") 1106 | local create_sql = [[ 1107 | CREATE TABLE `large_t` ( 1108 | `id` bigint(11) NOT NULL AUTO_INCREMENT, 1109 | PRIMARY KEY (`id`) 1110 | ) AUTO_INCREMENT=5000000312; 1111 | ]] 1112 | local drop_sql = [[ 1113 | DROP TABLE IF EXISTS `large_t`; 1114 | ]] 1115 | local insert_sql = [[ 1116 | INSERT INTO `large_t` VALUES(NULL); 1117 | ]] 1118 | local db, err = mysql:new() 1119 | if not db then 1120 | ngx.say("failed to instantiate mysql: ", err) 1121 | return 1122 | end 1123 | db:set_timeout(1000) 1124 | local ok, err = db:connect{ 1125 | host = "$TEST_NGINX_MYSQL_HOST", 1126 | port = $TEST_NGINX_MYSQL_PORT, 1127 | database="ngx_test", 1128 | user="ngx_test", 1129 | password="ngx_test"} 1130 | if not ok then 1131 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1132 | return 1133 | end 1134 | local res, err = db:query(drop_sql) 1135 | if not res then 1136 | ngx.say("drop table error:" .. err) 1137 | return 1138 | end 1139 | local res, err = db:query(create_sql) 1140 | if not res then 1141 | ngx.say("create table error:" .. err) 1142 | return 1143 | end 1144 | local res, err = db:query(insert_sql) 1145 | if not res then 1146 | ngx.say("insert table error:" .. err) 1147 | return 1148 | else 1149 | ngx.say(res.insert_id) 1150 | end 1151 | '; 1152 | --- response_body 1153 | 5000000312 1154 | --- no_error_log 1155 | [error] 1156 | 1157 | 1158 | 1159 | === TEST 19: fix packet number overflow 1160 | --- server_config 1161 | content_by_lua_block { 1162 | local mysql = require "resty.mysql" 1163 | local db = mysql:new() 1164 | 1165 | db:set_timeout(2000) -- 2 sec 1166 | 1167 | local ok, err, errno, sqlstate = db:connect({ 1168 | path = "$TEST_NGINX_MYSQL_PATH", 1169 | database = "ngx_test", 1170 | user = "ngx_test", 1171 | password = "ngx_test", 1172 | pool = "my_pool"}) 1173 | 1174 | if not ok then 1175 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1176 | return 1177 | end 1178 | 1179 | -- generate test data 1180 | local res, err, errno, sqlstate = db:query("drop table if exists cats") 1181 | if not res then 1182 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1183 | return 1184 | end 1185 | 1186 | res, err, errno, sqlstate = db:query("create table cats (id serial primary key, name varchar(5))") 1187 | if not res then 1188 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1189 | return 1190 | end 1191 | 1192 | for i = 1, 260 do 1193 | res, err, errno, sqlstate = db:query("insert into cats(name) values (\'abc\')") 1194 | if not res then 1195 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1196 | return 1197 | end 1198 | end 1199 | 1200 | -- according to the MySQL protocol, make packet number be equal to 255 1201 | -- packet number = header(1) + field(M) + eof(1) + row(N) + eof(1) 1202 | -- the following sql packet number is: 1 + 1 + 1 + 251 + 1 = 255 1203 | local res, err, errno, sqlstate = db:query("select id from cats limit 251") 1204 | db:close() 1205 | 1206 | if not res then 1207 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1208 | return 1209 | end 1210 | 1211 | if #res ~= 251 then 1212 | ngx.say("bad result, not got 251 rows") 1213 | return 1214 | end 1215 | 1216 | ngx.say("success") 1217 | } 1218 | --- response_body 1219 | success 1220 | --- no_error_log 1221 | [error] 1222 | --- timeout: 20 1223 | 1224 | 1225 | 1226 | === TEST 20: 251 columns 1227 | --- server_config 1228 | content_by_lua_block { 1229 | local mysql = require "resty.mysql" 1230 | local sql = require "create_sql_by_columns" 1231 | 1232 | local db = mysql:new() 1233 | 1234 | db:set_timeout(2000) -- 2 sec 1235 | 1236 | local ok, err, errno, sqlstate = db:connect({ 1237 | path = "$TEST_NGINX_MYSQL_PATH", 1238 | database = "ngx_test", 1239 | user = "ngx_test", 1240 | password = "ngx_test"}) 1241 | 1242 | if not ok then 1243 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1244 | return 1245 | end 1246 | 1247 | local columns = 251 1248 | 1249 | -- generate test data 1250 | local res, err, errno, sqlstate = db:query(sql.drop(columns)) 1251 | if not res then 1252 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1253 | return 1254 | end 1255 | 1256 | res, err, errno, sqlstate = db:query(sql.create(columns)) 1257 | if not res then 1258 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1259 | return 1260 | end 1261 | 1262 | res, err, errno, sqlstate = db:query(sql.insert(columns)) 1263 | if not res then 1264 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1265 | return 1266 | end 1267 | 1268 | local res, err, errno, sqlstate = db:query(sql.query(columns)) 1269 | db:close() 1270 | 1271 | if not res then 1272 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1273 | return 1274 | end 1275 | 1276 | if #res ~= 1 then 1277 | ngx.say("bad result, not got 1 rows") 1278 | return 1279 | end 1280 | 1281 | local row = res[1] 1282 | local count = 0 1283 | for _ in pairs(row) do 1284 | count = count + 1 1285 | end 1286 | if count ~= columns then 1287 | ngx.say("bad result, got ", count, " columns") 1288 | return 1289 | end 1290 | 1291 | ngx.say("success") 1292 | } 1293 | --- response_body 1294 | success 1295 | --- no_error_log 1296 | [error] 1297 | --- timeout: 20 1298 | 1299 | 1300 | 1301 | === TEST 21: MySQL has hard limit of 4096 columns per table 1302 | --- server_config 1303 | content_by_lua_block { 1304 | local mysql = require "resty.mysql" 1305 | local sql = require "create_sql_by_columns" 1306 | 1307 | local db = mysql:new() 1308 | 1309 | db:set_timeout(2000) -- 2 sec 1310 | 1311 | local ok, err, errno, sqlstate = db:connect({ 1312 | path = "$TEST_NGINX_MYSQL_PATH", 1313 | database = "ngx_test", 1314 | user = "ngx_test", 1315 | password = "ngx_test"}) 1316 | 1317 | if not ok then 1318 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1319 | return 1320 | end 1321 | 1322 | local columns = 4097 1323 | 1324 | -- generate test data 1325 | local res, err, errno, sqlstate = db:query(sql.drop(columns)) 1326 | if not res then 1327 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1328 | return 1329 | end 1330 | 1331 | res, err, errno, sqlstate = db:query(sql.create(columns)) 1332 | if not res then 1333 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1334 | return 1335 | end 1336 | 1337 | res, err, errno, sqlstate = db:query(sql.insert(columns)) 1338 | if not res then 1339 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1340 | return 1341 | end 1342 | 1343 | local res, err, errno, sqlstate = db:query(sql.query(columns)) 1344 | db:close() 1345 | 1346 | if not res then 1347 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1348 | return 1349 | end 1350 | 1351 | if #res ~= 1 then 1352 | ngx.say("bad result, not got 1 rows") 1353 | return 1354 | end 1355 | 1356 | local row = res[1] 1357 | local count = 0 1358 | for _ in pairs(row) do 1359 | count = count + 1 1360 | end 1361 | if count ~= columns then 1362 | ngx.say("bad result, got ", count, " columns") 1363 | return 1364 | end 1365 | 1366 | ngx.say("success") 1367 | } 1368 | --- response_body_like 1369 | bad result: .*Too many columns.*. 1370 | 1371 | --- no_error_log 1372 | [error] 1373 | --- timeout: 20 1374 | 1375 | 1376 | 1377 | === TEST 22: InnoDB has a limit of 1017 columns per table 1378 | --- server_config 1379 | content_by_lua_block { 1380 | local mysql = require "resty.mysql" 1381 | local sql = require "create_sql_by_columns" 1382 | 1383 | local db = mysql:new() 1384 | 1385 | db:set_timeout(2000) -- 2 sec 1386 | 1387 | local ok, err, errno, sqlstate = db:connect({ 1388 | path = "$TEST_NGINX_MYSQL_PATH", 1389 | database = "ngx_test", 1390 | user = "ngx_test", 1391 | password = "ngx_test"}) 1392 | 1393 | if not ok then 1394 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1395 | return 1396 | end 1397 | 1398 | local columns = 1018 1399 | 1400 | -- generate test data 1401 | local res, err, errno, sqlstate = db:query(sql.drop(columns)) 1402 | if not res then 1403 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1404 | return 1405 | end 1406 | 1407 | res, err, errno, sqlstate = db:query(sql.create(columns)) 1408 | if not res then 1409 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1410 | return 1411 | end 1412 | 1413 | res, err, errno, sqlstate = db:query(sql.insert(columns)) 1414 | if not res then 1415 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1416 | return 1417 | end 1418 | 1419 | local res, err, errno, sqlstate = db:query(sql.query(columns)) 1420 | db:close() 1421 | 1422 | if not res then 1423 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1424 | return 1425 | end 1426 | 1427 | if #res ~= 1 then 1428 | ngx.say("bad result, not got 1 rows") 1429 | return 1430 | end 1431 | 1432 | local row = res[1] 1433 | local count = 0 1434 | for _ in pairs(row) do 1435 | count = count + 1 1436 | end 1437 | if count ~= columns then 1438 | ngx.say("bad result, got ", count, " columns") 1439 | return 1440 | end 1441 | 1442 | ngx.say("success") 1443 | } 1444 | --- response_body_like chomp 1445 | bad result: .*?(?:Too many columns|Can't create table 'ngx_test\.test1018' \(errno: 139\)) 1446 | 1447 | --- no_error_log 1448 | [error] 1449 | --- timeout: 20 1450 | 1451 | 1452 | 1453 | === TEST 23: MyISAM columns per table > 1017 1454 | --- server_config 1455 | content_by_lua_block { 1456 | local mysql = require "resty.mysql" 1457 | local sql = require "create_sql_by_columns" 1458 | 1459 | local db = mysql:new() 1460 | 1461 | db:set_timeout(2000) -- 2 sec 1462 | 1463 | local ok, err, errno, sqlstate = db:connect({ 1464 | path = "$TEST_NGINX_MYSQL_PATH", 1465 | database = "ngx_test", 1466 | user = "ngx_test", 1467 | password = "ngx_test"}) 1468 | 1469 | if not ok then 1470 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1471 | return 1472 | end 1473 | 1474 | local columns = 1018 1475 | 1476 | -- generate test data 1477 | local res, err, errno, sqlstate = db:query(sql.drop(columns)) 1478 | if not res then 1479 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1480 | return 1481 | end 1482 | 1483 | res, err, errno, sqlstate = db:query(sql.create(columns, "MyISAM")) 1484 | if not res then 1485 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1486 | return 1487 | end 1488 | 1489 | res, err, errno, sqlstate = db:query(sql.insert(columns)) 1490 | if not res then 1491 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1492 | return 1493 | end 1494 | 1495 | local res, err, errno, sqlstate = db:query(sql.query(columns)) 1496 | db:close() 1497 | 1498 | if not res then 1499 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 1500 | return 1501 | end 1502 | 1503 | if #res ~= 1 then 1504 | ngx.say("bad result, not got 1 rows") 1505 | return 1506 | end 1507 | 1508 | local row = res[1] 1509 | local count = 0 1510 | for _ in pairs(row) do 1511 | count = count + 1 1512 | end 1513 | if count ~= columns then 1514 | ngx.say("bad result, got ", count, " columns") 1515 | return 1516 | end 1517 | 1518 | ngx.say("success") 1519 | } 1520 | --- response_body 1521 | success 1522 | --- no_error_log 1523 | [error] 1524 | --- timeout: 20 1525 | 1526 | 1527 | 1528 | === TEST 24: connected with no error when pool opts are provided 1529 | --- server_config 1530 | content_by_lua ' 1531 | local mysql = require "resty.mysql" 1532 | local db = mysql:new() 1533 | 1534 | db:set_timeout(1000) -- 1 sec 1535 | 1536 | local ok, err, errno, sqlstate = db:connect({ 1537 | host = "$TEST_NGINX_MYSQL_HOST", 1538 | port = $TEST_NGINX_MYSQL_PORT, 1539 | database = "ngx_test", 1540 | user = "ngx_test", 1541 | password = "ngx_test", 1542 | pool = "my_pool", 1543 | pool_size = 10, 1544 | backlog = 5 1545 | }) 1546 | 1547 | if not ok then 1548 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1549 | return 1550 | end 1551 | 1552 | ngx.say("connected to mysql ", db:server_ver()) 1553 | 1554 | db:close() 1555 | '; 1556 | --- response_body_like 1557 | connected to mysql \d\.[^\s\x00]+ 1558 | --- no_error_log 1559 | [error] 1560 | 1561 | 1562 | 1563 | === TEST 25: set pool_size option no backlog option 1564 | --- server_config 1565 | content_by_lua_block { 1566 | local mysql = require "resty.mysql" 1567 | 1568 | local function mysql_conn() 1569 | local db = mysql:new() 1570 | db:set_timeout(1000) -- 1 sec 1571 | 1572 | local ok, err, errno, sqlstate = db:connect({ 1573 | host = "$TEST_NGINX_MYSQL_HOST", 1574 | port = $TEST_NGINX_MYSQL_PORT, 1575 | database = "ngx_test", 1576 | user = "ngx_test", 1577 | password = "ngx_test", 1578 | pool = "my_pool", 1579 | pool_size = 5 1580 | }) 1581 | if not ok then 1582 | return err 1583 | end 1584 | return nil 1585 | end 1586 | 1587 | for i = 1,10 do 1588 | local err = mysql_conn() 1589 | if err ~= nil then 1590 | ngx.say(err) 1591 | return 1592 | end 1593 | end 1594 | ngx.say("connected to mysql") 1595 | } 1596 | --- response_body_like 1597 | connected to mysql 1598 | --- no_error_log 1599 | [error] 1600 | 1601 | 1602 | 1603 | === TEST 26: set the pool_size and backlog options at the same time 1604 | --- server_config 1605 | content_by_lua_block { 1606 | local mysql = require "resty.mysql" 1607 | 1608 | local function mysql_conn() 1609 | local db = mysql:new() 1610 | db:set_timeout(1000) -- 1 sec 1611 | 1612 | local ok, err, errno, sqlstate = db:connect({ 1613 | host = "$TEST_NGINX_MYSQL_HOST", 1614 | port = $TEST_NGINX_MYSQL_PORT, 1615 | database = "ngx_test", 1616 | user = "ngx_test", 1617 | password = "ngx_test", 1618 | pool = "my_pool", 1619 | pool_size = 5, 1620 | backlog = 5 1621 | }) 1622 | if not ok then 1623 | ngx.say(err) 1624 | return err 1625 | end 1626 | return nil 1627 | end 1628 | for i = 1,15 do 1629 | ngx.thread.spawn(mysql_conn) 1630 | end 1631 | } 1632 | --- response_body_like 1633 | failed to connect: too many waiting connect operations 1634 | failed to connect: too many waiting connect operations 1635 | failed to connect: too many waiting connect operations 1636 | failed to connect: too many waiting connect operations 1637 | failed to connect: too many waiting connect operations 1638 | failed to connect: timeout 1639 | failed to connect: timeout 1640 | failed to connect: timeout 1641 | failed to connect: timeout 1642 | failed to connect: timeout 1643 | --- error_log 1644 | lua tcp socket queued connect timed out 1645 | 1646 | 1647 | 1648 | === TEST 27: large insert_id exceeding a 32-bit signed integer value 1649 | --- http_config eval: $::HttpConfig 1650 | --- server_config 1651 | content_by_lua_block { 1652 | local mysql = require("resty.mysql") 1653 | local create_sql = [[ 1654 | CREATE TABLE `large_t` ( 1655 | `id` bigint(11) NOT NULL AUTO_INCREMENT, 1656 | PRIMARY KEY (`id`) 1657 | ) AUTO_INCREMENT= 1658 | ]] .. (math.pow(2, 32) - 1) 1659 | local drop_sql = [[ 1660 | DROP TABLE IF EXISTS `large_t`; 1661 | ]] 1662 | local insert_sql = [[ 1663 | INSERT INTO `large_t` VALUES(NULL); 1664 | ]] 1665 | local db, err = mysql:new() 1666 | if not db then 1667 | ngx.say("failed to instantiate mysql: ", err) 1668 | return 1669 | end 1670 | db:set_timeout(1000) 1671 | local ok, err = db:connect{ 1672 | host = "$TEST_NGINX_MYSQL_HOST", 1673 | port = $TEST_NGINX_MYSQL_PORT, 1674 | database="ngx_test", 1675 | user="ngx_test", 1676 | password="ngx_test"} 1677 | if not ok then 1678 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 1679 | return 1680 | end 1681 | local res, err = db:query(drop_sql) 1682 | if not res then 1683 | ngx.say("drop table error:" .. err) 1684 | return 1685 | end 1686 | local res, err = db:query(create_sql) 1687 | if not res then 1688 | ngx.say("create table error:" .. err) 1689 | return 1690 | end 1691 | local res, err = db:query(insert_sql) 1692 | if not res then 1693 | ngx.say("insert table error:" .. err) 1694 | return 1695 | else 1696 | ngx.say(res.insert_id) 1697 | end 1698 | } 1699 | --- response_body 1700 | 4294967295 1701 | --- no_error_log 1702 | [error] 1703 | -------------------------------------------------------------------------------- /t/ssl.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | #log_level 'warn'; 10 | 11 | no_long_string(); 12 | no_shuffle(); 13 | check_accum_error_log(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: send query w/o result set 20 | --- server_config 21 | content_by_lua ' 22 | local mysql = require "resty.mysql" 23 | local db = mysql:new() 24 | 25 | db:set_timeout(4000) -- 4 sec 26 | 27 | local ok, err, errno, sqlstate = db:connect({ 28 | host = "$TEST_NGINX_MYSQL_HOST", 29 | port = $TEST_NGINX_MYSQL_PORT, 30 | database = "ngx_test", 31 | user = "ngx_test", 32 | password = "ngx_test", 33 | ssl = true, 34 | }) 35 | 36 | if not ok then 37 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 38 | return 39 | end 40 | 41 | ngx.say("connected to mysql ", db:server_ver(), ".") 42 | 43 | local bytes, err = db:send_query("drop table if exists cats") 44 | if not bytes then 45 | ngx.say("failed to send query: ", err) 46 | end 47 | 48 | ngx.say("sent ", bytes, " bytes.") 49 | 50 | local res, err, errno, sqlstate = db:read_result() 51 | if not res then 52 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 53 | end 54 | 55 | local ljson = require "ljson" 56 | ngx.say("result: ", ljson.encode(res)) 57 | 58 | local ok, err = db:close() 59 | if not ok then 60 | ngx.say("failed to close: ", err) 61 | return 62 | end 63 | '; 64 | --- response_body_like chop 65 | ^connected to mysql \d\.[^\s\x00]+\. 66 | sent 30 bytes\. 67 | result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$ 68 | --- no_error_log 69 | [error] 70 | --- timeout: 5 71 | 72 | 73 | 74 | === TEST 2: send query w/o result set (verify) 75 | --- server_config 76 | lua_ssl_trusted_certificate ../../data/test.crt; # assuming used by the MySQL server 77 | content_by_lua ' 78 | local mysql = require "resty.mysql" 79 | local db = mysql:new() 80 | 81 | db:set_timeout(4000) -- 4 sec 82 | 83 | local ok, err, errno, sqlstate = db:connect({ 84 | host = "$TEST_NGINX_MYSQL_HOST", 85 | port = $TEST_NGINX_MYSQL_PORT, 86 | database = "ngx_test", 87 | user = "ngx_test", 88 | password = "ngx_test", 89 | ssl = true, 90 | ssl_verify = true, 91 | }) 92 | 93 | if not ok then 94 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 95 | return 96 | end 97 | 98 | ngx.say("connected to mysql ", db:server_ver(), ".") 99 | 100 | local bytes, err = db:send_query("drop table if exists cats") 101 | if not bytes then 102 | ngx.say("failed to send query: ", err) 103 | end 104 | 105 | ngx.say("sent ", bytes, " bytes.") 106 | 107 | local res, err, errno, sqlstate = db:read_result() 108 | if not res then 109 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 110 | end 111 | 112 | local ljson = require "ljson" 113 | ngx.say("result: ", ljson.encode(res)) 114 | 115 | local ok, err = db:close() 116 | if not ok then 117 | ngx.say("failed to close: ", err) 118 | return 119 | end 120 | '; 121 | --- response_body_like chop 122 | ^connected to mysql \d\.[^\s\x00]+\. 123 | sent 30 bytes\. 124 | result: \{"affected_rows":0,"insert_id":0,"server_status":2,"warning_count":[01]\}$ 125 | --- no_error_log 126 | [error] 127 | --- timeout: 5 128 | 129 | 130 | 131 | === TEST 3: send query w/o result set (verify, failed) 132 | --- server_config 133 | content_by_lua ' 134 | local mysql = require "resty.mysql" 135 | local db = mysql:new() 136 | 137 | db:set_timeout(4000) -- 4 sec 138 | 139 | local ok, err, errno, sqlstate = db:connect({ 140 | host = "$TEST_NGINX_MYSQL_HOST", 141 | port = $TEST_NGINX_MYSQL_PORT, 142 | database = "ngx_test", 143 | user = "ngx_test", 144 | password = "ngx_test", 145 | ssl = true, 146 | ssl_verify = true, 147 | }) 148 | 149 | if not ok then 150 | ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate) 151 | return 152 | end 153 | 154 | ngx.say("connected to mysql ", db:server_ver(), ".") 155 | 156 | local bytes, err = db:send_query("drop table if exists cats") 157 | if not bytes then 158 | ngx.say("failed to send query: ", err) 159 | end 160 | 161 | ngx.say("sent ", bytes, " bytes.") 162 | 163 | local res, err, errno, sqlstate = db:read_result() 164 | if not res then 165 | ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".") 166 | end 167 | 168 | local ljson = require "ljson" 169 | ngx.say("result: ", ljson.encode(res)) 170 | 171 | local ok, err = db:close() 172 | if not ok then 173 | ngx.say("failed to close: ", err) 174 | return 175 | end 176 | '; 177 | --- response_body 178 | failed to connect: failed to do ssl handshake: 18: self signed certificate: nil nil 179 | --- error_log 180 | lua ssl certificate verify error: (18: self signed certificate) 181 | --- timeout: 5 182 | -------------------------------------------------------------------------------- /t/version.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | no_long_string(); 10 | #no_diff(); 11 | 12 | run_tests(); 13 | 14 | __DATA__ 15 | 16 | === TEST 1: basic 17 | --- server_config 18 | content_by_lua ' 19 | local mysql = require "resty.mysql" 20 | ngx.say(mysql._VERSION) 21 | '; 22 | --- response_body_like chop 23 | ^\d+\.\d+$ 24 | --- no_error_log 25 | [error] 26 | -------------------------------------------------------------------------------- /t/world.t: -------------------------------------------------------------------------------- 1 | # vim:set ft= ts=4 sw=4 et: 2 | 3 | use t::Test; 4 | 5 | repeat_each(2); 6 | 7 | plan tests => repeat_each() * (3 * blocks()); 8 | 9 | #log_level 'warn'; 10 | 11 | no_long_string(); 12 | no_shuffle(); 13 | check_accum_error_log(); 14 | 15 | run_tests(); 16 | 17 | __DATA__ 18 | 19 | === TEST 1: test an old bug in table.new() on i386 in luajit v2.1 20 | --- server_config 21 | access_log off; 22 | content_by_lua ' 23 | -- jit.off() 24 | local mysql = require "resty.mysql" 25 | local db = mysql:new() 26 | 27 | local ok, err, errno, sqlstate = db:connect({ 28 | host = "$TEST_NGINX_MYSQL_HOST", 29 | port = $TEST_NGINX_MYSQL_PORT, 30 | database = "world", 31 | user = "ngx_test", 32 | password = "ngx_test"}) 33 | 34 | if not ok then 35 | ngx.log(ngx.ERR, "failed to connect: ", err) 36 | return ngx.exit(500) 37 | end 38 | 39 | local res, err, errno, sqlstate 40 | for j = 1, 10 do 41 | res, err, errno, sqlstate = db:query("select * from city order by ID limit 50", 50) 42 | if not res then 43 | ngx.log(ngx.ERR, "bad result #1: ", err, ": ", errno, ": ", sqlstate, ".") 44 | return ngx.exit(500) 45 | end 46 | end 47 | 48 | for _, row in ipairs(res) do 49 | local ncols = 0 50 | for k, v in pairs(row) do 51 | ncols = ncols + 1 52 | end 53 | ngx.say("ncols: ", ncols) 54 | end 55 | 56 | local ok, err = db:set_keepalive(10000, 50) 57 | if not ok then 58 | ngx.log(ngx.ERR, "failed to set keepalive: ", err) 59 | ngx.exit(500) 60 | end 61 | '; 62 | --- response_body eval 63 | "ncols: 5\n" x 50 64 | --- no_error_log 65 | [error] 66 | -------------------------------------------------------------------------------- /valgrind.suppress: -------------------------------------------------------------------------------- 1 | { 2 | 3 | Memcheck:Param 4 | write(buf) 5 | fun:__write_nocancel 6 | fun:ngx_log_error_core 7 | fun:ngx_resolver_read_response 8 | } 9 | { 10 | 11 | Memcheck:Cond 12 | fun:ngx_sprintf_num 13 | fun:ngx_vslprintf 14 | fun:ngx_log_error_core 15 | fun:ngx_resolver_read_response 16 | fun:ngx_epoll_process_events 17 | fun:ngx_process_events_and_timers 18 | fun:ngx_single_process_cycle 19 | fun:main 20 | } 21 | { 22 | 23 | Memcheck:Addr1 24 | fun:ngx_vslprintf 25 | fun:ngx_snprintf 26 | fun:ngx_sock_ntop 27 | fun:ngx_event_accept 28 | } 29 | { 30 | 31 | Memcheck:Param 32 | write(buf) 33 | fun:__write_nocancel 34 | fun:ngx_log_error_core 35 | fun:ngx_resolver_read_response 36 | fun:ngx_event_process_posted 37 | fun:ngx_process_events_and_timers 38 | fun:ngx_single_process_cycle 39 | fun:main 40 | } 41 | { 42 | 43 | Memcheck:Cond 44 | fun:ngx_sprintf_num 45 | fun:ngx_vslprintf 46 | fun:ngx_log_error_core 47 | fun:ngx_resolver_read_response 48 | fun:ngx_event_process_posted 49 | fun:ngx_process_events_and_timers 50 | fun:ngx_single_process_cycle 51 | fun:main 52 | } 53 | { 54 | 55 | Memcheck:Leak 56 | fun:malloc 57 | fun:ngx_alloc 58 | obj:* 59 | } 60 | { 61 | 62 | exp-sgcheck:SorG 63 | fun:ngx_http_lua_ndk_set_var_get 64 | } 65 | { 66 | 67 | exp-sgcheck:SorG 68 | fun:ngx_http_variables_init_vars 69 | fun:ngx_http_block 70 | } 71 | { 72 | 73 | exp-sgcheck:SorG 74 | fun:ngx_conf_parse 75 | } 76 | { 77 | 78 | exp-sgcheck:SorG 79 | fun:ngx_vslprintf 80 | fun:ngx_log_error_core 81 | } 82 | { 83 | 84 | Memcheck:Leak 85 | fun:malloc 86 | fun:ngx_alloc 87 | fun:ngx_calloc 88 | fun:ngx_event_process_init 89 | } 90 | { 91 | 92 | Memcheck:Param 93 | epoll_ctl(event) 94 | fun:epoll_ctl 95 | } 96 | { 97 | 98 | Memcheck:Leak 99 | fun:malloc 100 | fun:ngx_alloc 101 | fun:ngx_event_process_init 102 | } 103 | { 104 | 105 | Memcheck:Cond 106 | fun:ngx_conf_flush_files 107 | fun:ngx_single_process_cycle 108 | } 109 | { 110 | 111 | Memcheck:Cond 112 | fun:memcpy 113 | fun:ngx_vslprintf 114 | fun:ngx_log_error_core 115 | fun:ngx_http_charset_header_filter 116 | } 117 | { 118 | 119 | Memcheck:Param 120 | socketcall.setsockopt(optval) 121 | fun:setsockopt 122 | fun:drizzle_state_connect 123 | } 124 | { 125 | 126 | Memcheck:Leak 127 | fun:malloc 128 | fun:ngx_alloc 129 | fun:ngx_pool_cleanup_add 130 | } 131 | { 132 | 133 | Memcheck:Cond 134 | fun:ngx_conf_flush_files 135 | fun:ngx_single_process_cycle 136 | fun:main 137 | } 138 | { 139 | 140 | Memcheck:Leak 141 | fun:malloc 142 | fun:ngx_alloc 143 | fun:ngx_palloc_large 144 | fun:ngx_palloc 145 | fun:ngx_array_push 146 | fun:ngx_http_get_variable_index 147 | fun:ngx_http_memc_add_variable 148 | fun:ngx_http_memc_init 149 | fun:ngx_http_block 150 | fun:ngx_conf_parse 151 | fun:ngx_init_cycle 152 | fun:main 153 | } 154 | { 155 | 156 | Memcheck:Leak 157 | fun:malloc 158 | fun:ngx_alloc 159 | fun:ngx_event_process_init 160 | fun:ngx_single_process_cycle 161 | fun:main 162 | } 163 | { 164 | 165 | Memcheck:Leak 166 | fun:malloc 167 | fun:ngx_alloc 168 | fun:ngx_crc32_table_init 169 | fun:main 170 | } 171 | { 172 | 173 | Memcheck:Leak 174 | fun:malloc 175 | fun:ngx_alloc 176 | fun:ngx_event_process_init 177 | fun:ngx_worker_process_init 178 | fun:ngx_worker_process_cycle 179 | fun:ngx_spawn_process 180 | fun:ngx_start_worker_processes 181 | fun:ngx_master_process_cycle 182 | fun:main 183 | } 184 | { 185 | 186 | Memcheck:Leak 187 | fun:malloc 188 | fun:ngx_alloc 189 | fun:ngx_palloc_large 190 | fun:ngx_palloc 191 | fun:ngx_pcalloc 192 | fun:ngx_hash_init 193 | fun:ngx_http_variables_init_vars 194 | fun:ngx_http_block 195 | fun:ngx_conf_parse 196 | fun:ngx_init_cycle 197 | fun:main 198 | } 199 | { 200 | 201 | Memcheck:Leak 202 | fun:malloc 203 | fun:ngx_alloc 204 | fun:ngx_palloc_large 205 | fun:ngx_palloc 206 | fun:ngx_pcalloc 207 | fun:ngx_http_upstream_drizzle_create_srv_conf 208 | fun:ngx_http_upstream 209 | fun:ngx_conf_parse 210 | fun:ngx_http_block 211 | fun:ngx_conf_parse 212 | fun:ngx_init_cycle 213 | fun:main 214 | } 215 | { 216 | 217 | Memcheck:Leak 218 | fun:malloc 219 | fun:ngx_alloc 220 | fun:ngx_palloc_large 221 | fun:ngx_palloc 222 | fun:ngx_pcalloc 223 | fun:ngx_hash_keys_array_init 224 | fun:ngx_http_variables_add_core_vars 225 | fun:ngx_http_core_preconfiguration 226 | fun:ngx_http_block 227 | fun:ngx_conf_parse 228 | fun:ngx_init_cycle 229 | fun:main 230 | } 231 | { 232 | 233 | Memcheck:Leak 234 | fun:malloc 235 | fun:ngx_alloc 236 | fun:ngx_palloc_large 237 | fun:ngx_palloc 238 | fun:ngx_array_push 239 | fun:ngx_hash_add_key 240 | fun:ngx_http_add_variable 241 | fun:ngx_http_echo_add_variables 242 | fun:ngx_http_echo_handler_init 243 | fun:ngx_http_block 244 | fun:ngx_conf_parse 245 | fun:ngx_init_cycle 246 | } 247 | { 248 | 249 | Memcheck:Leak 250 | fun:malloc 251 | fun:ngx_alloc 252 | fun:ngx_palloc_large 253 | fun:ngx_palloc 254 | fun:ngx_pcalloc 255 | fun:ngx_http_upstream_drizzle_create_srv_conf 256 | fun:ngx_http_core_server 257 | fun:ngx_conf_parse 258 | fun:ngx_http_block 259 | fun:ngx_conf_parse 260 | fun:ngx_init_cycle 261 | fun:main 262 | } 263 | { 264 | 265 | Memcheck:Leak 266 | fun:malloc 267 | fun:ngx_alloc 268 | fun:ngx_palloc_large 269 | fun:ngx_palloc 270 | fun:ngx_pcalloc 271 | fun:ngx_http_upstream_drizzle_create_srv_conf 272 | fun:ngx_http_block 273 | fun:ngx_conf_parse 274 | fun:ngx_init_cycle 275 | fun:main 276 | } 277 | { 278 | 279 | Memcheck:Leak 280 | fun:malloc 281 | fun:ngx_alloc 282 | fun:ngx_palloc_large 283 | fun:ngx_palloc 284 | fun:ngx_array_push 285 | fun:ngx_hash_add_key 286 | fun:ngx_http_variables_add_core_vars 287 | fun:ngx_http_core_preconfiguration 288 | fun:ngx_http_block 289 | fun:ngx_conf_parse 290 | fun:ngx_init_cycle 291 | fun:main 292 | } 293 | { 294 | 295 | Memcheck:Leak 296 | fun:malloc 297 | fun:ngx_alloc 298 | fun:ngx_palloc_large 299 | fun:ngx_palloc 300 | fun:ngx_pcalloc 301 | fun:ngx_init_cycle 302 | fun:main 303 | } 304 | { 305 | 306 | Memcheck:Leak 307 | fun:malloc 308 | fun:ngx_alloc 309 | fun:ngx_palloc_large 310 | fun:ngx_palloc 311 | fun:ngx_hash_init 312 | fun:ngx_http_upstream_init_main_conf 313 | fun:ngx_http_block 314 | fun:ngx_conf_parse 315 | fun:ngx_init_cycle 316 | fun:main 317 | } 318 | { 319 | 320 | Memcheck:Leak 321 | fun:malloc 322 | fun:ngx_alloc 323 | fun:ngx_palloc_large 324 | fun:ngx_palloc 325 | fun:ngx_pcalloc 326 | fun:ngx_http_drizzle_keepalive_init 327 | fun:ngx_http_upstream_drizzle_init 328 | fun:ngx_http_upstream_init_main_conf 329 | fun:ngx_http_block 330 | fun:ngx_conf_parse 331 | fun:ngx_init_cycle 332 | fun:main 333 | } 334 | { 335 | 336 | Memcheck:Leak 337 | fun:malloc 338 | fun:ngx_alloc 339 | fun:ngx_palloc_large 340 | fun:ngx_palloc 341 | fun:ngx_hash_init 342 | fun:ngx_http_variables_init_vars 343 | fun:ngx_http_block 344 | fun:ngx_conf_parse 345 | fun:ngx_init_cycle 346 | fun:main 347 | } 348 | { 349 | 350 | Memcheck:Cond 351 | fun:index 352 | fun:expand_dynamic_string_token 353 | fun:_dl_map_object 354 | fun:map_doit 355 | fun:_dl_catch_error 356 | fun:do_preload 357 | fun:dl_main 358 | fun:_dl_sysdep_start 359 | fun:_dl_start 360 | } 361 | { 362 | 363 | Memcheck:Leak 364 | match-leak-kinds: definite 365 | fun:malloc 366 | fun:ngx_alloc 367 | fun:ngx_set_environment 368 | fun:ngx_single_process_cycle 369 | } 370 | { 371 | 372 | Memcheck:Leak 373 | match-leak-kinds: definite 374 | fun:malloc 375 | fun:ngx_alloc 376 | fun:ngx_set_environment 377 | fun:ngx_worker_process_init 378 | fun:ngx_worker_process_cycle 379 | } 380 | { 381 | 382 | Memcheck:Leak 383 | match-leak-kinds: definite 384 | fun:malloc 385 | fun:ngx_alloc 386 | fun:ngx_event_process_init 387 | fun:ngx_worker_process_init 388 | } 389 | --------------------------------------------------------------------------------