Fix a bug when Lua transaction was rolled back
twice when Lua procedure was invoked from CALL.

Change the way arguments to and from CALL
statement in the binary protocol are passed:
we used to pass everything from the wire as a single
binary blob. Now every field of the received
tuple is passed in as a separate string argument.

Everything passed back from Lua is converted to a tuple. This
makes CALL response similar to one of SELECT.

Improve box.pack() to convert its argument to integer when
necessary. Add an argument count check to box.pack().
Additionally, now box.pack() can be used to pack
operations of 'UPDATE'.

Use tarantool_lua_tostring() in iov_add_ret():
this function is now used to convert return values to
the binary protocol in box_lua.m. This is necessary
if we try to send boolean or nil over the
binary wire, since Lua C API lua_tostring()
does not convert them.

Add box.lua - a system Lua script
compiled into Tarantool and containing a bunch
of Lua code preloaded at startup.

Populate box.lua with implementation
of basic Lua functions:
box.select(), box.update(), box.replace(),
box.insert() (currently an alias for box.replace()),
box.delete(). They are all wrappers around box.process().

Move box_lua_init() to the beginning of mod_init(),
since Lua in future can be used in recover().

Fix a wrong assumption about the contents
of Lua stack in iov_add_multret().

Update sql.g Python SQL grammar to allow
an empty argumeent list for procedure in CALL
statement.

Add initial documentation on Lua procedures.

Add more Lua tests.

Unrelated: rename INSERT to REPLACE, and UPDATE_FIELDS to
UPDATE.
Remove SELECT_LIMIT, which is not used any more.
---
 core/tarantool_lua.m                      |   70 ++++++++++++++++-----
 doc/user/language-reference.xml           |   70 ++++++++++++++++++++-
 include/tarantool.h                       |    3 +
 mod/box/CMakeLists.txt                    |   13 +++-
 mod/box/box.h                             |    9 +--
 mod/box/box.lua                           |   51 +++++++++++++++
 mod/box/box.m                             |   30 +++++-----
 mod/box/box_lua.m                         |   97 +++++++++++++++++++----------
 mod/box/memcached.m                       |    2 +-
 test/box/configuration.result             |    2 +-
 test/box/lua.result                       |  Bin 1945 -> 5366 bytes
 test/box/lua.test                         |   55 +++++++++++++++-
 test/box/show.result                      |    4 +-
 test/box/stat.result                      |    4 +-
 test/box_big/lua.result                   |    8 +++
 test/box_big/lua.test                     |    4 +
 test/box_big/tarantool.cfg                |   12 ++++
 test/box_memcached/multiversioning.result |    2 +-
 test/lib/admin_connection.py              |    2 +-
 test/lib/sql.g                            |    4 +-
 test/lib/sql.py                           |   18 +++---
 test/lib/sql_ast.py                       |    4 +-
 22 files changed, 369 insertions(+), 95 deletions(-)
 create mode 100644 mod/box/box.lua
 create mode 100644 test/box_big/lua.result
 create mode 100644 test/box_big/lua.test

diff --git a/core/tarantool_lua.m b/core/tarantool_lua.m
index bd63ba3..fd736cb 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);
@@ -138,9 +168,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 +283,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 5163f54..48a92d5 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 c7d561c..6049f07 100644
--- a/include/tarantool.h
+++ b/include/tarantool.h
@@ -66,6 +66,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 09cab62..01f9535 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 8ca28ca..986b7d6 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 0000000..07fce68
--- /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 888a98c..e53d4c8 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);
@@ -1032,7 +1032,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));
@@ -1348,6 +1348,8 @@ mod_init(void)
 
        title("loading");
 
+       box_lua_init();
+
        /* initialization namespaces */
        namespace_init();
 
@@ -1401,8 +1403,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 10ced13..995bb2a 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 a135b05..7f8cd23 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/box/configuration.result b/test/box/configuration.result
index fcb3ae0..bb9285d 100644
--- a/test/box/configuration.result
+++ b/test/box/configuration.result
@@ -7,7 +7,7 @@
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
+  REPLACE:       { rps:  0    , total:  0           }
   SELECT_LIMIT:  { rps:  0    , total:  0           }
   SELECT:        { rps:  0    , total:  0           }
   UPDATE_FIELDS: { rps:  0    , total:  0           }
diff --git a/test/box/lua.result b/test/box/lua.result
index 
d706f2a7420cd3155a45af0698aa3cc9b4bdf57b..8be25cde308111bed7262c498ac1035a4ef4c480
 100644
GIT binary patch
literal 5366
zcmb_gZExE)5YE0D=zlnLA84btXZbCOfnqR<ZY$=sMY?_}vOtz-M~Ey6qEyEi_TP6$
ziXtt`M&br2EK1=$cf5P<g^dfp)$8?MwTk2+5!(a;vDx@Z*oQpZLB30)WDWN`PW!J~
z{XYK0g%6$<fgX6y*EEZgyv+b;i}ZJLMuM?mopO8q5030gWRO6VK<Y=CY%j%Q2SNcA
zWDoFDpLHSW08c^#s6U51<~e`W0yK!WNqi4-`<`YZ;8NNTQ6hQfY{+;T`+<W{q@nL3
zrhf3?SW8OiQ@cDF&oYtCfGzOg<$CFd;AiV%!;>6-_8=F)*sqL1TjuDeHSot2jpSh$
z)*^>Qz;{0`c!xEq@lNO|O_^D3fhbUCD7Zw}95#L)+(UlP0iBF5jT)H03{0GwPV+-r
zbQCM6F(2c|Fjgfy3_Z#Zjsxm~^L<G|*@`R<S*M1fKc7I_q6ve?(ZC1}PVL>A-CCw5
zIe41vXs|EW;N@>6HC|^tFEY#pl~C4iY>SqZhFiGTw=-|1cu$l(j|W<lR^MOla!whj
zDbDEdLvJ`5PdZ)jaBY0%jR(WY;o(aZSu7op6hXY{)fi!}#@7f`p&;?X6V4|=mfuGb
zO6TBeJb~ce&wNauOhPO$o0NUi%d6`FRAlHqOU$uBon6<Awm0flzHPH8iqe!!!eF8+
z@Nf9baxQaBX|j|`A4)#R$_ia1>30VIHgEm!nC{D(Zc?w66PeJ7|BO(7`Oky}Rbf!h
zQaD5?8+fbjPy^Qevov@=aHOjJxbYbdYbIO&7>6`unv3vmzF2et!8?H5qKfHb)7tXT
z6v_bKwOdL~sA7r14*3de<~-3gA4CuoS;oT|yuZ2ouvonReZIK+JinR$^#wP7h-1F?
zV{CAlzrl8u$u1O$EYehD*al4B#b?O7$ZsAzsG_Z`N0P5ESj$>M%}0^xr~#jEKE1nJ
ze4V3ew=F%pkRPf&Cna0IVN(hL`(TmAeAfELo|m2MwtjeJg)f6B0=e5PMJ%xca`+y}
zXc=>?7Jf!a39C$Olv80iKxZA;@yl~DZq*31Dv}_NL;@?X-GQ=TJL!!nv+`q!q3lui
z^szwUgR4gp#de6PSWQ6HTve77on`7*p{Z4`Og_TY8~7WuMCh`ZD6S|>edJ5+dP%-F
z1?v{q9=i4$Q|M~d(;t+@>Vv1y)e_P1Ni}Zey~P)8t~!BL*PIU@Kiz%#`tFZAq9s!D
zGo?~kV8B+MNjprZDB&RzPHAXzFmA4<zbC0xS#b0IZ&Z1sn<|d99GD3oGs<lu;t)#1
z(lSbXoPcq5%tat#v`SBnIHck%mk;#3CZUbtRSu;R0eTcgo_$awCVcu6qmCV63;t@Q
z<~_?}y|;~Zoc(lhanV4|CTJ%S4go$2J?_1ftrQ5Vl~$7(McwM}p1j`64kcCGj`)%)
zYe;>R80x~F>Cnm_RpqqyXkCXD!Hy{nb~JlmS>0(>+FtT>haoVAt7{v@p_;4AgyDMb
zctG}WY&KE6^$m)sN$foLcm}5Pw0vk(TCdmkuugQWB3+%19^tghEKJ>Lj%l^4SW{bn
z;5*n9L}2Vg*n_Ygsmon8QJ9!oSd&&}RzWI7Nys1R>WEWGSsLkroZ$2zank27Fat^*
z?j=mD5~jQxt1@M>Q(xRL0OjrLpsVghskYlmAr~8u7*OV~g$S2BPRk;RzT=$4n=0dU
zdO4j;1|E)ojUtW~>t%<`CwX<;;(aV-b5jL*x^b=x$~-%6+WCiaB%f0Oo5fz>*t$Y0
zQc<a=XbZf@<KvhUt|y~Q?|M4Ds<v8^IcDnrx!D7Lo4ze?O)T}LL?zT$6TWTY)%9q4
zJ@&@Kak&LISLM}Fv(Ezs+rWb-f;l#CB4r?ZLlzUa%gLZ>EmdARLglFrgbs*w+@8t6
oR?ZX1rc6=Sslce~kT<QXCY~vvmAOCau!-k*-=XmHT~A2=1GJD9djJ3c

delta 320
zcmeySIg@|Fk%><PC#x~4PmW-WHB8E{&?`tx&bCr0%_}Y~D9A4=Nlj4zi72Gy7v(0F
zC=?f@CTFH)rWPrvs~f7TPoBUiI+>kGgg+@UMIo^$y)-v9uS7xFXmUEE>E!230-H;i
zco`@6Fxx7j$eSpX<O2-=8LY0ZppjRao0M8Kxq?w;vJVUIWMLNj$#0psCNE%FAOLfb
zhI*#Dj)I})WJlHxOu<vEiI{>qYz3HtbJ_eRe`8CUe4mST@;!E;$&rk_>_%YM*iX)9
TRGhqu-Cu(UzcZ>csB-}TLh5G~

diff --git a/test/box/lua.test b/test/box/lua.test
index 38b2431..71fc0a3 100644
--- a/test/box/lua.test
+++ b/test/box/lua.test
@@ -16,17 +16,64 @@ 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 sql "call box.delete(0, 'pass')"
diff --git a/test/box/show.result b/test/box/show.result
index 292ddb5..cc69412 100644
--- a/test/box/show.result
+++ b/test/box/show.result
@@ -1,7 +1,7 @@
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
+  REPLACE:       { rps:  0    , total:  0           }
   SELECT_LIMIT:  { rps:  0    , total:  0           }
   SELECT:        { rps:  0    , total:  0           }
   UPDATE_FIELDS: { rps:  0    , total:  0           }
@@ -75,7 +75,7 @@ configuration:
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
+  REPLACE:       { rps:  0    , total:  0           }
   SELECT_LIMIT:  { rps:  0    , total:  0           }
   SELECT:        { rps:  0    , total:  0           }
   UPDATE_FIELDS: { rps:  0    , total:  0           }
diff --git a/test/box/stat.result b/test/box/stat.result
index 35383bd..b5e4fba 100644
--- a/test/box/stat.result
+++ b/test/box/stat.result
@@ -26,7 +26,7 @@ Insert OK, 1 row affected
 show stat
 ---
 statistics:
-  INSERT:        { rps:  2    , total:  10          }
+  REPLACE:       { rps:  2    , total:  10          }
   SELECT_LIMIT:  { rps:  0    , total:  0           }
   SELECT:        { rps:  0    , total:  0           }
   UPDATE_FIELDS: { rps:  0    , total:  0           }
@@ -45,7 +45,7 @@ statistics:
 show stat
 ---
 statistics:
-  INSERT:        { rps:  0    , total:  0           }
+  REPLACE:       { rps:  0    , total:  0           }
   SELECT_LIMIT:  { rps:  0    , total:  0           }
   SELECT:        { rps:  0    , total:  0           }
   UPDATE_FIELDS: { rps:  0    , total:  0           }
diff --git a/test/box_big/lua.result b/test/box_big/lua.result
new file mode 100644
index 0000000..c21c4e7
--- /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 0000000..8f75aee
--- /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 510ac98..2247dae 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 5fccf79..aff3b86 100644
--- a/test/box_memcached/multiversioning.result
+++ b/test/box_memcached/multiversioning.result
@@ -18,7 +18,7 @@ success: buf == reply
 show stat
 ---
 statistics:
-  INSERT:            { rps:  0    , total:  0           }
+  REPLACE:           { rps:  0    , total:  0           }
   SELECT_LIMIT:      { rps:  0    , total:  0           }
   SELECT:            { rps:  0    , total:  0           }
   UPDATE_FIELDS:     { rps:  0    , total:  0           }
diff --git a/test/lib/admin_connection.py b/test/lib/admin_connection.py
index 32562fc..26cb244 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 fd76835..2e41965 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 b962119..ad5b923 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 4430f5a..8a7538f 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)
-- 
1.7.0.4


_______________________________________________
Mailing list: https://launchpad.net/~tarantool-developers
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~tarantool-developers
More help   : https://help.launchpad.net/ListHelp

Reply via email to