Skip to content

Commit

Permalink
box: support square brackets in procedure resolution for Lua calls
Browse files Browse the repository at this point in the history
With the addition of square brackets, procedure resolution for Lua calls is
essentially parsing a JSON path ('*' placeholder forbidden) with a special
colon case for object methods, which we handle separately: refactor the
existing code to reuse JSON lexer.

Closes tarantool#8604

@TarantoolBot document
Title: JSON paths in procedure resolution for Lua calls

With the addition of square brackets to procedure resolution, Lua calls now
resolve any JSON path to a procedure. This is applicable to `net.box`
connection objects `call` method as well as `box.schema.func.call`.

Examples of JSON path function calls are provided in the test.
  • Loading branch information
CuriousGeorgiy committed Apr 25, 2023
1 parent 442ee72 commit ea5f5b3
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 46 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## feature/box

* Added support for square brackets in procedure resolution for Lua calls
(gh-8604).
105 changes: 59 additions & 46 deletions src/box/lua/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#include "mpstream/mpstream.h"
#include "box/session.h"
#include "box/iproto_features.h"
#include "json/json.h"

/**
* Handlers identifiers to obtain lua_Cfunction reference from
Expand Down Expand Up @@ -93,60 +94,65 @@ get_call_serializer(void)
}

/**
* A helper to find a Lua function by name and put it
* on top of the stack.
* Parse JSON path looking up tokens throughout the path.
*/
static int
box_lua_find(lua_State *L, const char *name, const char *name_end)
parse_json_path(lua_State *L, int *idx, struct json_lexer *lexer)
{
int index = LUA_GLOBALSINDEX;
int objstack = 0, top = lua_gettop(L);
const char *start = name, *end;

while ((end = (const char *) memchr(start, '.', name_end - start))) {
lua_checkstack(L, 3);
lua_pushlstring(L, start, end - start);
lua_gettable(L, index);
if (! lua_istable(L, -1)) {
diag_set(ClientError, ER_NO_SUCH_PROC,
name_end - name, name);
struct json_token token;
int rc = json_lexer_next_token(lexer, &token);
while (rc == 0) {
switch (token.type) {
case JSON_TOKEN_ANY:
return -1;
case JSON_TOKEN_NUM:
lua_pushnumber(L, token.num);
break;
case JSON_TOKEN_STR:
lua_pushlstring(L, token.str, token.len);
break;
default:
assert(token.type == JSON_TOKEN_END);
return 0;
}
start = end + 1; /* next piece of a.b.c */
index = lua_gettop(L); /* top of the stack */
}

/* box.something:method */
if ((end = (const char *) memchr(start, ':', name_end - start))) {
lua_checkstack(L, 3);
lua_pushlstring(L, start, end - start);
lua_gettable(L, index);
if (! (lua_istable(L, -1) ||
lua_islightuserdata(L, -1) || lua_isuserdata(L, -1) )) {
diag_set(ClientError, ER_NO_SUCH_PROC,
name_end - name, name);
return -1;
}

start = end + 1; /* next piece of a.b.c */
index = lua_gettop(L); /* top of the stack */
objstack = index - top;
lua_gettable(L, *idx);
if (!lua_isfunction(L, -1) &&
!lua_istable(L, -1) &&
!lua_islightuserdata(L, -1) &&
!lua_isuserdata(L, -1))
return -1;
rc = json_lexer_next_token(lexer, &token);
if (rc == 0 && token.type != JSON_TOKEN_END)
*idx = lua_gettop(L);
}
return rc;
}


lua_pushlstring(L, start, name_end - start);
lua_gettable(L, index);
if (!lua_isfunction(L, -1) && !lua_istable(L, -1)) {
/* lua_call or lua_gettable would raise a type error
* for us, but our own message is more verbose. */
diag_set(ClientError, ER_NO_SUCH_PROC,
name_end - name, name);
return -1;
/**
* A helper to find a Lua function by name and put it
* on top of the stack.
*/
static int
box_lua_find(lua_State *L, const char *name, const char *name_end)
{
int top = lua_gettop(L);
const char *method = memchr(name, ':', name_end - name);
const char *lookup_end = method != NULL ? method : name_end;
struct json_lexer lexer;
json_lexer_create(&lexer, name, (int)(lookup_end - name), 0);
int idx = LUA_GLOBALSINDEX;
if (parse_json_path(L, &idx, &lexer) != 0)
goto error;
int objstack = 0;
if (method != NULL) {
idx = lua_gettop(L);
objstack = idx - top;
lua_pushlstring(L, method + 1, name_end - method - 1);
lua_gettable(L, idx);
if (!lua_isfunction(L, -1) && !lua_istable(L, -1))
goto error;
}

/* setting stack that it would contain only
* the function pointer. */
if (index != LUA_GLOBALSINDEX) {
if (idx != LUA_GLOBALSINDEX) {
if (objstack == 0) { /* no object, only a function */
lua_replace(L, top + 1);
lua_pop(L, lua_gettop(L) - top - 1);
Expand All @@ -161,6 +167,13 @@ box_lua_find(lua_State *L, const char *name, const char *name_end)
}
}
return 1 + objstack;
error:
/*
* lua_call or lua_gettable would raise a type error
* for us, but our own message is more verbose.
*/
diag_set(ClientError, ER_NO_SUCH_PROC, name_end - name, name);
return -1;
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
local server = require('luatest.server')
local t = require('luatest')

local g = t.group()

g.before_all(function(cg)
cg.server = server:new()
cg.server:start()
end)

g.after_all(function(cg)
cg.server:drop()
end)

-- Checks that procedure resolution for Lua calls works correctly.
g.test_procedure_resolution = function(cg)
cg.server:exec(function()
local netbox = require('net.box')

rawset(_G, 'a', {
b = {
c = function() return 'c' end,
[555] = function() return 555 end
},
[777] = {
d = {
[444] = function() return 444 end,
e = function() return 'e' end
},
[666] = function() return 666 end
},
[555] = function() return 555 end,
[333] = netbox.self,
f = function() return 'f' end,
g = netbox.self
})

t.assert_equals(netbox.self:call('a.b.c'), a.b.c())
t.assert_equals(netbox.self:call('a.b["c"]'), a.b["c"]())
t.assert_equals(netbox.self:call('a.b[\'c\']'), a.b['c']())
t.assert_equals(netbox.self:call('a.b[555]'), a.b[555]())
t.assert_equals(netbox.self:call('a[777].d[444]'), a[777].d[444]())
t.assert_equals(netbox.self:call('a[777].d.e'), a[777].d.e())
t.assert_equals(netbox.self:call('a[777][666]'), a[777][666]())
t.assert_equals(netbox.self:call('a[555]'), a[555]())
t.assert_equals(netbox.self:call('a[333]:ping'), a[333]:ping())
t.assert_equals(netbox.self:call('a.f'), a.f())
t.assert_equals(netbox.self:call('a.g:ping'), a.g:ping())

t.assert_error(function()
netbox.self:call('')
end)
t.assert_error(function()
netbox.self:call('.')
end)
t.assert_error(function()
netbox.self:call(':')
end)
t.assert_error(function()
netbox.self:call('[')
end)
t.assert_error(function()
netbox.self:call(']')
end)
t.assert_error(function()
netbox.self:call('[]')
end)
t.assert_error(function()
netbox.self:call('a.')
end)
t.assert_error(function()
netbox.self:call('a.b.')
end)
t.assert_error(function()
netbox.self:call('a[b]')
end)
t.assert_error(function()
netbox.self:call('a[[]')
end)
t.assert_error(function()
netbox.self:call('a[[777]')
end)
t.assert_error(function()
netbox.self:call('a["b]')
end)
t.assert_error(function()
netbox.self:call('a["b\']')
end)
t.assert_error(function()
netbox.self:call('a[\'b]')
end)
t.assert_error(function()
netbox.self:call('a[\'b"]')
end)
t.assert_error(function()
netbox.self:call('a[\'\']')
end)
t.assert_error(function()
netbox.self:call('a[""]')
end)
t.assert_error(function()
netbox.self:call('a[\'\']')
end)
t.assert_error(function()
netbox.self:call('a["b""]')
end)
t.assert_error(function()
netbox.self:call('a["b"\']')
end)
t.assert_error(function()
netbox.self:call('a[\'b"\']')
end)
t.assert_error(function()
netbox.self:call('a["b\'"]')
end)
t.assert_error(function()
netbox.self:call('a[333]:')
end)
t.assert_error(function()
netbox.self:call('a[333]:ping:')
end)
t.assert_error(function()
netbox.self:call('a:[333]:ping:')
end)
t.assert_error(function()
netbox.self:call('a:[333]:')
end)
t.assert_error(function()
netbox.self:call('a[555].')
end)
t.assert_error(function()
netbox.self:call('a[555].')
end)
t.assert_error(function()
netbox.self:call('a[777].[666]')
end)
t.assert_error(function()
netbox.self:call('a[777]d[444]')
end)
t.assert_error(function()
netbox.self:call('a[777].d.[444]')
end)
t.assert_error(function()
netbox.self:call('a[777][666]e')
end)
t.assert_error(function()
netbox.self:call('a[555')
end)
t.assert_error(function()
netbox.self:call('a[555]..')
end)
t.assert_error(function()
netbox.self:call('a[555]..')
end)
t.assert_error(function()
netbox.self:call('a[777]..[666]')
end)
t.assert_error(function()
netbox.self:call('a[777].][666]')
end)
t.assert_error(function()
netbox.self:call('a]555[')
end)
t.assert_error(function()
netbox.self:call('a]555]')
end)
t.assert_error(function()
netbox.self:call('a]]')
end)
t.assert_error(function()
netbox.self:call('a[[555]')
end)
t.assert_error(function()
netbox.self:call('a[[555]]')
end)
t.assert_error(function()
netbox.self:call('a.b[c]')
end)
end)
end

0 comments on commit ea5f5b3

Please sign in to comment.