Question:
I am using closure tables to track user file permissions, and, after a JOIN, this results in multiple read booleans across multiple rows. I want to only select a row, if all of the joined rows are readable. Using UNION obviously does not achieve this.
Details:
Folders table:
CREATE TABLE IF NOT EXISTS folders
(
id INT NOT NULL AUTO_INCREMENT,
path VARCHAR(500) NOT NULL,
r BOOL NOT NULL DEFAULT FALSE,
PRIMARY KEY ( id )
)engine=innodb;
Files table:
CREATE TABLE IF NOT EXISTS files
(
id INT NOT NULL AUTO_INCREMENT,
parent_folder_id INT NOT NULL ,
path VARCHAR(500) NOT NULL,
r BOOL NOT NULL DEFAULT FALSE,
FOREIGN KEY ( parent_folder_id ) REFERENCES folders ( id ),
PRIMARY KEY ( id )
)engine=innodb;
Folder-Parent Folder closure table:
CREATE TABLE IF NOT EXISTS parent_folders
(
id INT NOT NULL AUTO_INCREMENT,
folder_id INT NOT NULL,
parent_folder_id INT NOT NULL,
FOREIGN KEY ( folder_id ) REFERENCES folders ( id ),
FOREIGN KEY ( parent_folder_id ) REFERENCES folders ( id ),
PRIMARY KEY ( id )
)engine=innodb;
Now, if I want to get all the readable files (ignoring for the moment that I have omitted users entirely), I would start out like so
SELECT
F.id, F.path, F.r, P.parent_folder_id, D.path, D.r
FROM
files AS F
LEFT JOIN parent_folders AS P
ON F.parent_folder_id = P.folder_id
LEFT JOIN folders AS D
ON P.parent_folder_id = D.id;
This will display a table of every file id, path and read permissions, as accessible from each of its parent folders like so
id path r id path r
......
0 /home/joe/foo/bar.txt True 1 /home/joe/foo True
1 /home/joe/foo/bar.txt True 2 /home/joe True
1 /home/joe/foo/bar.txt True 3 /home True
1 /home/joe/foo/bar.txt True 4 / True
2 /home/jim/foo/bar.txt True 5 /home/jim/foo True
2 /home/jim/foo/bar.txt True 6 /home/jim False
2 /home/jim/foo/bar.txt True 7 /home True
2 /home/jim/foo/bar.txt True 8 / True
....
In this case, I would like to SELECT /home/joe/foo/bar.txt because every parent folder leading down to it is readable, but I would not want to SELECT /home/jim/foo/bar.txt because one of its parent folders is not readable.
EDIT: Alternatively, I could rephrase the question like so: “Can I AND the values of one column across multiple rows?”
It can be done with non-standard SQL but this depends on your database vendor. For example, you might want to check hierarchical queries with the
CONNECT BYclause in Oracle. There may be something similar for MySQL. However, I would suggest against such a solution for three reasons:Instead, I would suggest the following approach which I have used in several medium to large scale projects:
For the
rfield use three-state booleans (TRUE,FALSEandNULL), whereNULLwill stand for “inherit”.Add a new field
effective_rfor each file (and maybe for each folder). This will contain the result of applying all the inheritance rules and can only beTRUEorFALSE. Of course, you will have to calculate this field on every change of the hierarchy, but this is faster as updates are not that often and when they happen, they affect only a part of the hierarchy.Define top-down propagation rules. In this case it is easy:
The rules can be much more complicated and sophisticated for user permissions.