Skip to content

Commit

Permalink
lua: create vstream implementation for Lua
Browse files Browse the repository at this point in the history
Thas patch creates vstream implementation for Lua and function
box.sql.new_execute() that uses this implementation. Also it
creates parameters binding for SQL statements executed through
box.

Part of #3505
Closes #3401
  • Loading branch information
ImeevMA committed Nov 30, 2018
1 parent 2c3de27 commit 4675788
Show file tree
Hide file tree
Showing 8 changed files with 449 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -182,6 +182,7 @@ set (server_sources
lua/crypto.c
lua/httpc.c
lua/utf8.c
lua/luastream.c
${lua_sources}
${PROJECT_SOURCE_DIR}/third_party/lua-yaml/lyaml.cc
${PROJECT_SOURCE_DIR}/third_party/lua-yaml/b64.c
Expand Down
191 changes: 191 additions & 0 deletions src/box/execute.c
Expand Up @@ -43,6 +43,8 @@
#include "tuple.h"
#include "sql/vdbe.h"
#include "vstream.h"
#include "lua/utils.h"
#include "lua/msgpack.h"

const char *sql_type_strs[] = {
NULL,
Expand Down Expand Up @@ -298,6 +300,195 @@ xrow_decode_sql(const struct xrow_header *row, struct sql_request *request,
return 0;
}

/**
* Decode a single bind column from Lua stack.
*
* @param L Lua stack.
* @param[out] bind Bind to decode to.
* @param idx Position of table with bind columns on Lua stack.
* @param i Ordinal bind number.
*
* @retval 0 Success.
* @retval -1 Memory or client error.
*/
static inline int
lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i)
{
struct luaL_field field;
char *buf;
lua_rawgeti(L, idx, i + 1);
luaL_tofield(L, luaL_msgpack_default, -1, &field);
bind->pos = i + 1;
if (field.type == MP_MAP) {
/*
* A named parameter is an MP_MAP with
* one key - {'name': value}.
* Report parse error otherwise.
*/
if (field.size != 1) {
diag_set(ClientError, ER_ILLEGAL_PARAMS, "SQL bind "\
"parameter should be {'name': value}");
return -1;
}
/*
* Get key and value of the only map element to
* lua stack.
*/
lua_pushnil(L);
lua_next(L, lua_gettop(L) - 1);
/* At first we should deal with the value. */
luaL_tofield(L, luaL_msgpack_default, -1, &field);
lua_pop(L, 1);
/* Now key is on the top of Lua stack. */
size_t name_len = 0;
bind->name = luaL_checklstring(L, -1, &name_len);
if (bind->name == NULL) {
diag_set(ClientError, ER_ILLEGAL_PARAMS, "SQL bind "\
"parameter should be {'name': value}");
return -1;
}
/*
* Name should be saved in allocated memory as it
* will be poped from Lua stack.
*/
buf = region_alloc(&fiber()->gc, name_len + 1);
if (buf == NULL) {
diag_set(OutOfMemory, name_len + 1, "region_alloc",
"buf");
return -1;
}
memcpy(buf, bind->name, name_len + 1);
bind->name = buf;
bind->name_len = name_len;
lua_pop(L, 1);
} else {
bind->name = NULL;
bind->name_len = 0;
}
switch (field.type) {
case MP_UINT: {
bind->i64 = field.ival;
bind->type = SQLITE_INTEGER;
bind->bytes = sizeof(bind->i64);
break;
}
case MP_INT:
bind->i64 = field.ival;
bind->type = SQLITE_INTEGER;
bind->bytes = sizeof(bind->i64);
break;
case MP_STR:
/*
* Data should be saved in allocated memory as it
* will be poped from Lua stack.
*/
buf = region_alloc(&fiber()->gc, field.sval.len + 1);
if (buf == NULL) {
diag_set(OutOfMemory, field.sval.len + 1,
"region_alloc", "buf");
return -1;
}
memcpy(buf, field.sval.data, field.sval.len + 1);
bind->s = buf;
bind->type = SQLITE_TEXT;
bind->bytes = field.sval.len;
break;
case MP_DOUBLE:
bind->d = field.dval;
bind->type = SQLITE_FLOAT;
bind->bytes = sizeof(bind->d);
break;
case MP_FLOAT:
bind->d = field.dval;
bind->type = SQLITE_FLOAT;
bind->bytes = sizeof(bind->d);
break;
case MP_NIL:
bind->type = SQLITE_NULL;
bind->bytes = 1;
break;
case MP_BOOL:
/* SQLite doesn't support boolean. Use int instead. */
bind->i64 = field.bval ? 1 : 0;
bind->type = SQLITE_INTEGER;
bind->bytes = sizeof(bind->i64);
break;
case MP_BIN:
bind->s = mp_decode_bin(&field.sval.data, &bind->bytes);
bind->type = SQLITE_BLOB;
break;
case MP_EXT:
/*
* Data should be saved in allocated memory as it
* will be poped from Lua stack.
*/
buf = region_alloc(&fiber()->gc, sizeof(field));
if (buf == NULL) {
diag_set(OutOfMemory, sizeof(field), "region_alloc",
"buf");
return -1;
}
memcpy(buf, &field, sizeof(field));
bind->s = buf;
bind->bytes = sizeof(field);
bind->type = SQLITE_BLOB;
break;
case MP_ARRAY:
diag_set(ClientError, ER_SQL_BIND_TYPE, "ARRAY",
sql_bind_name(bind));
return -1;
case MP_MAP:
diag_set(ClientError, ER_SQL_BIND_TYPE, "MAP",
sql_bind_name(bind));
return -1;
default:
unreachable();
}
lua_pop(L, 1);
return 0;
}

int
lua_sql_bind_list_decode(struct lua_State *L, struct sql_request *request,
int idx)
{
assert(request != NULL);
if (! lua_istable(L, idx)) {
diag_set(ClientError, ER_INVALID_MSGPACK, "SQL parameter list");
return -1;
}
uint32_t bind_count = lua_objlen(L, idx);
if (bind_count == 0)
return 0;
if (bind_count > SQL_BIND_PARAMETER_MAX) {
diag_set(ClientError, ER_SQL_BIND_PARAMETER_MAX,
(int) bind_count);
return -1;
}
struct region *region = &fiber()->gc;
uint32_t used = region_used(region);
size_t size = sizeof(struct sql_bind) * bind_count;
/*
* Memory allocated here will be freed in
* sqlite3_finalize() or in txn_commit()/txn_rollback() if
* there is an active transaction.
*/
struct sql_bind *bind = (struct sql_bind *) region_alloc(region, size);
if (bind == NULL) {
diag_set(OutOfMemory, size, "region_alloc", "bind");
return -1;
}
for (uint32_t i = 0; i < bind_count; ++i) {
if (lua_sql_bind_decode(L, &bind[i], idx, i) != 0) {
region_truncate(region, used);
return -1;
}
}
request->bind_count = bind_count;
request->bind = bind;
return 0;
}

/**
* Serialize a single column of a result set row.
* @param stmt Prepared and started statement. At least one
Expand Down
15 changes: 15 additions & 0 deletions src/box/execute.h
Expand Up @@ -52,6 +52,7 @@ struct region;
struct sql_bind;
struct xrow_header;
struct vstream;
struct lua_State;

/** EXECUTE request. */
struct sql_request {
Expand Down Expand Up @@ -141,6 +142,20 @@ int
xrow_decode_sql(const struct xrow_header *row, struct sql_request *request,
struct region *region);

/**
* Parse Lua table of SQL parameters and store a result
* into the @request->bind, bind_count.
* @param L Lua stack to get data from.
* @param request Request to save decoded parameters.
* @param idx Position of table with parameters on Lua stack.
*
* @retval 0 Success.
* @retval -1 Client or memory error.
*/
int
lua_sql_bind_list_decode(struct lua_State *L, struct sql_request *request,
int idx);

/**
* Prepare and execute an SQL statement.
* @param request IProto request.
Expand Down
36 changes: 36 additions & 0 deletions src/box/lua/sql.c
Expand Up @@ -5,7 +5,10 @@
#include "box/sql/sqliteInt.h"
#include "box/info.h"
#include "lua/utils.h"
#include "lua/luastream.h"
#include "info.h"
#include "box/execute.h"
#include "vstream.h"

static void
lua_push_column_names(struct lua_State *L, struct sqlite3_stmt *stmt)
Expand Down Expand Up @@ -110,6 +113,38 @@ lua_sql_execute(struct lua_State *L)
return lua_error(L);
}

static int
lbox_execute(struct lua_State *L)
{
struct sqlite3 *db = sql_get();
if (db == NULL)
return luaL_error(L, "not ready");

size_t length;
const char *sql = lua_tolstring(L, 1, &length);
if (sql == NULL)
return luaL_error(L, "usage: box.execute(sqlstring)");

struct sql_request request = {};
request.sql_text = sql;
request.sql_text_len = length;
if (lua_gettop(L) == 2 && lua_sql_bind_list_decode(L, &request, 2) != 0)
return luaT_error(L);
struct sql_response response = {.is_info_flattened = true};
if (sql_prepare_and_execute(&request, &response, &fiber()->gc) != 0)
return luaT_error(L);

int keys;
struct vstream vstream;
luavstream_init(&vstream, L);
lua_newtable(L);
if (sql_response_dump(&response, &keys, &vstream) != 0) {
lua_pop(L, 1);
return luaT_error(L);
}
return 1;
}

static int
lua_sql_debug(struct lua_State *L)
{
Expand All @@ -124,6 +159,7 @@ box_lua_sqlite_init(struct lua_State *L)
{
static const struct luaL_Reg module_funcs [] = {
{"execute", lua_sql_execute},
{"new_execute", lbox_execute},
{"debug", lua_sql_debug},
{NULL, NULL}
};
Expand Down
4 changes: 4 additions & 0 deletions src/box/lua/sql.h
Expand Up @@ -36,9 +36,13 @@ extern "C" {
#endif

struct lua_State;
struct luastream;

void box_lua_sqlite_init(struct lua_State *L);

void
luastream_init(struct luastream *stream, struct lua_State *L);

#ifdef __cplusplus
}
#endif
Expand Down

0 comments on commit 4675788

Please sign in to comment.