8

I am using a permission model where I have a table user_permissions. This table will hold one or more columns with a certain bigint. I will use the bits of each decimal number to compare with certain permission rules (the bit location will be a permission rule and the value will be the condition of the rule active or not active).

The problem with this approach is that I have limited number of bits to work when using a number such as bigint.

What is the best column type I can use in this case that works in a cross-database environment?

The tags represent the technologies I am aiming for, so any other solution related to those technologies are appreciated.

I was thinking to use @Lob annotation to store large data, is that the best practice?

UPDATE: The user_permission table extends the user with a 1:1 relationship and have bigint fields like bin_create, bin_read, bin_update, bin_delete that will hold the binary data as decimal numbers.

To clarify the question: I am considering comparing the permissions using bitwise operators. So let's assume I have a user with the permission value 10(1010), and an action requiring 13 (1101). So 10 & 13 == 8 (1000) -> The user have one permission matching the required permissions for the action, so I could allow or deny (it is up to the application rules define which). But with this approach I have a limited number of bits to work on (lets say I increase the permissions to be considered, so the numbers will increase too). The max bigint value is ~9223372036854775807 and that gives me the binary 111111111111111111111111111111111111111111111111111111111111111 with ~60 blocks and permission possibilities per field. So What is the best column type I can use in this case that works in a cross-database environment to store a huge quantity of binary blocks and with the possibility to work with bitwise operators in java?

Fagner Brack
  • 2,365
  • 4
  • 33
  • 69
  • Approximately how many bits will you store? 50, 500, 5000, 50000? – gaborsch Feb 18 '13 at 11:41
  • I want to store enough to give me a good number of permission rules to use but not too much that would turn my permission model into a huge and unmaintainable ciclomatic complexity problem. Maybe 100 would be more than enough, more than that is considered as too many permissions for a single field and need reestructuration. – Fagner Brack Feb 18 '13 at 14:33
  • I believe that this is not a really good idea to tie certain permissions to bits. Imagine that the permissions are evolving: some permissions become obsolete after some time, other permissions must be introduced, you have to subdivide one permission into 4 later, soon you run out of the 100 limit. If they don't have names, you are lost. If you don't maintain them (e.g. clear the unused bits, update the highest bit), you are lost. But it's your choice. I would recommend that if you use bits to store rights, the storage should not be too restrictive anyway. – gaborsch Feb 18 '13 at 16:42
  • I don't recommend `Lob`s, because it is very painful to load them. if you want to optimize for speed (I think that's why you use bits), probably you want to avoid them. – gaborsch Feb 18 '13 at 16:45
  • Permissions obsolete should be removed and not just ignored. It is all unit tested and each bit has a constant identifiyng its use. Also each bit permission will be properly documented for future reference. I don't understand the statement "I would recommend that if you use bits to store rights, the storage should not be too restrictive anyway". What you mean by too restrictive? Using long instead some byte array type? – Fagner Brack Feb 18 '13 at 20:42
  • By *restrictive* I meant that there should not be a hard limit for bits, e.g. 100 bits, because this would restrict you sooner or later. With other words, I'd say that the storage mode must be extensible. – gaborsch Feb 18 '13 at 22:30

3 Answers3

1

If it's the amount of data you can fit in a field that you're concerned about, why not store the number as a varchar? To my knowledge, pretty much any database will let you go up to at least a varchar(255). If you need more than 255 digits in the number, you could encode it in base 64 to squeeze it down more. If my mental arithmetic is right, that gives you 255 characters * 6 bits per character = 1530 different bits to use. If you need more than that, I might suggest your permissions model is a little excessive.

That's assuming that you're trying to crowd the data into a smallish space in the database. Your question isn't entirely clear on what you're trying to solve for. On the other end of the spectrum, you could unpack the bits and save each bit to its own field or its own row. For example, user_permissions could be a table with two columns: user and permission, where each row is one permission granted to one user.

Ryan Stewart
  • 126,015
  • 21
  • 180
  • 199
  • I think my answer still stands. Use varchar + encoding to squeeze a ton of bits into a field. Unpack it to a [BitSet](http://docs.oracle.com/javase/7/docs/api/java/util/BitSet.html) or [BigInteger](http://docs.oracle.com/javase/7/docs/api/java/math/BigInteger.html) for use in code. – Ryan Stewart Feb 15 '13 at 19:44
1

If you want to store your data in an optimal way, you have to name the target, where you want to optimize.

This is an optimal solution for MySQL (by defining BINARY(32)), you can try something similar on your favorite database:

@Column(columnDefinition = "BINARY(32)", length = 32, nullable = false)
private byte[] bits;

Sometimes with some JPA providers and databases the column definitions ends up with Lob. That's not the best solution, because reading a Lob is an external (very expensive) operation. Try to change either the provider, or the database (if you're working with pure JPA, you can try it).

Options for replacing Lobs are for example numeric columns (you can use e.g. 4 columns with 64bit width, or similar). If you want a nice solution, these container columns can be even @Embedded into your main class. But it all depends on your database.

This way you will have 256 bits (32 bytes), without any conversion and further calculation, and you will have the possibility to extend the range if you want. You have to be careful when changing the column definition, though.

gaborsch
  • 15,408
  • 6
  • 37
  • 48
  • I like the `@Embedded` approach. I can store a set of permissions in a 64bits field and then when I run out of slots I just create another field extending the first one, the access will be centralized in the original table, something like `byte[] bits = permissions.getRead() //get column read1 and read2`. That is what you meant, right? – Fagner Brack Feb 19 '13 at 15:21
  • @FagnerBrack Yes, something similar. You can make a `@Transient private byte[] bits` member, which is mapped to your embedded data representation. You can write the conversion in `getBits()` and `setBits()`. Even the actual columns can be in the original `TABLE`, no need to create a new table for that. – gaborsch Feb 19 '13 at 15:49
0

There are two different approaches:

1) Pretty data model

One row (user in your example) can have values (here a user permission which is one bit or a boolean value) where you don't know how many values are possible, i. e. the number of values is principally unlimited. The normal approach in SQL to handle this is a child table:

You create a table (and Java class for mapping / annotations) UserPermission, which contains the user id as a foreign key, a permission id and the boolean value. User id and permission id is a unique key for this table (you can add an id as a different primary key if you like). You even can add columns for the user who gave the permission, the date when this was done etc, if you want to have some auditing, but this is not necessary.

If you want to make it more pretty, then you also create a table (and Java class) Permission, which contains the permission id, a name for the permission and perhaps other information.

This solution needs more database space than your idea with the bits in an integer, but take in mind you won't have so many users compared to other data in the data base, so the extra amount doesn't matter.

2) Fast solution:

If the solution with extra tables needs to much overhead, because your permissions are not really important, and you worry an integer can be to short, then you can use the Java type BigInteger (the type allows bit manipulations) and map it with an @Column annotation to a NUMBER or DECIMAL in the database.

Bear in mind the size of a database's NUMBER also is limited (for example Oracle allows at maximum 10^40). If this might be a problem, then you must use solution 1).

One more disadvantage of solution 2) is, you never can use an index for the permissions. (A selection of all users having a certain permission set never will go over an index.)

I always would use solution 1).

Johanna
  • 5,223
  • 1
  • 21
  • 38
  • I updated the question. Is there any kind of type I can use to allow more binary blocks to be used? I personally like bits because of the ease to compare. Regarding indexes, the user will be indexed and since the table extends the user it will be fast enough. – Fagner Brack Feb 15 '13 at 15:37
  • Solution 2) is possible. You use the `@Column` annotation either for a ´long´ (or ´Long´) or a ´BigInteger´ variable (BigInteger can be easily converted to long by the ´longValue()` method for your bit manipulations). If you want to generate the dmd with Hibernate then you have to specify the length of the database column with the `length`or `columnDefinition` attribute. - Each database can handle numbers of this size. Just the type is called differently, `NUMBER(20)` for Oracle, `DECIMAL(20,0)` for MySQL. – Johanna Feb 18 '13 at 11:47
  • I don't want vendor specific solutions, so columnDefinition is not the way to go. I think `@column` length is the answer. I hope every db supports it somehow. – Fagner Brack Feb 18 '13 at 14:47
  • Note that the "20" in MySQL isn't the number of digits that can be stored. It's just the width of the largest number MySQL can store. MySQL uses 8 bytes for its largest numeric type. See this answer for details: http://stackoverflow.com/a/1632328/839646 – Ryan Stewart Feb 19 '13 at 02:53
  • @Ryan Stewart: That is not correct. DECIMAL(20) stores all the 20 digits completely, i. e. nothing is cut, and needs 12 bytes for storing one DECIMAL(20) number. Up to 65 digits is possible. See http://dev.mysql.com/doc/refman/5.5/en/fixed-point-types.html for DECIMAL and http://dev.mysql.com/doc/refman/5.5/en/storage-requirements.html (after the first table) for the number of bytes used by a DECIMAL. – Johanna Feb 19 '13 at 15:22
  • 1
    @Fagner Brack: If you don't use Hibernate to generate the dmd for you then you don't need the columnDefinition attribute. You directly can map a BigInteger to a DECIMAL or NUMBER with the `@Column` annotation. Every database has exact numeric types bigger than the Java long (if not the database would be unusable anyway because p. ex. financial values need this exact precision), but the maximum number of digits differs (p. ex. 40 for Oracle, 65 for MySQL). – Johanna Feb 19 '13 at 15:29