I'm running into an issue with recursive cte's on mariaDB/mysql. The setup isn't too complicated: there's a users table, a roles table, and a user_roles table that assign roles to users. However, roles can be nested, and the roles table contains a parent_id field to accomplish this. The parent_id field will be NULL when it's a root role, and contain the ID of another role if it's not.
This can be setup with following creates and inserts:
CREATE TABLE users (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
PRIMARY KEY (id)
);
CREATE TABLE roles (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
parent_id INT,
PRIMARY KEY (id)
);
CREATE TABLE user_roles (
id INT NOT NULL AUTO_INCREMENT,
user_id INT,
role_id INT,
PRIMARY KEY (id)
);
INSERT INTO users(name) VALUES ('Alice'),('Bob'),('Charlie');
INSERT INTO roles(name, parent_id) VALUES
('superuser',null),
('admin',1),
('ceo',1),
('employee', null);
INSERT INTO user_roles(user_id, role_id) VALUES
(1,2),
(2,3),
(3,4);
My goal is to create a query that selects all users where one of the user's roles (or one of it's ancenstors) matches a given role name. I came up with this:
SELECT * FROM users
WHERE EXISTS(
SELECT 1 FROM roles
INNER JOIN user_roles ON roles.id = user_roles.role_id
WHERE users.id = user_roles.user_id
AND EXISTS(
WITH RECURSIVE cte AS (
SELECT * FROM roles as roles_recursive
WHERE roles_recursive.id = roles.id
UNION ALL
SELECT roles_recursive.* FROM roles as roles_recursive
INNER JOIN cte ON cte.parent_id = roles_recursive.id
)
SELECT 1 from cte WHERE name='superuser'
)
);
I setup a DB fiddle to reproduce my issue. Please check out the full repro case over there. This produces different results on different engines:
- MySQL 8.0.18: my current development machine only runs macOS 10.13, so I'm limited to this MySQL version. On this version, I only get 'Alice' as a result, while 'Bob' should be in the results as well.
- The DB fiddle, MariaDB 10.3.30: produces an error: "ER_BAD_FIELD_ERROR: Unknown column 'roles.id' in 'where clause'". It looks like the roles-scope isn't available within the CTE.
- dbfiddle.uk seems to be running yet another version of MySQL and does return the correct result set.
Is there any way to do this that at least works in MariaDB? I'm not looking for solutions with simple joins, the roles should support a variable depth.