Skip to content

Commit 6e2e5b5

Browse files
Introduce keep_null option
Introduce keep_null option to return ffi's NULL instead of nil in conn:execute() and conn:prepare_executed(). Co-authored-by: Maxim Galaganov <voltage@lanport.net>
1 parent 8301898 commit 6e2e5b5

File tree

4 files changed

+106
-22
lines changed

4 files changed

+106
-22
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" or "conn:execute_prepared" (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" or "conn:execute_prepared" (true/false); default value: false
225244

226245
Throws an error on failure.
227246

mysql/driver.c

100644100755
Lines changed: 52 additions & 17 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
/*
@@ -171,9 +185,16 @@ lua_mysql_field_type_to_string(enum enum_field_types type)
171185

172186
/* Push value retrieved from mysql field to lua stack */
173187
static void
174-
lua_mysql_push_value(struct lua_State *L, MYSQL_FIELD *field,
175-
void *data, unsigned long len)
188+
lua_mysql_push_value(struct lua_State *L, MYSQL_FIELD *field, void *data,
189+
unsigned long len, int keep_null)
176190
{
191+
/*
192+
* In case of Lua nil field type isn't MYSQL_TYPE_NULL
193+
* actually.
194+
* Example: 'conn:execute('SELECT ? AS x', nil)'.
195+
*/
196+
if (data == NULL)
197+
field->type = MYSQL_TYPE_NULL;
177198
switch (field->type) {
178199
case MYSQL_TYPE_TINY:
179200
case MYSQL_TYPE_SHORT:
@@ -188,7 +209,10 @@ lua_mysql_push_value(struct lua_State *L, MYSQL_FIELD *field,
188209
}
189210

190211
case MYSQL_TYPE_NULL:
191-
lua_pushnil(L);
212+
if (keep_null == 1)
213+
luaL_pushnull(L);
214+
else
215+
lua_pushnil(L);
192216
break;
193217

194218
case MYSQL_TYPE_LONGLONG: {
@@ -239,10 +263,11 @@ lua_mysql_fetch_result(struct lua_State *L)
239263
unsigned long *len = mysql_fetch_lengths(result);
240264
unsigned col_no;
241265
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]);
266+
void *data = conn->keep_null == 1 && !row[col_no] ? NULL
267+
: row[col_no];
268+
lua_mysql_push_value(L, fields + col_no, data,
269+
data == NULL ? 0 : len[col_no],
270+
conn->keep_null);
246271
if (conn->use_numeric_result) {
247272
/* Assign to a column number. */
248273
lua_rawseti(L, -2, col_no + 1);
@@ -350,16 +375,19 @@ lua_mysql_stmt_push_row(struct lua_State *L)
350375
unsigned long col_count = lua_tonumber(L, 1);
351376
MYSQL_BIND *results = (MYSQL_BIND *)lua_topointer(L, 2);
352377
MYSQL_FIELD *fields = (MYSQL_FIELD *)lua_topointer(L, 3);
378+
int keep_null = lua_tointeger(L, 4);
353379

354380
lua_newtable(L);
355381
unsigned col_no;
356382
for (col_no = 0; col_no < col_count; ++col_no) {
357-
if (*results[col_no].is_null)
383+
void *data = NULL;
384+
if (*results[col_no].is_null && keep_null == 0)
358385
continue;
386+
else if (!(*results[col_no].is_null))
387+
data = results[col_no].buffer;
359388
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);
389+
lua_mysql_push_value(L, fields + col_no, data, data == NULL ? 0
390+
: *results[col_no].length, keep_null);
363391
lua_settable(L, -3);
364392
}
365393
return 1;
@@ -371,7 +399,7 @@ lua_mysql_stmt_push_row(struct lua_State *L)
371399
static int
372400
lua_mysql_execute_prepared(struct lua_State *L)
373401
{
374-
MYSQL *raw_conn = lua_check_mysqlconn(L, 1)->raw_conn;
402+
struct mysql_connection *conn = lua_check_mysqlconn(L, 1);
375403
size_t len;
376404
const char *sql = lua_tolstring(L, 2, &len);
377405
int ret_count = 0, fail = 0, error = 0;
@@ -389,7 +417,7 @@ lua_mysql_execute_prepared(struct lua_State *L)
389417
lua_pushnumber(L, 0);
390418
lua_newtable(L);
391419
ret_count = 2;
392-
stmt = mysql_stmt_init(raw_conn);
420+
stmt = mysql_stmt_init(conn->raw_conn);
393421
if ((error = !stmt))
394422
goto done;
395423
error = mysql_stmt_prepare(stmt, sql, len);
@@ -467,7 +495,8 @@ lua_mysql_execute_prepared(struct lua_State *L)
467495
lua_pushnumber(L, col_count);
468496
lua_pushlightuserdata(L, result_binds);
469497
lua_pushlightuserdata(L, fields);
470-
if ((fail = lua_pcall(L, 3, 1, 0)))
498+
lua_pushinteger(L, conn->keep_null);
499+
if ((fail = lua_pcall(L, 4, 1, 0)))
471500
goto done;
472501
lua_settable(L, -3);
473502
++row_idx;
@@ -476,7 +505,7 @@ lua_mysql_execute_prepared(struct lua_State *L)
476505

477506
done:
478507
if (error)
479-
ret_count = lua_mysql_push_error(L, raw_conn);
508+
ret_count = lua_mysql_push_error(L, conn->raw_conn);
480509
if (values)
481510
free(values);
482511
if (param_binds)
@@ -592,9 +621,9 @@ mysql_wait_for_io(my_socket socket, my_bool is_read, int timeout)
592621
static int
593622
lua_mysql_connect(struct lua_State *L)
594623
{
595-
if (lua_gettop(L) < 6) {
624+
if (lua_gettop(L) < 7) {
596625
luaL_error(L, "Usage: mysql.connect(host, port, user, "
597-
"password, db, use_numeric_result)");
626+
"password, db, use_numeric_result, keep_null)");
598627
}
599628

600629
const char *host = lua_tostring(L, 1);
@@ -603,6 +632,7 @@ lua_mysql_connect(struct lua_State *L)
603632
const char *pass = lua_tostring(L, 4);
604633
const char *db = lua_tostring(L, 5);
605634
const int use_numeric_result = lua_toboolean(L, 6);
635+
const int keep_null = lua_toboolean(L, 7);
606636

607637
MYSQL *raw_conn, *tmp_raw_conn = mysql_init(NULL);
608638
if (!tmp_raw_conn) {
@@ -650,6 +680,7 @@ lua_mysql_connect(struct lua_State *L)
650680
*conn_p = conn;
651681
(*conn_p)->raw_conn = raw_conn;
652682
(*conn_p)->use_numeric_result = use_numeric_result;
683+
(*conn_p)->keep_null = keep_null;
653684
luaL_getmetatable(L, mysql_driver_label);
654685
lua_setmetatable(L, -2);
655686

@@ -679,6 +710,10 @@ luaopen_mysql_driver(lua_State *L)
679710
if (mysql_library_init(0, NULL, NULL))
680711
luaL_error(L, "Failed to initialize mysql library");
681712

713+
/* Create NULL constant. */
714+
*(void **) luaL_pushcdata(L, luaL_ctypeid(L, "void *")) = NULL;
715+
luaL_nil_ref = luaL_ref(L, LUA_REGISTRYINDEX);
716+
682717
static const struct luaL_Reg methods [] = {
683718
{"execute_prepared", lua_mysql_execute_prepared},
684719
{"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: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,33 @@ 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+
541+
test:ok(res == '[[{"w":1}]]', 'execute keep_null disabled')
542+
end
543+
519544
local test = tap.test('mysql connector')
520-
test:plan(8)
545+
test:plan(9)
521546

522547
test:test('connection old api', test_old_api, conn)
523548
local pool_conn = p:get()
@@ -529,6 +554,7 @@ test:test('connection pool', test_connection_pool, p)
529554
test:test('connection reset', test_connection_reset, p)
530555
test:test('test_underlying_conn_closed_during_gc',
531556
test_underlying_conn_closed_during_gc, p)
557+
test:test('ffi null printing', test_ffi_null_printing, p)
532558
p:close()
533559

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

0 commit comments

Comments
 (0)