From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Vladislav Shpilevoy Subject: [PATCH v2 1/2] schema: move space_mt and index_mt definition out of space bless Date: Thu, 29 Mar 2018 17:31:44 +0300 Message-Id: In-Reply-To: References: <20180329105446.v427bzpwichq5ntb@esperanza> In-Reply-To: References: To: tarantool-patches@freelists.org Cc: vdavydov.dev@gmail.com, aleclarson List-ID: From: aleclarson Space_mt and index_mt are created individually for each space inside a giant space.bless function. It makes impossible to implement #3204: exposing space and index metatables to a user, because for this they must be global and shared between all spaces and indexes. Lets move their definition out of space.bless function, and do their duplicate inside. Needed #3204 --- src/box/lua/schema.lua | 608 +++++++++++++++++++--------------------- test/box-tap/schema_mt.test.lua | 79 ++++++ 2 files changed, 373 insertions(+), 314 deletions(-) create mode 100755 test/box-tap/schema_mt.test.lua diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 5a0f71559..ff89c8e58 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1074,221 +1074,321 @@ end internal.check_iterator_type = check_iterator_type -- export for net.box -function box.schema.space.bless(space) - local index_mt = {} - -- __len and __index - index_mt.len = function(index) - check_index_arg(index, 'len') - local ret = builtin.box_index_len(index.space_id, index.id) - if ret == -1 then - box.error() - end - return tonumber(ret) - end - -- index.bsize - index_mt.bsize = function(index) - check_index_arg(index, 'bsize') - local ret = builtin.box_index_bsize(index.space_id, index.id) - if ret == -1 then - box.error() - end - return tonumber(ret) - end - index_mt.__len = index_mt.len -- Lua 5.2 compatibility - index_mt.__newindex = function(table, index) - return error('Attempt to modify a read-only table') end - index_mt.__index = index_mt - -- min and max - index_mt.min_ffi = function(index, key) - check_index_arg(index, 'min') - local pkey, pkey_end = tuple_encode(key) - if builtin.box_index_min(index.space_id, index.id, - pkey, pkey_end, ptuple) ~= 0 then - box.error() -- error - elseif ptuple[0] ~= nil then - return tuple_bless(ptuple[0]) - else - return - end +local space_mt = {} +space_mt.__index = space_mt +box.schema.space_mt = space_mt + +function space_mt:len() + check_space_arg(self, 'len') + local pk = self.index[0] + if pk == nil then + return 0 -- empty space without indexes, return 0 end - index_mt.min_luac = function(index, key) - check_index_arg(index, 'min') - key = keify(key) - return internal.min(index.space_id, index.id, key); + return self.index[0]:len() +end +function space_mt:count(key, opts) + check_space_arg(self, 'count') + local pk = self.index[0] + if pk == nil then + return 0 -- empty space without indexes, return 0 end - index_mt.max_ffi = function(index, key) - check_index_arg(index, 'max') - local pkey, pkey_end = tuple_encode(key) - if builtin.box_index_max(index.space_id, index.id, - pkey, pkey_end, ptuple) ~= 0 then - box.error() -- error - elseif ptuple[0] ~= nil then - return tuple_bless(ptuple[0]) - else - return - end + return pk:count(key, opts) +end +function space_mt:bsize() + check_space_arg(self, 'bsize') + local s = builtin.space_by_id(self.id) + if s == nil then + box.error(box.error.NO_SUCH_SPACE, self.name) end - index_mt.max_luac = function(index, key) - check_index_arg(index, 'max') - key = keify(key) - return internal.max(index.space_id, index.id, key); + return builtin.space_bsize(s) +end +function space_mt:get(key) + check_space_arg(self, 'get') + return check_primary_index(self):get(key) +end +function space_mt:select(key, opts) + check_space_arg(self, 'select') + return check_primary_index(self):select(key, opts) +end +function space_mt:insert(tuple) + check_space_arg(self, 'insert') + return internal.insert(self.id, tuple) +end +function space_mt:replace(tuple) + check_space_arg(self, 'replace') + return internal.replace(self.id, tuple) +end +space_mt.put = space_mt.replace -- put is an alias for replace +function space_mt:update(key, ops) + check_space_arg(self, 'update') + return check_primary_index(self):update(key, ops) +end +function space_mt:upsert(tuple_key, ops, deprecated) + check_space_arg(self, 'upsert') + if deprecated ~= nil then + local msg = "Error: extra argument in upsert call: " + msg = msg .. tostring(deprecated) + msg = msg .. ". Usage :upsert(tuple, operations)" + box.error(box.error.PROC_LUA, msg) end - index_mt.random_ffi = function(index, rnd) - check_index_arg(index, 'random') - rnd = rnd or math.random() - if builtin.box_index_random(index.space_id, index.id, rnd, - ptuple) ~= 0 then - box.error() -- error - elseif ptuple[0] ~= nil then - return tuple_bless(ptuple[0]) - else - return - end + return internal.upsert(self.id, tuple_key, ops) +end +function space_mt:delete(key) + check_space_arg(self, 'delete') + return check_primary_index(self):delete(key) +end +-- Assumes that spaceno has a TREE (NUM) primary key +-- inserts a tuple after getting the next value of the +-- primary key and returns it back to the user +function space_mt:auto_increment(tuple) + check_space_arg(self, 'auto_increment') + local max_tuple = check_primary_index(self):max() + local max = 0 + if max_tuple ~= nil then + max = max_tuple[1] + end + table.insert(tuple, 1, max + 1) + return self:insert(tuple) +end +function space_mt:pairs(key, opts) + check_space_arg(self, 'pairs') + local pk = self.index[0] + if pk == nil then + -- empty space without indexes, return empty iterator + return fun.iter({}) + end + return pk:pairs(key, opts) +end +space_mt.__pairs = space_mt.pairs -- Lua 5.2 compatibility +space_mt.__ipairs = space_mt.pairs -- Lua 5.2 compatibility +function space_mt:truncate() + check_space_arg(self, 'truncate') + return internal.truncate(self.id) +end +function space_mt:format(format) + check_space_arg(self, 'format') + return box.schema.space.format(self.id, format) +end +function space_mt:drop() + check_space_arg(self, 'drop') + check_space_exists(self) + return box.schema.space.drop(self.id, self.name) +end +function space_mt:rename(name) + check_space_arg(self, 'rename') + check_space_exists(self) + return box.schema.space.rename(self.id, name) +end +function space_mt:create_index(name, options) + check_space_arg(self, 'create_index') + check_space_exists(self) + return box.schema.index.create(self.id, name, options) +end +function space_mt:run_triggers(yesno) + check_space_arg(self, 'run_triggers') + local s = builtin.space_by_id(self.id) + if s == nil then + box.error(box.error.NO_SUCH_SPACE, self.name) end - index_mt.random_luac = function(index, rnd) - check_index_arg(index, 'random') - rnd = rnd or math.random() - return internal.random(index.space_id, index.id, rnd); - end - -- iteration - index_mt.pairs_ffi = function(index, key, opts) - check_index_arg(index, 'pairs') - local pkey, pkey_end = tuple_encode(key) - local itype = check_iterator_type(opts, pkey + 1 >= pkey_end); - - local keybuf = ffi.string(pkey, pkey_end - pkey) - local pkeybuf = ffi.cast('const char *', keybuf) - local cdata = builtin.box_index_iterator(index.space_id, index.id, - itype, pkeybuf, pkeybuf + #keybuf); - if cdata == nil then - box.error() - end - return fun.wrap(iterator_gen, keybuf, - ffi.gc(cdata, builtin.box_iterator_free)) - end - index_mt.pairs_luac = function(index, key, opts) - check_index_arg(index, 'pairs') - key = keify(key) - local itype = check_iterator_type(opts, #key == 0); - local keymp = msgpack.encode(key) - local keybuf = ffi.string(keymp, #keymp) - local cdata = internal.iterator(index.space_id, index.id, itype, keymp); - return fun.wrap(iterator_gen_luac, keybuf, - ffi.gc(cdata, builtin.box_iterator_free)) - end - - -- index subtree size - index_mt.count_ffi = function(index, key, opts) - check_index_arg(index, 'count') - local pkey, pkey_end = tuple_encode(key) - local itype = check_iterator_type(opts, pkey + 1 >= pkey_end); - local count = builtin.box_index_count(index.space_id, index.id, - itype, pkey, pkey_end); - if count == -1 then - box.error() - end - return tonumber(count) - end - index_mt.count_luac = function(index, key, opts) - check_index_arg(index, 'count') - key = keify(key) - local itype = check_iterator_type(opts, #key == 0); - return internal.count(index.space_id, index.id, itype, key); - end - - index_mt.get_ffi = function(index, key) - check_index_arg(index, 'get') - local key, key_end = tuple_encode(key) - if builtin.box_index_get(index.space_id, index.id, - key, key_end, ptuple) ~= 0 then - return box.error() -- error - elseif ptuple[0] ~= nil then - return tuple_bless(ptuple[0]) - else - return - end + builtin.space_run_triggers(s, yesno) +end + +local index_mt = {} +index_mt.__index = index_mt +box.schema.index_mt = index_mt + +function index_mt:len() + check_index_arg(self, 'len') + local ret = builtin.box_index_len(self.space_id, self.id) + if ret == -1 then + box.error() end - index_mt.get_luac = function(index, key) - check_index_arg(index, 'get') - key = keify(key) - return internal.get(index.space_id, index.id, key) + return tonumber(ret) +end +function index_mt:bsize() + check_index_arg(self, 'bsize') + local ret = builtin.box_index_bsize(self.space_id, self.id) + if ret == -1 then + box.error() end - - local function check_select_opts(opts, key_is_nil) - local offset = 0 - local limit = 4294967295 - local iterator = check_iterator_type(opts, key_is_nil) - if opts ~= nil then - if opts.offset ~= nil then - offset = opts.offset - end - if opts.limit ~= nil then - limit = opts.limit - end - end - return iterator, offset, limit + return tonumber(ret) +end +index_mt.__len = index_mt.len -- Lua 5.2 compatibility +index_mt.__index = index_mt +function index_mt:min_ffi(key) + check_index_arg(self, 'min') + local pkey, pkey_end = tuple_encode(key) + if builtin.box_index_min(self.space_id, self.id, + pkey, pkey_end, ptuple) ~= 0 then + box.error() + elseif ptuple[0] ~= nil then + return tuple_bless(ptuple[0]) end +end +function index_mt:min_luac(key) + check_index_arg(self, 'min') + key = keify(key) + return internal.min(self.space_id, self.id, key) +end +function index_mt:max_ffi(key) + check_index_arg(self, 'max') + local pkey, pkey_end = tuple_encode(key) + if builtin.box_index_max(self.space_id, self.id, + pkey, pkey_end, ptuple) ~= 0 then + box.error() + elseif ptuple[0] ~= nil then + return tuple_bless(ptuple[0]) + end +end +function index_mt:max_luac(key) + check_index_arg(self, 'max') + key = keify(key) + return internal.max(self.space_id, self.id, key) +end +function index_mt:random_ffi(rnd) + check_index_arg(self, 'random') + rnd = rnd or math.random() + if builtin.box_index_random(self.space_id, self.id, rnd, ptuple) ~= 0 then + box.error() + elseif ptuple[0] ~= nil then + return tuple_bless(ptuple[0]) + end +end +function index_mt:random_luac(rnd) + check_index_arg(self, 'random') + rnd = rnd or math.random() + return internal.random(self.space_id, self.id, rnd) +end +function index_mt:pairs_ffi(key, opts) + check_index_arg(self, 'pairs') + local pkey, pkey_end = tuple_encode(key) + local itype = check_iterator_type(opts, pkey + 1 >= pkey_end) - index_mt.select_ffi = function(index, key, opts) - check_index_arg(index, 'select') - local key, key_end = tuple_encode(key) - local iterator, offset, limit = check_select_opts(opts, key + 1 >= key_end) - - local port = ffi.cast('struct port *', port_tuple) + local keybuf = ffi.string(pkey, pkey_end - pkey) + local pkeybuf = ffi.cast('const char *', keybuf) + local cdata = builtin.box_index_iterator(self.space_id, self.id, itype, + pkeybuf, pkeybuf + #keybuf) + if cdata == nil then + box.error() + end + return fun.wrap(iterator_gen, keybuf, + ffi.gc(cdata, builtin.box_iterator_free)) +end +function index_mt:pairs_luac(key, opts) + check_index_arg(self, 'pairs') + key = keify(key) + local itype = check_iterator_type(opts, #key == 0) + local keymp = msgpack.encode(key) + local keybuf = ffi.string(keymp, #keymp) + local cdata = internal.iterator(self.space_id, self.id, itype, keymp) + return fun.wrap(iterator_gen_luac, keybuf, + ffi.gc(cdata, builtin.box_iterator_free)) +end +function index_mt:count_ffi(key, opts) + check_index_arg(self, 'count') + local pkey, pkey_end = tuple_encode(key) + local itype = check_iterator_type(opts, pkey + 1 >= pkey_end) + local count = builtin.box_index_count(self.space_id, self.id, itype, pkey, + pkey_end) + if count == -1 then + box.error() + end + return tonumber(count) +end +function index_mt:count_luac(key, opts) + check_index_arg(self, 'count') + key = keify(key) + local itype = check_iterator_type(opts, #key == 0) + return internal.count(self.space_id, self.id, itype, key) +end +function index_mt:get_ffi(key) + check_index_arg(self, 'get') + local key, key_end = tuple_encode(key) + if builtin.box_index_get(self.space_id, self.id, key, key_end, + ptuple) ~= 0 then + return box.error() + elseif ptuple[0] ~= nil then + return tuple_bless(ptuple[0]) + end +end +function index_mt:get_luac(key) + check_index_arg(self, 'get') + key = keify(key) + return internal.get(self.space_id, self.id, key) +end - if builtin.box_select(index.space_id, index.id, - iterator, offset, limit, key, key_end, port) ~= 0 then - return box.error() +local function check_select_opts(opts, key_is_nil) + local offset = 0 + local limit = 4294967295 + local iterator = check_iterator_type(opts, key_is_nil) + if opts ~= nil then + if opts.offset ~= nil then + offset = opts.offset end - - local ret = {} - local entry = port_tuple.first - for i=1,tonumber(port_tuple.size),1 do - ret[i] = tuple_bless(entry.tuple) - entry = entry.next + if opts.limit ~= nil then + limit = opts.limit end - builtin.port_destroy(port); - return ret end + return iterator, offset, limit +end - index_mt.select_luac = function(index, key, opts) - check_index_arg(index, 'select') - local key = keify(key) - local iterator, offset, limit = check_select_opts(opts, #key == 0) - return internal.select(index.space_id, index.id, iterator, - offset, limit, key) - end +function index_mt:select_ffi(key, opts) + check_index_arg(self, 'select') + local key, key_end = tuple_encode(key) + local iterator, offset, limit = check_select_opts(opts, key + 1 >= key_end) - index_mt.update = function(index, key, ops) - check_index_arg(index, 'update') - return internal.update(index.space_id, index.id, keify(key), ops); - end - index_mt.delete = function(index, key) - check_index_arg(index, 'delete') - return internal.delete(index.space_id, index.id, keify(key)); - end + local port = ffi.cast('struct port *', port_tuple) - index_mt.info = function(index) - return internal.info(index.space_id, index.id); + if builtin.box_select(self.space_id, self.id, iterator, offset, limit, key, + key_end, port) ~= 0 then + return box.error() end - index_mt.drop = function(index) - check_index_arg(index, 'drop') - return box.schema.index.drop(index.space_id, index.id) - end - index_mt.rename = function(index, name) - check_index_arg(index, 'rename') - return box.schema.index.rename(index.space_id, index.id, name) + local ret = {} + local entry = port_tuple.first + for i=1,tonumber(port_tuple.size),1 do + ret[i] = tuple_bless(entry.tuple) + entry = entry.next end - index_mt.alter = function(index, options) - check_index_arg(index, 'alter') - if index.id == nil or index.space_id == nil then - box.error(box.error.PROC_LUA, "Usage: index:alter{opts}") - end - return box.schema.index.alter(index.space_id, index.id, options) + builtin.port_destroy(port) + return ret +end +function index_mt:select_luac(key, opts) + check_index_arg(self, 'select') + local key = keify(key) + local iterator, offset, limit = check_select_opts(opts, #key == 0) + return internal.select(self.space_id, self.id, iterator, offset, limit, key) +end +function index_mt:update(key, ops) + check_index_arg(self, 'update') + return internal.update(self.space_id, self.id, keify(key), ops) +end +function index_mt:delete(key) + check_index_arg(self, 'delete') + return internal.delete(self.space_id, self.id, keify(key)) +end +function index_mt:info() + return internal.info(self.space_id, self.id) +end +function index_mt:drop() + check_index_arg(self, 'drop') + return box.schema.index.drop(self.space_id, self.id) +end +function index_mt:rename(name) + check_index_arg(self, 'rename') + return box.schema.index.rename(self.space_id, self.id, name) +end +function index_mt:alter(options) + check_index_arg(self, 'alter') + if self.id == nil or self.space_id == nil then + box.error(box.error.PROC_LUA, "Usage: index:alter{opts}") end + return box.schema.index.alter(self.space_id, self.id, options) +end - -- true if reading operations may yield +function box.schema.space.bless(space) + local space_mt = table.deepcopy(space_mt) + local index_mt = table.deepcopy(index_mt) local read_yields = space.engine == 'vinyl' local read_ops = {'select', 'get', 'min', 'max', 'count', 'random', 'pairs'} for _, op in ipairs(read_ops) do @@ -1302,126 +1402,6 @@ function box.schema.space.bless(space) end index_mt.__pairs = index_mt.pairs -- Lua 5.2 compatibility index_mt.__ipairs = index_mt.pairs -- Lua 5.2 compatibility - -- - local space_mt = {} - space_mt.len = function(space) - check_space_arg(space, 'len') - local pk = space.index[0] - if pk == nil then - return 0 -- empty space without indexes, return 0 - end - return space.index[0]:len() - end - space_mt.count = function(space, key, opts) - check_space_arg(space, 'count') - local pk = space.index[0] - if pk == nil then - return 0 -- empty space without indexes, return 0 - end - return pk:count(key, opts) - end - space_mt.bsize = function(space) - check_space_arg(space, 'bsize') - local s = builtin.space_by_id(space.id) - if s == nil then - box.error(box.error.NO_SUCH_SPACE, space.name) - end - return builtin.space_bsize(s) - end - space_mt.__newindex = index_mt.__newindex - - space_mt.get = function(space, key) - check_space_arg(space, 'get') - return check_primary_index(space):get(key) - end - space_mt.select = function(space, key, opts) - check_space_arg(space, 'select') - return check_primary_index(space):select(key, opts) - end - space_mt.insert = function(space, tuple) - check_space_arg(space, 'insert') - return internal.insert(space.id, tuple); - end - space_mt.replace = function(space, tuple) - check_space_arg(space, 'replace') - return internal.replace(space.id, tuple); - end - space_mt.put = space_mt.replace; -- put is an alias for replace - space_mt.update = function(space, key, ops) - check_space_arg(space, 'update') - return check_primary_index(space):update(key, ops) - end - space_mt.upsert = function(space, tuple_key, ops, deprecated) - check_space_arg(space, 'upsert') - if deprecated ~= nil then - local msg = "Error: extra argument in upsert call: " - msg = msg .. tostring(deprecated) - msg = msg .. ". Usage :upsert(tuple, operations)" - box.error(box.error.PROC_LUA, msg) - end - return internal.upsert(space.id, tuple_key, ops); - end - space_mt.delete = function(space, key) - check_space_arg(space, 'delete') - return check_primary_index(space):delete(key) - end --- Assumes that spaceno has a TREE (NUM) primary key --- inserts a tuple after getting the next value of the --- primary key and returns it back to the user - space_mt.auto_increment = function(space, tuple) - check_space_arg(space, 'auto_increment') - local max_tuple = check_primary_index(space):max() - local max = 0 - if max_tuple ~= nil then - max = max_tuple[1] - end - table.insert(tuple, 1, max + 1) - return space:insert(tuple) - end - - space_mt.pairs = function(space, key, opts) - check_space_arg(space, 'pairs') - local pk = space.index[0] - if pk == nil then - -- empty space without indexes, return empty iterator - return fun.iter({}) - end - return pk:pairs(key, opts) - end - space_mt.__pairs = space_mt.pairs -- Lua 5.2 compatibility - space_mt.__ipairs = space_mt.pairs -- Lua 5.2 compatibility - space_mt.truncate = function(space) - check_space_arg(space, 'truncate') - return internal.truncate(space.id) - end - space_mt.format = function(space, format) - check_space_arg(space, 'format') - return box.schema.space.format(space.id, format) - end - space_mt.drop = function(space) - check_space_arg(space, 'drop') - check_space_exists(space) - return box.schema.space.drop(space.id, space.name) - end - space_mt.rename = function(space, name) - check_space_arg(space, 'rename') - check_space_exists(space) - return box.schema.space.rename(space.id, name) - end - space_mt.create_index = function(space, name, options) - check_space_arg(space, 'create_index') - check_space_exists(space) - return box.schema.index.create(space.id, name, options) - end - space_mt.run_triggers = function(space, yesno) - check_space_arg(space, 'run_triggers') - local s = builtin.space_by_id(space.id) - if s == nil then - box.error(box.error.NO_SUCH_SPACE, space.name) - end - builtin.space_run_triggers(s, yesno) - end - space_mt.__index = space_mt setmetatable(space, space_mt) if type(space.index) == 'table' and space.enabled then diff --git a/test/box-tap/schema_mt.test.lua b/test/box-tap/schema_mt.test.lua new file mode 100755 index 000000000..096c4ad60 --- /dev/null +++ b/test/box-tap/schema_mt.test.lua @@ -0,0 +1,79 @@ +#!/usr/bin/env tarantool +-- +-- pr-3204: expose space_mt, index_mt into box.schema. +-- + +local tap = require('tap') +local test = tap.test('schema_mt') + +test:plan(7) + +box.cfg{ + log="tarantool.log", +} + +local sp1 = box.schema.space.create('test') +local sp2 = box.schema.space.create('test2') + +test:is( + getmetatable(sp1), + getmetatable(sp2), + 'all spaces use the same metatable' +) + +local idx1 = sp1:create_index('primary') +local idx2 = sp2:create_index('primary') + +test:isnt( + getmetatable(idx1), + getmetatable(idx2), + 'all indexes have their own metatable' +) + +test:is( + idx1.get, + idx2.get, + 'memtx indexes share read methods' +) + +local sp3 = box.schema.space.create('test3', {engine='vinyl'}) +local sp4 = box.schema.space.create('test4', {engine='vinyl'}) + +local idx3 = sp3:create_index('primary') +local idx4 = sp4:create_index('primary') + +test:is( + idx3.get, + idx4.get, + 'vinyl indexes share read methods' +) + +test:isnt( + idx1.get, + idx3.get, + 'memtx and vinyl indexes have separate read methods' +) + +function box.schema.space_mt.foo() end + +test:is( + sp1.foo, + box.schema.space_mt.foo, + 'box.schema.space_mt is mutable' +) + +function box.schema.index_mt.foo() end + +test:is( + idx1.foo, + box.schema.index_mt.foo, + 'box.schema.index_mt is mutable' +) + +test:check() + +sp1:drop() +sp2:drop() +sp3:drop() +sp4:drop() +os.exit(0) -- 2.14.3 (Apple Git-98)