Skip to content

Commit 8e305a3

Browse files
committed
api: introduce keep_null option
Introduce keep_null option to return ffi's NULL instead of nil in conn:execute(). Co-authored-by: Maxim Galaganov <voltage@lanport.net> We can specify how lua_mysql_push_value() works with data == NULL and use this fact to simplify the code.
1 parent 8301898 commit 8e305a3

File tree

4 files changed

+106
-24
lines changed

4 files changed

+106
-24
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ Connect to a database.
9191
- `db` - database name
9292
- `use_numeric_result` - provide result of the "conn:execute" as ordered list
9393
(true/false); default value: false
94+
- `keep_null` - provide printing null fields in the result of the
95+
"conn:execute" (true/false); default value: false
9496

9597
Throws an error on failure.
9698

@@ -142,11 +144,26 @@ Throws an error on failure.
142144
```
143145

144146
*Example*:
147+
148+
(when `keep_null = false` or is not set on a pool/connection creation)
149+
150+
```
151+
tarantool> conn:execute("SELECT ? AS a, 'xx' AS b", NULL AS c , 42)
152+
---
153+
- - - a: 42
154+
b: xx
155+
- true
156+
...
157+
```
158+
159+
(when `keep_null = true` on a pool/connection creation)
160+
145161
```
146-
tarantool> conn:execute("SELECT ? AS a, 'xx' AS b", 42)
162+
tarantool> conn:execute("SELECT ? AS a, 'xx' AS b", NULL AS c, 42)
147163
---
148164
- - - a: 42
149165
b: xx
166+
c: null
150167
- true
151168
...
152169
```
@@ -222,6 +239,8 @@ Create a connection pool with count of size established connections.
222239
- `size` - count of connections in pool
223240
- `use_numeric_result` - provide result of the "conn:execute" as ordered list
224241
(true/false); default value: false
242+
- `keep_null` - provide printing null fields in the result of the
243+
"conn:execute" (true/false); default value: false
225244

226245
Throws an error on failure.
227246

mysql/driver.c

100644100755
Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,23 @@
4242
#define TIMEOUT_INFINITY 365 * 86400 * 100.0
4343
static const char mysql_driver_label[] = "__tnt_mysql_driver";
4444

45+
static int luaL_nil_ref = LUA_REFNIL;
46+
47+
/**
48+
* Push ffi's NULL (cdata<void *>: NULL) onto the stack.
49+
* Can be used as replacement of nil in Lua tables.
50+
* @param L stack
51+
*/
52+
static inline void
53+
luaL_pushnull(struct lua_State *L)
54+
{
55+
lua_rawgeti(L, LUA_REGISTRYINDEX, luaL_nil_ref);
56+
}
57+
4558
struct mysql_connection {
4659
MYSQL *raw_conn;
4760
int use_numeric_result;
61+
int keep_null;
4862
};
4963

5064
/*
@@ -169,11 +183,23 @@ lua_mysql_field_type_to_string(enum enum_field_types type)
169183
return mysql_field_type_strs[hash];
170184
}
171185

172-
/* Push value retrieved from mysql field to lua stack */
186+
/**
187+
* Push value retrieved from mysql field to lua stack.
188+
*
189+
* When `data` is NULL, `field` and len` parameters are
190+
* ignored and Lua nil or LuaJIT FFI NULL is pushed.
191+
*/
173192
static void
174-
lua_mysql_push_value(struct lua_State *L, MYSQL_FIELD *field,
175-
void *data, unsigned long len)
193+
lua_mysql_push_value(struct lua_State *L, MYSQL_FIELD *field, void *data,
194+
unsigned long len, int keep_null)
176195
{
196+
/*
197+
* Field type isn't MYSQL_TYPE_NULL actually in case of
198+
* Lua's nil passed as value.
199+
* Example: 'conn:execute('SELECT ? AS x', nil)'.
200+
*/
201+
if (data == NULL)
202+
field->type = MYSQL_TYPE_NULL;
177203
switch (field->type) {
178204
case MYSQL_TYPE_TINY:
179205
case MYSQL_TYPE_SHORT:
@@ -188,7 +214,10 @@ lua_mysql_push_value(struct lua_State *L, MYSQL_FIELD *field,
188214
}
189215

190216
case MYSQL_TYPE_NULL:
191-
lua_pushnil(L);
217+
if (keep_null == 1)
218+
luaL_pushnull(L);
219+
else
220+
lua_pushnil(L);
192221
break;
193222

194223
case MYSQL_TYPE_LONGLONG: {
@@ -239,10 +268,8 @@ lua_mysql_fetch_result(struct lua_State *L)
239268
unsigned long *len = mysql_fetch_lengths(result);
240269
unsigned col_no;
241270
for (col_no = 0; col_no < num_fields; ++col_no) {
242-
if (!row[col_no])
243-
continue;
244-
lua_mysql_push_value(L, fields + col_no,
245-
row[col_no], len[col_no]);
271+
lua_mysql_push_value(L, fields + col_no, row[col_no],
272+
len[col_no], conn->keep_null);
246273
if (conn->use_numeric_result) {
247274
/* Assign to a column number. */
248275
lua_rawseti(L, -2, col_no + 1);
@@ -350,16 +377,16 @@ lua_mysql_stmt_push_row(struct lua_State *L)
350377
unsigned long col_count = lua_tonumber(L, 1);
351378
MYSQL_BIND *results = (MYSQL_BIND *)lua_topointer(L, 2);
352379
MYSQL_FIELD *fields = (MYSQL_FIELD *)lua_topointer(L, 3);
380+
int keep_null = lua_tointeger(L, 4);
353381

354382
lua_newtable(L);
355383
unsigned col_no;
356384
for (col_no = 0; col_no < col_count; ++col_no) {
357-
if (*results[col_no].is_null)
358-
continue;
385+
void *data = *results[col_no].is_null ? NULL :
386+
results[col_no].buffer;
359387
lua_pushstring(L, fields[col_no].name);
360-
lua_mysql_push_value(L, fields + col_no,
361-
results[col_no].buffer,
362-
*results[col_no].length);
388+
lua_mysql_push_value(L, fields + col_no, data,
389+
*results[col_no].length, keep_null);
363390
lua_settable(L, -3);
364391
}
365392
return 1;
@@ -371,7 +398,7 @@ lua_mysql_stmt_push_row(struct lua_State *L)
371398
static int
372399
lua_mysql_execute_prepared(struct lua_State *L)
373400
{
374-
MYSQL *raw_conn = lua_check_mysqlconn(L, 1)->raw_conn;
401+
struct mysql_connection *conn = lua_check_mysqlconn(L, 1);
375402
size_t len;
376403
const char *sql = lua_tolstring(L, 2, &len);
377404
int ret_count = 0, fail = 0, error = 0;
@@ -389,7 +416,7 @@ lua_mysql_execute_prepared(struct lua_State *L)
389416
lua_pushnumber(L, 0);
390417
lua_newtable(L);
391418
ret_count = 2;
392-
stmt = mysql_stmt_init(raw_conn);
419+
stmt = mysql_stmt_init(conn->raw_conn);
393420
if ((error = !stmt))
394421
goto done;
395422
error = mysql_stmt_prepare(stmt, sql, len);
@@ -467,7 +494,8 @@ lua_mysql_execute_prepared(struct lua_State *L)
467494
lua_pushnumber(L, col_count);
468495
lua_pushlightuserdata(L, result_binds);
469496
lua_pushlightuserdata(L, fields);
470-
if ((fail = lua_pcall(L, 3, 1, 0)))
497+
lua_pushinteger(L, conn->keep_null);
498+
if ((fail = lua_pcall(L, 4, 1, 0)))
471499
goto done;
472500
lua_settable(L, -3);
473501
++row_idx;
@@ -476,7 +504,7 @@ lua_mysql_execute_prepared(struct lua_State *L)
476504

477505
done:
478506
if (error)
479-
ret_count = lua_mysql_push_error(L, raw_conn);
507+
ret_count = lua_mysql_push_error(L, conn->raw_conn);
480508
if (values)
481509
free(values);
482510
if (param_binds)
@@ -592,9 +620,9 @@ mysql_wait_for_io(my_socket socket, my_bool is_read, int timeout)
592620
static int
593621
lua_mysql_connect(struct lua_State *L)
594622
{
595-
if (lua_gettop(L) < 6) {
623+
if (lua_gettop(L) < 7) {
596624
luaL_error(L, "Usage: mysql.connect(host, port, user, "
597-
"password, db, use_numeric_result)");
625+
"password, db, use_numeric_result, keep_null)");
598626
}
599627

600628
const char *host = lua_tostring(L, 1);
@@ -603,6 +631,7 @@ lua_mysql_connect(struct lua_State *L)
603631
const char *pass = lua_tostring(L, 4);
604632
const char *db = lua_tostring(L, 5);
605633
const int use_numeric_result = lua_toboolean(L, 6);
634+
const int keep_null = lua_toboolean(L, 7);
606635

607636
MYSQL *raw_conn, *tmp_raw_conn = mysql_init(NULL);
608637
if (!tmp_raw_conn) {
@@ -650,6 +679,7 @@ lua_mysql_connect(struct lua_State *L)
650679
*conn_p = conn;
651680
(*conn_p)->raw_conn = raw_conn;
652681
(*conn_p)->use_numeric_result = use_numeric_result;
682+
(*conn_p)->keep_null = keep_null;
653683
luaL_getmetatable(L, mysql_driver_label);
654684
lua_setmetatable(L, -2);
655685

@@ -679,6 +709,10 @@ luaopen_mysql_driver(lua_State *L)
679709
if (mysql_library_init(0, NULL, NULL))
680710
luaL_error(L, "Failed to initialize mysql library");
681711

712+
/* Create NULL constant. */
713+
*(void **) luaL_pushcdata(L, luaL_ctypeid(L, "void *")) = NULL;
714+
luaL_nil_ref = luaL_ref(L, LUA_REGISTRYINDEX);
715+
682716
static const struct luaL_Reg methods [] = {
683717
{"execute_prepared", lua_mysql_execute_prepared},
684718
{"execute", lua_mysql_execute},

mysql/init.lua

100644100755
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ local function conn_get(pool, timeout)
3636
local status
3737
status, mysql_conn = driver.connect(pool.host, pool.port or 0,
3838
pool.user, pool.pass,
39-
pool.db, pool.use_numeric_result)
39+
pool.db, pool.use_numeric_result,
40+
pool.keep_null)
4041
if status < 0 then
4142
error(mysql_conn)
4243
end
@@ -165,7 +166,8 @@ local function pool_create(opts)
165166
for i = 1, opts.size do
166167
local status, conn = driver.connect(opts.host, opts.port or 0,
167168
opts.user, opts.password,
168-
opts.db, opts.use_numeric_result)
169+
opts.db, opts.use_numeric_result,
170+
opts.keep_null)
169171
if status < 0 then
170172
while queue:count() > 0 do
171173
local mysql_conn = queue:get()
@@ -185,6 +187,7 @@ local function pool_create(opts)
185187
db = opts.db,
186188
size = opts.size,
187189
use_numeric_result = opts.use_numeric_result,
190+
keep_null = opts.keep_null,
188191

189192
-- private variables
190193
queue = queue,
@@ -244,7 +247,8 @@ local function connect(opts)
244247

245248
local status, mysql_conn = driver.connect(opts.host, opts.port or 0,
246249
opts.user, opts.password,
247-
opts.db, opts.use_numeric_result)
250+
opts.db, opts.use_numeric_result,
251+
opts.keep_null)
248252
if status < 0 then
249253
error(mysql_conn)
250254
end

test/mysql.test.lua

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,32 @@ local function test_underlying_conn_closed_during_gc(test)
516516
test:ok(ffi.C.fcntl(handle, F_GETFD) == -1, 'descriptor is closed')
517517
end
518518

519+
local function test_ffi_null_printing(test, pool)
520+
test:plan(4)
521+
local function json_result(keep_null, prepared)
522+
local conn, err = mysql.connect({host = host, port = port, user = user,
523+
password = password, db = db, keep_null = keep_null})
524+
if conn == nil then error(err) end
525+
local rows
526+
if prepared then
527+
rows = conn:execute('SELECT 1 AS w, ? AS x', nil)
528+
else
529+
rows = conn:execute('SELECT 1 AS w, NULL AS x')
530+
end
531+
return json.encode(rows)
532+
end
533+
local res = json_result(true, true)
534+
test:ok(res == '[[{"x":null,"w":1}]]', 'execute_prepared keep_null enabled')
535+
res = json_result(false, true)
536+
test:ok(res == '[[{"w":1}]]', 'execute_prepared keep_null disabled')
537+
res = json_result(true, false)
538+
test:ok(res == '[[{"x":null,"w":1}]]', 'execute keep_null enabled')
539+
res = json_result(false, false)
540+
test:ok(res == '[[{"w":1}]]', 'execute keep_null disabled')
541+
end
542+
519543
local test = tap.test('mysql connector')
520-
test:plan(8)
544+
test:plan(9)
521545

522546
test:test('connection old api', test_old_api, conn)
523547
local pool_conn = p:get()
@@ -529,6 +553,7 @@ test:test('connection pool', test_connection_pool, p)
529553
test:test('connection reset', test_connection_reset, p)
530554
test:test('test_underlying_conn_closed_during_gc',
531555
test_underlying_conn_closed_during_gc, p)
556+
test:test('ffi null printing', test_ffi_null_printing, p)
532557
p:close()
533558

534559
os.exit(test:check() and 0 or 1)

0 commit comments

Comments
 (0)