Proposal about a "deep" versions for some jsonb functions

From: Dmitry Dolgov <9erthalion6(at)gmail(dot)com>
To: PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>
Subject: Proposal about a "deep" versions for some jsonb functions
Date: 2017-07-20 09:43:27
Message-ID: CA+q6zcU+gy1+dxQD09MSz8Zwqq+sPPfS-6GYKmyNqGVQDFeQbg@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi

As far as I know, since 9.5 we're missing some convenient features, namely a
deepversion of `jsonb_concat` and `jsonb_minus`. There are already few
feature
requests about `jsonb_minus` (see [1], [2]) and a lot of confusion and
requests
about a deep version of `jsonb_concat`. From my point of view they're pretty
much related, so I want to propose the following description for this
functionality and eventually implement it.

# jsonb_minus

```
jsonb_minus(jsonb, jsonb, deep=False)
```

Looks like we have to abandon "-" operator for that purpose (see a concern
about that in this thread [2]).

In general this functionality is something like the relative complement for
two
jsonb objects. Basically we're taking all the paths inside all jsonb objects
and remove duplicated paths from the left one. Of course an actual
implementation may be different, but I think it's a nice way of thinking
about
this logic.

Here are few examples, where "->" is an operation to get an actual value,
".->" - an operation to get a next key, "#->" an operation to get a value
from
an array ("-" operator is just for the sake of readability):

------------------------------------------------------------------------------

{"a": 1} - {"a": 1}
=> null

paths:
a -> 1

a -> 1

------------------------------------------------------------------------------

{"a": 1} - {"a": 2}
=> {"a": 1}

paths:
a -> 1

a -> 2

------------------------------------------------------------------------------

{"a": 1} - {"a": {"b": 1}}
=> {"a": 1}

paths:
a ->

a -> .b -> 1

------------------------------------------------------------------------------

{"a": 1, "b": {"c": 2}} - {"b": 1, "b": {"c": 3}}
=> {"b": {"c": 2}}

paths:
a -> 1
b .-> c -> 2

b -> 1
b .-> c -> 3

------------------------------------------------------------------------------

{"a": {"b": 1}} - {"a": {"b": 1}}
=> null

paths:
a .-> b -> 1

a .-> b -> 1

------------------------------------------------------------------------------

{"a": {"b": 1, "c": 2}} - {"a": {"b": 1}}
=> {"a": {"b": 1}}

paths:
a .-> b -> 1
a .-> c -> 2

a .-> b -> 1

------------------------------------------------------------------------------

{"a": {
"b": {"b1": 1},
"c": {"c2": 2}
}}

-

{"a": {
"b": {"b1": 1},
"c": {"c2": 3}
}}
=> {"a": {"c": {"c2": 2}}

paths:
a .-> b .-> b1 -> 1
a .-> c .-> c2 -> 2

a .-> b .-> b1 -> 1
a .-> c .-> c2 -> 3

------------------------------------------------------------------------------

{"a": [1, 2, 3]} - {"a": [1, 2]}
=> {"a": [3]}

paths:
a #-> 1
a #-> 2
a #-> 3

a #-> 1
a #-> 2

------------------------------------------------------------------------------

{"a": [{"b": 1}, {"c": 2}]} - {"a": [{"b": 1}, {"c": 3}]}
=> {"a": [{"c": 3}]}

paths:
a #-> b -> 1
a #-> c -> 2

a #-> b -> 1
a #-> c -> 3

But judging from the previous discussions, there is a demand for a bit
different behavior, when `jsonb_minus` is operating only on the top level of
jsonb objects. For that purpose I suggest introducing a flag `deep`, that
should be false by default (as for `jsonb_concat`), that will allow to
enable a
"deep logic" (a.k.a relative complement) I described above. With
`deep=False`
this function will behave similar to `hstore`:

{"a": 1, "b": {"c": 2}} - {"a": 1, "b": {"c": 3}}
=> {"a": 1}

# jsonb_concat

We already have this function implemented, but a "deep" mode is missing.

```
jsonb_concat(jsonb, jsonb, deep=False)
```

Basically we're taking all the paths inside all jsonb objects and override
duplicated paths in the left one, then add all unique paths from right one
to
the result.

Here are few examples for deep mode ("||" operator is just for the sake of
readability):

------------------------------------------------------------------------------

{"a": 1, "b": {"c": 2}} || {"a": 1, "b": {"d": 3}}
=> {"a": 1, "b": {"c": 2, "d": 3}}

paths:
a -> 1
b .-> c -> 2

a -> 1
b .-> d -> 3

------------------------------------------------------------------------------

{"a": 1, "b": {"c": 2}} || {"a": 1, "b": {"c": 3}}
=> {"a": 1, "b": {"c": 3}}

paths:
a -> 1
b .-> c -> 2

a -> 1
b .-> c -> 3

------------------------------------------------------------------------------

{"a": [1, 2, 3]} || {"a": [3, 4]}
=> {"a": [1, 2, 3, 4]}

paths:
a #-> 1
a #-> 2
a #-> 3

a #-> 3
a #-> 4

What do you think about that?

[1]:
https://www.postgresql.org/message-id/flat/CAHyXU0wtJ%2Bi-4MC5FPVc_oFu%2B3-tQVC8u04GmMNwYdPEAX1XSA%40mail(dot)gmail(dot)com#CAHyXU0wtJ+i-4MC5FPVc_oFu+3-tQVC8u04GmMNwYdPEAX1XSA(at)mail(dot)gmail(dot)com
[2]:
https://www.postgresql.org/message-id/flat/CAHyXU0wm0pkX0Gvzb5BH2jUAA_%3DswMJmyYuhBWzgOjfKxdrKfw%40mail(dot)gmail(dot)com#CAHyXU0wm0pkX0Gvzb5BH2jUAA_=swMJmyYuhBWzgOjfKxdrKfw(at)mail(dot)gmail(dot)com

Browse pgsql-hackers by date

  From Date Subject
Next Message Thomas Munro 2017-07-20 11:08:50 Re: Causal reads take II
Previous Message Fabien COELHO 2017-07-20 09:17:50 Re: Adding -E switch to pg_dumpall