1 module fdb.tuple.unpacker;
2 
3 import
4     std.array,
5     std.conv,
6     std.exception,
7     std.range,
8     std..string,
9     std.traits,
10     std.uuid;
11 
12 import
13     fdb.tuple.part,
14     fdb.tuple.segmented,
15     fdb.tuple.tupletype;
16 
17 private struct FDBVariant
18 {
19     const TupleType      type;
20     const shared ubyte[] slice;
21 
22     private ulong _length;
23     @property auto length()
24     {
25         return _length;
26     }
27 
28     private Part _part;
29     @property Part part()
30     {
31         if (_part.hasValue)
32             return _part;
33 
34         switch (type) with (TupleType)
35         {
36             case Nil:
37                 _part = readNull;
38                 break;
39             case Bytes:
40                 _part = readBytes;
41                 break;
42             case Utf8:
43                 _part = readStr;
44                 break;
45             case IntNeg8: .. case IntPos8:
46                 _part = readInt;
47                 break;
48             case Single:
49                 _part = readFloat!(float, uint, floatSignMask);
50                 break;
51             case Double:
52                 _part = readFloat!(double, ulong, doubleSignMask);
53                 break;
54             case Uuid128:
55                 _part = readUUID;
56                 break;
57             default:
58                 enforce(0, "Type " ~ type.to!string ~ " is not supported");
59                 break;
60         }
61 
62         return _part;
63     }
64 
65     this(Range)(in TupleType type, Range slice) pure
66     if (isInputRange!(Unqual!Range))
67     in
68     {
69         with (type)
70             if (isFDBIntegral || isFDBFloat || isFDBDouble || isFDBUUID)
71                 enforce(FDBsizeof == slice.length);
72     }
73     body
74     {
75         this.type  = type;
76         this.slice = slice;
77     }
78 
79     this(Range)(in TupleType type, Range buffer, in ulong offset) pure
80     if (isInputRange!(Unqual!Range))
81     {
82         if (type == TupleType.Nil ||
83             type.isFDBIntegral ||
84             type.isFDBFloat ||
85             type.isFDBDouble ||
86             type.isFDBUUID)
87         {
88             auto size = type.FDBsizeof;
89             enforce(offset + size <= buffer.length);
90 
91             this.type    = type;
92             this.slice   = cast(shared)buffer[offset .. offset + size];
93             this._length = size;
94         }
95         else
96         {
97             this.type  = type;
98             this.slice = cast(shared)buffer[offset .. $];
99         }
100     }
101 
102     private auto readNull() const pure @nogc
103     {
104         return null;
105     }
106 
107     private auto readBytes()
108     in
109     {
110         enforce(slice.length > 1);
111     }
112     body
113     {
114         ubyte[] result;
115         foreach(idx, b; slice)
116             if (b != byteArrayEndMarker)
117                 result ~= b;
118             else if (idx > 0 && slice[idx - 1] != 0x00)
119             {
120                 _length = idx + 1;
121                 break;
122             }
123 
124         return result;
125     }
126 
127     private auto readStr()
128     {
129         auto chars = (cast(char[])slice);
130         auto size  = chars.indexOf(0, 0);
131         if (size > -1)
132         {
133             chars   = chars[0..size];
134             _length = size + 1;
135         }
136         return chars.to!string;
137     }
138 
139     private auto readInt() const
140     {
141         Segmented!ulong dbValue;
142         dbValue.segments[0..slice.length] = slice.retro.array;
143 
144         long value;
145         if (type < TupleType.IntBase)
146         {
147             value = -(~dbValue.value);
148             auto size = type.FDBsizeof;
149             if (size < long.sizeof)
150             value |= (-1L << (size << 3));
151         }
152         else
153             value = dbValue.value;
154         return value;
155     }
156 
157     private auto readFloat(F, I, alias M)() const
158     {
159         Segmented!(F, ubyte, I) dbValue;
160         dbValue.segments[0..slice.length] = slice.retro.array;
161 
162         // check if value is positive or negative
163         if ((dbValue.alt & M) != 0)
164             dbValue.alt ^= M;
165         else // negative
166             dbValue.alt  = ~dbValue.alt;
167 
168         auto value = dbValue.value;
169         return value;
170     }
171 
172     private auto readUUID() const
173     {
174         UUID value;
175         value.data = slice[0..$];
176         return value;
177     }
178 }
179 
180 auto unpack(Range)(Range bytes)
181 if (isInputRange!(Unqual!Range))
182 {
183     ulong pos = 0;
184     Part[] parts;
185     while (pos < bytes.length)
186     {
187         auto marker = cast(TupleType)bytes[pos++];
188         auto var    = FDBVariant(marker, bytes, pos);
189 
190         parts ~= var.part;
191         pos   += var.length;
192     }
193     return parts;
194 }
195 
196 /**
197  * Returns single value if type matches T
198  */
199 auto unpack(T, size_t i = 0, Range)(Range bytes)
200 {
201     auto unpacked = unpack(bytes);
202     auto value    = unpacked[i].get!T;
203     return value;
204 }