diff --git a/include/lua/init.h b/include/lua/init.h index e6109c1fe0168d16408d5d555927ac692b285a98..b4dae65ef3deb0fed5afea7185057ffa1506ec58 100644 --- a/include/lua/init.h +++ b/include/lua/init.h @@ -110,4 +110,5 @@ tarantool_lua(struct lua_State *L, * */ int luaL_pushnumber64(struct lua_State *L, uint64_t val); + #endif /* INCLUDES_TARANTOOL_LUA_H */ diff --git a/src/box/box_lua.m b/src/box/box_lua.m index 9c41930f7e002344669f469614b296cd7ad81ad9..8df04988fc2f21899491d013b8ce4f7146a548e9 100644 --- a/src/box/box_lua.m +++ b/src/box/box_lua.m @@ -1186,12 +1186,6 @@ lbox_raise(lua_State *L) return 0; } -static const struct luaL_reg boxlib[] = { - {"process", lbox_process}, - {"raise", lbox_raise}, - {NULL, NULL} -}; - /** * A helper to find a Lua function by name and put it * on top of the stack. @@ -1279,6 +1273,328 @@ box_index_init_iterator_types(struct lua_State *L, int idx) } } +/** + * Pack our BER integer into luaL_Buffer + */ +static void +luaL_addvarint32(luaL_Buffer *b, u32 u32) +{ + char varint_buf[sizeof(u32)+1]; + struct tbuf tbuf = { .size = 0, .capacity = sizeof(varint_buf), + .data = varint_buf }; + write_varint32(&tbuf, u32); + luaL_addlstring(b, tbuf.data, tbuf.size); +} + +/** + * Convert box.pack() format specifier to Tarantool + * binary protocol UPDATE opcode + */ +static char format_to_opcode(char format) +{ + switch (format) { + case '=': return 0; + case '+': return 1; + case '&': return 2; + case '^': return 3; + case '|': return 4; + case ':': return 5; + case '#': return 6; + case '!': return 7; + case '-': return 8; + default: return format; + } +} + +/** + * Counterpart to @a format_to_opcode + */ +static char opcode_to_format(char opcode) +{ + switch (opcode) { + case 0: return '='; + case 1: return '+'; + case 2: return '&'; + case 3: return '^'; + case 4: return '|'; + case 5: return ':'; + case 6: return '#'; + case 7: return '!'; + case 8: return '-'; + default: return opcode; + } +} + +/** + * To use Tarantool/Box binary protocol primitives from Lua, we + * need a way to pack Lua variables into a binary representation. + * We do it by exporting a helper function + * + * box.pack(format, args...) + * + * which takes the format, which is very similar to Perl 'pack' + * format, and a list of arguments, and returns a binary string + * which has the arguments packed according to the format. + * + * For example, a typical SELECT packet packs in Lua like this: + * + * pkt = box.pack("iiiiiip", -- pack format + * 0, -- space id + * 0, -- index id + * 0, -- offset + * 2^32, -- limit + * 1, -- number of SELECT arguments + * 1, -- tuple cardinality + * key); -- the key to use for SELECT + * + * @sa doc/box-protocol.txt, binary protocol description + * @todo: implement box.unpack(format, str), for testing purposes + */ +static int +lbox_pack(struct lua_State *L) +{ + luaL_Buffer b; + const char *format = luaL_checkstring(L, 1); + /* first arg comes second */ + int i = 2; + int nargs = lua_gettop(L); + u16 u16buf; + u32 u32buf; + u64 u64buf; + size_t size; + const char *str; + + luaL_buffinit(L, &b); + + while (*format) { + if (i > nargs) + luaL_error(L, "box.pack: argument count does not match " + "the format"); + switch (*format) { + case 'B': + case 'b': + /* signed and unsigned 8-bit integers */ + u32buf = lua_tointeger(L, i); + if (u32buf > 0xff) + luaL_error(L, "box.pack: argument too big for " + "8-bit integer"); + luaL_addchar(&b, (char) u32buf); + break; + case 'S': + case 's': + /* signed and unsigned 8-bit integers */ + u32buf = lua_tointeger(L, i); + if (u32buf > 0xffff) + luaL_error(L, "box.pack: argument too big for " + "16-bit integer"); + u16buf = (u16) u32buf; + luaL_addlstring(&b, (char *) &u16buf, sizeof(u16)); + break; + case 'I': + case 'i': + /* signed and unsigned 32-bit integers */ + u32buf = lua_tointeger(L, i); + luaL_addlstring(&b, (char *) &u32buf, sizeof(u32)); + break; + case 'L': + case 'l': + /* signed and unsigned 64-bit integers */ + u64buf = tarantool_lua_tointeger64(L, i); + luaL_addlstring(&b, (char *) &u64buf, sizeof(u64)); + break; + case 'w': + /* Perl 'pack' BER-encoded integer */ + luaL_addvarint32(&b, lua_tointeger(L, i)); + break; + case 'A': + case 'a': + /* A sequence of bytes */ + str = luaL_checklstring(L, i, &size); + luaL_addlstring(&b, str, size); + break; + case 'P': + case 'p': + if (lua_type(L, i) == LUA_TNUMBER) { + u64buf = lua_tonumber(L, i); + if (u64buf > UINT32_MAX) { + str = (char *) &u64buf; + size = sizeof(u64); + } else { + u32buf = (u32) u64buf; + str = (char *) &u32buf; + size = sizeof(u32); + } + } else if (lua_type(L, i) == LUA_TCDATA) { + u64buf = tarantool_lua_tointeger64(L, i); + str = (char *) &u64buf; + size = sizeof(u64); + } else { + str = luaL_checklstring(L, i, &size); + } + luaL_addvarint32(&b, size); + luaL_addlstring(&b, str, size); + break; + case '=': + /* update tuple set foo = bar */ + case '+': + /* set field += val */ + case '-': + /* set field -= val */ + case '&': + /* set field & =val */ + case '|': + /* set field |= val */ + case '^': + /* set field ^= val */ + case ':': + /* splice */ + case '#': + /* delete field */ + case '!': + /* insert field */ + /* field no */ + u32buf = (u32) lua_tointeger(L, i); + luaL_addlstring(&b, (char *) &u32buf, sizeof(u32)); + luaL_addchar(&b, format_to_opcode(*format)); + break; + default: + luaL_error(L, "box.pack: unsupported pack " + "format specifier '%c'", *format); + } + i++; + format++; + } + luaL_pushresult(&b); + return 1; +} + +static int +lbox_unpack(struct lua_State *L) +{ + size_t format_size = 0; + const char *format = luaL_checklstring(L, 1, &format_size); + const char *f = format; + + size_t str_size = 0; + const u8 *str = (const u8 *) luaL_checklstring(L, 2, &str_size); + const u8 *end = str + str_size; + const u8 *s = str; + + int i = 0; + + char charbuf; + u8 u8buf; + u16 u16buf; + u32 u32buf; + +#define CHECK_SIZE(cur) if (unlikely((cur) >= end)) { \ + luaL_error(L, "box.unpack('%c'): got %d bytes (expected: %d+)", \ + *f, (int) (end - str), (int) 1 + ((cur) - str)); \ +} + while (*f) { + switch (*f) { + case 'b': + CHECK_SIZE(s); + u8buf = *(u8 *) s; + lua_pushnumber(L, u8buf); + s++; + break; + case 's': + CHECK_SIZE(s + 1); + u16buf = *(u16 *) s; + lua_pushnumber(L, u16buf); + s += 2; + break; + case 'i': + CHECK_SIZE(s + 3); + u32buf = *(u32 *) s; + lua_pushnumber(L, u32buf); + s += 4; + break; + case 'l': + CHECK_SIZE(s + 7); + luaL_pushnumber64(L, *(uint64_t*) s); + s += 8; + break; + case 'w': + /* load_varint32_s throws exception on error. */ + u32buf = load_varint32_s((void *)&s, end - s); + lua_pushnumber(L, u32buf); + break; + case 'P': + case 'p': + /* load_varint32_s throws exception on error. */ + u32buf = load_varint32_s((void *)&s, end - s); + CHECK_SIZE(s + u32buf - 1); + lua_pushlstring(L, (const char *) s, u32buf); + s += u32buf; + break; + case '=': + /* update tuple set foo = bar */ + case '+': + /* set field += val */ + case '-': + /* set field -= val */ + case '&': + /* set field & =val */ + case '|': + /* set field |= val */ + case '^': + /* set field ^= val */ + case ':': + /* splice */ + case '#': + /* delete field */ + case '!': + /* insert field */ + CHECK_SIZE(s + 4); + + /* field no */ + u32buf = *(u32 *) s; + + /* opcode */ + charbuf = *(char *) (s + 4); + charbuf = opcode_to_format(charbuf); + if (charbuf != *f) { + luaL_error(L, "box.unpack('%s'): " + "unexpected opcode: " + "offset %d, expected '%c'," + "found '%c'", + format, s - str, *f, charbuf); + } + + lua_pushnumber(L, u32buf); + s += 5; + break; + default: + luaL_error(L, "box.unpack: unsupported " + "format specifier '%c'", *f); + } + i++; + f++; + } + + assert(s <= end); + + if (s != end) { + luaL_error(L, "box.unpack('%s'): too many bytes: " + "unpacked %d, total %d", + format, s - str, str_size); + } + + return i; + +#undef CHECK_SIZE +} + +static const struct luaL_reg boxlib[] = { + {"process", lbox_process}, + {"raise", lbox_raise}, + {"pack", lbox_pack}, + {"unpack", lbox_unpack}, + {NULL, NULL} +}; + void mod_lua_init(struct lua_State *L) { diff --git a/src/lua/init.m b/src/lua/init.m index 5a101cf4ef41be731ab764a4649aa4bd8b6c8ed4..ef9f3bd804ea58f7250ebfcad5cf085525ba80b7 100644 --- a/src/lua/init.m +++ b/src/lua/init.m @@ -101,19 +101,6 @@ tarantool_lua_dup_out(struct lua_State *L, struct lua_State *child_L) const char *boxlib_name = "box"; -/** - * Pack our BER integer into luaL_Buffer - */ -static void -luaL_addvarint32(luaL_Buffer *b, u32 u32) -{ - char varint_buf[sizeof(u32)+1]; - struct tbuf tbuf = { .size = 0, .capacity = sizeof(varint_buf), - .data = varint_buf }; - write_varint32(&tbuf, u32); - luaL_addlstring(b, tbuf.data, tbuf.size); -} - uint64_t tarantool_lua_tointeger64(struct lua_State *L, int idx) { @@ -154,188 +141,6 @@ tarantool_lua_tointeger64(struct lua_State *L, int idx) return result; } -/** - * Convert box.pack() format specifier to Tarantool - * binary protocol UPDATE opcode - */ -static char format_to_opcode(char format) -{ - switch (format) { - case '=': return 0; - case '+': return 1; - case '&': return 2; - case '^': return 3; - case '|': return 4; - case ':': return 5; - case '#': return 6; - case '!': return 7; - case '-': return 8; - default: return format; - } -} - -/** - * Counterpart to @a format_to_opcode - */ -static char opcode_to_format(char opcode) -{ - switch (opcode) { - case 0: return '='; - case 1: return '+'; - case 2: return '&'; - case 3: return '^'; - case 4: return '|'; - case 5: return ':'; - case 6: return '#'; - case 7: return '!'; - case 8: return '-'; - default: return opcode; - } -} - -/** - * To use Tarantool/Box binary protocol primitives from Lua, we - * need a way to pack Lua variables into a binary representation. - * We do it by exporting a helper function - * - * box.pack(format, args...) - * - * which takes the format, which is very similar to Perl 'pack' - * format, and a list of arguments, and returns a binary string - * which has the arguments packed according to the format. - * - * For example, a typical SELECT packet packs in Lua like this: - * - * pkt = box.pack("iiiiiip", -- pack format - * 0, -- space id - * 0, -- index id - * 0, -- offset - * 2^32, -- limit - * 1, -- number of SELECT arguments - * 1, -- tuple cardinality - * key); -- the key to use for SELECT - * - * @sa doc/box-protocol.txt, binary protocol description - * @todo: implement box.unpack(format, str), for testing purposes - */ -static int -lbox_pack(struct lua_State *L) -{ - luaL_Buffer b; - const char *format = luaL_checkstring(L, 1); - /* first arg comes second */ - int i = 2; - int nargs = lua_gettop(L); - u16 u16buf; - u32 u32buf; - u64 u64buf; - size_t size; - const char *str; - - luaL_buffinit(L, &b); - - while (*format) { - if (i > nargs) - luaL_error(L, "box.pack: argument count does not match " - "the format"); - switch (*format) { - case 'B': - case 'b': - /* signed and unsigned 8-bit integers */ - u32buf = lua_tointeger(L, i); - if (u32buf > 0xff) - luaL_error(L, "box.pack: argument too big for " - "8-bit integer"); - luaL_addchar(&b, (char) u32buf); - break; - case 'S': - case 's': - /* signed and unsigned 8-bit integers */ - u32buf = lua_tointeger(L, i); - if (u32buf > 0xffff) - luaL_error(L, "box.pack: argument too big for " - "16-bit integer"); - u16buf = (u16) u32buf; - luaL_addlstring(&b, (char *) &u16buf, sizeof(u16)); - break; - case 'I': - case 'i': - /* signed and unsigned 32-bit integers */ - u32buf = lua_tointeger(L, i); - luaL_addlstring(&b, (char *) &u32buf, sizeof(u32)); - break; - case 'L': - case 'l': - /* signed and unsigned 64-bit integers */ - u64buf = tarantool_lua_tointeger64(L, i); - luaL_addlstring(&b, (char *) &u64buf, sizeof(u64)); - break; - case 'w': - /* Perl 'pack' BER-encoded integer */ - luaL_addvarint32(&b, lua_tointeger(L, i)); - break; - case 'A': - case 'a': - /* A sequence of bytes */ - str = luaL_checklstring(L, i, &size); - luaL_addlstring(&b, str, size); - break; - case 'P': - case 'p': - if (lua_type(L, i) == LUA_TNUMBER) { - u64buf = lua_tonumber(L, i); - if (u64buf > UINT32_MAX) { - str = (char *) &u64buf; - size = sizeof(u64); - } else { - u32buf = (u32) u64buf; - str = (char *) &u32buf; - size = sizeof(u32); - } - } else if (lua_type(L, i) == LUA_TCDATA) { - u64buf = tarantool_lua_tointeger64(L, i); - str = (char *) &u64buf; - size = sizeof(u64); - } else { - str = luaL_checklstring(L, i, &size); - } - luaL_addvarint32(&b, size); - luaL_addlstring(&b, str, size); - break; - case '=': - /* update tuple set foo = bar */ - case '+': - /* set field += val */ - case '-': - /* set field -= val */ - case '&': - /* set field & =val */ - case '|': - /* set field |= val */ - case '^': - /* set field ^= val */ - case ':': - /* splice */ - case '#': - /* delete field */ - case '!': - /* insert field */ - /* field no */ - u32buf = (u32) lua_tointeger(L, i); - luaL_addlstring(&b, (char *) &u32buf, sizeof(u32)); - luaL_addchar(&b, format_to_opcode(*format)); - break; - default: - luaL_error(L, "box.pack: unsupported pack " - "format specifier '%c'", *format); - } - i++; - format++; - } - luaL_pushresult(&b); - return 1; -} - static GCcdata* luaL_pushcdata(struct lua_State *L, CTypeID id, int bits) { @@ -359,127 +164,6 @@ luaL_pushnumber64(struct lua_State *L, uint64_t val) return 1; } - -static int -lbox_unpack(struct lua_State *L) -{ - size_t format_size = 0; - const char *format = luaL_checklstring(L, 1, &format_size); - const char *f = format; - - size_t str_size = 0; - const u8 *str = (const u8 *) luaL_checklstring(L, 2, &str_size); - const u8 *end = str + str_size; - const u8 *s = str; - - int i = 0; - - char charbuf; - u8 u8buf; - u16 u16buf; - u32 u32buf; - -#define CHECK_SIZE(cur) if (unlikely((cur) >= end)) { \ - luaL_error(L, "box.unpack('%c'): got %d bytes (expected: %d+)", \ - *f, (int) (end - str), (int) 1 + ((cur) - str)); \ -} - while (*f) { - switch (*f) { - case 'b': - CHECK_SIZE(s); - u8buf = *(u8 *) s; - lua_pushnumber(L, u8buf); - s++; - break; - case 's': - CHECK_SIZE(s + 1); - u16buf = *(u16 *) s; - lua_pushnumber(L, u16buf); - s += 2; - break; - case 'i': - CHECK_SIZE(s + 3); - u32buf = *(u32 *) s; - lua_pushnumber(L, u32buf); - s += 4; - break; - case 'l': - CHECK_SIZE(s + 7); - GCcdata *cd = luaL_pushcdata(L, CTID_UINT64, 8); - *(uint64_t*)cdataptr(cd) = *(uint64_t*) s; - s += 8; - break; - case 'w': - /* load_varint32_s throws exception on error. */ - u32buf = load_varint32_s((void *)&s, end - s); - lua_pushnumber(L, u32buf); - break; - case 'P': - case 'p': - /* load_varint32_s throws exception on error. */ - u32buf = load_varint32_s((void *)&s, end - s); - CHECK_SIZE(s + u32buf - 1); - lua_pushlstring(L, (const char *) s, u32buf); - s += u32buf; - break; - case '=': - /* update tuple set foo = bar */ - case '+': - /* set field += val */ - case '-': - /* set field -= val */ - case '&': - /* set field & =val */ - case '|': - /* set field |= val */ - case '^': - /* set field ^= val */ - case ':': - /* splice */ - case '#': - /* delete field */ - case '!': - /* insert field */ - CHECK_SIZE(s + 4); - - /* field no */ - u32buf = *(u32 *) s; - - /* opcode */ - charbuf = *(char *) (s + 4); - charbuf = opcode_to_format(charbuf); - if (charbuf != *f) { - luaL_error(L, "box.unpack('%s'): " - "unexpected opcode: " - "offset %d, expected '%c'," - "found '%c'", - format, s - str, *f, charbuf); - } - - lua_pushnumber(L, u32buf); - s += 5; - break; - default: - luaL_error(L, "box.unpack: unsupported " - "format specifier '%c'", *f); - } - i++; - f++; - } - - assert(s <= end); - - if (s != end) { - luaL_error(L, "box.unpack('%s'): too many bytes: " - "unpacked %d, total %d", - format, s - str, str_size); - } - - return i; - -#undef CHECK_SIZE -} - /** Report libev time (cheap). */ static int lbox_time(struct lua_State *L) @@ -496,14 +180,10 @@ lbox_time64(struct lua_State *L) return 1; } - - /** * descriptor for box methods */ static const struct luaL_reg boxlib[] = { - {"pack", lbox_pack}, - {"unpack", lbox_unpack}, {"time", lbox_time}, {"time64", lbox_time64}, {NULL, NULL} diff --git a/test/box/lua.result b/test/box/lua.result index 482a4370f7effd3ee8e4b5ea741a0c13325a234d..5a4d05d5bccd2a6d230d7d15bae000d39cc4036f 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -32,14 +32,14 @@ lua for n in pairs(box) do print(' - box.', n) end - box.space - box.insert - box.cfg - - box.uuid - - box.session - box.tuple + - box.session - box.select_limit + - box.index - box.dostring - box.process - - box.index - box.stat + - box.uuid - box.select - box.flags - box.unpack