diff --git a/changelogs/unreleased/gh-7350-1-based-fkey-field-no.md b/changelogs/unreleased/gh-7350-1-based-fkey-field-no.md new file mode 100644 index 0000000000000000000000000000000000000000..7ba04957830b0322a4edaecccf226db969fdab6f --- /dev/null +++ b/changelogs/unreleased/gh-7350-1-based-fkey-field-no.md @@ -0,0 +1,4 @@ +## bugfix/core + +* Fixed incorrect number of a foreign key field returned by + `space_object:format()` or `space_object.foreign_key` (gh-7350). diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index cb396fb137ff2dccd1d9ad9302236b57f6db2bc5..97c88cdadad405c575050bffb6b0c0a31d0df1ee 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -727,6 +727,38 @@ local function normalize_format(space_id, space_name, format) end box.internal.space.normalize_format = normalize_format -- for space.upgrade +local function denormalize_foreign_key_one(fkey) + assert(type(fkey.field) == 'string' or type(fkey.field) == 'number') + local result = fkey + if type(fkey.field) == 'number' then + -- convert to one-based index + result.field = result.field + 1 + end + return result +end + +local function denormalize_foreign_key(fkey) + local result = setmap{} + for k, v in pairs(fkey) do + result[k] = denormalize_foreign_key_one(v) + end + return result +end + +-- Convert zero-based foreign key field numbers to one-based +local function denormalize_format(format) + local result = setmetatable({}, { __serialize = 'seq' }) + for i, f in ipairs(format) do + result[i] = f + for k, v in pairs(f) do + if k == 'foreign_key' then + result[i][k] = denormalize_foreign_key(v) + end + end + end + return result +end + box.schema.space = {} box.schema.space.create = function(name, options) check_param(name, 'name', 'string') @@ -819,7 +851,7 @@ function box.schema.space.format(id, format) end if format == nil then - return tuple.format + return denormalize_format(tuple.format) else check_param(format, 'format', 'table') format = normalize_format(id, tuple.name, format) diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc index aaafe492b0838106799cd9146d69b9c0ac1f1b22..bc37e4af4ff6fa280d4a6e2ebd0bbaa180ac803a 100644 --- a/src/box/lua/space.cc +++ b/src/box/lua/space.cc @@ -269,13 +269,14 @@ lbox_push_space_constraint(struct lua_State *L, struct space *space, int i) /** * Helper function of lbox_push_space_foreign_key. * Push a value @a def to the top of lua stack @a L. + * ID-defined fields are converted to one-based index. */ static void lbox_push_field_id(struct lua_State *L, struct tuple_constraint_field_id *def) { if (def->name_len == 0) - lua_pushnumber(L, def->id); + lua_pushnumber(L, def->id + 1); else lua_pushstring(L, def->name); } diff --git a/test/engine-luatest/gh_6436_complex_foreign_key_test.lua b/test/engine-luatest/gh_6436_complex_foreign_key_test.lua index 78a1904ef18853fcdfd1c0a4e0a411e77047abd7..150710844b430e8065bf6f85135907073f71aaa1 100644 --- a/test/engine-luatest/gh_6436_complex_foreign_key_test.lua +++ b/test/engine-luatest/gh_6436_complex_foreign_key_test.lua @@ -283,9 +283,7 @@ g.test_complex_foreign_key_numeric = function(cg) local fkey = {cntr = {space=country.id, field={[4]=4, [3]=2, [2]=3}}} local city = box.schema.create_space('city', city_space_opts(fkey)) - t.assert_equals(city.foreign_key, - {cntr = {field = {[1] = 2, [2] = 1, [3] = 3}, - space = country.id}}); + t.assert_equals(city.foreign_key, fkey) city:create_index('pk') t.assert_equals(country:select{}, {{100, 1, 'earth', 'ru', 'Russia'}, diff --git a/test/engine-luatest/gh_6436_field_foreign_key_test.lua b/test/engine-luatest/gh_6436_field_foreign_key_test.lua index 50d6ab525b7cc1d79625036fb50dfd929658e270..c3df9a99829090f24992c003b2d2afe75ce3b9e9 100644 --- a/test/engine-luatest/gh_6436_field_foreign_key_test.lua +++ b/test/engine-luatest/gh_6436_field_foreign_key_test.lua @@ -287,12 +287,8 @@ g.test_foreign_key_numeric = function(cg) local city = box.schema.create_space('city', {engine=engine, format=fmt}) -- Check that fmt is not modified by create_space() t.assert_equals(fmt_copy, fmt) - -- Note that the format was normalized and field converted to zero-based. - t.assert_equals(city:format(), - { { name = "id", type = "unsigned"}, - { foreign_key = {country = {field = 1, space = country.id}}, - name = "country_code", type = "string"}, - { name = "name", type = "string"} }); + -- Check that format() returns one-based field number + t.assert_equals(city:format(), fmt) city:create_index('pk') t.assert_equals(country:select{}, {{1, 'ru', 'Russia'}, {2, 'fr', 'France'}}) diff --git a/test/engine-luatest/gh_7350_fkey_field_no_mismatch_test.lua b/test/engine-luatest/gh_7350_fkey_field_no_mismatch_test.lua new file mode 100644 index 0000000000000000000000000000000000000000..f431fe6a00bbe25db5287f307d2753f230980b81 --- /dev/null +++ b/test/engine-luatest/gh_7350_fkey_field_no_mismatch_test.lua @@ -0,0 +1,42 @@ +local server = require('test.luatest_helpers.server') +local t = require('luatest') +local g = t.group('gh-7350-fkey-field-no-mismatch', + {{engine = 'memtx'}, {engine = 'vinyl'}}) + +g.before_all(function(cg) + cg.server = server:new({alias = 'master'}) + cg.server:start() +end) + +g.after_all(function(cg) + cg.server:stop() + cg.server = nil +end) + +g.after_each(function(cg) + cg.server:exec(function() + if box.space.s2 then box.space.s2:drop() end + if box.space.s1 then box.space.s1:drop() end + end) +end) + +g.test_fkey_field_no = function(cg) + local engine = cg.params.engine + + cg.server:exec(function(engine) + local t = require('luatest') + local s1 = box.schema.create_space('s1', {engine=engine}) + + local fmt = {{name='f1', type='any', foreign_key={k1={space=s1.id, field=2}}}, + {name='f2', type='any', foreign_key={k2={space=s1.id, field='f3'}}}, + {name='f3', type='any', foreign_key={k3={space=s1.id, field=4}}}} + local fkey = {k4={space=s1.id, field={[5]=7, ['f6']='f5', [4]=6}}} + + local opts = {engine=engine, format=fmt, foreign_key=fkey} + local s2 = box.schema.create_space('s2', opts) + + t.assert_equals(s2:format(), fmt) + t.assert_equals(s2.foreign_key, fkey) + t.assert_equals(s2:format(s2:format()), nil) + end, {engine}) +end