Объединение json объектов при группировании в postgres - PullRequest
0 голосов
/ 06 марта 2020

Я пытаюсь построить структуру полномочий пользователя в Postgres 11.5.

Основная идея c состоит в том, что пользователь может принадлежать к нескольким группам, а группа может иметь разрешения для нескольких приложений. Полномочия пользователя (если таковые имеются) будут переопределять любые разрешения, установленные на уровне группы.

Разрешения на уровне пользователя и группы пользователей будут храниться как json объекты, которые я хочу объединить вместе с разрешениями пользователя, перезаписывая разрешения группы пользователей, если они есть. любое перекрытие.

Пример: Брендан, Джеймс и другие пользователи входят в те же группы пользователей, но Джеймс не должен иметь доступа к app2.

Настройка:

CREATE TABLE public.users
(
    uid character varying COLLATE pg_catalog."default" NOT NULL,
    ugid character varying[],
    permissions json,
    CONSTRAINT users_pkey PRIMARY KEY (uid)
);

INSERT INTO public.users VALUES
('brendan','{default,gisteam}','{}'),
('james','{default,gisteam}','{"app2":{"enabled":false}}');


CREATE TABLE public.usergroups
(
    ugid character varying COLLATE pg_catalog."default" NOT NULL,
    permissions json,
    CONSTRAINT usergroups_pkey PRIMARY KEY (ugid)
);
INSERT INTO public.usergroups VALUES
('default','{"app1":{"enabled":true}}'),
('gisteam','{"app2":{"enabled":true},"app3":{"enabled":true}}');

Запрос:

SELECT uid, json_agg(permissions)
FROM (
    SELECT 
        u.uid,
        ug.permissions,
        'group' AS type
    FROM public.users u
    JOIN public.usergroups ug
    ON ug.ugid = ANY(u.ugid)
    UNION ALL
    SELECT 
        uid,
        permissions,
        'user' AS type
    FROM public.users u2
) a
GROUP BY uid;

Фактические результаты запроса:

+---------+----------------------------------------------------------------------------------------------------------+
|   uid   |                                            final_permissions                                             |
+---------+----------------------------------------------------------------------------------------------------------+
| brendan | [{"app1":{"enabled":true}},{"app2":{"enabled":true},"app3":{"enabled":true}},{}]                         |
| james   | [{"app1":{"enabled":true}},{"app2":{"enabled":true},"app3":{"enabled":true}},{"app2":{"enabled":false}}] |
+---------+----------------------------------------------------------------------------------------------------------+

Это работает, но я бы хотел, чтобы объект был сплющен, а ключи слиты.

Желаемый результат:

+---------+---------------------------------------------------------------------------+
|   uid   |                             final_permissions                             |
+---------+---------------------------------------------------------------------------+
| brendan | {"app1":{"enabled":true},"app2":{"enabled":true},"app3":{"enabled":true}} |
| james   | {"app1":{"enabled":true},"app2":{"enabled":false},"app3":{"enabled":true}}|
+---------+---------------------------------------------------------------------------+

DB Fiddle: https://www.db-fiddle.com/f/9kb1v1T82YVxWERxnWLThL/3

Другая информация: Фактический объект разрешений, установленный на уровне группы пользователей для каждого приложения, будет более сложным, чем в примере, например, функция featureA включена, featureB отключена и т. Д. c и на самом деле в будущем будет добавлено больше приложений, поэтому я не не хочу жестко кодировать любые ссылки на определенные c приложения или функции, если это возможно.

Я полагаю, что технически, если проще, желаемый вывод будет объектом прав доступа только для одного пользователя, поэтому может заменить GROUP BY uid с WHERE uid = 'x'

Вопрос / Проблема: Как я могу изменить запрос для создания плоского / объединенного разрешения json объект?

edit : фиксированный json

1 Ответ

1 голос
/ 06 марта 2020

Указанный вами желаемый результат не является синтаксически действительным JSON. Если я угадаю, что вы на самом деле хотите, вы можете получить это с помощью jsonb_object_agg, а не jsonb_agg. Сначала нужно откатить выбранные вами значения, чтобы их можно было повторно объединить, что здесь делается путем бокового соединения с json_each:

select uid, jsonb_object_agg(key,value) 
from (
    SELECT 
        u.uid,
        ug.permissions,
        'group' AS type
    FROM public.users u
    JOIN public.usergroups ug
    ON ug.ugid = ANY(u.ugid)
    UNION ALL
    SELECT 
        uid,
        permissions,
        'user' AS type
    FROM public.users u2) a 
CROSS JOIN LATERAL json_each(permissions) 
GROUP BY uid;

Выход:

   uid   |                                  jsonb_object_agg                                  
---------+------------------------------------------------------------------------------------
 brendan | {"app1": {"enabled": true}, "app2": {"enabled": true}, "app3": {"enabled": true}}
 james   | {"app1": {"enabled": true}, "app2": {"enabled": false}, "app3": {"enabled": true}}

Ваш выбор "group" as type сбивает с толку, поскольку он никогда не используется.

...