1 module dpq.serialisers.composite;
2 
3 import dpq.serialisation;
4 import dpq.meta;
5 import dpq.attributes;
6 import dpq.exception;
7 import dpq.value;
8 
9 import std..string : format;
10 import std.typecons : Nullable, Typedef;
11 import std.traits;
12 import std.array : Appender;
13 import std.bitmanip;
14 import std.conv : to;
15 import std.datetime : SysTime;
16 
17 import libpq.libpq;
18 /**
19 	The default serialiser for any composite type (structs and classes)
20 
21 	For custom types, the data representation is the following
22 	 
23 	First 4 bytes are an int representing the number of members
24 	After that, the members are listed in the following way:
25 		- OID
26 		- length
27 		- value
28 
29 	Example: (bytes, decimal)
30 		[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]
31 		will represent a struct with two members, both OID 23, length 4, with values 1 and 2
32  */
33 struct CompositeTypeSerialiser
34 {
35 	/** 
36 		Only accepts structs and classes, will fail on Nullable or Typedef types
37 		which should be taken care of by toBytes function.
38 	 */
39 	static bool isSupportedType(T)()
40 	{
41 		return 
42 			is(T == class) || is(T == struct) && 
43 			!is(T == SysTime) &&
44 			!isInstanceOf!(Typedef, T);
45 	}
46 
47 	static Nullable!(ubyte[]) serialise(T)(T val)
48 	{
49 		static assert (
50 				isSupportedType!T,
51 				"'%s' is not supported by CompositeTypeSerialiser".format(T.stringof));
52 
53 		alias RT = Nullable!(ubyte[]);
54 
55 		if (isAnyNull(val))
56 			return RT.init;
57 
58 		alias members = serialisableMembers!T;
59 		ubyte[] data;
60 		
61 		// The number of members of this type
62 		data ~= nativeToBigEndian(cast(int) members.length);
63 
64 		foreach (mName; members)
65 		{
66 			auto member = __traits(getMember, val, mName);
67 			// The member's actual type without any qualifiers and such
68 			alias MT = RealType!(typeof(member));
69 
70 			// Element's Oid
71 			data ~= nativeToBigEndian(cast(int) oidForType!MT);
72 
73 			auto bytes = toBytes(member);
74 
75 			// Null values have length of -1
76 			if (bytes.isNull)
77 				data ~= nativeToBigEndian(cast(int) -1);
78 			else
79 			{
80 				// The element length and data itself
81 				data ~= nativeToBigEndian(bytes.length.to!int);
82 				data ~= bytes;
83 			}
84 		}
85 
86 		return RT(data);
87 	}
88 
89 	static T deserialise(T)(const (ubyte)[] bytes)
90 	{
91 		static assert (
92 				isSupportedType!T,
93 				"'%s' is not supported by CompositeTypeSerialiser".format(T.stringof));
94 
95 		alias members = serialisableMembers!T;
96 
97 		int length = bytes.read!int;
98 
99 		if (length != members.length)
100 			throw new DPQException("Length for %s (%d) does not actual match number of members (%s)".format(
101 						T.stringof,
102 						length,
103 						members.length
104 						));
105 		
106 		T result;
107 		foreach (mName; members)
108 		{
109 			auto member = __traits(getMember, result, mName);
110 			alias MT = RealType!(typeof(member));
111 
112 			Oid oid = cast(Oid) bytes.read!int;
113 			auto mLen = bytes.read!int;
114 
115 			// When a null value is encontered, leave the member to its init value
116 			if (mLen == -1)
117 				continue;
118 
119 			// Read the value
120 			__traits(getMember, result, mName) = fromBytes!MT(bytes[0 .. mLen], mLen);
121 
122 			// "Consume" the bytes that were just read
123 			bytes = bytes[mLen .. $];
124 		}
125 
126 		return result;
127 	}
128 }
129 
130 unittest
131 {
132 	import std.stdio;
133 
134 	writeln(" * CompositeTypeSerialiser");
135 
136 	struct Test2
137 	{
138 		int c = 3;
139 	}
140 
141 	struct Test
142 	{
143 		int a = 1;
144 		int b = 2;
145 
146 		Nullable!Test2 ntest2;
147 		Test2 test2;
148 	}
149 
150 	Test t = Test(1, 2);
151 	auto serialised = CompositeTypeSerialiser.serialise(t);
152 	auto deserialised = CompositeTypeSerialiser.deserialise!Test(serialised);
153 
154 
155 	// Why is this throwing AssertError???
156 	//assert(t == deserialised);
157 
158 	// The manual approach, I guess
159 	assert(deserialised.a == t.a);
160 	assert(deserialised.b == t.b);
161 	assert(deserialised.test2.c == t.test2.c);
162 	assert(deserialised.ntest2.isNull == t.ntest2.isNull);
163 }