commit - 05e8b1de2150300c868d04e0f2a578be5eca9d27
commit + dc26e47e1bacb3148bc3fc5753cc1c82afb521ef
blob - /dev/null
blob + 91de7c82362d26709d101a72e203bbb93fa78ef8 (mode 644)
--- /dev/null
+++ changelogs/unreleased/gh-4693-formats-for-standalone-tuples.md
+## feature/box
+
+* Introduced `box.tuple.format` that enables format definition for tuples
+ created via `box.tuple.new` (standalone tuples) (gh-4693).
+* **[Breaking change]** Disabled argument list syntax of `box.tuple.new` (this
+ was needed for gh-4693). It is possible to switch to the old behavior using
+ the compatibility option `box_tuple_new_vararg`.
blob - 7081a1e27de25bcc5476eaba12109895bf853cc9
blob + 234a8e81c83c60c679f787fa5ab324fb1e0c3284
--- src/box/CMakeLists.txt
+++ src/box/CMakeLists.txt
lua_source(lua_sources lua/load_cfg.lua load_cfg_lua)
lua_source(lua_sources lua/schema.lua schema_lua)
lua_source(lua_sources lua/tuple.lua tuple_lua)
+lua_source(lua_sources lua/tuple_format.lua tuple_format_lua)
lua_source(lua_sources lua/session.lua session_lua)
if (ENABLE_FEEDBACK_DAEMON)
lua_source(lua_sources lua/feedback_daemon.lua feedback_daemon_lua)
blob - a04222964e2fbc5598d875d2a1fd46bab82fdb23
blob + b0a3b49c4326ce2fa7ab5c5245efac3434bc7d72
--- src/box/lua/init.c
+++ src/box/lua/init.c
extern char session_lua[],
tuple_lua[],
+ tuple_format_lua[],
key_def_lua[],
schema_lua[],
load_cfg_lua[],
static const char *lua_sources[] = {
"box/session", NULL, session_lua,
"box/tuple", NULL, tuple_lua,
+ "box/tuple_format", NULL, tuple_format_lua,
"box/schema", NULL, schema_lua,
#if ENABLE_FEEDBACK_DAEMON
/*
blob - 3b96806d57d9e905502fc90365f78c2f5744661f
blob + a1e9b705d1dd32fcce9a4be8f28ecc6f7f4070e4
--- src/box/lua/schema.lua
+++ src/box/lua/schema.lua
fkey.space == space_name)
fkey = normalize_foreign_key_one(fkey, error_prefix, is_complex,
fkey_same_space)
- local fkey_name = fkey_same_space and space_name or
+ local fkey_name = fkey_same_space and (space_name or 'unknown') or
box.space[fkey.space].name
return {[fkey_name] = fkey}
end
end
return result
end
-box.internal.space.normalize_format = normalize_format -- for space.upgrade
+
+-- for space.upgrade and box.tuple.format.new
+box.internal.space.normalize_format = normalize_format
local function denormalize_foreign_key_one(fkey)
assert(type(fkey.field) == 'string' or type(fkey.field) == 'number')
return result
end
+box.internal.space.denormalize_format = denormalize_format
+
box.schema.space = {}
box.schema.space.create = function(name, options)
check_param(name, 'name', 'string')
blob - dcdbb3ae408712b4bf843bfcb7d87c2ccf9bf419
blob + 316c9cd7701d50c7b286f0f7f48e4c0a8e974737
--- src/box/lua/tuple.c
+++ src/box/lua/tuple.c
* SUCH DAMAGE.
*/
#include "box/lua/tuple.h"
+#include "box/lua/tuple_format.h"
#include "box/xrow_update.h"
#include "lua/utils.h" /* luaT_error() */
* (because it is usual for the module API).
*/
-/**
- * Encode a Lua values on a Lua stack as an MsgPack array.
- *
- * Raise a Lua error when encoding fails.
- *
- * Helper for <lbox_tuple_new>().
- */
-static int
-luaT_tuple_encode_values(struct lua_State *L, struct ibuf *buf)
-{
- struct mpstream stream;
- mpstream_init(&stream, buf, ibuf_reserve_cb, ibuf_alloc_cb, luamp_error,
- L);
- int argc = lua_gettop(L);
- mpstream_encode_array(&stream, argc);
- for (int k = 1; k <= argc; ++k) {
- if (luamp_encode(L, luaL_msgpack_default, &stream, k) != 0)
- return -1;
- }
- mpstream_flush(&stream);
- return 0;
-}
-
typedef void luaT_mpstream_init_f(struct mpstream *stream, struct lua_State *L,
void *buffer);
lbox_tuple_new(lua_State *L)
{
int argc = lua_gettop(L);
- if (argc < 1) {
- lua_newtable(L); /* create an empty tuple */
- ++argc;
- }
-
/*
- * Use backward-compatible parameters format:
- * box.tuple.new(1, 2, 3).
- */
- box_tuple_format_t *fmt = box_tuple_format_default();
- if (argc != 1 || (!lua_istable(L, 1) && !luaT_istuple(L, 1))) {
- struct ibuf *buf = cord_ibuf_take();
- struct tuple *tuple = NULL;
-
- if (luaT_tuple_encode_values(L, buf) != 0)
- goto cleanup;
- tuple = box_tuple_new(fmt, buf->buf, buf->buf + ibuf_used(buf));
-cleanup:
- cord_ibuf_drop(buf);
- if (tuple == NULL)
- return luaT_error(L);
- luaT_pushtuple(L, tuple);
- return 1;
- }
-
- /*
* Use the new parameters format:
- * box.tuple.new({1, 2, 3}).
+ * box.tuple.new({tuple_field1, tuple_field2, tuple_field3}, [options]).
*/
- struct tuple *tuple = luaT_tuple_new(L, 1, fmt);
+ struct tuple_format *format;
+ if (argc == 2)
+ format = luaT_check_tuple_format(L, -1);
+ else
+ format = box_tuple_format_default();
+ struct tuple *tuple = luaT_tuple_new(L, 1, format);
if (tuple == NULL)
return luaT_error(L);
/* box_tuple_new() doesn't leak on exception, see public API doc */
{"transform", lbox_tuple_transform},
{"tuple_to_map", lbox_tuple_to_map},
{"tuple_field_by_path", lbox_tuple_field_by_path},
- {NULL, NULL}
-};
-
-static const struct luaL_Reg lbox_tuplelib[] = {
{"new", lbox_tuple_new},
{NULL, NULL}
};
lua_pop(L, 1); /* box.internal */
luaL_register_type(L, tuple_iteratorlib_name,
lbox_tuple_iterator_meta);
- luaL_findtable(L, LUA_GLOBALSINDEX, tuplelib_name, 0);
- luaL_setfuncs(L, lbox_tuplelib, 0);
- lua_pop(L, 1);
tuple_serializer_update_options();
trigger_create(&tuple_serializer.update_trigger,
blob - 33aa444849b45d84fc154b8197d40b56fde9e23f
blob + 200308aab19cf1fc7316e72ecc1cacbd67cd744a
--- src/box/lua/tuple.lua
+++ src/box/lua/tuple.lua
local msgpackffi = require('msgpackffi')
local fun = require('fun')
local buffer = require('buffer')
+local compat = require('compat')
local internal = box.internal
local cord_ibuf_take = buffer.internal.cord_ibuf_take
local cord_ibuf_put = buffer.internal.cord_ibuf_put
local tuple_t = ffi.typeof('box_tuple_t')
local const_tuple_ref_t = ffi.typeof('box_tuple_t&')
+local NEW_OPTION_TYPES = {
+ format = function(format)
+ if type(format) ~= 'table' and not box.tuple.format.is(format) then
+ return false, "table, box.tuple.format"
+ end
+ return true
+ end
+}
+
+local new_tuple = function(...)
+ if compat.box_tuple_new_vararg.current == 'old' then
+ return internal.tuple.new{...}
+ end
+ local tuple, options = ...
+ if type(tuple) ~= 'table' and not box.tuple.is(tuple) then
+ tuple = {tuple}
+ end
+ internal.check_param_table(options, NEW_OPTION_TYPES)
+ if options == nil then
+ return internal.tuple.new(tuple)
+ end
+ local format
+ if box.tuple.format.is(options.format) then
+ format = options.format
+ else
+ format = box.tuple.format.new(options.format)
+ end
+ return internal.tuple.new(tuple, format)
+end
+
local is_tuple = function(tuple)
return tuple ~= nil and type(tuple) == 'cdata' and ffi.istype(const_tuple_ref_t, tuple)
end
-- Public API, additional to implemented in C.
+-- new() needs a wrapper in Lua, because format normalization needs to be done
+-- in Lua.
+box.tuple.new = new_tuple
+
-- is() is implemented in Lua, because then it is
-- easy to be JITed.
box.tuple.is = is_tuple
blob - 089928ae5bd8f4fee46a2df246f165c706dbf325
blob + d0faf5515739d602306dbd6551e77ed82c64401f
--- src/box/lua/tuple_format.c
+++ src/box/lua/tuple_format.c
}
static int
+lbox_tuple_format_is(struct lua_State *L)
+{
+ lua_pushboolean(L, luaL_testudata(L, 1, tuple_format_typename) != NULL);
+ return 1;
+}
+
+static int
lbox_tuple_format_gc(struct lua_State *L)
{
struct tuple_format *format = luaT_check_tuple_format(L, 1);
return luaT_push_tuple_format(L, format);
}
+/**
+ * Returns the tuple format object type name.
+ */
+static int
+lbox_tuple_format_tostring(struct lua_State *L)
+{
+ luaT_check_tuple_format(L, 1);
+ lua_pushstring(L, tuple_format_typename);
+ return 1;
+}
+
+/*
+ * Returns the format clause with which this tuple format was created.
+ */
+static int
+lbox_tuple_format_serialize(struct lua_State *L)
+{
+ struct tuple_format *format = luaT_check_tuple_format(L, 1);
+ if (format->data == NULL) {
+ lua_createtable(L, 0, 0);
+ return 1;
+ }
+ const char *data = format->data;
+ luamp_decode(L, luaL_msgpack_default, &data);
+ luaL_findtable(L, LUA_GLOBALSINDEX, "box.internal.space", 1);
+ lua_getfield(L, -1, "denormalize_format");
+ lua_remove(L, -2);
+ lua_pushvalue(L, -2);
+ lua_call(L, 1, 1);
+ return 1;
+}
+
+/*
+ * Simply returns `ipairs(format:totable())`.
+ */
+static int
+lbox_tuple_format_ipairs(struct lua_State *L)
+{
+ lbox_tuple_format_serialize(L);
+ lua_getfield(L, LUA_GLOBALSINDEX, "ipairs");
+ lua_insert(L, -2);
+ lua_call(L, 1, 3);
+ return 3;
+}
+
void
box_lua_tuple_format_init(struct lua_State *L)
{
const struct luaL_Reg lbox_tuple_format_meta[] = {
{"__gc", lbox_tuple_format_gc},
+ {"__serialize", lbox_tuple_format_serialize},
+ {"__tostring", lbox_tuple_format_tostring},
+ {"totable", lbox_tuple_format_serialize},
+ {"ipairs", lbox_tuple_format_ipairs},
+ {"pairs", lbox_tuple_format_ipairs},
{NULL, NULL}
};
luaL_register_type(L, tuple_format_typename, lbox_tuple_format_meta);
+ const struct luaL_Reg lbox_tuple_formatlib[] = {
+ {"is", lbox_tuple_format_is},
+ {NULL, NULL}
+ };
+ luaL_findtable(L, LUA_GLOBALSINDEX, "box.tuple.format", 0);
+ luaL_setfuncs(L, lbox_tuple_formatlib, 0);
+ lua_pop(L, 1);
+
const struct luaL_Reg box_tuple_formatlib_internal[] = {
{"new", lbox_tuple_format_new},
{NULL, NULL}
blob - /dev/null
blob + d4c64fe9dfdf9573883664f9222b4e097b80422c (mode 644)
--- /dev/null
+++ src/box/lua/tuple_format.lua
+-- new() needs a wrapper in Lua, because format normalization needs to be done
+-- in Lua.
+box.tuple.format.new = function(format)
+ box.internal.check_param(format, 'format', 'table')
+ format = box.internal.space.normalize_format(nil, nil, format)
+ return box.internal.tuple_format.new(format)
+end
blob - 00ae2ac75c67d1d889ce70baf034cc4e88a2b069
blob + 6727b5a8dd3b042f6c27827780c6bce6515da2a7
--- src/lua/compat.lua
+++ src/lua/compat.lua
string when decoded in Lua.
https://tarantool.io/compat/binary_data_decoding
+]]
+
+local BOX_TUPLE_NEW_VARARG_BRIEF = [[
+Whether `box.tuple.new` should interpret the argument list as an array of tuple
+fields (i.e., vararg) - this does not allow passing a tuple format as a second
+argument.
+
+https://tarantool.io/compat/box_tuple_new_vararg
]]
-- Returns an action callback that toggles a tweak.
tweaks.yaml_decode_binary_as_string = not is_new
tweaks.msgpack_decode_binary_as_string = not is_new
end,
+ },
+ box_tuple_new_vararg = {
+ default = 'new',
+ obsolete = nil,
+ brief = BOX_TUPLE_NEW_VARARG_BRIEF,
+ action = function() end,
},
}
blob - d21ae5108bfbdcbe3038456f6738cfb75b1eb7e7
blob + 73d9e79e84fe7b650ba0a8ab6be0862ccf38dd7c
--- test/app-luatest/msgpack_test.lua
+++ test/app-luatest/msgpack_test.lua
t.assert_equals(
msgpack.decode(msgpack.encode({mp, {foo = mp}})),
{mp:decode(), {foo = mp:decode()}})
- t.assert_equals(msgpack.object(box.tuple.new(1, 2, 3)):decode(), {1, 2, 3})
+ t.assert_equals(msgpack.object(box.tuple.new{1, 2, 3}):decode(), {1, 2, 3})
t.assert_equals(
msgpack.object({
foo = box.tuple.new(123),
blob - c54c30f57a7a6a500b2c7c5b77696efe4edaf3c2
blob + 98e4a7bcc78a2eda5bb5ba34d990d5a08e2783d6
--- test/box/call.result
+++ test/box/call.result
---
- []
...
-function return_tuple() return box.tuple.new(1, 2, 3) end
+function return_tuple() return box.tuple.new{1, 2, 3} end
---
...
conn:call("return_tuple")
---
- [1, 2, 3]
...
-function return_tuples() return box.tuple.new(1, 2, 3), box.tuple.new(3, 4, 5) end
+function return_tuples() return box.tuple.new{1, 2, 3}, box.tuple.new{3, 4, 5} end
---
...
conn:call("return_tuples")
blob - 2b0c2b622162f67e7dda8a98a0bf06941e33b785
blob + 2703a944fc678c208b9e5616f4df2e1892635317
--- test/box/call.test.lua
+++ test/box/call.test.lua
conn:call("return_emptytuple")
conn:eval("return return_emptytuple()")
-function return_tuple() return box.tuple.new(1, 2, 3) end
+function return_tuple() return box.tuple.new{1, 2, 3} end
conn:call("return_tuple")
conn:eval("return return_tuple()")
-function return_tuples() return box.tuple.new(1, 2, 3), box.tuple.new(3, 4, 5) end
+function return_tuples() return box.tuple.new{1, 2, 3}, box.tuple.new{3, 4, 5} end
conn:call("return_tuples")
conn:eval("return return_tuples()")
blob - 26c0a1efe5364ef2e6aed7d5d61c9b1e5f78974a
blob + 71f23edb3e4161b9cae4181c8ed82e93146f165c
--- test/box/sequence.result
+++ test/box/sequence.result
_ = s3:create_index('pk', {parts = {2, 'unsigned', 1, 'string'}, sequence = 'test'})
---
...
-s1:insert(box.tuple.new(nil)) -- 1
+s1:insert(box.tuple.new(box.NULL)) -- 1
---
- [1]
...
-s2:insert(box.tuple.new('a', nil)) -- 2
+s2:insert(box.tuple.new{'a', box.NULL}) -- 2
---
- ['a', 2]
...
-s3:insert(box.tuple.new('b', nil)) -- 3
+s3:insert(box.tuple.new{'b', box.NULL}) -- 3
---
- ['b', 3]
...
blob - e9eb993cd7cfd3019221bb738751d06923465eff
blob + 5a56b74e9fa507e749231977fa5a4c993b9e2938
--- test/box/sequence.test.lua
+++ test/box/sequence.test.lua
s3 = box.schema.space.create('test3')
_ = s3:create_index('pk', {parts = {2, 'unsigned', 1, 'string'}, sequence = 'test'})
-s1:insert(box.tuple.new(nil)) -- 1
-s2:insert(box.tuple.new('a', nil)) -- 2
-s3:insert(box.tuple.new('b', nil)) -- 3
+s1:insert(box.tuple.new(box.NULL)) -- 1
+s2:insert(box.tuple.new{'a', box.NULL}) -- 2
+s3:insert(box.tuple.new{'b', box.NULL}) -- 3
s1:truncate()
s2:truncate()
s3:truncate()
blob - 0298b2c9f7e294faaff6170ca6a7c96425c6e126
blob + ce022ada3620e43411d91622423fa8389f713750
--- test/box/tuple.result
+++ test/box/tuple.result
---
- [1]
...
-box.tuple.new(1, 2, 3, 4, 5)
----
-- [1, 2, 3, 4, 5]
-...
box.tuple.new{1, 2, 3, 4, 5}
---
- [1, 2, 3, 4, 5]
...
-box.tuple.new({'a', 'b'}, {'c', 'd'}, {'e', 'f'})
----
-- [['a', 'b'], ['c', 'd'], ['e', 'f']]
-...
box.tuple.new{{'a', 'b'}, {'c', 'd'}, {'e', 'f'}}
---
- [['a', 'b'], ['c', 'd'], ['e', 'f']]
-...
-box.tuple.new({1, 2}, 'x', 'y', 'z', {c = 3, d = 4}, {e = 5, f = 6})
----
-- [[1, 2], 'x', 'y', 'z', {'c': 3, 'd': 4}, {'e': 5, 'f': 6}]
...
box.tuple.new{{1, 2}, 'x', 'y', 'z', {c = 3, d = 4}, {e = 5, f = 6}}
---
- [[1, 2], 'x', 'y', 'z', {'c': 3, 'd': 4}, {'e': 5, 'f': 6}]
...
-box.tuple.new('x', 'y', 'z', {1, 2}, {c = 3, d = 4}, {e = 5, f = 6})
----
-- ['x', 'y', 'z', [1, 2], {'c': 3, 'd': 4}, {'e': 5, 'f': 6}]
-...
box.tuple.new{'x', 'y', 'z', {1, 2}, {c = 3, d = 4}, {e = 5, f = 6}}
---
- ['x', 'y', 'z', [1, 2], {'c': 3, 'd': 4}, {'e': 5, 'f': 6}]
- true
...
null = nil
-t = box.tuple.new({1, -2, 1.2, -1.2}, 'x', 'y', 'z', null, true, false,
+t = box.tuple.new{{1, -2, 1.2, -1.2}, 'x', 'y', 'z', null, true, false,
{bin = "\x08\x5c\xc2\x80\x12\x2f",
big_num = tonumber64('18446744073709551615'),
map = {key = "value"},
double=1.0000000001,
- utf8="Кудыкины горы"});
+ utf8="Кудыкины горы"}};
---
...
tostring(t);
b = uuid.fromstr("83eb4959-3de6-49fb-8890-6fb4423dd186")
---
...
-t = box.tuple.new(a, 2, b, "string")
+t = box.tuple.new{a, 2, b, "string"}
---
...
state, val = t:next()
blob - 4201e98606c6ddfa2f1c2c02cab3dbc481e5fe26
blob + 4841d4bbae3637a2077fbf96a0e7e4f42bf7961f
--- test/box/tuple.test.lua
+++ test/box/tuple.test.lua
box.tuple.new(1)
box.tuple.new{1}
-box.tuple.new(1, 2, 3, 4, 5)
box.tuple.new{1, 2, 3, 4, 5}
-box.tuple.new({'a', 'b'}, {'c', 'd'}, {'e', 'f'})
box.tuple.new{{'a', 'b'}, {'c', 'd'}, {'e', 'f'}}
-box.tuple.new({1, 2}, 'x', 'y', 'z', {c = 3, d = 4}, {e = 5, f = 6})
box.tuple.new{{1, 2}, 'x', 'y', 'z', {c = 3, d = 4}, {e = 5, f = 6}}
-box.tuple.new('x', 'y', 'z', {1, 2}, {c = 3, d = 4}, {e = 5, f = 6})
box.tuple.new{'x', 'y', 'z', {1, 2}, {c = 3, d = 4}, {e = 5, f = 6}}
t=box.tuple.new{'a','b','c'}
-- testing tostring
test_run:cmd("setopt delimiter ';'")
null = nil
-t = box.tuple.new({1, -2, 1.2, -1.2}, 'x', 'y', 'z', null, true, false,
+t = box.tuple.new{{1, -2, 1.2, -1.2}, 'x', 'y', 'z', null, true, false,
{bin = "\x08\x5c\xc2\x80\x12\x2f",
big_num = tonumber64('18446744073709551615'),
map = {key = "value"},
double=1.0000000001,
- utf8="Кудыкины горы"});
+ utf8="Кудыкины горы"}};
tostring(t);
t;
test_run:cmd("setopt delimiter ''");
-- output comparison.
a = uuid.fromstr("c8f0fa1f-da29-438c-a040-393f1126ad39")
b = uuid.fromstr("83eb4959-3de6-49fb-8890-6fb4423dd186")
-t = box.tuple.new(a, 2, b, "string")
+t = box.tuple.new{a, 2, b, "string"}
state, val = t:next()
state
val == a
blob - /dev/null
blob + 075e55f43f9a9b1237ff5b00dcabcfe9e28a5beb (mode 644)
--- /dev/null
+++ test/box-luatest/gh_4693_formats_for_standalone_tuples_test.lua
+local server = require('luatest.server')
+local t = require('luatest')
+
+local g = t.group()
+
+g.before_all(function(cg)
+ cg.server = server:new()
+ cg.server:start()
+end)
+
+g.after_all(function(cg)
+ cg.server:drop()
+end)
+
+-- Checks that `box.tuple.format.new` works as expected.
+g.test_box_tuple_format_new = function()
+ t.assert_equals(type(box.tuple.format.new{}), 'userdata')
+ t.assert(box.tuple.format.is(box.tuple.format.new{}))
+ t.assert_not(box.tuple.format.is(777))
+
+ local err_msg = "Illegal parameters, format should be a table"
+ t.assert_error_msg_content_equals(err_msg, function ()
+ box.tuple.format.new(777)
+ end)
+ err_msg = "Wrong space format field 1: unknown field type"
+ t.assert_error_msg_content_equals(err_msg, function ()
+ box.tuple.format.new{{'field', 'unknown'}}
+ end)
+ err_msg = "unsupported Lua type 'function'"
+ t.assert_error_msg_content_equals(err_msg, function ()
+ box.tuple.format.new{{'field', 'number',
+ nullable_action = function() end}}
+ end)
+ err_msg = "Space field 'field' is duplicate"
+ t.assert_error_msg_content_equals(err_msg, function ()
+ box.tuple.format.new{{'field'}, {'field'}}
+ end)
+end
+
+g.before_test('test_box_tuple_format_gc', function (cg)
+ cg.server:exec(function()
+ box.error.injection.set('ERRINJ_TUPLE_FORMAT_COUNT', 2)
+ end)
+end)
+
+-- Checks that tuple formats are garbage collected and recycled.
+g.test_box_tuple_format_gc = function(cg)
+ t.tarantool.skip_if_not_debug()
+
+ cg.server:exec(function()
+ box.tuple.format.new{{name = 'field0'}}
+ collectgarbage()
+ box.tuple.format.new{{name = 'field1'}}
+ end)
+end
+
+g.after_test('test_box_tuple_format_gc', function (cg)
+ cg.server:exec(function()
+ box.error.injection.set('ERRINJ_TUPLE_FORMAT_COUNT', -1)
+ end)
+end)
+
+
+-- Checks that box.tuple.format serialization works as expected.
+g.test_box_tuple_format_serialization = function(cg)
+ cg.server:exec(function()
+ local err_msg = "Illegal parameters, format should be a table"
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.format.new()
+ end)
+ local f = box.tuple.format.new{}
+ t.assert_equals(tostring(f), "box.tuple.format")
+ local mt = getmetatable(f)
+ t.assert_equals(mt.__serialize, mt.totable)
+ t.assert_equals(mt.ipairs, mt.pairs)
+
+ err_msg = "box.tuple.format expected, got no value"
+ t.assert_error_msg_contains(err_msg, function()
+ f.__tostring()
+ end)
+ t.assert_error_msg_contains(err_msg, function()
+ f.totable()
+ end)
+ t.assert_error_msg_contains(err_msg, function()
+ f.pairs()
+ end)
+
+ local test_tuple_format_contents = function(f, expected)
+ local actual = {}
+ for i, field in f:pairs() do
+ table.insert(actual, i, field)
+ end
+ t.assert_equals(actual, expected)
+ t.assert_equals(f:totable(), expected)
+ end
+
+ f = box.tuple.format.new{}
+ test_tuple_format_contents(f, {})
+
+ local f = box.tuple.format.new{}
+ test_tuple_format_contents(f, {})
+
+ f = box.tuple.format.new{{name = 'field1', type = 'string'},
+ {name = 'field2', type = 'number'}}
+ test_tuple_format_contents(f, {{name = 'field1', type = 'string'},
+ {name = 'field2', type = 'number'}})
+
+ local contents = {{name = 'field', type = 'string'}}
+ f = box.tuple.format.new { { 'field', 'string' } }
+ test_tuple_format_contents(f, contents)
+ f = box.tuple.format.new{{'field', type = 'string'}}
+ test_tuple_format_contents(f, contents)
+ f = box.tuple.format.new{{name = 'field', 'string'}}
+ test_tuple_format_contents(f, contents)
+ f = box.tuple.format.new{{name = 'field', type = 'string'}}
+ test_tuple_format_contents(f, contents)
+ contents = {{name = 'field1', type = 'string', is_nullable = true,
+ nullable_action = 'none', collation = 'unicode_uk_s2',
+ default = 'UPPER("string")',
+ constraint = {ck = 'box.schema.user.info'},
+ foreign_key = {fk = {space = '_space', field = 'name'}}},
+ {name = 'field2', type = 'any', nullable_action = 'ignore',
+ foreign_key = {fk = {space = '_space', field = 1}}}}
+ f = box.tuple.format.new(contents)
+ contents[1].collation =
+ box.space._collation.index.name:get{contents[1].collation}.id
+ contents[1].constraint =
+ {ck = box.space._func.index.name:get{contents[1].constraint.ck}.id}
+ local sid =
+ box.space._space.index.name:get{contents[1].foreign_key.fk.space}.id
+ contents[1].foreign_key.fk.space = sid
+ contents[2].foreign_key.fk.space = sid
+ test_tuple_format_contents(f, contents)
+ local fk = {field = 'name'}
+ contents = {{name = 'field1', type = 'any', foreign_key = fk}}
+ f = box.tuple.format.new(contents)
+ contents[1].foreign_key = {unknown = fk}
+ test_tuple_format_contents(f, contents)
+ end)
+end
+
+-- Checks that `box.tuple.new` with format option works as expected.
+g.test_box_tuple_new_with_format = function(cg)
+ cg.server:exec(function()
+ local f = {{name = 'field', type = 'number'}}
+ local options = {format = f}
+ local tuple = box.tuple.new({1}, options)
+ -- Checks that options table is not changed by box.tuple.new.
+ t.assert_equals(options, {format = f})
+ t.assert_equals(tuple:tomap{names_only = true}, {field = 1})
+ tuple = box.tuple.new(1, options)
+ t.assert_equals(tuple:tomap{names_only = true}, {field = 1})
+ tuple = box.tuple.new(box.tuple.new(1, options), options)
+ t.assert_equals(tuple:tomap{names_only = true}, {field = 1})
+ tuple = box.tuple.new({1, 'str'}, {format = f})
+ t.assert_equals(tuple:tomap{names_only = true}, {field = 1})
+ local err_msg = "Tuple field 1 (field) type does not match one " ..
+ "required by operation: expected number, got string"
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.new({'str'}, {format = f})
+ end)
+ err_msg = "Tuple field 1 (field) required by space format is missing"
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.new({}, {format = f})
+ end)
+ f = {{name = 'field', type = 'number', is_nullable = true}}
+ tuple = box.tuple.new({}, {format = f})
+ t.assert_equals(tuple:tomap{names_only = true}, {})
+ f = box.tuple.format.new{{'field', 'number'}}
+ tuple = box.tuple.new({1}, {format = f})
+ t.assert_equals(tuple:tomap{names_only = true}, {field = 1})
+ tuple = box.tuple.new({1, 'str'}, {format = f})
+ t.assert_equals(tuple:tomap{names_only = true}, {field = 1})
+ err_msg = "Illegal parameters, options should be a table"
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.new({'str'}, 'fmt')
+ end)
+ err_msg = "Illegal parameters, format should be a table"
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.new({'str'}, {})
+ end)
+
+ -- Checks that check constraint is disabled.
+ box.schema.func.create('ck', {is_deterministic = true,
+ body = "function () return false end"})
+ box.tuple.new({0}, {format = {{'field', constraint = 'ck'}}})
+ box.func.ck:drop()
+
+ -- Checks that foreign key constraint is disabled.
+ box.schema.space.create('s')
+ box.tuple.new({0}, {format =
+ {{'field', foreign_key = {space = 's', field = 1}}}})
+ box.space.s:drop()
+ box.tuple.format.new{{'field', foreign_key = {field = 1}}}
+ box.tuple.format.new{{'field', foreign_key = {fk = {field = 1}}}}
+ err_msg = 'Illegal parameters, format[1]: foreign key: space ' ..
+ 'nonexistent was not found'
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.format.new{{'field', foreign_key = {space = 'nonexistent',
+ field = 1}}}
+ end)
+ end)
+end
+
+-- Checks that `box.tuple.new` with scalar argument works correctly.
+g.test_tuple_new_with_scalar_argument = function()
+ t.assert_equals(box.tuple.new(1):totable(), {1})
+ t.assert_equals(box.tuple.new(box.tuple.new(1)):totable(), {1})
+end
+
+-- Checks that `box.tuple.new` backward compatibility works correctly.
+g.test_box_tuple_new_with_compat = function(cg)
+ cg.server:exec(function()
+ local compat = require('compat')
+
+ local err_msg = 'Illegal parameters, options should be a table'
+ t.assert_error_msg_content_equals(err_msg, function()
+ box.tuple.new(1, 2)
+ end)
+ compat.box_tuple_new_vararg = 'old'
+ t.assert_equals(box.tuple.new(1, 2, 3):totable(), {1, 2, 3})
+ local fmt_table = {{'field', 'number'}}
+ local fmt = box.tuple.format.new(fmt_table)
+ local tuple = box.tuple.new({1}, {format = fmt})
+ t.assert_equals(tuple:tomap{names_only = true}, {})
+ tuple = box.tuple.new(box.tuple.new{'str'}, {format = fmt_table})
+ t.assert_equals(tuple:tomap{names_only = true}, {})
+ end)
+end
blob - 7fbf91a6622eb7f31b742558ce2358dfb3645a8a
blob + cfff69002be08a421f92ff0a46f67967f6325c0d
--- test/box-py/call.result
+++ test/box-py/call.result
---
- {"c": {"106": [1, 1428578535], "2": [1, 1428578535]}, "pc": {"106": [1, 1428578535, 9243], "2": [1, 1428578535, 9243]}, "s": [1, 1428578535], "u": 1428578535, "v": []}
- true
-t = box.tuple.new('tuple', {1, 2, 3}, { k1 = 'v', k2 = 'v2'})
+t = box.tuple.new{'tuple', {1, 2, 3}, { k1 = 'v', k2 = 'v2'}}
---
...
eval (return t)()
blob - b758be817d84c6960744fd960b00a060db2ce37b
blob + 33e9f10354168d8074ea182ca75a5d3e3c6a560f
--- test/box-py/call.test.py
+++ test/box-py/call.test.py
test("{s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}")
test("true, {s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}")
test("{s = {1, 1428578535}, u = 1428578535, v = {}, c = {['2'] = {1, 1428578535}, ['106'] = { 1, 1428578535} }, pc = {['2'] = {1, 1428578535, 9243}, ['106'] = {1, 1428578535, 9243}}}, true")
-admin("t = box.tuple.new('tuple', {1, 2, 3}, { k1 = 'v', k2 = 'v2'})")
+admin("t = box.tuple.new{'tuple', {1, 2, 3}, { k1 = 'v', k2 = 'v2'}}")
test("t")
test("t, t, t")
test("{t}")
blob - 0624c326cb7873ae767923fe120d1ea5bc0421df
blob + 88e5630b17c19b8bae8a3f8d3e7d01ddffdcfd60
--- test/unit/lua_func_adapter.c
+++ test/unit/lua_func_adapter.c
int idx = generate_function(
"function(a, b, tuple) "
- "return box.tuple.new{a, b}, tuple, box.tuple.new{b, a}, "
- "box.tuple.new{a + b, a - b} end");
+ "return box.internal.tuple.new{a, b}, tuple, "
+ "box.internal.tuple.new{b, a}, "
+ "box.internal.tuple.new{a + b, a - b} end");
struct func_adapter *func = func_adapter_lua_create(tarantool_L, idx);
struct func_adapter_ctx ctx;
func_adapter_begin(func, &ctx);