Skip to content

Commit

Permalink
box: support brackets in name resolution for Lua calls
Browse files Browse the repository at this point in the history
Fefactor lua name resolution, simplify and comment.
Add an ability to specify path with brackets, for example in
'box.space[512]:get' or 'box.space["test"]:get'.
Only literals (strings and numbers) are supported.

Closes tarantool#8604

@TarantoolBot document
Title: square brackets in procedure resolution for Lua calls

Square brackets are now supported in Lua call procedure resolution. This is
applicable to `net.box` connection objects `call` method as well as
`box.schema.func.call`.

Examples of function calls with square brackets can be found in the test to
this patch.
  • Loading branch information
alyapunov committed May 16, 2023
1 parent 1d6043f commit eb9b55b
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 55 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).
116 changes: 61 additions & 55 deletions src/box/lua/call.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,74 +93,80 @@ get_call_serializer(void)
}

/**
* A helper to find a Lua function by name and put it
* on top of the stack.
* A helper to resolve a Lua function by full name, for example like:
* foo.bar['biz']["baz"][3].object:function
* Puts the function on top of the stack, followed by an object (if present).
* Returns number of items pushed (1 or 2) or -1 in case of error (diag is set).
*/
static int
box_lua_find(lua_State *L, const char *name, const char *name_end)
{
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);
return -1;
}
start = end + 1; /* next piece of a.b.c */
index = lua_gettop(L); /* top of the stack */
}
lua_checkstack(L, 3); /* No more than 3 entries are needed. */
int top = lua_gettop(L);

/* 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;
/* Take the first token. */
const char *start = name;
while (start != name_end && strchr(".:[", *start) == NULL)
start++;
lua_pushlstring(L, name, start - name);
lua_gettable(L, LUA_GLOBALSINDEX);

/* Take the rest tokens. */
while (start != name_end) {
if (!lua_istable(L, -1) &&
!lua_islightuserdata(L, -1) && !lua_isuserdata(L, -1))
goto no_such_proc;

char delim = *start++; /* skip delimiter. */
if (delim == '.') {
/* Look for the next token. */
const char *end = start;
while (end != name_end && strchr(".:[", *end) == NULL)
end++;
lua_pushlstring(L, start, end - start);
start = end;
} else if (delim == ':') {
lua_pushlstring(L, start, name_end - start);
lua_gettable(L, -2); /* get function from object. */
lua_insert(L, -2); /* swap function and object. */
break;
} else {
assert(delim == '[');
const char *end = memchr(start, ']', name_end - start);
if (end == NULL ||
(end != name_end && strchr(".:[", end[1]) == NULL))
goto no_such_proc;

if (end - start >= 2 && start[0] == end[-1] &&
(start[0] == '"' || start[0] == '\'')) {
/* Quoted string, just extract it. */
lua_pushlstring(L, start + 1, end - start - 2);
} else {
/* Must be a number, convert from string. */
lua_getfield(L, LUA_GLOBALSINDEX, "tonumber");
lua_pushlstring(L, start, end - start);
lua_call(L, 1, 1);
}
start = end + 1; /* skip closing bracket. */
}

start = end + 1; /* next piece of a.b.c */
index = lua_gettop(L); /* top of the stack */
objstack = index - top;
lua_gettable(L, -2); /* get child object from parent object. */
lua_remove(L, -2); /* drop previous parent object. */
}


lua_pushlstring(L, start, name_end - start);
lua_gettable(L, index);
if (!lua_isfunction(L, -1) && !lua_istable(L, -1)) {
/* Now at top+1 must be the function, and at top+2 may be the object. */
assert(lua_gettop(L) - top >= 1 && lua_gettop(L) - top <= 2);
if (!lua_isfunction(L, top + 1) && !lua_istable(L, top + 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;
goto no_such_proc;
}

/* setting stack that it would contain only
* the function pointer. */
if (index != LUA_GLOBALSINDEX) {
if (objstack == 0) { /* no object, only a function */
lua_replace(L, top + 1);
lua_pop(L, lua_gettop(L) - top - 1);
} else if (objstack == 1) { /* just two values, swap them */
lua_insert(L, -2);
lua_pop(L, lua_gettop(L) - top - 2);
} else { /* long path */
lua_insert(L, top + 1);
lua_insert(L, top + 2);
lua_pop(L, objstack - 1);
objstack = 1;
}
}
return 1 + objstack;
return lua_gettop(L) - top;

no_such_proc:
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,117 @@
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()
cg.server:exec(function()
local netbox = require('net.box')

local 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,
[-1] = function() return -1 end,
[333] = netbox.self,
f = function() return 'f' end,
g = netbox.self
}
rawset(_G, 'a', a)
end)
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')
local function test(proc)
t.assert_equals(netbox.self:call(proc),
netbox.self:eval('return ' .. proc .. '()'))
end

test('a.b.c')
test('a.b.c')
test('a.b["c"]')
test('a.b[\'c\']')
test('a.b[555]')
test('a[777].d[444]')
test('a[777].d.e')
test('a[777][666]')
test('a[555]')
test('a[555.]')
test('a[-1]')
test('a[333]:ping')
test('a.f')
test('a.g:ping')
end)
end

-- Checks that error detection in procedure resolution for Lua calls works
-- correctly.
g.test_procedure_resolution_errors = function(cg)
cg.server:exec(function()
local netbox = require('net.box')
local function test(proc)
t.assert_error(function() netbox.self:call(proc) end)
end

test('')
test('.')
test(':')
test('[')
test(']')
test('[]')
test('a.')
test('l:')
test('a.b.')
test('a[b]')
test('a[[]')
test('a[[777]')
test('a["b]')
test('a["b\']')
test('a[\'b]')
test('a[\'b"]')
test('a[\'\']')
test('a[""]')
test('a[\'\']')
test('a["b""]')
test('a["b"\']')
test('a[\'b"\']')
test('a["b\'"]')
test('a[333]:')
test('a[333]:ping:')
test('a:[333]:ping:')
test('a:[333]:')
test('a[555].')
test('a[555].')
test('a[777].[666]')
test('a[777]d[444]')
test('a[777].d.[444]')
test('a[777][666]e')
test('a[555')
test('a[555]..')
test('a[555]..')
test('a[777]..[666]')
test('a[777].][666]')
test('a]555[')
test('a]555]')
test('a]]')
test('a[[555]')
test('a[[555]]')
test('a.b[c]')
end)
end

0 comments on commit eb9b55b

Please sign in to comment.