1 /**
2 Primitives used for marshalling.
3 
4 Copyright: Copyright Boris-Barboris 2017.
5 License: MIT
6 Authors: Boris-Barboris
7 */
8 
9 module dpeq.marshalling;
10 
11 import std.algorithm: canFind;
12 import std.exception: enforce;
13 import std.bitmanip: nativeToBigEndian, bigEndianToNative;
14 import std.conv: to;
15 import std.meta;
16 import std.traits;
17 import std.typecons: Nullable, Tuple;
18 import std.variant;
19 import std.uuid: UUID;
20 
21 import dpeq.constants;
22 import dpeq.exceptions;
23 
24 
25 
26 /// Value type descriptor, constisting of an oid of the type itself and a
27 /// boolean flag wich indicates wether the value can be null.
28 struct FieldSpec
29 {
30     ObjectID typeId;
31     bool nullable;
32 }
33 
34 
35 /** Default compile-time one-to-many mapper, wich for ObjectID of some Postgress type
36 * gives it's native type representation, and marshalling and demarshalling
37 * functions. You can extend it with two custom mappers: Pre and Post. */
38 template DefaultFieldMarshaller(FieldSpec field, alias Pre = NopMarshaller,
39     alias Post = PromiscuousStringMarshaller)
40 {
41     static if (Pre!field.canDigest)
42     {
43         // must alias to native type representing this value
44         alias type = Pre!field.type;
45 
46         // By default, this format code will be passed in Bind message
47         // as parameter formatCode.
48         enum formatCode = Pre!field.formatCode;
49 
50         // demarshaller should accept all formatCodes that make sense for this
51         // type, and it must throw in unhandled case.
52         alias demarshal = Pre!field.demarshal;
53 
54         // marshaller only needs to support one formatCode, mentioned above
55         alias marshal = Pre!field.marshal;
56     }
57     else static if (StaticFieldMarshaller!field.canDigest)
58     {
59         alias type = StaticFieldMarshaller!field.type;
60         enum formatCode = StaticFieldMarshaller!field.formatCode;
61         alias demarshal = StaticFieldMarshaller!field.demarshal;
62         alias marshal = StaticFieldMarshaller!field.marshal;
63     }
64     else static if (Post!field.canDigest)
65     {
66         alias type = Post!field.type;
67         enum formatCode = Post!field.formatCode;
68         alias demarshal = Post!field.demarshal;
69         alias marshal = Post!field.marshal;
70     }
71     else
72         static assert(0, "Unknown typeId " ~ field.typeId.to!string ~
73             ", cannot (de)marshal");
74 }
75 
76 /// Can't marshal shit
77 template NopMarshaller(FieldSpec type)
78 {
79     enum canDigest = false;
80 }
81 
82 /// This is a fallback marshaller that simply accepts any type as a string
83 template PromiscuousStringMarshaller(FieldSpec field)
84 {
85     mixin MarshTemplate!(string, FormatCode.Text, "StringField");
86 }
87 
88 /// Types handled by dpeq natively
89 template StaticFieldMarshaller(FieldSpec field)
90 {
91     static if (field.typeId == StaticPgTypes.BOOLEAN)
92         mixin MarshTemplate!(bool, FormatCode.Binary, "FixedField");
93     else static if (field.typeId == StaticPgTypes.BIGINT)
94         mixin MarshTemplate!(long, FormatCode.Binary, "FixedField");
95     else static if (field.typeId == StaticPgTypes.SMALLINT)
96         mixin MarshTemplate!(short, FormatCode.Binary, "FixedField");
97     else static if (field.typeId == StaticPgTypes.INT)
98         mixin MarshTemplate!(int, FormatCode.Binary, "FixedField");
99     else static if (field.typeId == StaticPgTypes.OID)
100         mixin MarshTemplate!(int, FormatCode.Binary, "FixedField");
101     else static if (field.typeId == StaticPgTypes.VARCHAR)
102         mixin MarshTemplate!(string, FormatCode.Text, "StringField");
103     else static if (field.typeId == StaticPgTypes.CHARACTER)
104         mixin MarshTemplate!(string, FormatCode.Text, "StringField");
105     else static if (field.typeId == StaticPgTypes.TEXT)
106         mixin MarshTemplate!(string, FormatCode.Text, "StringField");
107     else static if (field.typeId == StaticPgTypes.UUID)
108         mixin MarshTemplate!(UUID, FormatCode.Binary, "UuidField");
109     else static if (field.typeId == StaticPgTypes.REAL)
110         mixin MarshTemplate!(float, FormatCode.Binary, "FixedField");
111     else static if (field.typeId == StaticPgTypes.DOUBLE)
112         mixin MarshTemplate!(double, FormatCode.Binary, "FixedField");
113     else
114         enum canDigest = false;
115 }
116 
117 // to prevent code duplication
118 mixin template MarshTemplate(NativeT, FormatCode fcode, string suffix)
119 {
120     enum canDigest = true;
121     enum formatCode = fcode;
122     static if (field.nullable)
123     {
124         alias type = Nullable!NativeT;
125         mixin("alias demarshal = demarshalNullable" ~ suffix ~ "!(NativeT);");
126         mixin("alias marshal = marshalNullable" ~ suffix ~ "!(NativeT);");
127     }
128     else
129     {
130         alias type = NativeT;
131         mixin("alias demarshal = demarshal" ~ suffix ~ "!(NativeT);");
132         mixin("alias marshal = marshal" ~ suffix ~ "!(NativeT);");
133     }
134 }
135 
136 
137 template FCodeOfFSpec(alias Marsh = DefaultFieldMarshaller)
138 {
139     template F(FieldSpec spec)
140     {
141         enum F = Marsh!spec.formatCode;
142     }
143 }
144 
145 /// Utility template to quickly get an array of format codes from an array of
146 /// FieldSpecs
147 template FSpecsToFCodes(FieldSpec[] specs, alias Marsh = DefaultFieldMarshaller)
148 {
149     enum FSpecsToFCodes = [staticMap!(FCodeOfFSpec!Marsh.F, aliasSeqOf!specs)];
150 }
151 
152 /*
153 ///////////////////////////////////////////////////////////////////////////
154 // Marshaller implementations. Marshaller writes only body of the data
155 // and returns count of bytes written, -1 if it's a null value,
156 // and -2 if the buffer is too small to fit whole value.
157 ///////////////////////////////////////////////////////////////////////////
158 */
159 
160 pragma(inline, true)
161 int marshalNull(ubyte[] to)
162 {
163     return -1;  // special case, -1 length is null value in eq protocol.
164 }
165 
166 // I don't really know how versatile are these functions, so let's keep
167 // them FixedField instead of NumericField
168 
169 int marshalNullableFixedField(T)(ubyte[] to, in Nullable!T ptr)
170 {
171     if (ptr.isNull)
172         return marshalNull(to);
173     return marshalFixedField!T(to, ptr.get);
174 }
175 
176 int marshalFixedField(T)(ubyte[] to, in T val)
177 {
178     if (T.sizeof > to.length)
179         return -2;
180     auto arr = nativeToBigEndian!T(val);
181     to[0 .. arr.length] = arr;
182     return arr.length;
183 }
184 
185 int marshalNullableStringField(Dummy = void)(ubyte[] to, in Nullable!string val)
186 {
187     if (val.isNull)
188         return marshalNull(to);
189     return marshalStringField(to, val.get);
190 }
191 
192 int marshalStringField(Dummy = void)(ubyte[] to, in string s)
193 {
194     if (s.length > to.length)
195         return -2;
196     for (int i = 0; i < s.length; i++)
197         to[i] = cast(const(ubyte)) s[i];
198     return s.length.to!int;
199 }
200 
201 /// Service function, used for marshalling of protocol messages.
202 /// Data strings are passed without trailing nulls.
203 int marshalCstring(ubyte[] to, in string s)
204 {
205     if (s.length + 1 > to.length)
206         return -2;
207     for (int i = 0; i < s.length; i++)
208         to[i] = cast(const(ubyte)) s[i];
209     to[s.length] = cast(ubyte)0;
210     return (s.length + 1).to!int;
211 }
212 
213 int marshalNullableUuidField(Dummy = void)(ubyte[] to, in Nullable!UUID val)
214 {
215     if (val.isNull)
216         return marshalNull(to);
217     return marshalUuidField(to, val.get);
218 }
219 
220 int marshalUuidField(Dummy = void)(ubyte[] to, in UUID val)
221 {
222     if (to.length < 16)
223         return -2;
224     for (int i = 0; i < 16; i++)
225         to[i] = val.data[i];
226     return 16;
227 }
228 
229 /*
230 //////////////////////////////////////////////////////////////////////////////
231 // Demarshalling implementations. Demarshallers take byte array that contains
232 // data body, it's format code and length according to field prefix.
233 //////////////////////////////////////////////////////////////////////////////
234 */
235 
236 
237 /// Simple demarshal of some numeric type.
238 pragma(inline)
239 T demarshalNumber(T = int)(const(ubyte)[] from)
240     if (isNumeric!T)
241 {
242     return bigEndianToNative!T(from[0 .. T.sizeof]);
243 }
244 
245 /// psql uses `t` and `f` for boolean
246 bool to(T: bool)(in string s)
247 {
248     if (s == "t")
249         return true;
250     if (s == "f")
251         return false;
252     throw new PsqlMarshallingException("Unable to unmarshal bool from string " ~ s);
253 }
254 
255 T demarshalFixedField(T)(const(ubyte)[] from, in FormatCode fCode, in int len)
256 {
257     enforce!PsqlMarshallingException(len > 0, "zero-sized fixed non-nullable field");
258     if (fCode == FormatCode.Binary)
259     {
260         enforce!PsqlMarshallingException(len == T.sizeof, "Field size mismatch");
261         return bigEndianToNative!T(from[0 .. T.sizeof]);
262     }
263     else if (fCode == FormatCode.Text)
264         return demarshalString(from[0 .. len]).to!T;
265     else
266         throw new PsqlMarshallingException("Unsupported FormatCode");
267 }
268 
269 Nullable!T demarshalNullableFixedField(T)
270     (const(ubyte)[] from, in FormatCode fCode, in int len)
271 {
272     if (len == -1)
273         return Nullable!T.init;
274     if (fCode == FormatCode.Binary)
275     {
276         enforce!PsqlMarshallingException(len == T.sizeof, "Field size mismatch, " ~
277             T.stringof ~ ", actual = " ~ len.to!string);
278         return Nullable!T(bigEndianToNative!T(from[0 .. T.sizeof]));
279     }
280     else if (fCode == FormatCode.Text)
281         return Nullable!T(demarshalString(from[0 .. len]).to!T);
282     else
283         throw new PsqlMarshallingException("Unsupported FormatCode");
284 }
285 
286 string demarshalStringField(Dummy = void)
287     (const(ubyte)[] from, in FormatCode fc, in int len)
288 {
289     assert(fc == FormatCode.Text, "binary string?");
290     enforce!PsqlMarshallingException(len >= 0, "null string in non-nullable demarshaller");
291     if (len == 0)
292         return "";
293     string res = (cast(immutable(char)*)(from.ptr))[0 .. len.to!size_t];
294     return res;
295 }
296 
297 /// returns inplace-constructed string without allocations. Hacky.
298 Nullable!string demarshalNullableStringField(Dummy = void)
299     (const(ubyte)[] from, in FormatCode fc, in int len)
300 {
301     assert(fc == FormatCode.Text, "binary string?");
302     if (len == -1)
303         return Nullable!string.init;
304     if (len == 0)
305         return Nullable!string("");
306     string res = (cast(immutable(char)*)(from.ptr))[0 .. len.to!size_t];
307     return Nullable!string(res);
308 }
309 
310 /// dpeq utility function
311 string demarshalString(const(ubyte)[] from)
312 {
313     return (cast(immutable(char)*)(from.ptr))[0 .. from.length];
314 }
315 
316 /// dpeq utility function. Demarshal zero-terminated string from byte buffer.
317 string demarshalProtocolString(const(ubyte)[] from, ref size_t length)
318 {
319     size_t l = 0;
320     while (from[l])
321     {
322         l++;
323         if (l >= from.length)
324             throw new PsqlMarshallingException("Null-terminated string is not " ~
325                 "null-terminated");
326     }
327     length = l + 1;
328     if (l == 0)
329         return string.init;
330     return demarshalString(from[0..l]);
331 }
332 
333 Nullable!UUID demarshalNullableUuidField(Dummy = void)
334 (const(ubyte)[] from, in FormatCode fc, in int len)
335 {
336     if (len == -1)
337         return Nullable!UUID.init;
338     return Nullable!UUID(demarshalUuidField(from, fc, len));
339 }
340 
341 UUID demarshalUuidField(Dummy = void)
342 (const(ubyte)[] from, in FormatCode fc, in int len)
343 {
344     enforce!PsqlMarshallingException(len > 0, "null uuid in non-nullable demarshaller");
345     if (fc == FormatCode.Binary)
346     {
347         enforce!PsqlMarshallingException(len == 16, "uuid is not 16-byte");
348         ubyte[16] data;
349         for (int i = 0; i < 16; i++)
350             data[i] = from[i];
351         return UUID(data);
352     }
353     else if (fc == FormatCode.Text)
354     {
355         scope string val = (cast(immutable(char)*)(from.ptr))[0 .. len.to!size_t];
356         return UUID(val);
357     }
358     else
359         throw new PsqlMarshallingException("Unsupported FormatCode");
360 }
361 
362 
363 /*
364 /////////////////////////////////////////////////////////////////
365 // Dynamic conversion code, suitable for dynamic typing
366 /////////////////////////////////////////////////////////////////
367 */
368 
369 /// prototype of a nullable variant demarshaller, used in converter
370 alias VariantDemarshaller =
371     NullableVariant function(const(ubyte)[] buf, in FormatCode fc, in int len);
372 
373 /// std.variant.Variant subtype that is better suited for holding SQL null.
374 /// Null NullableVariant is essentially a valueless Variant instance.
375 struct NullableVariant
376 {
377     Variant variant;
378     alias variant this;
379 
380     this(T)(T value)
381     {
382         variant = value;
383     }
384 
385     /// this property will be true if psql return null in this column
386     @safe bool isNull() const { return !variant.hasValue; }
387 
388     string toString()
389     {
390         if (isNull)
391             // may conflict with "null" string, but this can be said about any string
392             return "null";
393         else
394             return variant.toString();
395     }
396 }
397 
398 NullableVariant wrapToVariant(alias f)(const(ubyte)[] buf, in FormatCode fc, in int len)
399 {
400     auto nullableResult = f(buf, fc, len);
401     if (nullableResult.isNull)
402         return NullableVariant();
403     else
404         return NullableVariant(nullableResult.get);
405 }
406 
407 /// Default converter hash map. You can extend it, or define your own.
408 class VariantConverter(alias Marsh = DefaultFieldMarshaller)
409 {
410     static immutable VariantDemarshaller[ObjectID] demarshallers;
411 
412     @disable private this();
413 
414     shared static this()
415     {
416         VariantDemarshaller[ObjectID] aa;
417         // iterate over StaticPgTypes and take demarshallers from StaticFieldMarshaller
418         foreach (em; __traits(allMembers, StaticPgTypes))
419         {
420             // this assumes nullable return fields. Variant will wrap Nullable
421             // of some native type.
422             enum FieldSpec spec =
423                 FieldSpec(__traits(getMember, StaticPgTypes, em), true);
424             /*pragma(msg, "registering default demarshaller for ",
425                 StaticFieldMarshaller!spec.type, " in hash table");*/
426             aa[spec.typeId] = &wrapToVariant!(Marsh!spec.demarshal);
427         }
428         demarshallers = cast(immutable VariantDemarshaller[ObjectID]) aa;
429     }
430 
431     static NullableVariant demarshal(
432         const(ubyte)[] fieldBody, ObjectID type, FormatCode fc, int len)
433     {
434         immutable(VariantDemarshaller)* func = type in demarshallers;
435         if (func)
436             return (*func)(fieldBody, fc, len);
437         else
438         {
439             // fallback to nullable string demarshaller
440             if (fc == FormatCode.Text)
441                 return demarshallers[StaticPgTypes.VARCHAR](fieldBody, fc, len);
442             else
443                 throw new PsqlMarshallingException(
444                     "Unable to deduce demarshaller for binary format of a type " ~
445                     type.to!string);
446         }
447     }
448 }