diff --git a/CMakeLists.txt b/CMakeLists.txt index d1bf192c64876318d5f1d3870b186aaafe304e43..c025ea65ad309b58da5e493610c3e793a1b34777 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ find_program(CAT cat) find_program(GIT git) find_program(RAGEL ragel) find_program(CONFETTI confetti) +find_program(LD ld) set(luadir ${PROJECT_BINARY_DIR}/third_party/luajit/src) link_directories(${luadir}) @@ -119,7 +120,7 @@ execute_process (COMMAND ${GIT} describe HEAD # set (CPACK_PACKAGE_VERSION_MAJOR "1") set (CPACK_PACKAGE_VERSION_MINOR "4") -set (CPACK_PACKAGE_VERSION_PATCH "1") +set (CPACK_PACKAGE_VERSION_PATCH "2") if (TARANTOOL_VERSION STREQUAL "") set (TARANTOOL_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") diff --git a/cfg/CMakeLists.txt b/cfg/CMakeLists.txt index 4e043d0a5ef03bd8267c33c73d090c6457f682e0..fe9e66f14199a84524b82a35f86ae7f326129f3c 100644 --- a/cfg/CMakeLists.txt +++ b/cfg/CMakeLists.txt @@ -30,12 +30,10 @@ macro(generate_config mod) # their sources when configuring the project. execute_process(COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.h - ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c - ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.cfg) + ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c) add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.h ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c - ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.cfg COMMAND ${ECHO} '%{' > ${mod}_tmp.cfg COMMAND ${ECHO} '\#include \"cfg/warning.h\"' >> ${mod}_tmp.cfg COMMAND ${ECHO} '\#include \"cfg/tarantool_${mod}_cfg.h\"' >> ${mod}_tmp.cfg @@ -46,7 +44,6 @@ add_custom_command( COMMAND ${CONFETTI} -i ${mod}_tmp.cfg -n tarantool_cfg -c ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.c -h ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.h - -f ${CMAKE_SOURCE_DIR}/cfg/tarantool_${mod}_cfg.cfg COMMAND ${CMAKE_COMMAND} -E remove ${mod}_tmp.cfg DEPENDS ${CMAKE_SOURCE_DIR}/cfg/core_cfg.cfg_tmpl ${CMAKE_SOURCE_DIR}/cfg/warning.h diff --git a/cfg/tarantool_box_cfg.cfg b/cfg/tarantool_box_cfg.cfg deleted file mode 100644 index 3b2e6f843551889a9154ec3c4689613c94220008..0000000000000000000000000000000000000000 --- a/cfg/tarantool_box_cfg.cfg +++ /dev/null @@ -1,142 +0,0 @@ - -# username to switch to -username = NULL - -# tarantool bind ip address, applies to master -# and replication ports. INADDR_ANY is the default value. -bind_ipaddr = "INADDR_ANY" - -# save core on abort/assert -# deprecated; use ulimit instead -coredump = 0 - -# admin port -# used for admin's connections -admin_port = 0 - -# Replication clients should use this port (bind_ipaddr:replication_port). -replication_port = 0 - -# Log verbosity, possible values: ERROR=1, CRIT=2, WARN=3, INFO=4(default), DEBUG=5 -log_level = 4 - -# Size of slab arena in GB -slab_alloc_arena = 1 - -# Size of minimal allocation unit -slab_alloc_minimal = 64 - -# Growth factor, each subsequent unit size is factor * prev unit size -slab_alloc_factor = 2 - -# working directory (daemon will chdir(2) to it) -work_dir = NULL - -# name of pid file -pid_file = "tarantool.pid" - -# logger command will be executed via /bin/sh -c {} -# example: 'exec cronolog /var/log/tarantool/%Y-%m/%Y-%m-%d/tarantool.log' -# example: 'exec extra/logger.pl /var/log/tarantool/tarantool.log' -# when logger is not configured all logging going to STDERR -logger = NULL - -# make logging nonblocking, this potentially can lose some logging data -logger_nonblock = 1 - -# delay between loop iterations -io_collect_interval = 0 - -# size of listen backlog -backlog = 1024 - -# network io readahead -readahead = 16320 - -# # BOX -# Snapshot directory (where snapshots get saved/read) -snap_dir = "." - -# WAL directory (where WALs get saved/read) -wal_dir = "." - -# Primary port (where updates are accepted) -primary_port = 0 - -# Secondary port (where only selects are accepted) -secondary_port = 0 - -# Warn about requests which take longer to process, in seconds. -too_long_threshold = 0.5 - -# A custom process list (ps) title string, appended after the standard -# program title. -custom_proc_title = NULL - -# Memcached protocol support is enabled if memcached_port is set -memcached_port = 0 - -# namespace used for memcached emulation -memcached_namespace = 23 - -# Memcached expiration is on if memcached_expire is set. -memcached_expire = 0 - -# maximum rows to consider per expire loop iteration -memcached_expire_per_loop = 1024 - -# tarantool will try to iterate over all rows within this time -memcached_expire_full_sweep = 3600 - -# Do not write into snapshot faster than snap_io_rate_limit MB/sec -snap_io_rate_limit = 0 - -# Write no more rows in WAL -rows_per_wal = 500000 - -# fsync WAL delay, only issue fsync if last fsync was wal_fsync_delay -# seconds ago. -# WARNING: actually, several last requests may stall fsync for much longer -wal_fsync_delay = 0 - -# size of WAL writer request buffer -wal_writer_inbox_size = 128 - -# Local hot standby (if enabled, the server will run in hot -# standby mode, continuously fetching WAL records from wal_dir, -# until it is able to bind to the primary port. -# In local hot standby mode the server only accepts reads. -local_hot_standby = 0 - -# Delay, in seconds, between successive re-readings of wal_dir. -# The re-scan is necessary to discover new WAL files or snapshots. -wal_dir_rescan_delay = 0.1 - -# Panic if there is an error reading a snapshot or WAL. -# By default, panic on any snapshot reading error and ignore errors -# when reading WALs. -panic_on_snap_error = 1 -panic_on_wal_error = 0 - -# Replication mode (if enabled, the server, once -# bound to the primary port, will connect to -# replication_source (ipaddr:port) and run continously -# fetching records from it.. In replication mode the server -# only accepts reads. -replication_source = NULL -namespace = [ { - enabled = -1 - cardinality = -1 - estimated_rows = 0 - index = [ { - type = "" - unique = -1 - key_field = [ { - fieldno = -1 - type = "" - } - ] - } - ] - } -] diff --git a/connector/c/CMakeLists.txt b/connector/c/CMakeLists.txt index d5bd9b7d455c5432f5578380adc445bc84f8def8..4e27db259071bd52007dfdc301b19c1131bae4ab 100644 --- a/connector/c/CMakeLists.txt +++ b/connector/c/CMakeLists.txt @@ -15,7 +15,6 @@ if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") set (tnt_cflags "${tnt_cflags} -Werror") endif() - #============================================================================# # Build tnt projects #============================================================================# @@ -45,6 +44,10 @@ set (tnt_sources # Builds #----------------------------------------------------------------------------# +# Here we manage to build static/dynamic libraries ourselves, +# do not use the top level settings. +string(REPLACE "-static" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + # # Static library # diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m index bd63ba3e757fcdea227b95b4c0015bfbf3e799b4..20ace99567999b55fdf09c902e7f3fc480b6af20 100644 --- a/core/tarantool_lua.m +++ b/core/tarantool_lua.m @@ -50,6 +50,21 @@ luaL_addvarint32(luaL_Buffer *b, u32 u32) luaL_addlstring(b, tbuf.data, tbuf.len); } +/* 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; + default: return format; + } +} + /** * To use Tarantool/Box binary protocol primitives from Lua, we * need a way to pack Lua variables into a binary representation. @@ -63,7 +78,7 @@ luaL_addvarint32(luaL_Buffer *b, u32 u32) * * For example, a typical SELECT packet packs in Lua like this: * - * pkt = box.pack("uuuuuup", -- pack format + * pkt = box.pack("iiiiiip", -- pack format * 0, -- namespace id * 0, -- index id * 0, -- offset @@ -81,41 +96,56 @@ lbox_pack(struct lua_State *L) luaL_Buffer b; const char *format = luaL_checkstring(L, 1); int i = 2; /* first arg comes second */ + int nargs = lua_gettop(L); + u32 u32buf; + 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) { /* signed and unsigned 32-bit integers */ case 'I': case 'i': { - u32 u32 = luaL_checkinteger(L, i); - luaL_addlstring(&b, (const char *)&u32, sizeof(u32)); + u32buf = lua_tointeger(L, i); + luaL_addlstring(&b, (char *) &u32buf, sizeof(u32)); break; } /* Perl 'pack' BER-encoded integer */ case 'w': - luaL_addvarint32(&b, luaL_checkinteger(L, i)); + luaL_addvarint32(&b, lua_tointeger(L, i)); break; /* A sequence of bytes */ case 'A': case 'a': - { - size_t size; - const char *str = luaL_checklstring(L, i, &size); + str = luaL_checklstring(L, i, &size); luaL_addlstring(&b, str, size); break; - } case 'P': case 'p': - { - size_t size; - const char *str = luaL_checklstring(L, i, &size); + if (lua_type(L, i) == LUA_TNUMBER) { + u32buf= (u32) lua_tointeger(L, i); + str = (char *) &u32buf; + size = sizeof(u32); + } 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 */ + u32buf= (u32) lua_tointeger(L, i); /* field no */ + 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); @@ -127,10 +157,36 @@ lbox_pack(struct lua_State *L) return 1; } +static int +lbox_unpack(struct lua_State *L) +{ + const char *format = luaL_checkstring(L, 1); + int i = 2; /* first arg comes second */ + int nargs = lua_gettop(L); + u32 u32buf; + + while (*format) { + if (i > nargs) + luaL_error(L, "box.unpack: argument count does not match the format"); + switch (*format) { + case 'i': + u32buf = * (u32 *) lua_tostring(L, i); + lua_pushnumber(L, u32buf); + break; + default: + luaL_error(L, "box.unpack: unsupported pack " + "format specifier '%c'", *format); + } /* end switch */ + i++; + format++; + } + return i-2; +} /** A descriptor for box.tbuf object methods */ static const struct luaL_reg boxlib[] = { {"pack", lbox_pack}, + {"unpack", lbox_unpack}, {NULL, NULL} }; @@ -138,9 +194,11 @@ static const struct luaL_reg boxlib[] = { * lua_tostring does no use __tostring metamethod, and it has * to be called if we want to print Lua userdata correctly. */ -static const char * +const char * tarantool_lua_tostring(struct lua_State *L, int index) { + if (index < 0) /* we need an absolute index */ + index = lua_gettop(L) + index + 1; lua_getglobal(L, "tostring"); lua_pushvalue(L, index); lua_call(L, 1, 1); /* pops both "tostring" and its argument */ @@ -251,9 +309,17 @@ tarantool_lua_dostring(struct lua_State *L, const char *str) int r = luaL_loadstring(L, tbuf_str(buf)); if (r) { lua_pop(L, 1); /* pop the error message */ - return luaL_dostring(L, str); + r = luaL_loadstring(L, str); + if (r) + return r; + } + @try { + lua_call(L, 0, LUA_MULTRET); + } @catch (ClientError *e) { + lua_pushstring(L, e->errmsg); + return 1; } - return lua_pcall(L, 0, LUA_MULTRET, 0); + return 0; } void diff --git a/doc/user/language-reference.xml b/doc/user/language-reference.xml index 5163f54e41c8a9898efba6480d849b58c55f85aa..48a92d5382d627c906b7cce6a64b7fa6e01c5329 100644 --- a/doc/user/language-reference.xml +++ b/doc/user/language-reference.xml @@ -118,8 +118,76 @@ <section> <title>Writing stored procedures in Lua</title> <para> - Lua. + Lua is a light-weight, multi-paradigm embeddable language. + Tarantool/Box supports allows user to dynamically define, + alter, drop using the administrative console. + The procedures can be invoked Lua both from the administrative + console and using a binary protocol, for example: +<programlisting> +tarantool> lua function f1() return 'hello' end +--- +... +tarantool> call f1() +Found 1 tuple: +['hello'] +</programlisting> + </para> + <para> + There is a single global Lua interpreter state, which is + shared across all connections. Each connection, however, is + running in its own Lua <quote>thread</quote> -- a mechanism, akin to + Tarantool <quote>fibers</quote>. + Anything, prefixed with "lua " on the administrative console + is sent directly to the interpreter. In the binary protocol, + however, it is only possible to invoke Lua functions, but not + define or modify them. + A special command code designates invocation of a stored + program in the binary protocol. The tuple, sent as argument + of the command, is passed into the stored procedure, each + field of the tuple converted to a string parameter of the + procedure. As long as currently Tarantool tuples are + type-agnostic, Lua strings is chosen as the transport means + between the server and the interpreter. + </para> + <para> + Everything value, returned from a stored function by means of + <quote>return</quote> clause, is converted to Tarantool/Box tuple + and sent back to the client in binary form. </para> + <para> + It's possible not only to invoke trivial Lua code, but call + into Tarantool/Box storage functionality, using <quote>box</quote> + Lua library. + The main means of communication between Lua and Tarantool + is <quote>box.process()</quote> function, which allows + to send any kind of request to the server in the binary form. + Function <quote>box.process()</quote> is a server-side outlet + for Tarantool binary protocol. Any tuple returned by the + server is converted to a Lua object of tupe <quote>box.tuple</quote> + and appended to the return list of <quote>box.process()</quote>. + </para> + <para> + A few wrappers are defined to simplify the most common + tasks: + <itemizedlist> + <listitem><para><quote>box.select(namespace, key, ...)</quote> + to retrieve tuples. </para></listitem> + <listitem><para><quote>box.replace(namespace, ...)</quote> + to insert and replace tuples. The tuple is constructed + from all the remaining arguments passed into the function.</para></listitem> + <listitem><para><quote>box.update(namespace, key, tuple)</quote> and <quote>box.delete(namespace, key)</quote>for updates and deletes respectively.</para></listitem> + </itemizedlist> + The Lua source code of these wrappers, as well as a more + extensive documentation can be found in <filename>mod/box/box.lua</filename> file in the source tree. + </para> + <section> + <title>Replication of stored procedures</title> + <para> + The CALL statement itself does not enter Tarantool write ahead + log. Instead, the actual updates and deletes, performed by + the procedure, generate their own log events. + </para> + </section> </section> diff --git a/include/tarantool.h b/include/tarantool.h index 8eb90dcc512ea157d5b4ac36317d4523095008d3..980d171585c315961d4c44f691e8ddf54e6bc744 100644 --- a/include/tarantool.h +++ b/include/tarantool.h @@ -67,6 +67,9 @@ struct lua_State *tarantool_lua_init(); * Created with tarantool_lua_init(). */ extern struct lua_State *tarantool_L; +/* Call Lua 'tostring' built-in to print userdata nicely. */ +const char * +tarantool_lua_tostring(struct lua_State *L, int index); extern struct tarantool_cfg cfg; extern const char *cfg_filename; diff --git a/mod/box/CMakeLists.txt b/mod/box/CMakeLists.txt index 09cab62f2c02b81034a5e8a7e8aee5cb9fbb0189..01f9535b38f5c73fb6ac15cc1cef1cc74910d158 100644 --- a/mod/box/CMakeLists.txt +++ b/mod/box/CMakeLists.txt @@ -4,17 +4,26 @@ add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.m -o mod/box/memcached-grammar.m DEPENDS ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.rl) # Do not clean memcached-grammar.m in 'make clean'. -set_property(DIRECTORY PROPERTY CLEAN_NO_CUSTOM 1) +set_property(DIRECTORY PROPERTY CLEAN_NO_CUSTOM true) +set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES box.lua.o) # Do not try to randomly re-generate memcached-grammar.m # after a fresh checkout/branch switch. execute_process(COMMAND ${CMAKE_COMMAND} -E touch_nocreate ${CMAKE_SOURCE_DIR}/mod/box/memcached-grammar.m) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o + COMMAND ${LD} -r -b binary box.lua -o ${CMAKE_CURRENT_BINARY_DIR}/box.lua.o + DEPENDS box.lua) + +set_source_files_properties(box.lua.o + PROPERTIES EXTERNAL_OBJECT true) + set_source_files_properties(memcached-grammar.m PROPERTIES HEADER_FILE_ONLY true) set_source_files_properties(memcached.m PROPERTIES COMPILE_FLAGS "-Wno-uninitialized") -tarantool_module("box" tuple.m index.m box.m box_lua.m memcached.m memcached-grammar.m) +tarantool_module("box" tuple.m index.m box.m box_lua.m memcached.m memcached-grammar.m + box.lua.o) diff --git a/mod/box/box.h b/mod/box/box.h index 8ca28ca9201d55b9dd8cd7cc8e3126baabd1cdad..986b7d673a2613c78a2c3fd88b7e0f2d73c67d28 100644 --- a/mod/box/box.h +++ b/mod/box/box.h @@ -58,7 +58,6 @@ struct box_out { }; extern struct box_out box_out_quiet; -extern struct box_out box_out_iproto; struct box_txn { u16 op; @@ -101,18 +100,18 @@ struct box_txn { _(DELETE, 8) _(UPDATE_FIELDS, 9) _(INSERT,10) + _(JUBOX_ALIVE, 11) _(SELECT_LIMIT, 12) _(SELECT_OLD, 14) + _(SELECT_LIMIT, 15) _(UPDATE_FIELDS_OLD, 16) - _(JUBOX_ALIVE, 11) DO NOT use these ids! */ #define MESSAGES(_) \ - _(INSERT, 13) \ - _(SELECT_LIMIT, 15) \ + _(REPLACE, 13) \ _(SELECT, 17) \ - _(UPDATE_FIELDS, 19) \ + _(UPDATE, 19) \ _(DELETE_1_3, 20) \ _(DELETE, 21) \ _(CALL, 22) diff --git a/mod/box/box.lua b/mod/box/box.lua new file mode 100644 index 0000000000000000000000000000000000000000..07fce68538534a688ea903b483cbea9f52694fad --- /dev/null +++ b/mod/box/box.lua @@ -0,0 +1,51 @@ +-- +-- +-- +function box.select(namespace, index, ...) + key = {...} + return select(2, -- skip the first return from select, number of tuples + box.process(17, box.pack('iiiiii'..string.rep('p', #key), + namespace, + index, + 0, -- offset + 4294967295, -- limit + 1, -- key count + #key, -- key cardinality + unpack(key)))) +end +-- +-- delete can be done only by the primary key, whose +-- index is always 0. It doesn't accept compound keys +-- +function box.delete(namespace, key) + return select(2, -- skip the first return, tuple count + box.process(21, box.pack('iiip', namespace, + 1, -- flags, BOX_RETURN_TUPLE + 1, -- cardinality + key))) +end + +-- insert or replace a tuple +function box.replace(namespace, ...) + tuple = {...} + return select(2, + box.process(13, box.pack('iii'..string.rep('p', #tuple), + namespace, + 1, -- flags, BOX_RETURN_TUPLE + #tuple, -- cardinality + unpack(tuple)))) +end + +box.insert = box.replace + +function box.update(namespace, key, format, ...) + ops = {...} + return select(2, + box.process(19, box.pack('iiipi'..format, + namespace, + 1, -- flags, BOX_RETURN_TUPLE + 1, -- cardinality + key, -- primary key + #ops/2, -- op count + ...))) +end diff --git a/mod/box/box.m b/mod/box/box.m index 73a88fb019bbc0bdf41a612412b326b382155d18..386735aa432e4ac53c169094c0aeae9613a1cab1 100644 --- a/mod/box/box.m +++ b/mod/box/box.m @@ -167,8 +167,8 @@ prepare_replace(struct box_txn *txn, size_t cardinality, struct tbuf *data) /* * If the tuple doesn't exist, insert a GHOST * tuple in all indices in order to avoid a race - * condition when another INSERT comes along: - * a concurrent INSERT, UPDATE, or DELETE, returns + * condition when another REPLACE comes along: + * a concurrent REPLACE, UPDATE, or DELETE, returns * an error when meets a ghost tuple. * * Tuple reference counter will be incremented in @@ -318,7 +318,7 @@ do_field_splice(struct tbuf *field, void *args_data, u32 args_data_size) } static void __attribute__((noinline)) -prepare_update_fields(struct box_txn *txn, struct tbuf *data) +prepare_update(struct box_txn *txn, struct tbuf *data) { struct tbuf **fields; void *field; @@ -426,7 +426,7 @@ prepare_update_fields(struct box_txn *txn, struct tbuf *data) out: txn->out->dup_u32(tuples_affected); - if (txn->flags & BOX_RETURN_TUPLE) + if (txn->flags & BOX_RETURN_TUPLE && txn->tuple) txn->out->add_tuple(txn->tuple); } @@ -550,7 +550,7 @@ commit_delete(struct box_txn *txn) static bool op_is_select(u32 op) { - return op == SELECT || op == SELECT_LIMIT; + return op == SELECT || op == CALL; } static void @@ -578,7 +578,7 @@ iov_add_tuple(struct box_tuple *tuple) } } -struct box_out box_out_iproto = { +static struct box_out box_out_iproto = { iov_add_u32, iov_dup_u32, iov_add_tuple @@ -695,7 +695,7 @@ txn_rollback(struct box_txn *txn) unlock_tuples(txn); - if (txn->op == INSERT) + if (txn->op == REPLACE) rollback_replace(txn); } @@ -712,7 +712,7 @@ box_dispatch(struct box_txn *txn, struct tbuf *data) say_debug("box_dispatch(%i)", txn->op); switch (txn->op) { - case INSERT: + case REPLACE: txn_assign_n(txn, data); txn->flags |= read_u32(data) & BOX_ALLOWED_REQUEST_FLAGS; cardinality = read_u32(data); @@ -756,10 +756,10 @@ box_dispatch(struct box_txn *txn, struct tbuf *data) break; } - case UPDATE_FIELDS: + case UPDATE: txn_assign_n(txn, data); txn->flags |= read_u32(data) & BOX_ALLOWED_REQUEST_FLAGS; - prepare_update_fields(txn, data); + prepare_update(txn, data); break; case CALL: txn->flags |= read_u32(data) & BOX_ALLOWED_REQUEST_FLAGS; @@ -804,7 +804,7 @@ box_xlog_sprint(struct tbuf *buf, const struct tbuf *t) messages_strs[op], n); switch (op) { - case INSERT: + case REPLACE: flags = read_u32(b); cardinality = read_u32(b); if (b->len != valid_tuple(b, cardinality)) @@ -822,7 +822,7 @@ box_xlog_sprint(struct tbuf *buf, const struct tbuf *t) tuple_print(buf, key_len, key); break; - case UPDATE_FIELDS: + case UPDATE: flags = read_u32(b); key_len = read_u32(b); key = read_field(b); @@ -1051,7 +1051,7 @@ convert_snap_row_to_wal(struct tbuf *t) { struct tbuf *r = tbuf_alloc(fiber->gc_pool); struct box_snap_row *row = box_snap_row(t); - u16 op = INSERT; + u16 op = REPLACE; u32 flags = 0; tbuf_append(r, &op, sizeof(op)); @@ -1374,6 +1374,8 @@ mod_init(void) title("loading"); atexit(mod_free); + box_lua_init(); + /* initialization namespaces */ namespace_init(); @@ -1427,8 +1429,6 @@ mod_init(void) if (cfg.memcached_port != 0) fiber_server("memcached", cfg.memcached_port, memcached_handler, NULL, NULL); - - box_lua_init(); } int diff --git a/mod/box/box_lua.m b/mod/box/box_lua.m index 10ced13d3ef787f34d4e92676cb97e4c41fa5927..995bb2a866921f3892fe94282b60212a224fdfdd 100644 --- a/mod/box/box_lua.m +++ b/mod/box/box_lua.m @@ -39,6 +39,9 @@ #include "pickle.h" #include "tuple.h" +/* contents of box.lua */ +extern const char _binary_box_lua_start; + /** * All box connections share the same Lua state. We use * Lua coroutines (lua_newthread()) to have multiple @@ -143,36 +146,58 @@ static const struct luaL_reg lbox_tuple_meta [] = { void iov_add_ret(struct lua_State *L, int index) { int type = lua_type(L, index); + struct box_tuple *tuple; switch (type) { case LUA_TNUMBER: - box_out_iproto.dup_u32((u32) lua_tointeger(L, index)); + case LUA_TSTRING: + { + size_t len; + const char *str = lua_tolstring(L, index, &len); + tuple = tuple_alloc(len + varint32_sizeof(len)); + tuple->cardinality = 1; + memcpy(save_varint32(tuple->data, len), str, len); break; - case LUA_TUSERDATA: + } + case LUA_TNIL: + case LUA_TBOOLEAN: { - struct box_tuple *tuple = lua_istuple(L, index); - if (tuple != NULL) { - box_out_iproto.add_tuple(tuple); - break; - } + const char *str = tarantool_lua_tostring(L, index); + size_t len = strlen(str); + tuple = tuple_alloc(len + varint32_sizeof(len)); + tuple->cardinality = 1; + memcpy(save_varint32(tuple->data, len), str, len); + break; } + case LUA_TUSERDATA: + tuple = lua_istuple(L, index); + if (tuple) + break; default: /* - * LUA_TNONE, LUA_TNIL, LUA_TBOOLEAN, LUA_TSTRING, - * LUA_TTABLE, LUA_THREAD, LUA_TFUNCTION + * LUA_TNONE, LUA_TTABLE, LUA_THREAD, LUA_TFUNCTION */ tnt_raise(ClientError, :ER_PROC_RET, lua_typename(L, type)); break; } + tuple_txn_ref(in_txn(), tuple); + iov_add(&tuple->bsize, tuple_len(tuple)); } -/** Add nargs elements on the top of Lua stack to fiber iov. */ +/** + * Add all elements from Lua stack to fiber iov. + * + * To allow clients to understand a complex return from + * a procedure, we are compatible with SELECT protocol, + * and return the number of return values first, and + * then each return value as a tuple. + */ -void iov_add_multret(struct lua_State *L, int nargs) +void iov_add_multret(struct lua_State *L) { - while (nargs > 0) { - iov_add_ret(L, -nargs); - nargs--; - } + int nargs = lua_gettop(L); + iov_dup(&nargs, sizeof(u32)); + for (int i = 1; i <= nargs; ++i) + iov_add_ret(L, i); } static void @@ -206,14 +231,15 @@ static struct box_out box_out_lua = { /* }}} */ -static inline void +static inline struct box_txn * txn_enter_lua(lua_State *L) { - struct box_txn *txn = in_txn(); - if (txn == NULL) - txn = txn_begin(); + struct box_txn *old_txn = in_txn(); + fiber->mod_data.txn = NULL; + struct box_txn *txn = fiber->mod_data.txn = txn_begin(); txn->out = &box_out_lua; txn->L = L; + return old_txn; } /** @@ -246,16 +272,12 @@ static int lbox_process(lua_State *L) return luaL_error(L, "box.process(CALL, ...) is not allowed"); } int top = lua_gettop(L); /* to know how much is added by rw_callback */ + + struct box_txn *old_txn = txn_enter_lua(L); @try { - txn_enter_lua(L); rw_callback(op, &req); - /* - * @todo: when multi-statement transactions are - * implemented, restore the original txn->out here. - */ - assert(in_txn() == NULL); - } @catch (ClientError *e) { - return luaL_error(L, "%d:%s", e->errcode, e->errmsg); + } @finally { + fiber->mod_data.txn = old_txn; } return lua_gettop(L) - top; } @@ -293,6 +315,8 @@ void box_lua_find(lua_State *L, const char *name, const char *name_end) tnt_raise(ClientError, :ER_NO_SUCH_PROC, name_end - name, name); } + if (index != LUA_GLOBALSINDEX) + lua_remove(L, index); } @@ -317,14 +341,17 @@ void box_lua_call(struct box_txn *txn __attribute__((unused)), u32 field_len = read_varint32(data); void *field = read_str(data, field_len); /* proc name */ box_lua_find(L, field, field + field_len); - lua_checkstack(L, 1); - /* Push the rest of args (a tuple) as is. */ - lua_pushlstring(L, data->data, data->len); - - int top = lua_gettop(L); - lua_call(L, 1, LUA_MULTRET); + /* Push the rest of args (a tuple). */ + u32 nargs = read_u32(data); + luaL_checkstack(L, nargs, "call: out of stack"); + for (int i = 0; i < nargs; i++) { + field_len = read_varint32(data); + field = read_str(data, field_len); + lua_pushlstring(L, field, field_len); + } + lua_call(L, nargs, LUA_MULTRET); /* Send results of the called procedure to the client. */ - iov_add_multret(L, lua_gettop(L) - top); + iov_add_multret(L); } @finally { /* * Allow the used coro to be garbage collected. @@ -345,6 +372,8 @@ mod_lua_init(struct lua_State *L) lua_pushstring(L, tuplelib_name); lua_setfield(L, -2, "__metatable"); luaL_register(L, NULL, lbox_tuple_meta); + /* Load box.lua */ + (void) luaL_dostring(L, &_binary_box_lua_start); return L; } diff --git a/mod/box/memcached.m b/mod/box/memcached.m index a135b05d3b720c0c769381ae683439fcfaec1f6f..7f8cd2342184e08f26b602515592aa3fd78a6047 100644 --- a/mod/box/memcached.m +++ b/mod/box/memcached.m @@ -105,7 +105,7 @@ store(void *key, u32 exptime, u32 flags, u32 bytes, u8 *data) * Use a box dispatch wrapper which handles correctly * read-only/read-write modes. */ - rw_callback(INSERT, req); + rw_callback(REPLACE, req); } static void diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5834c41b5c2e878f42e81adb9002fd07555bc6e3..15bb5c2e83d1698729f84149d7507307c07aa440 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,6 @@ tarantool_client("box/connector" ${CMAKE_SOURCE_DIR}/test/box/connector.c) install (PROGRAMS tarantool DESTINATION bin) install (DIRECTORY lib DESTINATION bin) -install (FILES ${CMAKE_SOURCE_DIR}/box/tarantool.cfg - ${CMAKE_SOURCE_DIR}/box/00000000000000000001.snap +install (FILES ${CMAKE_SOURCE_DIR}/test/box/tarantool.cfg + ${CMAKE_SOURCE_DIR}/test/box/00000000000000000001.snap DESTINATION bin) diff --git a/test/box/admin.result b/test/box/admin.result index 99da6c3fdb8fa597aa13901ff8c0ba89c5bc0dc5..9dab733a04efc4ed2ae508a834c087f5724493f6 100644 --- a/test/box/admin.result +++ b/test/box/admin.result @@ -1,13 +1,12 @@ show stat --- statistics: - INSERT: { rps: 0 , total: 0 } - SELECT_LIMIT: { rps: 0 , total: 0 } - SELECT: { rps: 0 , total: 0 } - UPDATE_FIELDS: { rps: 0 , total: 0 } - DELETE_1_3: { rps: 0 , total: 0 } - DELETE: { rps: 0 , total: 0 } - CALL: { rps: 0 , total: 0 } + REPLACE: { rps: 0 , total: 0 } + SELECT: { rps: 0 , total: 0 } + UPDATE: { rps: 0 , total: 0 } + DELETE_1_3: { rps: 0 , total: 0 } + DELETE: { rps: 0 , total: 0 } + CALL: { rps: 0 , total: 0 } ... help --- @@ -75,13 +74,12 @@ configuration: show stat --- statistics: - INSERT: { rps: 0 , total: 0 } - SELECT_LIMIT: { rps: 0 , total: 0 } - SELECT: { rps: 0 , total: 0 } - UPDATE_FIELDS: { rps: 0 , total: 0 } - DELETE_1_3: { rps: 0 , total: 0 } - DELETE: { rps: 0 , total: 0 } - CALL: { rps: 0 , total: 0 } + REPLACE: { rps: 0 , total: 0 } + SELECT: { rps: 0 , total: 0 } + UPDATE: { rps: 0 , total: 0 } + DELETE_1_3: { rps: 0 , total: 0 } + DELETE: { rps: 0 , total: 0 } + CALL: { rps: 0 , total: 0 } ... insert into t0 values (1, 'tuple') Insert OK, 1 row affected diff --git a/test/box/configuration.result b/test/box/configuration.result index fcb3ae026187908c0bb4357f048684a718497dac..42375c7c1bdc30653b089c7cc55568f151a7b402 100644 --- a/test/box/configuration.result +++ b/test/box/configuration.result @@ -7,11 +7,10 @@ show stat --- statistics: - INSERT: { rps: 0 , total: 0 } - SELECT_LIMIT: { rps: 0 , total: 0 } - SELECT: { rps: 0 , total: 0 } - UPDATE_FIELDS: { rps: 0 , total: 0 } - DELETE_1_3: { rps: 0 , total: 0 } - DELETE: { rps: 0 , total: 0 } - CALL: { rps: 0 , total: 0 } + REPLACE: { rps: 0 , total: 0 } + SELECT: { rps: 0 , total: 0 } + UPDATE: { rps: 0 , total: 0 } + DELETE_1_3: { rps: 0 , total: 0 } + DELETE: { rps: 0 , total: 0 } + CALL: { rps: 0 , total: 0 } ... diff --git a/test/box/fifo.lua b/test/box/fifo.lua new file mode 100644 index 0000000000000000000000000000000000000000..e8915fad3e026901c6b16d02b9fd95c6c6391fab --- /dev/null +++ b/test/box/fifo.lua @@ -0,0 +1,31 @@ +fifomax = 5 +function find_or_create_fifo(name) + fifo = box.select(0, 0, name) + if fifo == nil then + fifo = {} + for i = 1, fifomax do fifo[i] = 0 end + fifo = box.insert(0, name, 3, 3, unpack(fifo)) + end + return fifo +end +function fifo_push(name, val) + fifo = find_or_create_fifo(name) + top = box.unpack('i', fifo[1]) + bottom = box.unpack('i', fifo[2]) + if top == fifomax+2 then -- % size + top = 3 + elseif top ~= bottom then -- was not empty + top = top + 1 + end + if bottom == fifomax + 2 then -- % size + bottom = 3 + elseif bottom == top then + bottom = bottom + 1 + end + return box.update(0, name, '=p=p=p', 1, top, 2, bottom, top, val) +end +function fifo_top(name) + fifo = find_or_create_fifo(name) + top = box.unpack('i', fifo[1]) + return box.unpack('i', fifo[top]) +end diff --git a/test/box/lua.result b/test/box/lua.result index d706f2a7420cd3155a45af0698aa3cc9b4bdf57b..40ed053735cae9576e9e29ab961ef38f38c860a7 100644 --- a/test/box/lua.result +++ b/test/box/lua.result @@ -12,20 +12,26 @@ lua print(' lua says: hello') ... lua for n in pairs(box) do print(' - box.', n) end --- - - box.process + - box.delete - box.pack + - box.select + - box.insert + - box.unpack + - box.replace + - box.update + - box.process ... lua box.pack() --- -error: 'bad argument #1 to ''?'' (string expected, got no value)' +error: 'Lua error: bad argument #1 to ''?'' (string expected, got no value)' ... lua box.pack(1) --- -error: 'box.pack: unsupported pack format specifier ''1''' +error: 'Lua error: box.pack: argument count does not match the format' ... lua box.pack('abc') --- -error: 'bad argument #2 to ''?'' (string expected, got no value)' +error: 'Lua error: box.pack: argument count does not match the format' ... lua print(box.pack('a', ' - hello')) --- @@ -45,40 +51,251 @@ lua print(box.pack('www', 0x30, 0x30, 0x30)) ... lua print(box.pack('www', 0x3030, 0x30)) --- -error: '[string "return print(box.pack(''www'', 0x3030, 0x30))"]:1: bad argument #4 to ''pack'' (number expected, got no value)' +error: 'Lua error: [string "return print(box.pack(''www'', 0x3030, 0x30))"]:1: box.pack: argument count does not match the format' ... lua print(string.byte(box.pack('w', 212345), 1, 2)) --- 140250 ... +lua print(string.sub(box.pack('p', 1684234849), 2)) +--- +abcd +... lua print(box.pack('p', 'this string is 45 characters long 1234567890 ')) --- -this string is 45 characters long 1234567890 ... -lua box.process(13, box.pack('iiippp', 0, 1, 3, box.pack('i', 1), 'testing', 'lua rocks')) +lua box.process(13, box.pack('iiippp', 0, 1, 3, 1, 'testing', 'lua rocks')) --- - 1 - 1: {'testing', 'lua rocks'} ... -lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1))) +lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1)) --- - 0 - 1: {'testing', 'lua rocks'} ... -lua box.process(21, box.pack('iiip', 0, 1, 1, box.pack('i', 1))) +lua box.process(21, box.pack('iiip', 0, 1, 1, 1)) --- - 1 - 1: {'testing', 'lua rocks'} ... -lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1))) +lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1)) --- - 0 ... lua box.process(22, box.pack('iii', 0, 0, 0)) --- -error: 'box.process(CALL, ...) is not allowed' +error: 'Lua error: box.process(CALL, ...) is not allowed' ... call box.process('abc', 'def') -An error occurred: ER_PROC_LUA, 'Lua error: bad argument #2 to '?' (string expected, got no value)�' +An error occurred: ER_ILLEGAL_PARAMS, 'Illegal parameters, unsupported command code, check the error log�' call box.pack('test') -An error occurred: ER_PROC_LUA, 'Lua error: box.pack: unsupported pack format specifier ''�' +An error occurred: ER_PROC_LUA, 'Lua error: box.pack: argument count does not match the format�' +call box.pack('p', 'this string is 45 characters long 1234567890 ') +Found 1 tuple: +['-this string is 45 characters long 1234567890 '] +call box.pack('p', 'ascii symbols are visible starting from code 20') +Found 1 tuple: +['/ascii symbols are visible starting from code 20'] +lua function f1() return 'testing', 1, false, -1, 1.123, 1e123, nil end +--- +... +lua f1() +--- + - testing + - 1 + - false + - -1 + - 1.123 + - 1e+123 + - nil +... +call f1() +Found 7 tuples: +['testing'] +['1'] +['false'] +['-1'] +['1.123'] +['1e+123'] +['nil'] +lua f1=nil +--- +... +call f1() +An error occurred: ER_NO_SUCH_PROC, 'Procedure f1 is not defined�' +lua function f1() return f1 end +--- +... +call f1() +An error occurred: ER_PROC_RET, 'Return type 'function' is not supported in the binary protocol�' +insert into t0 values (1, 'test box delete') +Insert OK, 1 row affected +call box.delete(0, '���') +Found 1 tuple: +[1, 'test box delete'] +call box.delete(0, '���') +No match +insert into t0 values (1, 'test box delete') +Insert OK, 1 row affected +lua box.delete(0, 1) +--- + - 1: {'test box delete'} +... +lua box.delete(0, 1) +--- +... +insert into t0 values ('abcd', 'test box delete') +Insert OK, 1 row affected +call box.delete(0, '���') +No match +call box.delete(0, 'abcd') +Found 1 tuple: +[1684234849, 'test box delete'] +call box.delete(0, 'abcd') +No match +insert into t0 values ('abcd', 'test box delete') +Insert OK, 1 row affected +lua box.delete(0, 'abcd') +--- + - 1684234849: {'test box delete'} +... +lua box.delete(0, 'abcd') +--- +... +call box.select(0, 0, 'abcd') +No match +insert into t0 values ('abcd', 'test box.select()') +Insert OK, 1 row affected +call box.select(0, 0, 'abcd') +Found 1 tuple: +[1684234849, 'test box.select()'] +lua box.select(0, 0, 'abcd') +--- + - 1684234849: {'test box.select()'} +... +lua box.select(0, 0) +--- +error: 'Illegal parameters, key must be single valued' +... +lua box.select(0, 1) +--- +error: 'No index #1 is defined in namespace 0' +... +lua box.select(0) +--- +error: 'Illegal parameters, key must be single valued' +... +call box.replace(0, 'abcd', 'hello', 'world') +Found 1 tuple: +[1684234849, 'hello', 'world'] +call box.replace(0, 'defc', 'goodbye', 'universe') +Found 1 tuple: +[1667655012, 'goodbye', 'universe'] +call box.select(0, 0, 'abcd') +Found 1 tuple: +[1684234849, 'hello', 'world'] +call box.select(0, 0, 'defc') +Found 1 tuple: +[1667655012, 'goodbye', 'universe'] +call box.replace(0, 'abcd') +Found 1 tuple: +[1684234849] +call box.select(0, 0, 'abcd') +Found 1 tuple: +[1684234849] +call box.delete(0, 'abcd') +Found 1 tuple: +[1684234849] +call box.delete(0, 'defc') +Found 1 tuple: +[1667655012, 'goodbye', 'universe'] +call box.insert(0, 'test', 'old', 'abcd') +Found 1 tuple: +[1953719668, 'old', 1684234849] +call box.update(0, 'test', '=p=p', 0, 'pass', 1, 'new') +Found 1 tuple: +[1936941424, 'new', 1684234849] +call box.select(0, 0, 'pass') +Found 1 tuple: +[1936941424, 'new', 1684234849] +call box.update(0, 'miss', '+p', 2, '���') +No match +call box.update(0, 'pass', '+p', 2, '���') +Found 1 tuple: +[1936941424, 'new', 1684234850] +lua box.update(0, 'pass', '+p', 2, 1) +--- + - 1936941424: {'new', 1684234851} +... +call box.select(0, 0, 'pass') +Found 1 tuple: +[1936941424, 'new', 1684234851] +lua function field_x(namespace, key, field_index) return (box.select(namespace, 0, key))[field_index] end +--- +... +call field_x(0, 'pass', 0) +Found 1 tuple: +[1936941424] +call field_x(0, 'pass', 1) +Found 1 tuple: +['new'] +call box.delete(0, 'pass') +Found 1 tuple: +[1936941424, 'new', 1684234851] +lua dofile('/opt/local/work/tarantool-newlua/test/box/fifo.lua') +--- +... +lua fifo_max +--- + - nil +... +lua fifo_push('test', 1) +--- + - 1953719668: {3, 4, 1, 0, 0, 0, 0} +... +lua fifo_push('test', 2) +--- + - 1953719668: {4, 5, 1, 2, 0, 0, 0} +... +lua fifo_push('test', 3) +--- + - 1953719668: {5, 6, 1, 2, 3, 0, 0} +... +lua fifo_push('test', 4) +--- + - 1953719668: {6, 7, 1, 2, 3, 4, 0} +... +lua fifo_push('test', 5) +--- + - 1953719668: {7, 3, 1, 2, 3, 4, 5} +... +lua fifo_push('test', 6) +--- + - 1953719668: {3, 4, 6, 2, 3, 4, 5} +... +lua fifo_push('test', 7) +--- + - 1953719668: {4, 5, 6, 7, 3, 4, 5} +... +lua fifo_push('test', 8) +--- + - 1953719668: {5, 6, 6, 7, 8, 4, 5} +... +lua fifo_top('test') +--- + - 8 +... +lua box.delete(0, 'test') +--- + - 1953719668: {5, 6, 6, 7, 8, 4, 5} +... +lua fifo_top('test') +--- + - 0 +... +lua box.delete(0, 'test') +--- + - 1953719668: {3, 3, 0, 0, 0, 0, 0} +... diff --git a/test/box/lua.test b/test/box/lua.test index 38b24311a825313c0fcf325408154052e7177ea8..58487ef3a121cc1fdef1e848e06b30e65422e952 100644 --- a/test/box/lua.test +++ b/test/box/lua.test @@ -1,4 +1,6 @@ # encoding: tarantool +import os +import sys # Test Lua from admin console. Whenever producing output, # make sure it's a valid YAML. exec admin "lua" @@ -16,17 +18,85 @@ exec admin "lua print(box.pack('w', 0x30))" exec admin "lua print(box.pack('www', 0x30, 0x30, 0x30))" exec admin "lua print(box.pack('www', 0x3030, 0x30))" exec admin "lua print(string.byte(box.pack('w', 212345), 1, 2))" +exec admin "lua print(string.sub(box.pack('p', 1684234849), 2))" exec admin "lua print(box.pack('p', 'this string is 45 characters long 1234567890 '))" # Test the low-level box.process() call, which takes a binary packet # and passes it to box for execution. # insert: -exec admin "lua box.process(13, box.pack('iiippp', 0, 1, 3, box.pack('i', 1), 'testing', 'lua rocks'))" +exec admin "lua box.process(13, box.pack('iiippp', 0, 1, 3, 1, 'testing', 'lua rocks'))" # select: -exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1)))" +exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1))" # delete: -exec admin "lua box.process(21, box.pack('iiip', 0, 1, 1, box.pack('i', 1)))" +exec admin "lua box.process(21, box.pack('iiip', 0, 1, 1, 1))" # check delete: -exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, box.pack('i', 1)))" +exec admin "lua box.process(17, box.pack('iiiiiip', 0, 0, 0, 2^31, 1, 1, 1))" exec admin "lua box.process(22, box.pack('iii', 0, 0, 0))" exec sql "call box.process('abc', 'def')" exec sql "call box.pack('test')" +exec sql "call box.pack('p', 'this string is 45 characters long 1234567890 ')" +exec sql "call box.pack('p', 'ascii symbols are visible starting from code 20')" +exec admin "lua function f1() return 'testing', 1, false, -1, 1.123, 1e123, nil end" +exec admin "lua f1()" +exec sql "call f1()" +exec admin "lua f1=nil" +exec sql "call f1()" +exec admin "lua function f1() return f1 end" +exec sql "call f1()" + +exec sql "insert into t0 values (1, 'test box delete')" +exec sql "call box.delete(0, '\1\0\0\0')" +exec sql "call box.delete(0, '\1\0\0\0')" +exec sql "insert into t0 values (1, 'test box delete')" +exec admin "lua box.delete(0, 1)" +exec admin "lua box.delete(0, 1)" +exec sql "insert into t0 values ('abcd', 'test box delete')" +exec sql "call box.delete(0, '\1\0\0\0')" +exec sql "call box.delete(0, 'abcd')" +exec sql "call box.delete(0, 'abcd')" +exec sql "insert into t0 values ('abcd', 'test box delete')" +exec admin "lua box.delete(0, 'abcd')" +exec admin "lua box.delete(0, 'abcd')" +exec sql "call box.select(0, 0, 'abcd')" +exec sql "insert into t0 values ('abcd', 'test box.select()')" +exec sql "call box.select(0, 0, 'abcd')" +exec admin "lua box.select(0, 0, 'abcd')" +exec admin "lua box.select(0, 0)" +exec admin "lua box.select(0, 1)" +exec admin "lua box.select(0)" +exec sql "call box.replace(0, 'abcd', 'hello', 'world')" +exec sql "call box.replace(0, 'defc', 'goodbye', 'universe')" +exec sql "call box.select(0, 0, 'abcd')" +exec sql "call box.select(0, 0, 'defc')" +exec sql "call box.replace(0, 'abcd')" +exec sql "call box.select(0, 0, 'abcd')" +exec sql "call box.delete(0, 'abcd')" +exec sql "call box.delete(0, 'defc')" +exec sql "call box.insert(0, 'test', 'old', 'abcd')" +exec sql "call box.update(0, 'test', '=p=p', 0, 'pass', 1, 'new')" +exec sql "call box.select(0, 0, 'pass')" +exec sql "call box.update(0, 'miss', '+p', 2, '\1\0\0\0')" +exec sql "call box.update(0, 'pass', '+p', 2, '\1\0\0\0')" +exec admin "lua box.update(0, 'pass', '+p', 2, 1)" +exec sql "call box.select(0, 0, 'pass')" +exec admin "lua function field_x(namespace, key, field_index) return (box.select(namespace, 0, key))[field_index] end" +exec sql "call field_x(0, 'pass', 0)" +exec sql "call field_x(0, 'pass', 1)" +exec sql "call box.delete(0, 'pass')" +fifo_lua = os.path.abspath("box/fifo.lua") +# don't log the path name +sys.stdout.push_filter("exec admin .*", "exec admin ...") +exec admin "lua dofile('{0}')".format(fifo_lua) +sys.stdout.pop_filter() +exec admin "lua fifo_max" +exec admin "lua fifo_push('test', 1)" +exec admin "lua fifo_push('test', 2)" +exec admin "lua fifo_push('test', 3)" +exec admin "lua fifo_push('test', 4)" +exec admin "lua fifo_push('test', 5)" +exec admin "lua fifo_push('test', 6)" +exec admin "lua fifo_push('test', 7)" +exec admin "lua fifo_push('test', 8)" +exec admin "lua fifo_top('test')" +exec admin "lua box.delete(0, 'test')" +exec admin "lua fifo_top('test')" +exec admin "lua box.delete(0, 'test')" diff --git a/test/box/stat.result b/test/box/stat.result index 35383bd2f79d22db254e66a4afe6f1e345e6b2b8..37f80dd38859d4070850a2d8a8c5c0e774415fc1 100644 --- a/test/box/stat.result +++ b/test/box/stat.result @@ -26,13 +26,12 @@ Insert OK, 1 row affected show stat --- statistics: - INSERT: { rps: 2 , total: 10 } - SELECT_LIMIT: { rps: 0 , total: 0 } - SELECT: { rps: 0 , total: 0 } - UPDATE_FIELDS: { rps: 0 , total: 0 } - DELETE_1_3: { rps: 0 , total: 0 } - DELETE: { rps: 0 , total: 0 } - CALL: { rps: 0 , total: 0 } + REPLACE: { rps: 2 , total: 10 } + SELECT: { rps: 0 , total: 0 } + UPDATE: { rps: 0 , total: 0 } + DELETE_1_3: { rps: 0 , total: 0 } + DELETE: { rps: 0 , total: 0 } + CALL: { rps: 0 , total: 0 } ... # # restart server @@ -45,13 +44,12 @@ statistics: show stat --- statistics: - INSERT: { rps: 0 , total: 0 } - SELECT_LIMIT: { rps: 0 , total: 0 } - SELECT: { rps: 0 , total: 0 } - UPDATE_FIELDS: { rps: 0 , total: 0 } - DELETE_1_3: { rps: 0 , total: 0 } - DELETE: { rps: 0 , total: 0 } - CALL: { rps: 0 , total: 0 } + REPLACE: { rps: 0 , total: 0 } + SELECT: { rps: 0 , total: 0 } + UPDATE: { rps: 0 , total: 0 } + DELETE_1_3: { rps: 0 , total: 0 } + DELETE: { rps: 0 , total: 0 } + CALL: { rps: 0 , total: 0 } ... delete from t0 where k0 = 0 Delete OK, 1 row affected diff --git a/test/box_big/lua.result b/test/box_big/lua.result new file mode 100644 index 0000000000000000000000000000000000000000..c21c4e7a1f7110584e9033511cf1d58a731e3edd --- /dev/null +++ b/test/box_big/lua.result @@ -0,0 +1,8 @@ +insert into t1 values ('brave', 'new', 'world') +Insert OK, 1 row affected +call box.select(1, 1, 'new', 'world') +Found 1 tuple: +['brave', 'new', 'world'] +call box.delete(1, 'brave') +Found 1 tuple: +['brave', 'new', 'world'] diff --git a/test/box_big/lua.test b/test/box_big/lua.test new file mode 100644 index 0000000000000000000000000000000000000000..8f75aee5117f11d7f9b08268f20663243c1cc0f9 --- /dev/null +++ b/test/box_big/lua.test @@ -0,0 +1,4 @@ +# encoding: tarantool +exec sql "insert into t1 values ('brave', 'new', 'world')" +exec sql "call box.select(1, 1, 'new', 'world')" +exec sql "call box.delete(1, 'brave')" diff --git a/test/box_big/tarantool.cfg b/test/box_big/tarantool.cfg index 510ac98441311ff37da80d8b813046a8553cddb0..2247dae5d07b1efad64f11e69f3009f4cf96ebef 100644 --- a/test/box_big/tarantool.cfg +++ b/test/box_big/tarantool.cfg @@ -19,3 +19,15 @@ namespace[0].index[1].type = "TREE" namespace[0].index[1].unique = 0 namespace[0].index[1].key_field[0].fieldno = 1 namespace[0].index[1].key_field[0].type = "STR" + +namespace[1].enabled = 1 +namespace[1].index[0].type = "HASH" +namespace[1].index[0].unique = 1 +namespace[1].index[0].key_field[0].fieldno = 0 +namespace[1].index[0].key_field[0].type = "STR" +namespace[1].index[1].type = "TREE" +namespace[1].index[1].unique = 1 +namespace[1].index[1].key_field[0].fieldno = 1 +namespace[1].index[1].key_field[0].type = "STR" +namespace[1].index[1].key_field[1].fieldno = 2 +namespace[1].index[1].key_field[1].type = "STR" diff --git a/test/box_memcached/multiversioning.result b/test/box_memcached/multiversioning.result index 5fccf79ed9a98a086c841b1e2577bdc065b9461b..390a36142461b70dae23e79c44efc74442688d88 100644 --- a/test/box_memcached/multiversioning.result +++ b/test/box_memcached/multiversioning.result @@ -18,10 +18,9 @@ success: buf == reply show stat --- statistics: - INSERT: { rps: 0 , total: 0 } - SELECT_LIMIT: { rps: 0 , total: 0 } + REPLACE: { rps: 0 , total: 0 } SELECT: { rps: 0 , total: 0 } - UPDATE_FIELDS: { rps: 0 , total: 0 } + UPDATE: { rps: 0 , total: 0 } DELETE_1_3: { rps: 0 , total: 0 } DELETE: { rps: 0 , total: 0 } CALL: { rps: 0 , total: 0 } diff --git a/test/lib/admin_connection.py b/test/lib/admin_connection.py index 32562fcd9fd857c4576213e57ec62d411418333c..26cb244a4ea67ad66c898ff203fdaea9b5c4c222 100644 --- a/test/lib/admin_connection.py +++ b/test/lib/admin_connection.py @@ -27,7 +27,7 @@ import sys import re from tarantool_connection import TarantoolConnection -is_admin_re = re.compile("^\s*(show|save|exec|exit|reload|help)", re.I) +is_admin_re = re.compile("^\s*(show|save|lua|exit|reload|help)", re.I) ADMIN_SEPARATOR = '\n' diff --git a/test/lib/sql.g b/test/lib/sql.g index fd768357caace9953e5c8e7de14425ab84a16d01..2e41965d5681285c6626438bb1c80f1bb41efa4e 100644 --- a/test/lib/sql.g +++ b/test/lib/sql.g @@ -63,8 +63,8 @@ parser sql: {{ return disjunction }} rule opt_limit: {{ return 0xffffffff }} | LIMIT NUM {{ return int(NUM) }} - rule value_list: '\(' expr {{ value_list = [expr] }} - [("," expr {{ value_list.append(expr) }} )+] + rule value_list: '\(' {{ value_list = [] }} + [expr {{ value_list = [expr] }} [("," expr {{ value_list.append(expr) }} )+]] '\)' {{ return value_list }} rule update_list: predicate {{ update_list = [predicate] }} [(',' predicate {{ update_list.append(predicate) }})+] diff --git a/test/lib/sql.py b/test/lib/sql.py index b9621195b994c69500b615bfc683854b02416635..ad5b92331629c14eea1e855c43480b60d9b64a1b 100644 --- a/test/lib/sql.py +++ b/test/lib/sql.py @@ -166,14 +166,16 @@ class sql(runtime.Parser): def value_list(self, _parent=None): _context = self.Context(_parent, self._scanner, 'value_list', []) self._scan("'\\('", context=_context) - expr = self.expr(_context) - value_list = [expr] - if self._peek('","', "'\\)'", context=_context) == '","': - while 1: - self._scan('","', context=_context) - expr = self.expr(_context) - value_list.append(expr) - if self._peek('","', "'\\)'", context=_context) != '","': break + value_list = [] + if self._peek("'\\)'", '","', 'NUM', 'STR', context=_context) in ['NUM', 'STR']: + expr = self.expr(_context) + value_list = [expr] + if self._peek('","', "'\\)'", context=_context) == '","': + while 1: + self._scan('","', context=_context) + expr = self.expr(_context) + value_list.append(expr) + if self._peek('","', "'\\)'", context=_context) != '","': break self._scan("'\\)'", context=_context) return value_list diff --git a/test/lib/sql_ast.py b/test/lib/sql_ast.py index 4430f5a8e50fe271c6da250e01b05e20c4b058ac..8a7538fc90d847823c2f3c98a8eec447bb49a7eb 100644 --- a/test/lib/sql_ast.py +++ b/test/lib/sql_ast.py @@ -311,7 +311,9 @@ class StatementCall(StatementSelect): def __init__(self, proc_name, value_list): self.proc_name = proc_name - self.value_list= value_list +# the binary protocol passes everything into procedure as strings +# convert input to strings to avoid data mangling by the protocol + self.value_list = map(lambda val: str(val), value_list) def pack(self): buf = ctypes.create_string_buffer(PACKET_BUF_LEN)