I have the 4 following tables in MySQL 8.0:
-- auto-generated definition
CREATE TABLE user
(
id VARCHAR(255) NOT NULL
PRIMARY KEY,
name VARCHAR(255) NULL,
email VARCHAR(255) NOT NULL,
CONSTRAINT user_email_unique
UNIQUE (email)
)
CHARSET = utf8mb4;
-- auto-generated definition
CREATE TABLE role_inheritance
(
role_name VARCHAR(255) NOT NULL,
child_name VARCHAR(255) NOT NULL,
PRIMARY KEY (role_name, child_name),
CONSTRAINT role_inheritance_child_name_foreign
FOREIGN KEY (child_name) REFERENCES role (name)
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT role_inheritance_role_name_foreign
FOREIGN KEY (role_name) REFERENCES role (name)
ON UPDATE CASCADE ON DELETE CASCADE
)
CHARSET = utf8mb4;
CREATE INDEX role_inheritance_child_name_index
ON role_inheritance (child_name);
CREATE INDEX role_inheritance_role_name_index
ON role_inheritance (role_name);
-- auto-generated definition
CREATE TABLE role_assignment
(
user_id VARCHAR(255) NOT NULL,
role_name VARCHAR(255) NOT NULL,
scope VARCHAR(255) NULL,
PRIMARY KEY (user_id, role_name),
CONSTRAINT role_assignment_role_name_foreign
FOREIGN KEY (role_name) REFERENCES role (name)
ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT role_assignment_user_id_foreign
FOREIGN KEY (user_id) REFERENCES user (id)
ON UPDATE CASCADE ON DELETE CASCADE
)
CHARSET = utf8mb4;
CREATE INDEX role_assignment_role_name_index
ON role_assignment (role_name);
CREATE INDEX role_assignment_user_id_index
ON role_assignment (user_id);
-- auto-generated definition
CREATE TABLE role
(
name VARCHAR(255) NOT NULL
PRIMARY KEY
)
CHARSET = utf8mb4;
What I am attempting to do is to allow me to find all of the inherited roles given an entry in role_assignment
for a given user. The idea would be that if you assign a role a child, then any children of the assigned child would also become a child of the role. I currently have a way to find all children of a role via:
WITH RECURSIVE
inheritance AS (SELECT child_name
FROM role_inheritance
WHERE role_name = 'admin'
UNION ALL
SELECT ri.child_name
FROM inheritance i,
role_inheritance ri
WHERE ri.role_name = i.child_name)
SELECT *
FROM role r
WHERE r.name IN (SELECT child_name
FROM inheritance)
However, I want to set it up that I can do a full join between the user
, role_assignment
, and role_inheritance
tables to show that a user does indeed have a certain child role if they are assigned a role in role_assignment
.
For example, take the following tables (subbing ids for names for easier clarification):
Roles
note: these were randomly generated for testing purposes
name |
---|
admin |
e-services |
functionalities |
models |
networks |
sub-user |
super-admin |
thing |
user |
Role Inheritance
role_name | child_name |
---|---|
admin | functionalities |
admin | user |
super-admin | admin |
super-admin | networks |
super-admin | users |
thing | e-services |
thing | functionalities |
user | e-services |
user | models |
user | sub-user |
Role Assignment
user_id | role_name | scope |
---|---|---|
Kelly | admin | null |
Chris | user | null |
I want to achieve a result output that looks like this:
role_name | user_id |
---|---|
admin | Kelly |
e-services | Kelly |
functionalities | Kelly |
models | Kelly |
sub-user | Kelly |
user | Kelly |
e-services | Chris |
models | Chris |
sub-user | Chris |
user | Chris |
I attempted to use the following SQL, but it uses a method that will be deprecated in MySQL 9, and also does not remove "admin" and "functionalities" from Chris:
SELECT roles.name AS role_name, @id := u.id AS user_id
FROM user u
CROSS JOIN (WITH RECURSIVE
user_roles AS (SELECT role_name
FROM role_assignment where user_id = @id),
inheritance AS (SELECT child_name
FROM role_inheritance
WHERE role_name IN
(SELECT role_name
FROM user_roles)
UNION ALL
SELECT ri.child_name
FROM inheritance i,
role_inheritance ri
WHERE ri.role_name = i.child_name)
SELECT *
FROM role r
WHERE r.name IN (SELECT child_name
FROM inheritance)
OR r.name IN (SELECT role_name
FROM user_roles)) AS roles
ORDER BY user_id
How can I achieve this?
CodePudding user response:
If I follow you correctly, you can use the following logic:
with recursive cte as (
select user_id, role_name from role_assignment
union all
select c.user_id, ri.child_name
from cte c
inner join role_inheritance ri on ri.role_name = c.role_name
)
select * from cte order by user_id, role_name
This scans all users declared in role_assignment
, and then recursively follows the parent/child relationship of roles. As a result, you get a list of all users, with all the role they are assigned. I don't see the need to bring the user_roles
table as in your query.
In this demo on DB Fiddle with your sample data, this yields:
user_id | role_name |
---|---|
Chris | e-services |
Chris | models |
Chris | sub-user |
Chris | user |
Kelly | admin |
Kelly | e-services |
Kelly | functionalities |
Kelly | models |
Kelly | sub-user |
Kelly | user |