From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: From: Vladislav Shpilevoy Subject: [PATCH 1/2] schema: expose space_mt and index_mt on `box.schema` table Date: Fri, 23 Mar 2018 16:07:08 +0300 Message-Id: <39d1b0b99479563fadd0d9701702ae7c782b18db.1521810303.git.v.shpilevoy@tarantool.org> In-Reply-To: References: In-Reply-To: References: To: tarantool-patches@freelists.org Cc: vdavydov.dev@gmail.com, aleclarson List-ID: From: aleclarson This commit allows userland to extend the space and index metatables with their own functions or even metamethods. Reducing barriers for this kind of experimentation is vital for user contribution toward the improvement of Tarantool's API. Also, this commit reuses functions between all spaces/indexes. The metatables are exposed on `box` to make them simple to discover. The suggested alternatives were `box.internal` and `box.schema`. In a future commit, we should look into "freezing" the built-in methods so the user cannot accidentally override them. This is especially important when updating Tarantool. Closes #3204 --- src/box/lua/schema.lua | 639 ++++++++++++++++++++-------------------- test/box-tap/schema_mt.test.lua | 74 +++++ 2 files changed, 398 insertions(+), 315 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..3fb626efd 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1074,218 +1074,348 @@ 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 - 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); +local space_mt = {} +space_mt.__index = space_mt +box.schema.space_mt = 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 - 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 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 - 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 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 - 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 builtin.space_bsize(s) +end + +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 - 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 + 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 - index_mt.get_luac = function(index, key) - check_index_arg(index, 'get') - key = keify(key) - return internal.get(index.space_id, index.id, key) + 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 - 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 +local index_mt = {} +index_mt.__index = index_mt +box.schema.index_mt = 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.__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 +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); +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 +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); +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 +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); - 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 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 - local port = ffi.cast('struct port *', port_tuple) +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 +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) +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 +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) - 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(index.space_id, index.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) + 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.rename = function(index, name) - check_index_arg(index, 'rename') - return box.schema.index.rename(index.space_id, index.id, name) + builtin.port_destroy(port); + return ret +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 + +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 + +index_mt.info = function(index) + return internal.info(index.space_id, index.id); +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) +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 - 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) + return box.schema.index.alter(index.space_id, index.id, options) +end + +index_mt.__pairs = index_mt.pairs -- Lua 5.2 compatibility +index_mt.__ipairs = index_mt.pairs -- Lua 5.2 compatibility + +function box.schema.space.bless(space) + local index_mt = {} + index_mt.__index = function(index, key) + return index_mt[key] or box.schema.index_mt[key] end -- true if reading operations may yield @@ -1294,136 +1424,15 @@ function box.schema.space.bless(space) for _, op in ipairs(read_ops) do if read_yields then -- use Lua/C implmenetation - index_mt[op] = index_mt[op .. "_luac"] + index_mt[op] = box.schema.index_mt[op .. "_luac"] else -- use FFI implementation - index_mt[op] = index_mt[op .. "_ffi"] + index_mt[op] = box.schema.index_mt[op .. "_ffi"] end 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) + setmetatable(space, box.schema.space_mt) if type(space.index) == 'table' and space.enabled then for j, index in pairs(space.index) do if type(j) == 'number' 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..7e531dd01 --- /dev/null +++ b/test/box-tap/schema_mt.test.lua @@ -0,0 +1,74 @@ +#!/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() +os.exit(0) -- 2.14.3 (Apple Git-98)