I have this code
type
TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType6, xsTyp7, xsType8); // up to FXSample30;
..
private
FX
the easiest way to store set in database (as @DavidHeffernan mentioned in comment) is to convert your set to bit-mask.
in int32 (integer) value you have 32 bits and can save set
up to 32 fields;
Delphi has TIntegerSet
(see http://docwiki.embarcadero.com/Libraries/en/System.SysUtils.TIntegerSet) type defined in SysUtils
. it is declared as:
TIntegerSet = set of 0..SizeOf(Integer) * 8 - 1;
so using it, it is simple to convert set to integer and back (just casting TIngeterSet to integer or vice versa);
bit-mask is also good option because it is only one INT
field in your database table.
also you can create separate table in your DB to store set content (main table (id, ...), and setValuesTable (main_id, setElementValue)) (this option is good for using in db queries)
here is an example of using TIntegerSet
:
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
type
TXSample = (xsType1, xsType2, xsType3, xsType4, xsType5, xsType6, xsType7, xsType8);
TSampleSet = set of TXSample;
function SampleSetToInteger(ss : TSampleSet) : integer;
var intset : TIntegerSet;
s : TXSample;
begin
intSet := [];
for s in ss do
include(intSet, ord(s));
result := integer(intSet);
end;
function IntegerToSampleSet(mask : integer) : TSampleSet;
var intSet : TIntegerSet;
b : byte;
begin
intSet := TIntegerSet(mask);
result := [];
for b in intSet do
include(result, TXSample(b));
end;
var xs : TSampleSet;
mask : integer;
begin
xs := [xsType2, xsType6 .. xsType8];
mask := SampleSetToInteger(xs); //integer mask
xs := IntegerToSampleSet(mask);
end.
Or we can make compiler forget about the types completly and then define what it should see (in case we know in compile-time what it sould see). This solution is so awful as it can be written on just one line.
type
// Controls.TCMMouseWheel relies on TShiftState not exceeding 2 bytes in size
TShiftState = set of (ssShift, ssAlt, ssCtrl,
ssLeft, ssRight, ssMiddle,
ssDouble, ssTouch, ssPen,
ssCommand, ssHorizontal);
var
Shifts : TShiftState;
Value : Integer;
begin
Shifts := TShiftState((Pointer(@Value))^):
Value := (PInteger(@Shifts))^;
if ssShift in TShiftState((Pointer(@Value))^) then
Exit;
end;
It happens that unused (top) bits are set (or not) but it has no influence on set
operations (in
, =
, +
, -
, *
.. ).
This line in Delphi:
Shifts := TShiftState((Pointer(@Value))^);
is like this in Assembler (Delphi XE6):
lea eax,[ebp-$0c]
mov ax,[eax]
mov [ebp-$06],ax
On Delphi 2007 (where is TShiftState
is smaller so Byte
can be used) this Assembler:
movzx eax,[esi]
mov [ebp-$01],al
Directly typecasting a set variable is not possible in Delphi, but internally Delphi stores the set as a byte-value. By using an untyped move, it is easy copied into an integer. Note that these functions only go up to a size of 32 (bounds of an integer). To increase the bounds, use Int64 instead.
function SetToInt(const aSet;const Size:integer):integer;
begin
Result := 0;
Move(aSet, Result, Size);
end;
procedure IntToSet(const Value:integer;var aSet;const Size:integer);
begin
Move(Value, aSet, Size);
end;
Demo
type
TMySet = set of (mssOne, mssTwo, mssThree, mssTwelve=12);
var
mSet: TMySet;
aValue:integer;
begin
IntToSet(7,mSet,SizeOf(mSet));
Include(mSet,mssTwelve);
aValue := SetToInt(mSet, SizeOf(mSet));
end;