I have two tables, roles and permissions, both of which use ltree for maintaining a tree structure. I also have a pivot table, roles_permissions, which serves to connect the two tables. How can I join them to each other – and bring in all the relations – in a reasonably efficient way?
In this system, the ancestry determines the capabilities of a role or permission. A role below building_access will inherit the rights and permissions of its parent. I can quite easily query roles or permissions and determine the entire line of ancestry for a given record.
What I am trying to do is query a role and determine the complete tree of permissions for that role. I am also trying to query all roles and determine the same list of permissions, for each and every role, aggregating it into a string.
SELECT r.name AS "role", CONCAT('["', string_agg(p.name, '", "' ORDER BY p.id), '"]') AS "permissions" FROM roles r JOIN roles_permissions rp ON rp.role = r.id JOIN permissions p ON p.id = rp.permission WHERE r.path @> 'company_employee.warehouse_employee' GROUP BY r.id; Actual Results: ---------------------+------------------------------------------------- role | permissions ---------------------+------------------------------------------------- company employee | ["building access", "break room access"] warehouse employee | ["warehouse access", "warehouse stock access"] Intended Results: ---------------------+------------------------------------------------- role | permissions ---------------------+------------------------------------------------- company employee | ["building access", "break room access"] warehouse employee | ["building access", "break room access", "warehouse access", "warehouse stock access"] This query functions to give me the direct permissions for every role, but it doesn't get the ancestors of those permissions to establish the actual total permissions for the role.
My sample database is as displayed below:
CREATE EXTENSION ltree; CREATE EXTENSION tablefunc; CREATE TABLE roles ( id int PRIMARY KEY, name text NOT NULL, path ltree ); CREATE INDEX roles_path_idx ON roles USING gist (path); INSERT INTO roles (id, name, path) VALUES (1, 'company employee', 'company_employee'), (2, 'warehouse employee', 'company_employee.warehouse_employee'), (3, 'warehouse manager', 'company_employee.warehouse_employee.warehouse_manager'); CREATE TABLE permissions ( id int PRIMARY KEY, name text NOT NULL, path ltree ); CREATE INDEX permissions_path_idx ON permissions USING gist (path); INSERT INTO permissions (id, name, path) VALUES (1, 'building access', 'building_access'), (2, 'break room access', 'building_access.break_room_access'), (3, 'warehouse access', 'building_access.warehouse_access'), (4, 'warehouse stock access', 'building_access.warehouse_access.warehouse_stock_access'), (5, 'warehouse security access', 'building_access.warehouse_access.warehouse_security_access'); CREATE TABLE roles_permissions ( role int REFERENCES roles, permission int REFERENCES permissions, PRIMARY KEY (role, permission) ); INSERT INTO roles_permissions (role, permission) VALUES (1, 1), (1, 2), (2, 3), (2, 4), (3, 5);
warehouse employeerole havebreak room accesspermission?array_aggwhich McNets use rather than thestring_aggidea. It's far better. Also, never double quote identifiers.