1 ///
2 module dpq.serialisation;
3 
4 // TODO: merge all serialisers' imports
5 import dpq.serialisers.composite;
6 import dpq.serialisers.array;
7 import dpq.serialisers.scalar;
8 import dpq.serialisers.systime;
9 import dpq.serialisers..string;
10 import dpq.serialisers.bytea;
11 
12 import dpq.meta;
13 import dpq.attributes;
14 import dpq.value : Type;
15 
16 import std.datetime : SysTime, DateTime;
17 import std.typecons : Nullable, TypedefType;
18 import std.traits;
19 import std.bitmanip;
20 import std..string : format;
21 import std.meta;
22 
23 import libpq.libpq;
24 
25 
26 /**
27    Converts the given type to an ubyte[], as PostgreSQL expects it. Ignores
28    any Nullable specifiers and Typedefs.
29  */
30 package Nullable!(ubyte[]) toBytes(T)(T val)
31 {
32    alias AT = RealType!T;
33 
34    if (isAnyNull(val))
35       return Nullable!(ubyte[]).init;
36 
37    alias serialiser = SerialiserFor!AT;
38 
39    static if (isInstanceOf!(Nullable, T))
40       auto av = val.get;
41    else
42       auto av = val;
43 
44    return serialiser.serialise(cast(AT) av);
45 }
46 
47 /*****************************************************************************/
48 
49 struct SerialiserAttribute(alias T)
50 {
51    alias serialiser = T;
52 }
53 
54 SerialiserAttribute!T serialiser(alias T)()
55 {
56    return SerialiserAttribute!T();
57 }
58 
59 template SerialiserFor(T)
60    if (isBuiltinType!T)
61 {
62    static if (isSomeString!T)
63       alias SerialiserFor = StringSerialiser;
64    else static if (is(T == ubyte[]))
65       alias SerialiserFor = ByteaSerialiser;
66    else static if (isArray!T)
67       alias SerialiserFor = ArraySerialiser;
68    else static if (isScalarType!T)
69       alias SerialiserFor = ScalarSerialiser;
70 }
71 
72 template SerialiserFor(alias T)
73    if (!isBuiltinType!T)
74 {
75    import std.meta;
76 
77    alias UDAs = getUDAs!(T, SerialiserAttribute);
78 
79    // First see if a custom serialiser is specified for the type
80    static if (UDAs.length > 0)
81       alias SerialiserFor = UDAs[0].serialiser;
82    else
83    {
84       alias RT = RealType!T;
85 
86       static if (isBuiltinType!RT)
87          alias SerialiserFor = SerialiserFor!RT;
88       // Otherwise, pick one from the bunch of pre-set ones.
89       else static if (isArray!RT)
90          alias SerialiserFor = ArraySerialiser;
91       // Support for SysTime
92       else static if (is(RT == SysTime))
93          alias SerialiserFor = SysTimeSerialiser;
94       else static if (is(RT == class) || is(RT == struct))
95          alias SerialiserFor = CompositeTypeSerialiser;
96       else
97          static assert(false, "Cannot find serialiser for " ~ T.stringof);
98    }
99 }
100 
101 unittest
102 {
103    import std.stdio;
104 
105    writeln(" * SerialiserFor");
106 
107    struct Test1 {}
108 
109    static assert(is(SerialiserFor!int == ScalarSerialiser));
110    static assert(is(SerialiserFor!Test1 == CompositeTypeSerialiser));
111    static assert(is(SerialiserFor!(int[][]) == ArraySerialiser));
112    static assert(is(SerialiserFor!(Test1[][]) == ArraySerialiser));
113 
114    @serialiser!Test1() struct Test2 {}
115 
116    static assert(is(SerialiserFor!(Test2) == Test1));
117 }
118 
119 package T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0)
120       if (isInstanceOf!(Nullable, T))
121 {
122    alias AT = RealType!T;
123 
124    return T(fromBytes!AT(bytes, len).get);
125 }
126 
127 package Nullable!T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0)
128       if (!isInstanceOf!(Nullable, T))
129 {
130    if (len == -1)
131       return Nullable!T.init;
132 
133    alias AT = RealType!T;
134 
135    return Nullable!T(cast(T) fromBytesImpl!AT(bytes, len));
136 }
137 
138 package T fromBytesImpl(T)(const(ubyte)[] bytes, size_t len)
139 {
140    alias serialiser = SerialiserFor!T;
141    return Nullable!T(serialiser.deserialise!T(bytes[0 .. len])).get;
142 }
143 
144 unittest
145 {
146    import std.bitmanip;
147    import std..string;
148    import std.stdio;
149 
150    writeln(" * fromBytes");
151 
152    int x = 123;
153 
154    auto bs = nativeToBigEndian(x);
155    assert(fromBytes!int(bs, x.sizeof) == x);
156 
157    x = -555;
158    bs = nativeToBigEndian(x);
159    assert(fromBytes!int(bs, x.sizeof) == x);
160 
161    x = int.min;
162    bs = nativeToBigEndian(x);
163    assert(fromBytes!int(bs, x.sizeof) == x);
164 
165    x = int.max;
166    bs = nativeToBigEndian(x);
167    assert(fromBytes!int(bs, x.sizeof) == x);
168 
169    string s = "some random string";
170    assert(fromBytes!string(s.representation, s.representation.length) == s);
171 
172    s = "";
173    assert(fromBytes!string(s.representation, s.representation.length) == s);
174 }
175 
176 /*****************************************************************************/
177 
178 bool isAnyNull(T)(T val)
179 {
180    static if (is(T == class))
181       return val is null;
182    else static if (isInstanceOf!(Nullable, T))
183       return val.isNull;
184    else
185       return false;
186 }
187 
188 /**
189    Shortuct to the type's serialiser's oidForType
190  */
191 Oid oidFor(T)()
192 {
193    alias RT = RealType!T;
194    return SerialiserFor!RT.oidForType!RT;
195 }
196 
197 deprecated("Use Serialisers and their oidForType instead")
198 template typeOid(T)
199 {
200       alias TU = std.typecons.Unqual!T;
201       static if (isArray!T && !isSomeString!T)
202       {
203          alias BT = BaseType!T;
204          static if (is(BT == int))
205             enum typeOid = Type.INT4ARRAY;
206          else static if (is(BT == long))
207             enum typeOid = Type.INT8ARRAY;
208          else static if (is(BT == short))
209             enum typeOid = Type.INT2ARRAY;
210          else static if (is(BT == float))
211             enum typeOid = Type.FLOAT4ARRAY;
212          else static if (is(BT == string))
213             enum typeOid = Type.TEXTARRAY;
214          else static if (is(BT == byte) || is (BT == ubyte))
215             enum typeOid = Type.BYTEA;
216          else
217             static assert(false, "Cannot map array type " ~ T.stringof ~ " to Oid");
218       }
219       else
220       {
221          static if (is(TU == int))
222             enum typeOid = Type.INT4;
223          else static if (is(TU == long))
224             enum typeOid = Type.INT8;
225          else static if (is(TU == bool))
226             enum typeOid = Type.BOOL;
227          else static if (is(TU == byte))
228             enum typeOid = Type.CHAR;
229          else static if (is(TU == char))
230             enum typeOid = Type.CHAR;
231          else static if (isSomeString!TU)
232             enum typeOid = Type.TEXT;
233          else static if (is(TU == short))
234             enum typeOid = Type.INT2;
235          else static if (is(TU == float))
236             enum typeOid = Type.FLOAT4;
237          else static if (is(TU == double))
238             enum typeOid = Type.FLOAT8;
239          else static if (is(TU == SysTime))
240             enum typeOid = Type.TIMESTAMP;
241 
242          /**
243             Since unsigned types are not supported by PostgreSQL, we use signed
244             types for them. Transfer and representation in D will still work correctly,
245             but SELECTing them in the psql console, or as a string might result in
246             a negative number.
247 
248             It is recommended not to use unsigned types in structures, that will
249             be used in the DB directly.
250          */
251          else static if (is(TU == ulong))
252             enum typeOid = Type.INT8;
253          else static if (is(TU == uint))
254             enum typeOid = Type.INT4;
255          else static if (is(TU == ushort) || is(TU == char))
256             enum typeOid = Type.INT2;
257          else static if (is(TU == ubyte))
258             enum typeOid = Type.CHAR;
259          else
260             // Try to infer
261             enum typeOid = Type.INFER;
262       }
263 }
264 
265 unittest
266 {
267    import std.stdio;
268    writeln("\t * oidFor");
269 
270    static assert(oidFor!int == Type.INT4, "int");
271    static assert(oidFor!string == Type.TEXT, "string");
272    assert(oidFor!(int[]) == Type.INT4ARRAY, "int[]");
273    assert(oidFor!(int[][]) == Type.INT4ARRAY, "int[][]");
274    assert(oidFor!(ubyte[]) == Type.BYTEA, "ubyte[]");
275 }
276 
277 /**
278    Custom serialisers - Serialiser is a struct providing all the required data
279    that dpq needs to serialise/deserialise the custom type, ensure it exists in
280    the schema, and in some cases, receive the type's OID.
281 
282    All serialisers must support the following static methods:
283 
284     - static bool isSupportedType(T)();
285    Must return true iff all the other functions in serialiser know how to handle this type
286 
287     - static T deserialise(T)(ubyte[]);
288    Must return T, when given postgresql-compatible representation of the type
289 
290     - static ubyte[] serialise(T)(T val);
291    Must return postgresql-compatible binary representation of the type
292 
293     - static Oid oidForType(T)();
294    Must return the given type's OID, as recognised by PostgreSQL
295 
296     - static string nameForType(T)();
297    Must return a valid, unescaped name for the type, as recognised by PostgreSQL
298 
299     - static void ensureExistence(T)(Connection conn);
300    Must ensure the type exists and can be used in the DB, can simply return
301    if no work is needed.
302    Must not throw or otherwise fail unless type creation failed, in case the type
303    does not yet exist, it should be silently created.
304 
305    Example:
306       -----------------------
307       struct MyTypeSerialiser
308       {
309          static bool isSupportedType(T)()
310          {
311             // magic
312          }
313 
314          static T deserialise(T)(ubyte[])
315          {
316             // magic
317          }
318 
319          static ubyte[] serialise(T)(T val)
320          {
321             // magic
322          }
323 
324          static Oid oidForType(T)()
325          {
326             // magic
327          }
328 
329          static string nameForType(T)()
330          {
331             // magic
332          }
333 
334          static void ensureExistence(T)(Connection conn)
335          {
336             // magic
337          }
338       }
339       -----------------------
340  */