====== Roll your own bitmasks ======
SQL's bitwise operators only work for integer datatype ranges -- even if one of your args is (''VAR'')''BINARY'', the other can be no larger than a ''BIGINT''. If you need more than 63 positions in a bitmask, you need to roll your own.
This approach supports the storage of bitmasks ''VARCHAR'' fields, in this implementation up to 255 characters long. Each (non-Unicode) character occupies one byte, and the character's ASCII code serves as a ''TINYINT'' that can be be used as a native bitwise operand.
===== Print bitmask =====
This is just for convenience/confidence! It prints out the bitmask **left to right**. The index of the first bit position is zero.
IF OBJECT_ID('dbo.fn_PrintBitmask') IS NOT NULL
DROP FUNCTION dbo.fn_PrintBitmask
GO
CREATE FUNCTION dbo.fn_PrintBitmask (
@str VARCHAR(255)
)
RETURNS VARCHAR(2040)
AS
BEGIN
DECLARE @bitstring VARCHAR(MAX) = @str + ':'
SELECT @bitstring += ' ' -- space for ease of reading
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 1 = 1 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 2 = 2 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 4 = 4 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 8 = 8 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 16 = 16 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 32 = 32 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 64 = 64 THEN '1' ELSE '0' END
+ CASE WHEN ASCII(SUBSTRING(@str, N, 1)) & 128 = 128 THEN '1' ELSE '0' END
--, N
--, SUBSTRING(@str, N, 1)
--, ASCII(SUBSTRING(@str, N, 1))
FROM dbo.Numbers
WHERE N <= LEN(@str)
ORDER BY N
RETURN @bitstring
END
===== Check if a bit is set =====
IF OBJECT_ID('dbo.fn_IsBitSet') IS NOT NULL
DROP FUNCTION dbo.fn_IsBitSet
GO
CREATE FUNCTION dbo.fn_IsBitSet (
@str VARCHAR(255)
, @bitpos INT
)
RETURNS BIT
AS
BEGIN
DECLARE @charPos INT = (@bitpos / 8) + 1
DECLARE @bitVal INT = POWER(2, @bitpos % 8)
IF ASCII(SUBSTRING(@str, @charPos, 1)) & @bitVal = @bitVal
RETURN 1
RETURN 0
END
===== Flipping bits =====
This function is a generic bit (un)setter. It takes a ''@set'' ''BIT'' parameter and is idempotent.
IF OBJECT_ID('dbo.fn_ChangeBit') IS NOT NULL
DROP FUNCTION dbo.fn_ChangeBit
GO
CREATE FUNCTION dbo.fn_ChangeBit (
@str VARCHAR(255)
, @bitpos INT
, @set BIT
)
RETURNS VARCHAR(255)
AS
BEGIN
DECLARE @charPos INT = (@bitpos / 8) + 1
DECLARE @bitVal INT = POWER(2, @bitpos % 8)
DECLARE @target INT = @bitVal
DECLARE @incr INT = @bitVal
IF @set = 0
BEGIN
SET @target = 0
SET @incr *= -1
END
IF ASCII(SUBSTRING(@str, @charPos, 1)) & @bitVal = @target
RETURN @str
RETURN SUBSTRING(@str, 1, @charPos - 1)
+ CHAR(ASCII(SUBSTRING(@str, @charPos, 1)) + @incr)
+ SUBSTRING(@str, @charPos + 1, LEN(@str))
END
====Set bit ====
This is a wrapper for ''dbo.fn_ChangeBit''.
IF OBJECT_ID('dbo.fn_SetBit') IS NOT NULL
DROP FUNCTION dbo.fn_SetBit
GO
CREATE FUNCTION dbo.fn_SetBit (
@str VARCHAR(255)
, @bitpos INT
)
RETURNS VARCHAR(255)
AS
BEGIN
RETURN dbo.fn_ChangeBit(@str, @bitPos, 1)
END
==== Unset bit ====
Like ''dbo.fn_SetBit'', a wrapper for ''dbo.fn_ChangeBit''.
IF OBJECT_ID('dbo.fn_UnsetBit') IS NOT NULL
DROP FUNCTION dbo.fn_UnsetBit
GO
CREATE FUNCTION dbo.fn_UnsetBit (
@str VARCHAR(255)
, @bitpos INT
)
RETURNS VARCHAR(255)
AS
BEGIN
RETURN dbo.fn_ChangeBit(@str, @bitPos, 0)
END
{{tag> bitmasks tsql development functions}}