Integer Based Bit Manipulation - SQL

Introduction

Hopefully my topic isn't too far off.  In this post I'm going to cover what I like to think of as easy bit manipulation.  Well, it's easier anyway.  The basic concept is that an integer is comprised of bits.  Integers are easier to look at and play with than bits, especially if we're storing bit values in SQL and we want to update them...  Using logical bitwise operators, we can query one integer with another to see which bits they have in common.  When performing the query, if one value is strictly a bit, we can see if that bit is 'on' in the second value, which is a sum of one or more bits.  Alternatively, we can test two summed bits to see if one is a union of the other.  To me this means all of one exists completely in the other.

I plan to post a C# extension method version of the SQL functions below in a day or two.  Since I'm not using VS.Net 2008 full time yet, I need to convert what I have to extension methods.  Well, I guess I don't need to, but since I think extension methods are cool, I'm going to:)

Example

I'll start with a security example, but the context can be anything at all.  We need two values, one is the test bit and one is a sum of bits.

Since we're doing permissions, our test bits might look like the following.

  • CanView = 1
  • CanEdit = 2
  • CanSubmit = 4
  • CanApprove = 8
  • CanReject = 16

What about that sum of bits?  Sure, lets say we have an administrator with all bits.  Their sum of bits would be 1 + 2 + 4 + 8 + 16 = 31.  Perhaps we have a contributor, they can only view edit and submit, they would have 1 + 2 + 4 = 7.

  Admin Contributor
Integer 31 7
Bits 0001 1111 0000 0111

Ok, that was easy, let's say that users with Admin permissions have a 31 and contributors have a 7.  Suddenly we have the need to perform a check, can the user Approve? Can the user Edit?

Let's see, we need another table to consider this, let's examine the bits of CanApprove and CanEdit.

  CanApprove CanEdit
Integer 8 2
Bits 0000 1000 0000 0010

So, can the Admin approve or edit?  How about the Contributor?

 

Admin

 

Contributor

Summed Bits

0

0

0

1

1

1

1

1

0

0

0

0

0

1

1

1

CanApprove

0

0

0

0

1

0

0

0

0

0

0

0

1

0

0

0

CanEdit

0

0

0

0

0

0

1

0

0

0

0

0

0

0

1

0

I hope this is legible and understandable so far.  This last table is trying to line up the summed bits of the admin and contributors juxtaposed with the bits of CanApprove and CanEdit to simplify comparison.  The green shows a match and the red shows a miss.  Above we can see that the integer value for admin has both the CanApprove and CanEdit bits.  At the same time, we see that the Contributor bits do not contain the CanApprove bit, but they do contain the CanEdit bits.

Pretty exciting stuff isn't it :)

Uses

Hopefully at this point, I have successfully demonstrated how integers can be used to define flags and then store them in aggregate.  Where I work we primarily use them in two instances.

First in granular page permissions such as the example above, allowing functionality based on permissions.  After authorizing the user on a page, we then enable UI components based on the bits of the component compared to the bits of the user.

Next, we use this as a way to store multiple bit fields.  Some might warn about performance, no we haven't tested performance here, because we haven't seen an issue.  For example, let's say we send out a form with multiple checkboxes on it.  If we have eight bit fields on a form, instead of persisting to eight bit columns we persist to one integer called FormBits.  When we need to know if something was checked or not, we simply examine the integer for the bits we need.

Code

Enough talk, what about this SQL stuff?  Below are methods to test and read bit values SQL.  There are some comments in the code, but I'm not going to elaborate on the bitwise operators.

SQL

  1. CREATE FUNCTION [Utilities].[SetBit]
  2. (
  3.   -- the bits to check as an integer
  4.   @iBits INT
  5.   -- this bit to test
  6.   -- this must be a bit value
  7.   -- e.g. 1, 2, 4, 8, 16, 32, 64, etc
  8.   -- non bit values will produce unknown results
  9.   -- e.g. 3, 5, 6, 7, 9, 10, 11, etc
  10.   ,@iTestBit INT
  11.   -- should the bit be on or off
  12.   ,@iBitState BIT
  13. )
  14. RETURNS INT
  15. AS
  16. BEGIN
  17.   DECLARE @BitState BIT;
  18.   -- we are validating that the testbit paramater is a bit
  19.   -- if it isn't, we return null
  20.   IF (Utilities.IsBit(@iTestBit) = 0)
  21.     RETURN NULL;
  22.   -- determine the current state of the test bit
  23.   SET @BitState = Utilities.ReadBit(@iBits, @iTestBit);
  24.   -- leave if the value is correct
  25.   IF (@BitState = @iBitState)
  26.     RETURN @iBits;
  27.   IF (@BitState = 0 AND @iBitState = 1)
  28.     RETURN @iBits + @iTestBit;
  29.   RETURN @iBits - @iTestBit;
  30. END;
  31.  
  32. CREATE FUNCTION [Utilities].[ReadBit]
  33. (
  34.   -- the bits to check as an integer
  35.   @iBits INT
  36.   -- this bit to test
  37.   -- this must be a bit value
  38.   -- e.g. 1, 2, 4, 8, 16, 32, 64, etc
  39.   -- non bit values will produce unknown results
  40.   -- e.g. 3, 5, 6, 7, 9, 10, 11, etc
  41.   ,@iTestBit INT
  42. )
  43. RETURNS BIT
  44. AS
  45. BEGIN
  46.   DECLARE @ComparisonResults INT;
  47.   -- we are validating that the testbit paramater is a bit
  48.   -- if it isn't, we return null
  49.   IF (Utilities.IsBit(@iTestBit) = 0) BEGIN
  50.     RETURN NULL;
  51.   END;
  52.   -- bitwise and the bits and the test bit
  53.   SET @ComparisonResults = @iBits & @iTestBit;
  54.   -- if the result equals the test bit, the bit is present
  55.   IF (@ComparisonResults = @iTestBit) BEGIN
  56.     RETURN 1;
  57.   END;
  58.   ELSE BEGIN
  59.     RETURN 0;
  60.   END;
  61.   -- this return is required by the function
  62.   -- it should never be hit
  63.   RETURN 0;
  64. END;
  65. CREATE FUNCTION [Utilities].[IsBit]
  66. (
  67.   @iTestBit INT
  68. )
  69. RETURNS BIT
  70. AS
  71. BEGIN
  72.   -- This function will take an integer and return true if it is a bit value, i.e. 1, 2, 4, 8
  73.   DECLARE @Result BIT;
  74.   IF @iTestBit = 1
  75.     -- Special case of TestBit = 1
  76.     SET @Result = 1;
  77.   ELSE IF ROUND(LOG(@iTestBit)/LOG(2),1) = LOG(@iTestBit)/LOG(2)
  78.     -- if the LOG Base 2 of the TestBit value is an INTEGER, you have found 2, 4, 8, 16
  79.     SET @Result = 1;
  80.   ELSE
  81.     SET @Result = 0;
  82.   RETURN @Result;
  83. END;
  84. CREATE FUNCTION Utilities.IsBitUnion
  85. (
  86.     -- the bits to check as an integer
  87.     @iBits INT
  88.     -- this bits to test
  89.     ,@iTestBits INT
  90. )
  91. RETURNS BIT
  92. AS
  93. BEGIN
  94.     DECLARE @ComparisonResults INT;
  95.     -- bitwise and the bits and the test bits
  96.     SET @ComparisonResults = @iBits & @iTestBits;
  97.  
  98.     -- if the input bits equals the test bits, we have a union
  99.     IF (@ComparisonResults = @iTestBits) BEGIN
  100.         RETURN 1;
  101.     END;
  102.  
  103.     RETURN 0;
  104. END;
  105. -- CanView = 1
  106. -- CanEdit = 2
  107. -- CanSubmit = 4
  108. -- CanApprove = 8
  109. -- CanReject = 16
  110. -- Admin = 31
  111. -- Contributor = 7
  112. SELECT Utilities.ReadBit(31, 8) -- 1 : Admin has CanApprove
  113. SELECT Utilities.ReadBit(31, 2) -- 1 : Admin has CanEdit
  114. SELECT Utilities.IsBitUnion(31, 10) -- 1 : Admin has CanApprove & CanEdit
  115. SELECT Utilities.ReadBit(7, 8) -- 0 : Contributor does not have CanApprove
  116. SELECT Utilities.ReadBit(7, 2) -- 1 : Contributor has CanEdit
  117. SELECT Utilities.IsBitUnion(7, 10) -- 0 : Contributor does not have CanApprove & CanEdit
  118. SELECT Utilities.SetBit(7, 8, 1) -- 15 : Add CanApprove to Contributor
  119. SELECT Utilities.ReadBit(15, 8) -- 1 : Contributor has CanApprove
  120. SELECT Utilities.IsBitUnion(15, 10) -- 1 : Contributor has CanApprove & CanEdit

The IsBit function can be used as a constraint on columns that hold the individual bits that make up permissions and to validate that the test bit input to the ReadBit function is a bit.  If a non-bit were passed to readbit, bad things happen.

Thank you for reading my post, any and all feedback is encouraged!
Ben

P.S. As a quick SQL rant/question, what is up with synonyms not being allowed for schema bound objects?  Where I work, we have a shared database that multiple project databases reference in one way or another.  The Utilities.IsBit function above is employed in databases to ensure that any configured bit values are bits.  Makes sense right?  Well, since we cannot use a schema bound object in a check constraint, we have to have multiple copies of the function.  This is also the case with other entities that we would otherwise use a synonym to reference.  Is it me, or is this an unnecessary inconvenience?

2 Comments

  • Ben, nice article. I've done one similar on BitField masks some months ago too.

    http://weblogs.asp.net/alessandro/archive/2007/10/02/one-bit-masks-for-access-control-setting-permissions-in-your-asp-net-applications.aspx

    Have a good day.

  • @alessandro I remember seeing that when it came out, but totally forgot about it last night. Hopefully my article is different enough to not be plagiarism...

Comments have been disabled for this content.