diff --git a/kong/db/schema/init.lua b/kong/db/schema/init.lua index 32578af5efae5..5b2c5b1f67df0 100644 --- a/kong/db/schema/init.lua +++ b/kong/db/schema/init.lua @@ -1752,9 +1752,14 @@ function Schema:process_auto_fields(data, context, nulls, opts) local deprecation = sdata.deprecation for k, v in pairs(new_values) do if type(v) == "table" then - data[k] = deprecation and table_merge(v, data[k]) - or table_merge(data[k] or {}, v) - elseif not deprecation or data[k] == nil then + local source = {} + if data[k] and data[k] ~= null then + source = data[k] + end + data[k] = deprecation and null_aware_table_merge(v, source) + or table_merge(source, v) + + elseif not deprecation or (data[k] == nil or data[k] == null) then data[k] = v end end diff --git a/kong/tools/table.lua b/kong/tools/table.lua index 66fcb4feeb40c..a86c831b18ebd 100644 --- a/kong/tools/table.lua +++ b/kong/tools/table.lua @@ -45,6 +45,29 @@ function _M.table_merge(t1, t2) end +--- Merges two table together but does not replace values from `t1` if `t2` for a given key has `ngx.null` value +-- A new table is created with a non-recursive copy of the provided tables +-- @param t1 The first table +-- @param t2 The second table +-- @return The (new) merged table +function _M.null_aware_table_merge(t1, t2) + local res = {} + if t1 then + for k,v in pairs(t1) do + res[k] = v + end + end + if t2 then + for k,v in pairs(t2) do + if res[k] == nil or v ~= ngx.null then + res[k] = v + end + end + end + return res +end + + --- Checks if a value exists in a table. -- @param arr The table to use -- @param val The value to check diff --git a/spec/01-unit/01-db/01-schema/01-schema_spec.lua b/spec/01-unit/01-db/01-schema/01-schema_spec.lua index 183ed0bdeedaf..a6c03d3d8455f 100644 --- a/spec/01-unit/01-db/01-schema/01-schema_spec.lua +++ b/spec/01-unit/01-db/01-schema/01-schema_spec.lua @@ -4151,6 +4151,11 @@ describe("schema", function() local input = { username = "test1", name = "ignored", record = { x = "ignored" }, y = "test1" } local output, _ = TestSchema:process_auto_fields(input) assert.same({ name = "test1", record = { x = "test1" } }, output) + + -- deprecated fields does take precedence if the new fields are null + local input = { username = "overwritten-1", name = ngx.null, record = { x = ngx.null }, y = "overwritten-2" } + local output, _ = TestSchema:process_auto_fields(input) + assert.same({ name = "overwritten-1", record = { x = "overwritten-2" } }, output) end) it("does not take precedence if deprecated", function() @@ -4202,6 +4207,11 @@ describe("schema", function() local input = { username = "ignored", name = "test1", record = { x = "test1" }, y = "ignored" } local output, _ = TestSchema:process_auto_fields(input) assert.same({ name = "test1", record = { x = "test1" } }, output) + + -- deprecated fields does take precedence if the new fields are null + local input = { username = "overwritten-1", name = ngx.null, record = { x = ngx.null }, y = "overwritten-2" } + local output, _ = TestSchema:process_auto_fields(input) + assert.same({ name = "overwritten-1", record = { x = "overwritten-2" } }, output) end) it("can produce multiple fields", function()