134 lines
5.1 KiB
MySQL
134 lines
5.1 KiB
MySQL
|
--liquibase formatted sql
|
||
|
|
||
|
|
||
|
-- ============================================================================
|
||
|
--changeset JSONB-CHANGES-DELTA:1 endDelimiter:--//
|
||
|
-- ----------------------------------------------------------------------------
|
||
|
/*
|
||
|
Recursively compares two jsonb values and returns what has changed.
|
||
|
This is a kind of right sided json diff.
|
||
|
*/
|
||
|
|
||
|
create or replace function jsonb_changes_delta(oldJson jsonb, newJson jsonb)
|
||
|
returns jsonb
|
||
|
called on null input
|
||
|
language plpgsql as $$
|
||
|
declare
|
||
|
diffJson jsonb;
|
||
|
oldJsonElement record;
|
||
|
begin
|
||
|
raise notice '>>> diffing: % % vs. % %', jsonb_typeof(oldJson), oldJson, jsonb_typeof(newJson), newJson;
|
||
|
|
||
|
if oldJson is null or jsonb_typeof(oldJson) = 'null' or
|
||
|
newJson is null or jsonb_typeof(newJson) = 'null' then
|
||
|
return newJson;
|
||
|
end if;
|
||
|
|
||
|
diffJson = newJson;
|
||
|
for oldJsonElement in select * from jsonb_each(oldJson)
|
||
|
loop
|
||
|
raise notice 'intermediate result: %', diffJson;
|
||
|
raise notice 'record: %', oldJsonElement;
|
||
|
if diffJson @> jsonb_build_object(oldJsonElement.key, oldJsonElement.value) then
|
||
|
raise notice 'ignoring equal: %', oldJsonElement.key;
|
||
|
diffJson = diffJson - oldJsonElement.key;
|
||
|
elsif diffJson ? oldJsonElement.key then
|
||
|
if jsonb_typeof(newJson -> (oldJsonElement.key)) = 'object' then
|
||
|
raise notice 'diffing new: % -> %', oldJsonElement.key, newJson -> (oldJsonElement.key);
|
||
|
diffJson = diffJson ||
|
||
|
jsonb_build_object(oldJsonElement.key,
|
||
|
jsonb_changes_delta(oldJsonElement.value, newJson -> (oldJsonElement.key)));
|
||
|
else
|
||
|
raise notice 'not an object: %, leaving %', oldJsonElement.key, newJson -> (oldJsonElement.key);
|
||
|
end if;
|
||
|
else
|
||
|
raise notice 'nulling old: %', oldJsonElement.key;
|
||
|
diffJson = diffJson || jsonb_build_object(oldJsonElement.key, null);
|
||
|
end if;
|
||
|
end loop;
|
||
|
raise notice '<<< result: %', diffJson;
|
||
|
return diffJson;
|
||
|
end; $$;
|
||
|
|
||
|
/*
|
||
|
Tests jsonb_diff.
|
||
|
*/
|
||
|
do language plpgsql $$
|
||
|
declare
|
||
|
expected text;
|
||
|
actual text;
|
||
|
begin
|
||
|
|
||
|
select jsonb_changes_delta(null::jsonb, null::jsonb) into actual;
|
||
|
if actual is not null then
|
||
|
raise exception 'jsonb_diff #1 failed:% expected: %,% actually: %', E'\n', expected, E'\n', actual;
|
||
|
end if;
|
||
|
|
||
|
select jsonb_changes_delta(null::jsonb, '{"a": "new"}'::jsonb) into actual;
|
||
|
expected := '{"a": "new"}'::jsonb;
|
||
|
if actual <> expected then
|
||
|
raise exception 'jsonb_diff #2 failed:% expected: %,% actual: %', E'\n', expected, E'\n', actual;
|
||
|
end if;
|
||
|
|
||
|
select jsonb_changes_delta('{"a": "old"}'::jsonb, '{"a": "new"}'::jsonb) into actual;
|
||
|
expected := '{"a": "new"}'::jsonb;
|
||
|
if actual <> expected then
|
||
|
raise exception 'jsonb_diff #3 failed:% expected: %,% actual: %', E'\n', expected, E'\n', actual;
|
||
|
end if;
|
||
|
|
||
|
select jsonb_changes_delta('{"a": "old"}'::jsonb, '{"a": "old"}'::jsonb) into actual;
|
||
|
expected := '{}'::jsonb;
|
||
|
if actual <> expected then
|
||
|
raise exception 'jsonb_diff #4 failed:% expected: %,% actual: %', E'\n', expected, E'\n', actual;
|
||
|
end if;
|
||
|
|
||
|
select jsonb_changes_delta(
|
||
|
$json${
|
||
|
"a": "same",
|
||
|
"b": "old",
|
||
|
"c": "set",
|
||
|
"d": "set",
|
||
|
"e": null,
|
||
|
"i": {
|
||
|
"x": "equal",
|
||
|
"y": "old",
|
||
|
"z": "old"
|
||
|
},
|
||
|
"j": {
|
||
|
"k": "set"
|
||
|
},
|
||
|
"l": {
|
||
|
"m": "equal",
|
||
|
"n": null
|
||
|
}
|
||
|
}$json$::jsonb,
|
||
|
$json${
|
||
|
"a": "same",
|
||
|
"b": "new",
|
||
|
"c": null,
|
||
|
"e": {
|
||
|
"f": "set"
|
||
|
},
|
||
|
"g": "set",
|
||
|
"i": {
|
||
|
"x": "equal",
|
||
|
"y": "new",
|
||
|
"z": null
|
||
|
},
|
||
|
"j": null,
|
||
|
"l": {
|
||
|
"m": "equal",
|
||
|
"n": null
|
||
|
}
|
||
|
}$json$::jsonb
|
||
|
)
|
||
|
into actual;
|
||
|
expected :=
|
||
|
$json${"b": "new", "c": null, "d": null, "e": {"f": "set"}, "g": "set", "i": {"y": "new", "z": null}, "j": null}$json$;
|
||
|
if actual <> expected then
|
||
|
raise exception 'jsonb_diff #5 failed:% expected: %,% actual: %', E'\n', expected, E'\n', actual;
|
||
|
end if;
|
||
|
end; $$;
|
||
|
--//
|
||
|
|