diff --git a/apisix/core/table.lua b/apisix/core/table.lua index 4346863079cf..ed9450a8d854 100644 --- a/apisix/core/table.lua +++ b/apisix/core/table.lua @@ -23,6 +23,7 @@ local newproxy = newproxy local getmetatable = getmetatable local setmetatable = setmetatable local select = select +local tostring = tostring local new_tab = require("table.new") local nkeys = require("table.nkeys") local ipairs = ipairs @@ -91,7 +92,7 @@ end -- @usage -- local arr = {"a", "b", "c"} -- local idx = core.table.array_find(arr, "b") -- idx = 2 -function _M.array_find(array, val) +local function array_find(array, val) if type(array) ~= "table" then return nil end @@ -104,6 +105,7 @@ function _M.array_find(array, val) return nil end +_M.array_find = array_find -- only work under lua51 or luajit @@ -117,19 +119,28 @@ end local deepcopy do - local function _deepcopy(orig, copied) - -- prevent infinite loop when a field refers its parent - copied[orig] = true + local function _deepcopy(orig, copied, parent, opts) -- If the array-like table contains nil in the middle, -- the len might be smaller than the expected. -- But it doesn't affect the correctness. local len = #orig local copy = new_tab(len, nkeys(orig) - len) + -- prevent infinite loop when a field refers its parent + copied[orig] = copy for orig_key, orig_value in pairs(orig) do - if type(orig_value) == "table" and not copied[orig_value] then - copy[orig_key] = _deepcopy(orig_value, copied) - else + local path = parent .. "." .. tostring(orig_key) + if opts and array_find(opts.shallows, path) then copy[orig_key] = orig_value + else + if type(orig_value) == "table" then + if copied[orig_value] then + copy[orig_key] = copied[orig_value] + else + copy[orig_key] = _deepcopy(orig_value, copied, path, opts) + end + else + copy[orig_key] = orig_value + end end end @@ -144,13 +155,13 @@ do local copied_recorder = {} - function deepcopy(orig) + function deepcopy(orig, opts) local orig_type = type(orig) if orig_type ~= 'table' then return orig end - local res = _deepcopy(orig, copied_recorder) + local res = _deepcopy(orig, copied_recorder, "self", opts) _M.clear(copied_recorder) return res end diff --git a/apisix/init.lua b/apisix/init.lua index 103a8c1d7584..4b2ad17e1ef5 100644 --- a/apisix/init.lua +++ b/apisix/init.lua @@ -246,15 +246,7 @@ local function parse_domain_in_route(route) -- don't modify the modifiedIndex to avoid plugin cache miss because of DNS resolve result -- has changed - local parent = route.value.upstream.parent - if parent then - route.value.upstream.parent = nil - end - route.dns_value = core.table.deepcopy(route.value) - if parent then - route.value.upstream.parent = parent - route.dns_value.upstream.parent = parent - end + route.dns_value = core.table.deepcopy(route.value, { shallows = { "self.upstream.parent"}}) route.dns_value.upstream.nodes = new_nodes core.log.info("parse route which contain domain: ", core.json.delay_encode(route, true)) diff --git a/apisix/plugin.lua b/apisix/plugin.lua index 90d26f4ae37b..b3dadcb49588 100644 --- a/apisix/plugin.lua +++ b/apisix/plugin.lua @@ -584,7 +584,7 @@ end local function merge_service_route(service_conf, route_conf) - local new_conf = core.table.deepcopy(service_conf) + local new_conf = core.table.deepcopy(service_conf, { shallows = {"self.value.upstream.parent"}}) new_conf.value.service_id = new_conf.value.id new_conf.value.id = route_conf.value.id new_conf.modifiedIndex = route_conf.modifiedIndex @@ -658,7 +658,7 @@ end local function merge_service_stream_route(service_conf, route_conf) -- because many fields in Service are not supported by stream route, -- so we copy the stream route as base object - local new_conf = core.table.deepcopy(route_conf) + local new_conf = core.table.deepcopy(route_conf, { shallows = {"self.value.upstream.parent"}}) if service_conf.value.plugins then for name, conf in pairs(service_conf.value.plugins) do if not new_conf.value.plugins then @@ -706,7 +706,8 @@ local function merge_consumer_route(route_conf, consumer_conf, consumer_group_co return route_conf end - local new_route_conf = core.table.deepcopy(route_conf) + local new_route_conf = core.table.deepcopy(route_conf, + { shallows = {"self.value.upstream.parent"}}) if consumer_group_conf then for name, conf in pairs(consumer_group_conf.value.plugins) do diff --git a/apisix/plugins/ai.lua b/apisix/plugins/ai.lua index 39430c7ad014..278201d4e56e 100644 --- a/apisix/plugins/ai.lua +++ b/apisix/plugins/ai.lua @@ -69,7 +69,9 @@ local default_keepalive_pool = {} local function create_router_matching_cache(api_ctx) orig_router_http_matching(api_ctx) - return core.table.deepcopy(api_ctx) + return core.table.deepcopy(api_ctx, { + shallows = { "self.matched_route.value.upstream.parent" } + }) end diff --git a/t/core/table.t b/t/core/table.t index c3ec5a7c0d1f..38616ae535bf 100644 --- a/t/core/table.t +++ b/t/core/table.t @@ -215,3 +215,147 @@ GET /t GET /t --- response_body ok + + + +=== TEST 8: deepcopy copy same table only once +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local tmp = { name = "tmp", priority = 1, enabled = true } + local origin = { a = { b = tmp }, c = tmp} + local copy = core.table.deepcopy(origin) + if not core.table.deep_eq(copy, origin) then + ngx.say("copy: ", json.encode(expect), ", origin: ", json.encode(actual)) + return + end + if copy.a.b ~= copy.c then + ngx.say("copy.a.b should be the same as copy.c") + return + end + ngx.say("ok") + } + } +--- request +GET /t +--- response_body +ok + + + +=== TEST 9: reference same table +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local deepcopy = core.table.deepcopy + local tab1 = {name = "tab1"} + local tab2 = { + a = tab1, + b = tab1 + } + local tab_copied = deepcopy(tab2) + + ngx.say("table copied: ", require("toolkit.json").encode(tab_copied)) + + ngx.say("tab1 == tab2.a: ", tab1 == tab2.a) + ngx.say("tab2.a == tab2.b: ", tab2.a == tab2.b) + + ngx.say("tab_copied.a == tab1: ", tab_copied.a == tab1) + ngx.say("tab_copied.a == tab_copied.b: ", tab_copied.a == tab_copied.b) + } + } +--- request +GET /t +--- response_body +table copied: {"a":{"name":"tab1"},"b":{"name":"tab1"}} +tab1 == tab2.a: true +tab2.a == tab2.b: true +tab_copied.a == tab1: false +tab_copied.a == tab_copied.b: true + + + +=== TEST 10: reference table self(root node) +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local deepcopy = core.table.deepcopy + local tab1 = {name = "tab1"} + local tab2 = { + a = tab1, + } + tab2.c = tab2 + + local tab_copied = deepcopy(tab2) + + ngx.say("tab_copied.a == tab1: ", tab_copied.a == tab_copied.b) + ngx.say("tab_copied == tab_copied.c: ", tab_copied == tab_copied.c) + } + } +--- request +GET /t +--- response_body +tab_copied.a == tab1: false +tab_copied == tab_copied.c: true + + + +=== TEST 11: reference table self(sub node) +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local deepcopy = core.table.deepcopy + local tab_org = { + a = { + a2 = "a2" + }, + } + tab_org.b = tab_org.a + + local tab_copied = deepcopy(tab_org) + ngx.say("table copied: ", require("toolkit.json").encode(tab_copied)) + ngx.say("tab_copied.a == tab_copied.b: ", tab_copied.a == tab_copied.b) + } + } +--- request +GET /t +--- response_body +table copied: {"a":{"a2":"a2"},"b":{"a2":"a2"}} +tab_copied.a == tab_copied.b: true + + + +=== TEST 12: shallow copy +--- config + location /t { + content_by_lua_block { + local core = require("apisix.core") + local deepcopy = core.table.deepcopy + local t1 = {name = "tab1"} + local t2 = {name = "tab2"} + local tab = { + a = {b = {c = t1}}, + x = {y = t2}, + } + local tab_copied = deepcopy(tab, { shallows = { "self.a.b.c" }}) + + ngx.say("table copied: ", require("toolkit.json").encode(tab_copied)) + + ngx.say("tab_copied.a.b.c == tab.a.b.c1: ", tab_copied.a.b.c == tab.a.b.c) + ngx.say("tab_copied.a.b.c == t1: ", tab_copied.a.b.c == t1) + ngx.say("tab_copied.x.y == tab.x.y: ", tab_copied.x.y == tab.x.y) + ngx.say("tab_copied.x.y == t2: ", tab_copied.x.y == t2) + } + } +--- request +GET /t +--- response_body +table copied: {"a":{"b":{"c":{"name":"tab1"}}},"x":{"y":{"name":"tab2"}}} +tab_copied.a.b.c == tab.a.b.c1: true +tab_copied.a.b.c == t1: true +tab_copied.x.y == tab.x.y: false +tab_copied.x.y == t2: false