[tarantool-patches] [PATCH v3 5/7] box: ephemeral space creation and deletion in Lua

  • From: imeevma@xxxxxxxxxxxxx
  • To: tarantool-patches@xxxxxxxxxxxxx
  • Date: Tue, 24 Jul 2018 14:58:19 +0300

Import functions to create ephemeral space in Lua and some
its methods that do not require index.

Part of #3375.
---
 src/box/lua/misc.cc               |  21 ++++
 src/box/lua/misc.h                |  12 +++
 src/box/lua/schema.lua            |  87 +++++++++++++++
 src/box/lua/space.cc              | 216 ++++++++++++++++++++++++++++++++------
 test/box/ephemeral_space.result   | 165 +++++++++++++++++++++++++++++
 test/box/ephemeral_space.test.lua |  58 ++++++++++
 test/engine/iterator.result       |   2 +-
 7 files changed, 526 insertions(+), 35 deletions(-)
 create mode 100644 test/box/ephemeral_space.result
 create mode 100644 test/box/ephemeral_space.test.lua

diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc
index bc76065..13ca18c 100644
--- a/src/box/lua/misc.cc
+++ b/src/box/lua/misc.cc
@@ -39,6 +39,8 @@
 #include "box/port.h"
 #include "box/lua/tuple.h"
 
+static uint32_t CTID_STRUCT_SPACE_POINTER = 0;
+
 /** {{{ Miscellaneous utils **/
 
 char *
@@ -55,6 +57,19 @@ lbox_encode_tuple_on_gc(lua_State *L, int idx, size_t *p_len)
        return (char *) region_join_xc(gc, *p_len);
 }
 
+struct space *
+lua_checkephemeralspace(struct lua_State *L, int idx)
+{
+       uint32_t ctypeid = 0;
+       void *data = luaL_checkcdata(L, idx, &ctypeid);
+       if (ctypeid != CTID_STRUCT_SPACE_POINTER) {
+               luaL_error(L, "Invalid argument #%d (space expected, got %s)",
+                          idx, lua_typename(L, ctypeid));
+               return NULL;
+       }
+       return *(struct space **) data;
+}
+
 /* }}} */
 
 /** {{{ Lua/C implementation of index:select(): used only by Vinyl **/
@@ -115,6 +130,12 @@ lbox_select(lua_State *L)
 void
 box_lua_misc_init(struct lua_State *L)
 {
+       int rc = luaL_cdef(L, "struct space;");
+       assert(rc == 0);
+       (void) rc;
+       CTID_STRUCT_SPACE_POINTER = luaL_ctypeid(L, "struct space *");
+       assert(CTID_STRUCT_SPACE_POINTER != 0);
+
        static const struct luaL_Reg boxlib_internal[] = {
                {"select", lbox_select},
                {NULL, NULL}
diff --git a/src/box/lua/misc.h b/src/box/lua/misc.h
index dfedfe3..6162baa 100644
--- a/src/box/lua/misc.h
+++ b/src/box/lua/misc.h
@@ -42,6 +42,18 @@ struct lua_State;
 char *
 lbox_encode_tuple_on_gc(struct lua_State *L, int idx, size_t *p_len);
 
+/**
+ * Get space from Lua stack.
+ *
+ * @param L Lua stack to get space from.
+ * @param idx Index by which to get the space from @a L.
+ *
+ * @retval struct space *space - success.
+ * @retval NULL - error.
+ */
+struct space *
+lua_checkephemeralspace(struct lua_State *L, int idx);
+
 void
 box_lua_misc_init(struct lua_State *L);
 
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index d14dd74..5962ce2 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -477,6 +477,63 @@ box.schema.space.create = function(name, options)
     return box.space[id], "created"
 end
 
+local space_new_ephemeral = box.internal.space.space_new_ephemeral
+box.internal.space.space_new_ephemeral = nil
+local space_delete_ephemeral = box.internal.space.space_delete_ephemeral
+box.internal.space.space_delete_ephemeral = nil
+local space_ephemeral_methods = box.internal.space_ephemeral_methods
+box.internal.space_ephemeral_methods = nil
+local space_ephemeral_mt = {}
+
+box.schema.space.create_ephemeral = function(options)
+    local options_template = {
+        engine = 'string',
+        field_count = 'number',
+        format = 'table',
+    }
+    local options_defaults = {
+        engine = 'memtx',
+        field_count = 0,
+    }
+    check_param_table(options, options_template)
+    options = update_param_table(options, options_defaults)
+    local format = options.format and options.format or {}
+    check_param(format, 'format', 'table')
+    format = update_format(format)
+
+    local space = {}
+    space.space = space_new_ephemeral(options.engine,
+                                      options.field_count, format)
+    space.space_format = format
+    space.engine = options.engine
+    space.field_count = options.field_count
+    space.index = {}
+    setmetatable(space, space_ephemeral_mt)
+    -- Set GC for result
+    space.proxy = newproxy(true)
+    getmetatable(space.proxy).__gc = function(self)
+        space:drop()
+    end
+    return space
+end
+
+box.schema.space.drop_ephemeral = function(space)
+    check_param(space.space, 'space', 'cdata')
+    space_delete_ephemeral(space.space)
+    getmetatable(space.proxy).__gc = nil
+    for k,_ in pairs(space) do
+        space[k] = nil
+    end
+    local dropped_mt = {
+        __index = function()
+            error('The space is dropped and can not be used')
+        end
+    }
+    setmetatable(space, dropped_mt)
+end
+
+box.schema.create_ephemeral_space = box.schema.space.create_ephemeral
+
 -- space format - the metadata about space fields
 function box.schema.space.format(id, format)
     local _space = box.space._space
@@ -1080,6 +1137,12 @@ local function check_space_arg(space, method)
         error(string.format(fmt, method, method))
     end
 end
+local function check_ephemeral_space_arg(space, method)
+    if type(space) ~= 'table' or param_type(space.space) ~= 'cdata' then
+        local fmt = 'Use space:%s(...) instead of space.%s(...)'
+        error(string.format(fmt, method, method))
+    end
+end
 box.internal.check_space_arg = check_space_arg -- for net.box
 
 -- Helper function for nicer error messages
@@ -1505,6 +1568,30 @@ end
 space_mt.frommap = box.internal.space.frommap
 space_mt.__index = space_mt
 
+-- Metatable for ephemeral space
+space_ephemeral_mt.format = function(space)
+    check_ephemeral_space_arg(space, 'format')
+    return space.space_format
+end
+space_ephemeral_mt.run_triggers = function(space, yesno)
+    check_ephemeral_space_arg(space, 'run_triggers')
+    builtin.space_run_triggers(space.space, yesno)
+end
+space_ephemeral_mt.frommap = function(space, map, options)
+    check_ephemeral_space_arg(space, 'frommap')
+    if type(map) ~= 'table' then
+        error('Usage: space:frommap(map, opts)')
+    end
+    options = options or {}
+    return space_ephemeral_methods.frommap(space.space, map, options)
+end
+space_ephemeral_mt.bsize = function(space)
+    check_ephemeral_space_arg(space, 'bsize')
+    return builtin.space_bsize(space.space)
+end
+space_ephemeral_mt.drop = box.schema.space.drop_ephemeral
+space_ephemeral_mt.__index = space_ephemeral_mt
+
 box.schema.index_mt = base_index_mt
 box.schema.memtx_index_mt = memtx_index_mt
 box.schema.vinyl_index_mt = vinyl_index_mt
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index ca3fefc..6f21907 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -49,6 +49,7 @@ extern "C" {
 #include "box/sequence.h"
 #include "box/coll_id_cache.h"
 #include "box/replication.h" /* GROUP_LOCAL */
+#include "box/lua/misc.h"
 
 /**
  * Trigger function for all spaces
@@ -441,19 +442,154 @@ static struct trigger on_alter_space_in_lua = {
 };
 
 /**
+ * Create an ephemeral space and push in on Lua stack.
+ *
+ * @param L Lua stack in which ephemeral space will be pushed to.
+ * @param space_def Space definition of ephemeral space.
+ * @param index_def Index definition of new index for ephemeral
+ * space.
+ *
+ * @retval not nil ephemeral space created.
+ * @retval nil error, A reason is returned in
+ * the second value.
+ */
+static inline int
+box_space_new_ephemeral(struct lua_State *L, struct space_def *space_def,
+                       struct index_def *index_def)
+{
+       struct rlist key_list;
+       rlist_create(&key_list);
+       if (index_def != NULL)
+               rlist_add_entry(&key_list, index_def, link);
+       struct space *space = space_new_ephemeral(space_def, &key_list);
+       space_def_delete(space_def);
+       if (index_def != NULL)
+               index_def_delete(index_def);
+       if (space == NULL)
+               return luaT_error(L);
+       uint32_t ctypeid = luaL_ctypeid(L, "struct space *");
+       struct space **ptr =
+               (struct space **) luaL_pushcdata(L, ctypeid);
+       *ptr = space;
+       return 1;
+}
+
+/**
+ * Create an ephemeral space.
+ *
+ * @param L Lua stack to next get arguments from:
+ * const char *engine_name, uint32_t field_count,
+ * tuple format
+ *
+ * @retval not nil ephemeral space created.
+ * @retval nil error, A reason is returned in
+ * the second value.
+ */
+static int
+lbox_space_new_ephemeral(struct lua_State *L)
+{
+       if (lua_gettop(L) != 3 || !lua_istable(L, 3))
+               return luaL_error(L, "Error with creating ephemeral space");
+       const char *engine_name = luaL_checkstring (L, 1);
+       uint32_t exact_field_count = luaL_checknumber (L, 2);
+       size_t format_len;
+       const char *format = lbox_encode_tuple_on_gc(L, 3, &format_len);
+
+       struct region *region = &fiber()->gc;
+       uint32_t field_count;
+       struct field_def *fields;
+       const char *name = "ephemeral";
+       uint32_t name_len = strlen(name);
+
+       if (space_format_decode(&fields, format, &field_count, name, name_len,
+                               ER_CREATE_SPACE, region) != 0)
+               return luaT_error(L);
+       if (exact_field_count != 0 && exact_field_count < field_count) {
+               return luaL_error(L, "exact_field_count must be either 0 or"\
+                                 ">= formatted field count");
+       }
+       struct space_def *space_def =
+               space_def_new(0, 0, exact_field_count, name, name_len,
+                             engine_name, strlen(engine_name),
+                             &space_opts_default, fields, field_count);
+       space_def_destroy_fields(fields, field_count);
+       if (space_def == NULL)
+               return luaT_error(L);
+       return box_space_new_ephemeral(L, space_def, NULL);
+}
+
+/**
+ * Delete an ephemeral space.
+ *
+ * @param L Lua stack to get space from.
+ */
+static int
+lbox_space_delete_ephemeral(struct lua_State *L)
+{
+       if (lua_gettop(L) != 1)
+               return luaL_error(L, "Usage: ephemeral_space:drop()");
+       struct space *space = lua_checkephemeralspace(L, 1);
+       space_delete(space);
+       return 0;
+}
+
+/**
  * Make a tuple or a table Lua object by map.
- * @param Lua space object.
- * @param Lua map table object.
- * @param Lua opts table object (optional).
+ *
+ * @param L Lua stack to next get table map from.
+ * Map should be on second position in this stack.
+ * @param space space to get format from.
+ * @param table flag to decide what to return.
+ *
+ * @retval not nil A tuple or a table conforming to a space
+ * format.
+ * @retval nil, err Can not built a tuple. A reason is returned
+ * in the second value.
+ */
+static inline int
+box_space_frommap(struct lua_State *L, struct space *space, bool table)
+{
+       assert(space->format != NULL);
+       struct tuple_dictionary *dict = space->format->dict;
+       lua_createtable(L, space->def->field_count, 0);
+
+       lua_pushnil(L);
+       while (lua_next(L, 2) != 0) {
+               uint32_t fieldno;
+               size_t key_len;
+               const char *key = lua_tolstring(L, -2, &key_len);
+               uint32_t key_hash = lua_hashstring(L, -2);
+               if (tuple_fieldno_by_name(dict, key, key_len, key_hash,
+                                         &fieldno)) {
+                       lua_pushnil(L);
+                       lua_pushstring(L, tt_sprintf("Unknown field '%s'",
+                                                    key));
+                       return 2;
+               }
+               lua_rawseti(L, -3, fieldno+1);
+       }
+       if (table)
+               return 1;
+
+       lua_replace(L, 1);
+       lua_settop(L, 1);
+       return lbox_tuple_new(L);
+}
+
+/**
+ * Make a tuple or a table Lua object by map for usual
+ * spaces.
+ *
+ * @param L Lua stack to next get arguments from:
+ * uint32_t space_id, table map, table options (optional)
  * @retval not nil A tuple or a table conforming to a space
- *         format.
- * @retval nil, err Can not built a tuple. A reason is returned in
- *         the second value.
+ * format.
+ * @retval nil, err Can not built a tuple. A reason is returned
+ * in the second value.
  */
 static int
 lbox_space_frommap(struct lua_State *L)
 {
-       struct tuple_dictionary *dict = NULL;
        uint32_t id = 0;
        struct space *space = NULL;
        int argc = lua_gettop(L);
@@ -478,36 +614,35 @@ lbox_space_frommap(struct lua_State *L)
                                             "doesn't exist", id));
                return 2;
        }
-       assert(space->format != NULL);
-
-       dict = space->format->dict;
-       lua_createtable(L, space->def->field_count, 0);
-
-       lua_pushnil(L);
-       while (lua_next(L, 2) != 0) {
-               uint32_t fieldno;
-               size_t key_len;
-               const char *key = lua_tolstring(L, -2, &key_len);
-               uint32_t key_hash = lua_hashstring(L, -2);
-               if (tuple_fieldno_by_name(dict, key, key_len, key_hash,
-                                         &fieldno)) {
-                       lua_pushnil(L);
-                       lua_pushstring(L, tt_sprintf("Unknown field '%s'",
-                                                    key));
-                       return 2;
-               }
-               lua_rawseti(L, -3, fieldno+1);
-       }
-       if (table)
-               return 1;
-
-       lua_replace(L, 1);
-       lua_settop(L, 1);
-       return lbox_tuple_new(L);
+       return box_space_frommap(L, space, table);
 usage_error:
        return luaL_error(L, "Usage: space:frommap(map, opts)");
 }
 
+/**
+ * Make a tuple or a table Lua object by map for ephemeral
+ * spaces.
+ *
+ * @param L Lua stack to next get arguments from:
+ * struct space *space, table map, table options
+ * @retval not nil A tuple or a table conforming to a space
+ * format.
+ * @retval nil, err Can not built a tuple. A reason is returned
+ * in the second value.
+ */
+static int
+lbox_space_frommap_ephemeral(struct lua_State *L)
+{
+       if (lua_gettop(L) != 3 || !lua_istable(L, 2) || !lua_istable(L, 3))
+               return luaL_error(L, "Usage: space:frommap(map, opts)");
+       struct space *space = lua_checkephemeralspace(L, 1);
+       lua_getfield(L, 3, "table");
+       if (!lua_isboolean(L, -1) && !lua_isnil(L, -1))
+               return luaL_error(L, "Usage: space:frommap(map, opts)");
+       bool table = lua_toboolean(L, -1);
+       return box_space_frommap(L, space, table);
+}
+
 void
 box_lua_space_init(struct lua_State *L)
 {
@@ -595,12 +730,25 @@ box_lua_space_init(struct lua_State *L)
        lua_setfield(L, -2, "REPLICA_MAX");
        lua_pushnumber(L, SQL_BIND_PARAMETER_MAX);
        lua_setfield(L, -2, "SQL_BIND_PARAMETER_MAX");
-       lua_pop(L, 2); /* box, schema */
+       lua_pop(L, 1); /* schema */
+       lua_getfield(L, -1, "internal");
+       lua_newtable(L);
+       lua_setfield(L, -2, "space_ephemeral_methods");
+       lua_pop(L, 2); /* box, internal */
 
        static const struct luaL_Reg space_internal_lib[] = {
                {"frommap", lbox_space_frommap},
+               {"space_new_ephemeral", lbox_space_new_ephemeral},
+               {"space_delete_ephemeral", lbox_space_delete_ephemeral},
                {NULL, NULL}
        };
        luaL_register(L, "box.internal.space", space_internal_lib);
        lua_pop(L, 1);
+       static const struct luaL_Reg space_ephemeral_lib[] = {
+               {"frommap", lbox_space_frommap_ephemeral},
+               {NULL, NULL}
+       };
+       luaL_register(L, "box.internal.space_ephemeral_methods",
+                     space_ephemeral_lib);
+       lua_pop(L, 1);
 }
diff --git a/test/box/ephemeral_space.result b/test/box/ephemeral_space.result
new file mode 100644
index 0000000..b0e91bb
--- /dev/null
+++ b/test/box/ephemeral_space.result
@@ -0,0 +1,165 @@
+-- Ephemeral space: create and drop
+s = box.schema.space.create_ephemeral()
+---
+...
+s.index
+---
+- []
+...
+s.engine
+---
+- memtx
+...
+s.field_count
+---
+- 0
+...
+s:drop()
+---
+...
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+---
+...
+options = {engine = 'memtx', field_count = 7, format = format}
+---
+...
+s = box.schema.space.create_ephemeral(options)
+---
+...
+s.index
+---
+- []
+...
+s.engine
+---
+- memtx
+...
+s.field_count
+---
+- 7
+...
+s:drop()
+---
+...
+s = box.schema.space.create_ephemeral({engine = 'other'})
+---
+- error: Space engine 'other' does not exist
+...
+s = box.schema.space.create_ephemeral({field_count = 'asd'})
+---
+- error: Illegal parameters, options parameter 'field_count' should be of type 
number
+...
+s = box.schema.space.create_ephemeral({format = 'a'})
+---
+- error: Illegal parameters, options parameter 'format' should be of type table
+...
+-- Multiple creation and drop
+for j = 1,10 do for i=1,10 do s = box.schema.space.create_ephemeral(); 
s:drop(); end; collectgarbage('collect'); end
+---
+...
+-- Multiple drop
+s = box.schema.space.create_ephemeral()
+---
+...
+s:drop()
+---
+...
+s:drop()
+---
+- error: 'builtin/box/schema.lua:529: The space is dropped and can not be used'
+...
+-- Drop using function from box.schema
+s = box.schema.space.create_ephemeral()
+---
+...
+box.schema.space.drop_ephemeral(s)
+---
+...
+s
+---
+- []
+...
+-- Ephemeral space: methods
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+---
+...
+options = {engine = 'memtx', field_count = 7, format = format}
+---
+...
+s = box.schema.space.create_ephemeral(options)
+---
+...
+s:format()
+---
+- - name: field1
+    type: unsigned
+  - name: field2
+    type: string
+...
+s:run_triggers(true)
+---
+...
+s:drop()
+---
+...
+format = {}
+---
+...
+format[1] = {name = 'aaa', type = 'unsigned'}
+---
+...
+format[2] = {name = 'bbb', type = 'unsigned'}
+---
+...
+format[3] = {name = 'ccc', type = 'unsigned'}
+---
+...
+format[4] = {name = 'ddd', type = 'unsigned'}
+---
+...
+s = box.schema.space.create_ephemeral({format = format})
+---
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4})
+---
+- [2, 4, 3, 1]
+...
+s:frommap({ddd = 1, aaa = 2, bbb = 3})
+---
+- [2, 3, null, 1]
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, eee = 4})
+---
+- null
+- Unknown field 'eee'
+...
+s:frommap()
+---
+- error: 'builtin/box/schema.lua:1583: Usage: space:frommap(map, opts)'
+...
+s:frommap({})
+---
+- []
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = true})
+---
+- - 2
+  - 4
+  - 3
+  - 1
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = false})
+---
+- [2, 4, 3, 1]
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = box.NULL})
+---
+- [2, null, 3, 1]
+...
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
+---
+- [2, 4, 3, 1]
+...
+s:drop()
+---
+...
diff --git a/test/box/ephemeral_space.test.lua 
b/test/box/ephemeral_space.test.lua
new file mode 100644
index 0000000..a7e3404
--- /dev/null
+++ b/test/box/ephemeral_space.test.lua
@@ -0,0 +1,58 @@
+-- Ephemeral space: create and drop
+
+s = box.schema.space.create_ephemeral()
+s.index
+s.engine
+s.field_count
+s:drop()
+
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+options = {engine = 'memtx', field_count = 7, format = format}
+s = box.schema.space.create_ephemeral(options)
+s.index
+s.engine
+s.field_count
+s:drop()
+
+s = box.schema.space.create_ephemeral({engine = 'other'})
+s = box.schema.space.create_ephemeral({field_count = 'asd'})
+s = box.schema.space.create_ephemeral({format = 'a'})
+
+-- Multiple creation and drop
+for j = 1,10 do for i=1,10 do s = box.schema.space.create_ephemeral(); 
s:drop(); end; collectgarbage('collect'); end
+
+-- Multiple drop
+s = box.schema.space.create_ephemeral()
+s:drop()
+s:drop()
+
+-- Drop using function from box.schema
+s = box.schema.space.create_ephemeral()
+box.schema.space.drop_ephemeral(s)
+s
+
+
+-- Ephemeral space: methods
+format = {{name='field1', type='unsigned'}, {name='field2', type='string'}}
+options = {engine = 'memtx', field_count = 7, format = format}
+s = box.schema.space.create_ephemeral(options)
+s:format()
+s:run_triggers(true)
+s:drop()
+
+format = {}
+format[1] = {name = 'aaa', type = 'unsigned'}
+format[2] = {name = 'bbb', type = 'unsigned'}
+format[3] = {name = 'ccc', type = 'unsigned'}
+format[4] = {name = 'ddd', type = 'unsigned'}
+s = box.schema.space.create_ephemeral({format = format})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4})
+s:frommap({ddd = 1, aaa = 2, bbb = 3})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, eee = 4})
+s:frommap()
+s:frommap({})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = true})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {table = false})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = box.NULL})
+s:frommap({ddd = 1, aaa = 2, ccc = 3, bbb = 4}, {dummy = true})
+s:drop()
diff --git a/test/engine/iterator.result b/test/engine/iterator.result
index 98b0b3e..2552ddd 100644
--- a/test/engine/iterator.result
+++ b/test/engine/iterator.result
@@ -4213,7 +4213,7 @@ s:replace{35}
 ...
 state, value = gen(param,state)
 ---
-- error: 'builtin/box/schema.lua:1051: usage: next(param, state)'
+- error: 'builtin/box/schema.lua:1108: usage: next(param, state)'
 ...
 value
 ---
-- 
2.7.4


Other related posts:

  • » [tarantool-patches] [PATCH v3 5/7] box: ephemeral space creation and deletion in Lua - imeevma