1 ///
2 module dpq.serialisers.composite;
3 
4 import dpq.serialisation;
5 import dpq.meta;
6 import dpq.attributes;
7 import dpq.exception;
8 import dpq.value;
9 import dpq.connection;
10 
11 import std..string : format, join;
12 import std.typecons : Nullable, Typedef;
13 import std.traits;
14 import std.array : Appender;
15 import std.bitmanip;
16 import std.conv : to;
17 import std.datetime : SysTime;
18 
19 import libpq.libpq;
20 /**
21 	The default serialiser for any composite type (structs and classes)
22 
23 	For custom types, the data representation is the following
24 	 
25 	First 4 bytes are an int representing the number of members
26 	After that, the members are listed in the following way:
27 		- OID
28 		- length
29 		- value
30 
31 	Example: (bytes, decimal)
32 		[0 0 0 2 , 0 0 0 23 , 0 0 0 4 , 0 0 0 1 , 0 0 0 23 , 0 0 0 4 , 0 0 0 2]
33 		will represent a struct with two members, both OID 23, length 4, with values 1 and 2
34  */
35 struct CompositeTypeSerialiser
36 {
37 	/** 
38 		Only accepts structs and classes, will fail on Nullable or Typedef types
39 		which should be taken care of by toBytes function.
40 	 */
41 	static bool isSupportedType(T)()
42 	{
43 		return 
44 			is(T == class) || is(T == struct) && 
45 			!is(T == SysTime) &&
46 			!isInstanceOf!(Typedef, T);
47 	}
48 
49 	static void enforceSupportedType(T)()
50 	{
51 		static assert (
52 				isSupportedType!T,
53 				"'%s' is not supported by CompositeTypeSerialiser".format(T.stringof));
54 	}
55 
56 	static Nullable!(ubyte[]) serialise(T)(T val)
57 	{
58 		enforceSupportedType!T;
59 
60 		alias RT = Nullable!(ubyte[]);
61 
62 		if (isAnyNull(val))
63 			return RT.init;
64 
65 		alias members = serialisableMembers!T;
66 		ubyte[] data;
67 		
68 		// The number of members of this type
69 		data ~= nativeToBigEndian(cast(int) members.length);
70 
71 		foreach (mName; members)
72 		{
73 			auto member = __traits(getMember, val, mName);
74 			// The member's actual type without any qualifiers and such
75 			alias MT = RealType!(typeof(member));
76 
77 			// Element's Oid
78 			data ~= nativeToBigEndian(cast(int) SerialiserFor!MT.oidForType!MT);
79 
80 			auto bytes = toBytes(member);
81 
82 			// Null values have length of -1
83 			if (bytes.isNull)
84 				data ~= nativeToBigEndian(cast(int) -1);
85 			else
86 			{
87 				// The element length and data itself
88 				data ~= nativeToBigEndian(bytes.length.to!int);
89 				data ~= bytes;
90 			}
91 		}
92 
93 		return RT(data);
94 	}
95 
96 	static T deserialise(T)(const (ubyte)[] bytes)
97 	{
98 		enforceSupportedType!T;
99 
100 		alias members = serialisableMembers!T;
101 
102 		int length = bytes.read!int;
103 
104 		if (length != members.length)
105 			throw new DPQException("Length for %s (%d) does not actual match number of members (%s)".format(
106 						T.stringof,
107 						length,
108 						members.length
109 						));
110 		
111 		T result;
112 		foreach (mName; members)
113 		{
114 			auto member = __traits(getMember, result, mName);
115 			alias OT = typeof(member);
116 			alias MT = RealType!OT;
117 
118 			Oid oid = cast(Oid) bytes.read!int;
119 			auto mLen = bytes.read!int;
120 
121 			// When a null value is encontered, leave the member to its init value
122 			if (mLen == -1)
123 				continue;
124 
125 			// Read the value
126 			__traits(getMember, result, mName) = cast(OT) fromBytes!MT(bytes[0 .. mLen], mLen);
127 
128 			// "Consume" the bytes that were just read
129 			bytes = bytes[mLen .. $];
130 		}
131 
132 		return result;
133 	}
134 
135 	static void ensureExistence(T)(Connection conn)
136 	{
137 		alias members = serialisableMembers!T;
138 
139 		string typeName = SerialiserFor!T.nameForType!T;
140 		if ((typeName in _customOids) != null)
141 			return;
142 
143 		string escTypeName = conn.escapeIdentifier(typeName);
144 
145 		string[] columns;
146 
147 		foreach (mName; members)
148 		{
149 			enum member = "T." ~ mName;
150 
151 			alias MType = RealType!(typeof(mixin(member)));
152 			alias serialiser = SerialiserFor!MType;
153 			serialiser.ensureExistence!MType(conn);
154 
155 			string attrName = attributeName!(mixin(member));
156 			string escAttrName = conn.escapeIdentifier(attrName);
157 
158 			static if (hasUDA!(mixin(member), PGTypeAttribute))
159 				string attrType = getUDAs!(mixin(member), PGTypeAttribute)[0].type;
160 			else
161 				string attrType = serialiser.nameForType!MType;
162 
163 			columns ~= escAttrName ~ " " ~ attrType;
164 		}
165 
166 		try 
167 		{
168 			conn.exec("CREATE TYPE %s AS (%s)".format(escTypeName, columns.join(", ")));
169 		} catch (DPQException e) {} // Horrible, but just means the type already exists
170 
171 		conn.addOidsFor(typeName);
172 	}
173 
174 	static string nameForType(T)()
175 	{
176 		enforceSupportedType!T;
177 
178 		return relationName!(RealType!T);
179 	}
180 
181 	private static Oid[string] _customOids;
182 	static Oid oidForType(T)()
183 	{
184 		enforceSupportedType!T;
185 		
186 		auto oid = nameForType!T in _customOids;
187 		assert(
188 				oid != null,
189 				"Oid for type %s not found. Did you run ensureSchema?".format(T.stringof));
190 
191 		return *oid;
192 	}
193 
194 	static void addCustomOid(string typeName, Oid oid)
195 	{
196 		_customOids[typeName] = oid;
197 	}
198 }
199 
200 // Very basic tests
201 unittest
202 {
203 	import std.stdio;
204 
205 	writeln(" * CompositeTypeSerialiser");
206 
207 	struct Test2
208 	{
209 		int c = 3;
210 	}
211 
212 	struct Test
213 	{
214 		int a = 1;
215 		int b = 2;
216 
217 		// test nullable too
218 		Nullable!Test2 ntest2;
219 		Test2 test2;
220 	}
221 
222 	// An OID must exist for types being serialised
223 	CompositeTypeSerialiser.addCustomOid("test2", 999999);
224 
225 	Test t = Test(1, 2);
226 	auto serialised = CompositeTypeSerialiser.serialise(t);
227 	auto deserialised = CompositeTypeSerialiser.deserialise!Test(serialised);
228 
229 	// Why is this throwing AssertError???
230 	//assert(t == deserialised);
231 
232 	// The manual approach, I guess
233 	assert(deserialised.a == t.a);
234 	assert(deserialised.b == t.b);
235 	assert(deserialised.test2.c == t.test2.c);
236 	assert(deserialised.ntest2.isNull == t.ntest2.isNull);
237 }