Let's assume I have a table of items:
CREATE TABLE items
(
item serial PRIMARY KEY,
...
);
Now, I want to introduce the concept of "permissions" for each item (please note, I'm not talking about database access permissions here, but business logic permissions for that item). Each item has default permissions and also per-user permissions that may override default permissions.
I tried to think of several ways to implement this and came up with the following solutions, but I'm not sure on which is the best and why:
1) The Boolean Solution
Use a boolean column for each permission:
CREATE TABLE items
(
item serial PRIMARY KEY,
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
PRIMARY KEY(item, user),
can_change_description boolean NOT NULL,
can_change_price boolean NOT NULL,
can_delete_item_from_store boolean NOT NULL,
...
);
Advantages: Each permission is named.
Disadvantages: There are dozens of permissions which increases the number of columns significantly and you have to define them twice (once in each table).
2) The Integer Solution
Use an integer and treat it as a bitfield (i.e. bit 0 is for can_change_description
, bit 1 is for can_change_price
, and so on, and use bitwise operations to set or read permissions).
CREATE DOMAIN permissions AS integer;
Advantages: very fast.
Disadvantages: You have to keep track of which bit stands for which permission in both the database and the front-end interface.
3) The Bitfield Solution
Same as 2), but use bit(n)
. Most likely the same advantages and disadvantages, maybe slightly slower.
4) The Enum Solution
Use an enum type for the permissions:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
and then create an extra table for default permissions:
CREATE TABLE item_default_permissions
(
item int NOT NULL REFERENCES items(item),
perm permission NOT NULL,
PRIMARY KEY(item, perm)
);
and change the per-user definition table to:
CREATE TABLE item_per_user_permissions
(
item int NOT NULL REFERENCES items(item),
user int NOT NULL REFERENCES users(user),
perm permission NOT NULL,
PRIMARY KEY(item, user, perm)
);
Advantages: Easy to name individual permissions (you don't have to handle bit positions).
Disadvantages: Even when just retrieving the default permissions, it requires accessing two additional tables: first, the default permissions table, and second, the system catalog storing the enum values.
Especially because the default permissions must be retrieved for every single page view of that item, the performance impact of the last alternative might be significant.
5) The Enum Array Solution
Same as 4), but use an array to hold all the (default) permissions:
CREATE TYPE permission AS ENUM ('can_change_description', 'can_change_price', .....);
CREATE TABLE items
(
item serial PRIMARY KEY,
granted_permissions permission ARRAY,
...
);
Advantages: Easy to name individual permissions (you don't have to handle bit positions).
Disadvantages: Breaks the 1st normal form and is a bit ugly. Takes up a considerable number of bytes in a row if number of permissions is large (about 50).
Can you think of other alternatives?
Which approach should be taken and why?
Please note: this is a modified version of a question posted earlier on Stackoverflow.
bigint
fields (each good for 64 bits) or a bit string. I wrote a couple of related answers on SO that might be of help.