├── LICENSE ├── Makefile ├── README.md ├── host.c ├── luamysql-demo.lua └── luamysql.c /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Guoyu Ou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | 3 | ifeq ($(debug), y) 4 | CFLAGS := -g 5 | else 6 | CFLAGS := -O2 -DNDEBUG 7 | endif 8 | 9 | CFLAGS := $(CFLAGS) -Wl,-E -Wall -Werror -fPIC 10 | 11 | LUADIR := $(HOME)/workspace/lua 12 | 13 | INCLUDE := -I$(LUADIR)/src 14 | LIBS := -L$(LUADIR)/src -llua -lmysqlclient -lm -ldl 15 | 16 | TARGET := luamysql luamysql.so 17 | 18 | .PHONY: all clean deps 19 | 20 | all: deps $(TARGET) 21 | 22 | luamysql: luamysql.o host.o 23 | $(CC) $(CFLAGS) $^ -o $@ $(LIBS) 24 | 25 | luamysql.so: luamysql.o 26 | $(CC) $(CFLAGS) $^ -shared -o $@ $(LIBS) 27 | 28 | deps: 29 | $(MAKE) posix MYCFLAGS="-fPIC -DLUA_USE_DLOPEN -Wl,-E" MYLIBS="-ldl" -C $(LUADIR) 30 | 31 | .c.o: 32 | $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ 33 | 34 | clean: 35 | rm -f $(TARGET) *.o 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `lua-mysql` is a MySQL client library for Lua. It is compatible with Lua 5.2.3(or above) and based on the [MySQL C API](http://dev.mysql.com/doc/refman/5.6/en/c-api-function-overview.html) provided by the official MySQL library. 2 | 3 | Lua APIs 4 | ======== 5 | 6 | First you need to use `mysql = require('luamysql')` to import a table named `mysql`(or any other valid name). 7 | 8 | * `client, errmsg = mysql.newclient(dbarg)` 9 | 10 | - Attempts to establish a connection to a MySQL server specified by `dbarg`. If successfully executed, a valid MySQL client `client`, plus a `nil` for `errmsg`, are returned; otherwise `client` will be `nil` and an error message `errmsg` is returned. See `luamysql-demo.lua` for more details about `dbarg`. 11 | 12 | * `errmsg = client:ping()` 13 | 14 | - Checks whether the connection to the server is working. If the connection has gone down and auto-reconnect is enabled an attempt to reconnect is made. If the connection is down and auto-reconnect is disabled, an error message `errmsg` is returned. 15 | 16 | * `errmsg = client:selectdb(dbname)` 17 | 18 | - Causes the database specified by `dbname` to become the default (current) database of the `client`. `nil` is returned if successfully executed, otherwise an error message `errmsg` is returned. 19 | 20 | * `errmsg = client:setcharset(charset)` 21 | 22 | - Sets the default character set to be `charset` for the current connection. `nil` is returned if successfully executed, otherwise an error message `errmsg` is returned. 23 | 24 | * `result, errmsg = client:escape(str)` 25 | 26 | - Converts the `str` to a legal SQL string that can be used in a SQL statement. If successfully executed, a `result` containing a valid string, and a `nil` for `errmsg`, are returned; otherwise the `result` will be `nil`, and an error message `errmsg` is returned. 27 | 28 | * `result, errmsg = client:execute(sqlstr)` 29 | 30 | - Executes a SQL statement `sqlstr`. If successfully executed, a `result` containing all information, and a `nil` for `errmsg`, are returned; otherwise the `result` will be `nil`, and the error message `errmsg` tells what happened. 31 | 32 | * `size = result:size()` 33 | 34 | - Returns the number of record(s) in the `result`. `nil` is returned if error occurs. 35 | 36 | * `fieldnamelist = result:fieldnamelist()` 37 | 38 | - Returns the fieldname list for the `result`. `nil` is returned if error occurs. 39 | 40 | * `result:recordlist()` 41 | 42 | - Returns an iteration closure function which can be used in the `for ... in` form. `nil` is returned if error occurs. Each record returned by the iterator function is an array containing values corresponding to the fieldname list which is the result of `result:fieldnamelist()`. 43 | 44 | See `luamysql-demo.lua` for more details. 45 | 46 | C Library 47 | ========= 48 | 49 | `lua-mysql` can also be integrated with C/C++ programs for executing Lua scripts. See `host.c` for how to import it into C/C++ environment. 50 | 51 | FAQ 52 | === 53 | 54 | * If you try to run `luamysql-demo.lua` with the following command: 55 | 56 | > $ lua luamysql-demo.lua 57 | 58 | but encounter an error message like this: 59 | 60 | > lua dynamic libraries not enabled; check your Lua installation 61 | 62 | which means you need to re-compile Lua with extra arguments to enable loading dynamic libraries. For example, in Linux systems: 63 | 64 | > $ make posix MYCFLAGS="-DLUA_USE_DLOPEN -fPIC" MYLIBS=-ldl 65 | 66 | * If there is an error message like this: 67 | 68 | > multiple Lua VMs detected 69 | 70 | you may re-compile the Lua interpreter with option `-Wl,-E`. 71 | 72 | Enjoy it! 73 | -------------------------------------------------------------------------------- /host.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern int luaopen_luamysql(lua_State* l); 6 | 7 | int main(int argc, char* argv[]) 8 | { 9 | lua_State* l; 10 | 11 | if (argc != 2) { 12 | fprintf(stderr, "usage: %s luafile\n", argv[0]); 13 | return -1; 14 | } 15 | 16 | l = luaL_newstate(); 17 | luaL_openlibs(l); 18 | 19 | luaopen_luamysql(l); /* this will put the function table on top of the stack */ 20 | lua_setglobal(l, "mysql"); 21 | 22 | if (luaL_dofile(l, argv[1]) != 0) 23 | fprintf(stderr, "luaL_dofile() error: %s.\n", lua_tostring(l, -1)); 24 | 25 | lua_close(l); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /luamysql-demo.lua: -------------------------------------------------------------------------------- 1 | local mysql = require('luamysql') 2 | 3 | dbarg = { 4 | host = "127.0.0.1", -- required 5 | port = 3306, -- required 6 | user = "ouonline", -- optional 7 | password = "ouonline", -- optional 8 | db = "test", -- optional 9 | } 10 | 11 | ------------------------------------------------------------------------------- 12 | 13 | client, errmsg = mysql.newclient(dbarg) 14 | if errmsg ~= nil then 15 | io.write("connect to mysql error: ", errmsg, "\n") 16 | return 17 | end 18 | 19 | errmsg = client:selectdb(dbarg.db) 20 | if errmsg ~= nil then 21 | io.write("selectdb error: ", errmsg, "\n") 22 | return 23 | end 24 | 25 | errmsg = client:setcharset("utf8") 26 | if errmsg ~= nil then 27 | io.write("setcharset error: ", errmsg, "\n") 28 | return 29 | end 30 | 31 | errmsg = client:ping() 32 | if errmsg ~= nil then 33 | io.write("ping: ", errmsg, "\n") 34 | return 35 | end 36 | 37 | result, errmsg = client:escape("'ouonline'") 38 | if errmsg ~= nil then 39 | io.write("escape error: ", errmsg, "\n") 40 | return 41 | end 42 | io.write("excape string -> ", result, "\n") 43 | 44 | result, errmsg = client:execute("select * from test") 45 | if errmsg ~= nil then 46 | io.write("execute error: ", errmsg, "\n") 47 | return 48 | end 49 | 50 | io.write("result size = ", result:size(), "\n") 51 | 52 | fieldnamelist = result:fieldnamelist() 53 | if fieldnamelist == nil then 54 | io.write("get fieldnamelist error.\n") 55 | return 56 | end 57 | 58 | for record in result:recordlist() do 59 | io.write("--------------------------------\n") 60 | for k, v in pairs(record) do 61 | io.write("[", k, "] -> ", fieldnamelist[k], ": ", v, "\n") 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /luamysql.c: -------------------------------------------------------------------------------- 1 | #include "lua.h" 2 | #include "lauxlib.h" 3 | #include "lualib.h" 4 | #include 5 | 6 | /* ------------------------------------------------------------------------- */ 7 | 8 | #define mysql_init_error(l, fmt, ...) \ 9 | do { \ 10 | lua_pushnil(l); /* empty result */ \ 11 | lua_pushfstring(l, fmt, ##__VA_ARGS__); /* error message */ \ 12 | } while (0) 13 | 14 | /* return 0 if ok */ 15 | static int get_args(lua_State* l, const char** host, unsigned int* port, 16 | const char** user, const char** password, 17 | const char** db) { 18 | int argtype; 19 | 20 | if (lua_gettop(l) != 1) { 21 | mysql_init_error(l, "1 argument required, but %d is provided.", 22 | lua_gettop(l)); 23 | return 2; 24 | } 25 | 26 | if (!lua_istable(l, 1)) { 27 | mysql_init_error(l, "argument #1 expects a table, but a %s is provided.", 28 | lua_typename(l, lua_type(l, 1))); 29 | return 2; 30 | } 31 | 32 | lua_getfield(l, 1, "host"); 33 | if (!lua_isstring(l, -1)) { 34 | argtype = lua_type(l, -1); 35 | mysql_init_error(l, "argument#1::`host' expects a string, but a %s is provided.", 36 | lua_typename(l, argtype)); 37 | return 2; 38 | } 39 | *host = lua_tostring(l, -1); 40 | 41 | lua_getfield(l, 1, "port"); 42 | if (!lua_isnumber(l, -1)) { 43 | argtype = lua_type(l, -1); 44 | mysql_init_error(l, "argument#1::`port' expects a number, but a %s is provided.", 45 | lua_typename(l, argtype)); 46 | return 2; 47 | } 48 | *port = lua_tointeger(l, -1); 49 | 50 | lua_getfield(l, 1, "user"); 51 | if (!lua_isnil(l, -1)) { 52 | if (!lua_isstring(l, -1)) { 53 | argtype = lua_type(l, -1); 54 | mysql_init_error(l, "argument#1::`user' expects a string, but a %s is provided.", 55 | lua_typename(l, argtype)); 56 | return 2; 57 | } 58 | *user = lua_tostring(l, -1); 59 | } 60 | 61 | lua_getfield(l, 1, "password"); 62 | if (!lua_isnil(l, -1)) { 63 | if (!lua_isstring(l, -1)) { 64 | argtype = lua_type(l, -1); 65 | mysql_init_error(l, "argument#1::`password' expects a string, but a %s is provided.", 66 | lua_typename(l, argtype)); 67 | return 2; 68 | } 69 | *password = lua_tostring(l, -1); 70 | } 71 | 72 | lua_getfield(l, 1, "db"); 73 | if (!lua_isnil(l, -1)) { 74 | if (!lua_isstring(l, -1)) { 75 | argtype = lua_type(l, -1); 76 | mysql_init_error(l, "argument#1::`db' expects a string, but a %s is provided.", 77 | lua_typename(l, argtype)); 78 | return 2; 79 | } 80 | *db = lua_tostring(l, -1); 81 | } 82 | 83 | return 0; 84 | } 85 | 86 | /* return a connection and an error message */ 87 | static int l_new_mysqlclient(lua_State* l) { 88 | MYSQL* conn; 89 | unsigned int port; 90 | const char *host, *user = NULL, *password = NULL, *db = NULL; 91 | const char* errmsg; 92 | 93 | if (get_args(l, &host, &port, &user, &password, &db) != 0) { 94 | return 2; 95 | } 96 | 97 | conn = lua_newuserdata(l, sizeof(MYSQL)); 98 | lua_pushvalue(l, lua_upvalueindex(1)); 99 | lua_setmetatable(l, -2); 100 | 101 | if (!mysql_init(conn)) { 102 | errmsg = "mysql_init() failed."; 103 | goto conn_err; 104 | } 105 | 106 | if (!mysql_real_connect(conn, host, user, password, db, port, NULL, 0)) { 107 | errmsg = mysql_error(conn); 108 | goto conn_err; 109 | } 110 | 111 | lua_pushnil(l); /* errmsg */ 112 | return 2; 113 | 114 | conn_err: 115 | lua_pop(l, 1); /* pop the newuserdata */ 116 | mysql_init_error(l, errmsg); 117 | return 2; 118 | } 119 | 120 | /* ------------------------------------------------------------------------- */ 121 | 122 | /* return an error message if fails, otherwise nil */ 123 | static int l_mysqlclient_ping(lua_State* l) { 124 | MYSQL* conn = lua_touserdata(l, 1); 125 | if (mysql_ping(conn) == 0) { 126 | lua_pushnil(l); 127 | } else { 128 | lua_pushstring(l, mysql_error(conn)); 129 | } 130 | return 1; 131 | } 132 | 133 | /* return an error message if fails, otherwise nil */ 134 | static int l_mysqlclient_selectdb(lua_State* l) { 135 | MYSQL* conn; 136 | const char* db; 137 | 138 | if (!lua_isstring(l, 2)) { 139 | lua_pushfstring(l, "argument #2 expects a db name, but given a %s.", 140 | lua_typename(l, lua_type(l, 2))); 141 | return 1; 142 | } 143 | 144 | conn = lua_touserdata(l, 1); 145 | db = lua_tostring(l, 2); 146 | 147 | if (mysql_select_db(conn, db) == 0) { 148 | lua_pushnil(l); 149 | } else { 150 | lua_pushstring(l, mysql_error(conn)); 151 | } 152 | 153 | return 1; 154 | } 155 | 156 | /* return an error message if fails, otherwise nil */ 157 | static int l_mysqlclient_setcharset(lua_State* l) { 158 | MYSQL* conn; 159 | const char* charset; 160 | 161 | if (!lua_isstring(l, 2)) { 162 | lua_pushfstring(l, "argument #2 expects a charset string, but given a %s.", 163 | lua_typename(l, lua_type(l, 2))); 164 | return 1; 165 | } 166 | 167 | conn = lua_touserdata(l, 1); 168 | charset = lua_tostring(l, 2); 169 | 170 | if (mysql_set_character_set(conn, charset) == 0) { 171 | lua_pushnil(l); 172 | } else { 173 | lua_pushstring(l, mysql_error(conn)); 174 | } 175 | 176 | return 1; 177 | } 178 | 179 | /* return the escaped string and the error message */ 180 | static int l_mysqlclient_escape(lua_State* l) { 181 | MYSQL* conn; 182 | const char* content; 183 | char* buf; 184 | luaL_Buffer lbuf; 185 | unsigned long len; 186 | 187 | if (!lua_isstring(l, 2)) { 188 | int type = lua_type(l, 2); 189 | lua_pushnil(l); 190 | lua_pushfstring(l, "argument #2 expects a sql string, but given a %s.", 191 | lua_typename(l, type)); 192 | return 2; 193 | } 194 | 195 | content = lua_tolstring(l, 2, &len); 196 | if (len == 0) { 197 | lua_pushstring(l, ""); 198 | lua_pushnil(l); 199 | return 2; 200 | } 201 | 202 | buf = luaL_buffinitsize(l, &lbuf, len * 2 + 1); 203 | if (!buf) { 204 | lua_pushnil(l); 205 | lua_pushstring(l, "allocating buffer failed."); 206 | return 2; 207 | } 208 | 209 | conn = lua_touserdata(l, 1); 210 | len = mysql_real_escape_string(conn, buf, content, len); 211 | if (len == (unsigned long)(-1)) { 212 | lua_pushnil(l); 213 | luaL_addstring(&lbuf, mysql_error(conn)); 214 | luaL_pushresult(&lbuf); 215 | } else { 216 | luaL_pushresultsize(&lbuf, len); 217 | lua_pushnil(l); 218 | } 219 | 220 | return 2; 221 | } 222 | 223 | /* return the result set and the error message */ 224 | static int l_mysqlclient_execute(lua_State* l) { 225 | int err; 226 | MYSQL* conn; 227 | const char* sqlstr; 228 | unsigned long sqllen = 0; 229 | 230 | if (!lua_isstring(l, 2)) { 231 | int type = lua_type(l, 2); 232 | lua_pushnil(l); 233 | lua_pushfstring(l, "argument #2 expects a sql string, but given a %s.", 234 | lua_typename(l, type)); 235 | return 2; 236 | } 237 | 238 | sqlstr = lua_tolstring(l, 2, &sqllen); 239 | if (sqllen == 0) { 240 | lua_pushnil(l); 241 | lua_pushstring(l, "invalid SQL statement."); 242 | return 2; 243 | } 244 | 245 | conn = lua_touserdata(l, 1); 246 | err = mysql_real_query(conn, sqlstr, sqllen); 247 | if (err) { 248 | lua_pushnil(l); 249 | lua_pushstring(l, mysql_error(conn)); 250 | } else { 251 | MYSQL_RES** result = lua_newuserdata(l, sizeof(MYSQL_RES*)); 252 | *result = mysql_store_result(conn); 253 | lua_pushvalue(l, lua_upvalueindex(1)); 254 | lua_setmetatable(l, -2); 255 | lua_pushnil(l); 256 | } 257 | 258 | return 2; 259 | } 260 | 261 | static int l_mysqlclient_gc(lua_State* l) { 262 | mysql_close(lua_touserdata(l, 1)); 263 | return 0; 264 | } 265 | 266 | /* ------------------------------------------------------------------------- */ 267 | 268 | /* return the fieldnamelist, or nil if fails. */ 269 | static int l_mysqlresult_fieldnamelist(lua_State* l) { 270 | int i; 271 | MYSQL_RES** result = lua_touserdata(l, 1); 272 | int nr_field = mysql_num_fields(*result); 273 | MYSQL_FIELD* fieldlist = mysql_fetch_fields(*result); 274 | 275 | lua_createtable(l, nr_field, 0); 276 | for (i = 0; i < nr_field; ++i) { 277 | lua_pushstring(l, fieldlist[i].name); 278 | lua_rawseti(l, -2, i + 1); 279 | } 280 | return 1; 281 | } 282 | 283 | /* return the number of record(s), or nil if fails. */ 284 | static int l_mysqlresult_size(lua_State* l) { 285 | MYSQL_RES** result = lua_touserdata(l, 1); 286 | lua_pushinteger(l, mysql_num_rows(*result)); 287 | return 1; 288 | } 289 | 290 | static int l_mysqlresult_record_iter(lua_State* l) { 291 | MYSQL_RES** result = lua_touserdata(l, lua_upvalueindex(1)); 292 | MYSQL_ROW row = mysql_fetch_row(*result); 293 | if (row) { 294 | int i; 295 | unsigned long* lengths = mysql_fetch_lengths(*result); 296 | int nr_field = lua_tointeger(l, lua_upvalueindex(2)); 297 | 298 | lua_createtable(l, nr_field, 0); 299 | for (i = 0; i < nr_field; ++i) { 300 | lua_pushlstring(l, row[i], lengths[i]); 301 | lua_rawseti(l, -2, i + 1); 302 | } 303 | 304 | return 1; 305 | } 306 | return 0; 307 | } 308 | 309 | /* return a record iterator, or nil if fails. */ 310 | static int l_mysqlresult_recordlist(lua_State* l) { 311 | MYSQL_RES** result = lua_touserdata(l, 1); 312 | lua_pushvalue(l, 1); 313 | lua_pushinteger(l, mysql_num_fields(*result)); 314 | lua_pushcclosure(l, l_mysqlresult_record_iter, 2); 315 | return 1; 316 | } 317 | 318 | static int l_mysqlresult_gc(lua_State* l) { 319 | MYSQL_RES** result = lua_touserdata(l, 1); 320 | mysql_free_result(*result); 321 | return 0; 322 | } 323 | 324 | /* ------------------------------------------------------------------------- */ 325 | 326 | static void create_mysqlresult_funcs(lua_State* l) { 327 | lua_newtable(l); 328 | 329 | lua_pushvalue(l, -1); 330 | lua_setfield(l, -2, "__index"); 331 | 332 | lua_pushcfunction(l, l_mysqlresult_size); 333 | lua_setfield(l, -2, "size"); 334 | 335 | lua_pushcfunction(l, l_mysqlresult_fieldnamelist); 336 | lua_setfield(l, -2, "fieldnamelist"); 337 | 338 | lua_pushcfunction(l, l_mysqlresult_recordlist); 339 | lua_setfield(l, -2, "recordlist"); 340 | 341 | lua_pushcfunction(l, l_mysqlresult_gc); 342 | lua_setfield(l, -2, "__gc"); 343 | } 344 | 345 | static void create_mysqlclient_funcs(lua_State* l) { 346 | lua_newtable(l); 347 | 348 | lua_pushvalue(l, -1); 349 | lua_setfield(l, -2, "__index"); 350 | 351 | lua_pushcfunction(l, l_mysqlclient_ping); 352 | lua_setfield(l, -2, "ping"); 353 | 354 | lua_pushcfunction(l, l_mysqlclient_selectdb); 355 | lua_setfield(l, -2, "selectdb"); 356 | 357 | lua_pushcfunction(l, l_mysqlclient_setcharset); 358 | lua_setfield(l, -2, "setcharset"); 359 | 360 | lua_pushcfunction(l, l_mysqlclient_escape); 361 | lua_setfield(l, -2, "escape"); 362 | 363 | create_mysqlresult_funcs(l); 364 | lua_pushcclosure(l, l_mysqlclient_execute, 1); 365 | lua_setfield(l, -2, "execute"); 366 | 367 | lua_pushcfunction(l, l_mysqlclient_gc); 368 | lua_setfield(l, -2, "__gc"); 369 | } 370 | 371 | int luaopen_luamysql(lua_State* l) { 372 | lua_createtable(l, 0, 1); 373 | create_mysqlclient_funcs(l); 374 | lua_pushcclosure(l, l_new_mysqlclient, 1); 375 | lua_setfield(l, -2, "newclient"); 376 | return 1; 377 | } 378 | --------------------------------------------------------------------------------