Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions apisix/core/table.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -104,6 +105,7 @@ function _M.array_find(array, val)

return nil
end
_M.array_find = array_find


-- only work under lua51 or luajit
Expand All @@ -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

Expand All @@ -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
Expand Down
10 changes: 1 addition & 9 deletions apisix/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
7 changes: 4 additions & 3 deletions apisix/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion apisix/plugins/ai.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
144 changes: 144 additions & 0 deletions t/core/table.t
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading