From 2022492f21ca65bd0eb7e73adb96648784bf2bd8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 23 Aug 2022 09:36:39 +0200 Subject: [PATCH] implements jsonb-changes-delta --- .../db/changelog/004-jsonb-changes-delta.sql | 134 ++++++++++++++++++ .../db/changelog/db.changelog-master.yaml | 2 + 2 files changed, 136 insertions(+) create mode 100644 src/main/resources/db/changelog/004-jsonb-changes-delta.sql diff --git a/src/main/resources/db/changelog/004-jsonb-changes-delta.sql b/src/main/resources/db/changelog/004-jsonb-changes-delta.sql new file mode 100644 index 00000000..04ef1c18 --- /dev/null +++ b/src/main/resources/db/changelog/004-jsonb-changes-delta.sql @@ -0,0 +1,134 @@ +--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; + continue; + 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; $$; +--// + diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index c7175eb0..929a6f33 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -5,6 +5,8 @@ databaseChangeLog: file: db/changelog/002-int-to-var.sql - include: file: db/changelog/003-random-in-range.sql + - include: + file: db/changelog/004-jsonb-changes-delta.sql - include: file: db/changelog/005-uuid-ossp-extension.sql - include: