1 module dpq.serialisation;
2 
3 // TODO: merge all serialisers' imports
4 import dpq.serialisers.composite;
5 import dpq.serialisers.array;
6 import dpq.serialisers.scalar;
7 import dpq.serialisers.systime;
8 import dpq.serialisers..string;
9 
10 import dpq.meta;
11 import dpq.attributes;
12 import dpq.value : Type;
13 
14 import std.datetime : SysTime, DateTime;
15 import std.typecons : Nullable, TypedefType;
16 import std.traits;
17 import std.bitmanip;
18 import std..string : format;
19 import std.meta;
20 
21 import libpq.libpq;
22 
23 
24 /**
25 	Converts the given type to an ubyte[], as PostgreSQL expects it. Ignores
26 	any Nullable specifiers and Typedefs.
27  */
28 package Nullable!(ubyte[]) toBytes(T)(T val)
29 {
30 	alias AT = RealType!T;
31 
32 	if (isAnyNull(val))
33 		return Nullable!(ubyte[]).init;
34 
35 	alias serialiser = SerialiserFor!AT;
36 	return serialiser.serialise(cast(AT) val);
37 }
38 
39 /*****************************************************************************/
40 
41 struct SerialiserAttribute(alias T)
42 {
43 	alias serialiser = T;
44 }
45 
46 SerialiserAttribute!T serialiser(alias T)()
47 {
48 	return SerialiserAttribute!T();
49 }
50 
51 template SerialiserFor(T)
52 	if (isBuiltinType!T)
53 {
54 	static if (isSomeString!T)
55 		alias SerialiserFor = StringSerialiser;
56 	else static if (isArray!T)
57 		alias SerialiserFor = ArraySerialiser;
58 	else static if (isScalarType!T)
59 		alias SerialiserFor = ScalarSerialiser;
60 }
61 
62 template SerialiserFor(alias T)
63 	if (!isBuiltinType!T)
64 {
65 	import std.meta;
66 
67 	alias UDAs = getUDAs!(T, SerialiserAttribute);
68 
69 	// First see if a custom serialiser is specified for the type
70 	static if (UDAs.length > 0)
71 		alias SerialiserFor = UDAs[0].serialiser;
72 	else
73 	{
74 		// Otherwise, pick one from the bunch of pre-set ones.
75 		static if (isArray!T)
76 			alias SerialiserFor = ArraySerialiser;
77 		// Support for SysTime
78 		else static if (is(T == SysTime))
79 			alias SerialiserFor = SysTimeSerialiser;
80 		else static if (is(T == class) || is(T == struct))
81 			alias SerialiserFor = CompositeTypeSerialiser;
82 		else
83 			static assert(false, "Cannot find serialiser for " ~ T.stringof);
84 	}
85 }
86 
87 unittest
88 {
89 	import std.stdio;
90 
91 	writeln(" * SerialiserFor");
92 
93 	struct Test1 {}
94 
95 	static assert(is(SerialiserFor!int == ScalarSerialiser));
96 	static assert(is(SerialiserFor!Test1 == CompositeTypeSerialiser));
97 	static assert(is(SerialiserFor!(int[][]) == ArraySerialiser));
98 	static assert(is(SerialiserFor!(Test1[][]) == ArraySerialiser));
99 	
100 	@serialiser!Test1() struct Test2 {}
101 
102 	static assert(is(SerialiserFor!Test2 == Test1));
103 }
104 
105 package T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0)
106 		if (isInstanceOf!(Nullable, T))
107 {
108 	alias AT = RealType!T;
109 
110 	return T(fromBytes!AT(bytes, len));
111 }
112 
113 package Nullable!T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0)
114 		if (!isInstanceOf!(Nullable, T))
115 {
116 	if (len == -1)
117 		return Nullable!T.init;
118 
119 	alias AT = RealType!T;
120 
121 	return Nullable!T(cast(T) fromBytesImpl!AT(bytes, len));
122 }
123 
124 package T fromBytesImpl(T)(const(ubyte)[] bytes, size_t len)
125 {
126 	alias serialiser = SerialiserFor!T;
127 	return Nullable!T(serialiser.deserialise!T(bytes[0 .. len]));
128 }
129 
130 unittest
131 {
132 	import std.bitmanip;
133 	import std..string;
134 	import std.stdio;
135 
136 	writeln(" * fromBytes");
137 
138 	int x = 123;
139 
140 	const (ubyte)[] bs = nativeToBigEndian(x);
141 	assert(fromBytes!int(bs, x.sizeof) == x);
142 
143 	x = -555;
144 	bs = nativeToBigEndian(x);
145 	assert(fromBytes!int(bs, x.sizeof) == x);
146 
147 	x = int.min;
148 	bs = nativeToBigEndian(x);
149 	assert(fromBytes!int(bs, x.sizeof) == x);
150 
151 	x = int.max;
152 	bs = nativeToBigEndian(x);
153 	assert(fromBytes!int(bs, x.sizeof) == x);
154 
155 	string s = "some random string";
156 	assert(fromBytes!string(s.representation, s.representation.length) == s);
157 
158 	s = "";
159 	assert(fromBytes!string(s.representation, s.representation.length) == s);
160 }
161 
162 /*****************************************************************************/
163 
164 bool isAnyNull(T)(T val)
165 {
166 	static if (is(T == class))
167 		return val is null;
168 	else static if (isInstanceOf!(Nullable, T))
169 		return val.isNull;
170 	else
171 		return false;
172 }
173 
174 // TODO: this for arrays
175 Type oidForType(T)()
176 		if (!isArray!T)
177 {
178 	import dpq.connection : _dpqCustomOIDs;
179 	enum oid = typeOid!T;
180 
181 	static if (oid == Type.INFER)
182 	{
183 		Oid* p;
184 		if ((p = relationName!T in _dpqCustomOIDs) != null)
185 			return cast(Type) *p;
186 	}
187 
188 	return oid;
189 }
190 
191 template typeOid(T)
192 {
193 		alias TU = std.typecons.Unqual!T;
194 		static if (isArray!T && !isSomeString!T)
195 		{
196 			alias BT = BaseType!T;
197 			static if (is(BT == int))
198 				enum typeOid = Type.INT4ARRAY;
199 			else static if (is(BT == long))
200 				enum typeOid = Type.INT8ARRAY;
201 			else static if (is(BT == short))
202 				enum typeOid = Type.INT2ARRAY;
203 			else static if (is(BT == float))
204 				enum typeOid = Type.FLOAT4ARRAY;
205 			else static if (is(BT == byte) || is (BT == ubyte))
206 				enum typeOid = Type.BYTEA;
207 			else
208 				static assert(false, "Cannot map array type " ~ T.stringof ~ " to Oid");
209 		}
210 		else
211 		{
212 			static if (is(TU == int))
213 				enum typeOid = Type.INT4;
214 			else static if (is(TU == long))
215 				enum typeOid = Type.INT8;
216 			else static if (is(TU == bool))
217 				enum typeOid = Type.BOOL;
218 			else static if (is(TU == byte))
219 				enum typeOid = Type.CHAR;
220 			else static if (is(TU == char))
221 				enum typeOid = Type.CHAR;
222 			else static if (isSomeString!TU)
223 				enum typeOid = Type.TEXT;
224 			else static if (is(TU == short))
225 				enum typeOid = Type.INT2;
226 			else static if (is(TU == float))
227 				enum typeOid = Type.FLOAT4;
228 			else static if (is(TU == double))
229 				enum typeOid = Type.FLOAT8;
230 			else static if (is(TU == SysTime))
231 				enum typeOid = Type.TIMESTAMP;
232 
233 			/**
234 				Since unsigned types are not supported by PostgreSQL, we use signed
235 				types for them. Transfer and representation in D will still work correctly,
236 				but SELECTing them in the psql console, or as a string might result in 
237 				a negative number.
238 
239 				It is recommended not to use unsigned types in structures, that will
240 				be used in the DB directly.
241 			*/
242 			else static if (is(TU == ulong))
243 				enum typeOid = Type.INT8;
244 			else static if (is(TU == uint))
245 				enum typeOid = Type.INT4;
246 			else static if (is(TU == ushort) || is(TU == char))
247 				enum typeOid = Type.INT2;
248 			else static if (is(TU == ubyte))
249 				enum typeOid = Type.CHAR;
250 			else
251 				// Try to infer
252 				enum typeOid = Type.INFER;
253 		}
254 }
255 
256 unittest
257 {
258 	import std.stdio;
259 	writeln("\t * typeOid");
260 
261 	static assert(typeOid!int == Type.INT4, "int");
262 	static assert(typeOid!string == Type.TEXT, "string");
263 	static assert(typeOid!(int[]) == Type.INT4ARRAY, "int[]");
264 	static assert(typeOid!(int[][]) == Type.INT4ARRAY, "int[][]");
265 	static assert(typeOid!(ubyte[]) == Type.BYTEA, "ubyte[]");
266 }