I’m trying to create performant logic for determining permissions within a hierarchical organization.
Employees are assigned to one or more units. Units are hierarchical with (theoretically) infinite depth (in reality it’s no more than 6 layers).
For example, employee Jane
may be the Supervisor
of the Accounts Receivable
unit (a child of the Accounting
unit), and also Member
of the Ethics Committee
(a child of Committees
, which is itself a child of Office of the CEO
).
As the Supervisor
of Accounts Receivable
, Jane
should have permission to view personnel files of everyone else in Accounts Receivable
, but not in the Ethics Committee
since she’s just a Member
. Similarly, regular employees of the Accounts Receivable
unit should not be able to view one another’s profiles, though they’d all need permission to, say, view the accounting records of the company.
I imagine the database architecture for this will look something like:
| **employees** | **units** | **positions** | **assignments** | **permissions** |
| ------------- | ----------- | ------------- | --------------- | --------------- |
| id | id | id | employee_id | unit_id |
| name | name | title | unit_id | is_management |
| | parent_path | is_management | position_id | ability |
With that in mind, how can I write a performant query to determine which permissions Jane
has over Sam
, an Accountant
in Accounts Receivable
, versus over Bill
, a Receptionist
in Office of the CEO
?
The closest I have is something like:
create function permissions(actor employees, subject employees) returns setof permissions as $$
begin
for unit_id in select unit_id from assignments where employee_id = subject.id loop
select permissions.name
from assignments
left join units on (unit.id = assignments.unit_id)
left join positions on (positions.id = assignments.position_id)
left join permissions on (
permissions.unit_id = units.id
and permissions.is_management = positions.is_management
)
where assignments.user_id = actor.id
and (units.parent_path = unit_id or units.parent_path @> unit_id)
end loop;
end;
$$ language plpgsql stable;