From 0afe1f78a51990d7849a11339fe9c5af06165bd4 Mon Sep 17 00:00:00 2001 From: Serge Petrenko <sergepetrenko@tarantool.org> Date: Thu, 12 Aug 2021 22:50:19 +0300 Subject: [PATCH] lua: introduce table.equals method Introduce table.equals for comparing tables. The method respects __eq metamethod, if provided. Needed-for #5894 @TarantoolBot document Title: lua: new method table.equals Document the new lua method table.equals It compares two tables deeply. For example: ``` tarantool> t1 = {a=3} --- ... tarantool> t2 = {a=3} --- ... tarantool> t1 == t2 --- - false ... tarantool> table.equals(t1, t2) --- - true ... ``` The method respects the __eq metamethod. When both tables being compared have the same __eq metamethod, it's used for comparison (just like this is done in Lua 5.1) --- .../unreleased/introduce-table-equals.md | 4 ++ src/lua/table.lua | 29 +++++++++++ test/app-tap/table.test.lua | 49 ++++++++++++++++++- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/introduce-table-equals.md diff --git a/changelogs/unreleased/introduce-table-equals.md b/changelogs/unreleased/introduce-table-equals.md new file mode 100644 index 0000000000..282b56c43b --- /dev/null +++ b/changelogs/unreleased/introduce-table-equals.md @@ -0,0 +1,4 @@ +## feature/lua + +* Introduce method `table.equals`. It compares 2 tables by value and respects + `__eq` metamethod. diff --git a/src/lua/table.lua b/src/lua/table.lua index 8fa9b876ac..edd60d1be5 100644 --- a/src/lua/table.lua +++ b/src/lua/table.lua @@ -57,6 +57,34 @@ local function table_shallowcopy(orig) return copy end +--- Compare two lua tables +-- Supports __eq metamethod for comparing custom tables with metatables +-- @function equals +-- @return true when the two tables are equal (false otherwise). +local function table_equals(a, b) + if type(a) ~= 'table' or type(b) ~= 'table' then + return type(a) == type(b) and a == b + end + local mta = getmetatable(a) + local mtb = getmetatable(b) + -- Let Lua decide what should happen when at least one of the tables has a + -- metatable. + if mta and mta.__eq or mtb and mtb.__eq then + return a == b + end + for k, v in pairs(a) do + if not table_equals(v, b[k]) then + return false + end + end + for k, _ in pairs(b) do + if not a[k] then + return false + end + end + return true +end + -- table library extension local table = require('table') -- require modifies global "table" module and adds "clear" function to it. @@ -65,3 +93,4 @@ require('table.clear') table.copy = table_shallowcopy table.deepcopy = table_deepcopy +table.equals = table_equals diff --git a/test/app-tap/table.test.lua b/test/app-tap/table.test.lua index 60c095fdfc..ae6fdfa2d1 100755 --- a/test/app-tap/table.test.lua +++ b/test/app-tap/table.test.lua @@ -8,7 +8,7 @@ yaml.cfg{ encode_invalid_as_nil = true, } local test = require('tap').test('table') -test:plan(31) +test:plan(44) do -- check basic table.copy (deepcopy) local example_table = { @@ -223,4 +223,51 @@ do -- check usage of not __copy metamethod on second level + shallow ) end +do -- check table.equals + test:ok(table.equals({}, {}), "table.equals for empty tables") + test:is(table.equals({}, {1}), false, "table.equals with one empty table") + test:is(table.equals({1}, {}), false, "table.equals with one empty table") + test:is(table.equals({key = box.NULL}, {key = nil}), false, + "table.equals for box.NULL and nil") + test:is(table.equals({key = nil}, {key = box.NULL}), false, + "table.equals for box.NULL and nil") + local tbl_a = { + first = { + 1, + 2, + {}, + }, + second = { + a = { + {'something'}, + }, + b = 'something else', + }, + [3] = 'some value', + } + local tbl_b = table.deepcopy(tbl_a) + local tbl_c = table.copy(tbl_a) + test:ok(table.equals(tbl_a, tbl_b), "table.equals for complex tables") + test:ok(table.equals(tbl_a, tbl_c), + "table.equals for shallow copied tables") + tbl_c.second.a = 'other thing' + test:ok(table.equals(tbl_a, tbl_c), + "table.equals for shallow copied tables after modification") + test:is(table.equals(tbl_a, tbl_b), false, "table.equals does a deep check") + local mt = { + __eq = function(a, b) -- luacheck: no unused args + return true + end} + local tbl_d = setmetatable({a = 15}, mt) + local tbl_e = setmetatable({b = 2, c = 3}, mt) + test:ok(table.equals(tbl_d, tbl_e), "table.equals respects __eq") + test:is(table.equals(tbl_d, {a = 15}), false, + "table.equals when metatables don't match") + test:is(table.equals({a = 15}, tbl_d), false, + "table.equals when metatables don't match") + local tbl_f = setmetatable({a = 15}, {__eq = function() return true end}) + test:is(table.equals(tbl_d, tbl_f), false, + "table.equals when metatables don't match") +end + os.exit(test:check() == true and 0 or 1) -- GitLab