1 ///
2 module dpq.serialisers.array;
3 
4 import std.typecons : Nullable;
5 import std.bitmanip;
6 import std.traits;
7 import std.conv : to;
8 import std..string : format;
9 
10 import dpq.meta;
11 import dpq.serialisation;
12 import dpq.exception;
13 import dpq.value : Type;
14 import dpq.connection : Connection;
15 
16 import libpq.libpq;
17 
18 
19 
20 
21 /*-------------------------------------------------------------------------
22  *
23  * array.h
24  *	  Declarations for Postgres arrays.
25  *
26  * A standard varlena array has the following internal structure:
27  *	  <vl_len_>		- standard varlena header word
28  *	  <ndim>		- number of dimensions of the array
29  *	  <dataoffset>	- offset to stored data, or 0 if no nulls bitmap
30  *	  <elemtype>	- element type OID
31  *	  <dimensions>	- length of each array axis (C array of int)
32  *	  <lower bnds>	- lower boundary of each dimension (C array of int)
33  *	  <null bitmap> - bitmap showing locations of nulls (OPTIONAL)
34  *	  <actual data> - whatever is the stored data
35  *-------------------------------------------------------------------------
36  */
37 /**
38 	reverse-enigneering the source
39 
40 	Name         | size in B       | notes
41 	------------------------------------
42 	ndim         | 4               | more than 0, less than MAXDIM
43 	data_offset  | 4               | data offset in bytes, if > 0, reads null bitmap
44 	element_type | sizeof Oid      | cannot be spec_element_type, whatever that is
45 	------------------------------------
46 
47     --- ndim times
48 	dim          | 4               | appends to array of dim -- if lbound + dim - 1 < lbound, fail (???)
49 	lBound       | 4               | appends to array of lBound
50 	---
51 
52 	--- nitems (calculated) times
53 	itemlen      | 4               | if itemlen is < -1 or itemlen > more than buffer remainder
54 	                               | -1 means null (?)
55 	value        | itemlen         | element's receiveproc is called
56 	---
57 
58 */
59 
60 struct ArraySerialiser
61 {
62 	static bool isSupportedType(T)()
63 	{
64 		// BYTEA uses its own serialiser
65 		// strings are arrays, but not handled by this serialiser
66 		return 
67 			isArray!T &&
68 			!isSomeString!T &&
69 			!is(T == ubyte[]) &&
70 			!is(T == byte[]);
71 	}
72 
73 	static void enforceSupportedType(T)()
74 	{
75 		static assert (
76 				isSupportedType!T,
77 				"'%s' is not supported by ArraySerialiser".format(T.stringof));
78 	}
79 
80 	static Nullable!(ubyte[]) serialise(T)(T val)
81 	{
82 		enforceSupportedType!T;
83 
84 		alias RT = Nullable!(ubyte[]);
85 		ubyte[] result;
86 		import std.stdio;
87 	
88 		// ndim
89 		result ~= nativeToBigEndian(cast(int) ArrayDimensions!T);
90 
91 		// data offset is 0, no NULL bitmap
92 		result ~= nativeToBigEndian(cast(int) 0);
93 
94 		// OID of the array elements
95 		alias BT = BaseType!T;
96 		result ~= nativeToBigEndian(cast(int) SerialiserFor!BT.oidForType!BT);
97 
98 		// Dimension size for every dimension
99 		int[] dimSizes;
100 		void setDimensions(T)(T data, int dim = 0)
101 		{
102 			// Remember the dimension size, make sure array is not jagged
103 			if (dimSizes.length <= dim)
104 				dimSizes ~= data.length.to!int;
105 			else if (dimSizes[dim] != data.length)
106 				throw new DPQException("Multidimensional arrays must have sub-arrays with matching dimensions.");
107 
108 			// Loop through array
109 			static if (isSupportedType!(RealType!(ForeachType!T)))
110 			{
111 				foreach (v; data)
112 				{
113 					if (isAnyNull(v))
114 						throw new DPQException(
115 								"Multidimensional array can not have NULL sub-arrays");
116 					setDimensions(v, dim + 1);
117 				}
118 			}
119 		}
120 		setDimensions(val);
121 
122 		foreach(dimSize; dimSizes)
123 		{
124 			// Dimension size
125 			result ~= nativeToBigEndian(cast(int) dimSize);
126 			// Lower bound
127 			result ~= nativeToBigEndian(cast(int) 1);
128 		}
129 
130 		// Writes all the values, left-to-right to data
131 		void write(T)(T data)
132 		{
133 			static if (isSupportedType!(RealType!(ForeachType!T)))
134 				foreach (v; data)
135 					write(v);
136 			else
137 			{
138 				foreach (v; data)
139 				{
140 					auto bs = toBytes(v);
141 
142 					if (bs.isNull)
143 						result ~= nativeToBigEndian(cast(int) -1); // NULL element
144 					else
145 					{
146 						result ~= nativeToBigEndian(cast(int) bs.length);
147 						result ~= bs;
148 					}
149 				}
150 			}
151 		}
152 		write(val);
153 
154 		return RT(result);
155 	}
156 
157 	static T deserialise(T)(const(ubyte)[] bytes)
158 	{
159 		enforceSupportedType!T;
160 
161 		// Basic array info
162 		int nDims = bytes.read!int;
163 		int offset = bytes.read!int;
164 		Oid oid = bytes.read!int;
165 
166 		int[] dimSizes;
167 		int[] lowerBounds;
168 		dimSizes.length = nDims;
169 		lowerBounds.length = nDims;
170 
171 		// Read dimension sizes and lower bounds
172 		foreach (i; 0 .. nDims)
173 		{
174 			dimSizes[i] = bytes.read!int;
175 			lowerBounds[i] = bytes.read!int;
176 		}
177 		
178 		// I don't know what to do with this.
179 		ubyte[] nullBitmap;
180 		foreach (i; 0 .. offset)
181 			nullBitmap ~= bytes.read!ubyte;
182 
183 		// Offset used for reading the actual data later
184 		TI assemble(TI)(int dim = 0)
185 		{
186 			TI arr;
187 			alias FT = RealType!(ForeachType!TI);
188 
189 			// Recurse into the next dimension
190 			static if (isSupportedType!FT)
191 			{
192 				static if (isDynamicArray!TI)
193 					arr.length = dimSizes[dim];
194 
195 				assert(
196 						dimSizes[dim] == arr.length, 
197 						"Array sizes do not match, you probably specified an incorrect static array size somewhere.");
198 
199 				foreach (i; 0 .. dimSizes[dim])
200 					arr[i] = assemble!FT(dim + 1);
201 
202 				return arr;
203 			}
204 			// last dimension
205 			else
206 			{
207 				TI inner;
208 				static if (isDynamicArray!TI)
209 					inner.length = dimSizes[dim];
210 
211 				// For each of the elements, read its size, then their actual value
212 				foreach (i; 0 .. dimSizes[dim])
213 				{
214 					int len = bytes.read!int;
215 
216 					// We're using "global" offset here, because we're reading the array left-to-right
217 					inner[i] = fromBytes!FT(bytes[0 .. len], len);
218 
219 					// "Consume" the array
220 					bytes = bytes[len .. $];
221 				}
222 				return cast(TI) inner;
223 			}
224 		}
225 
226 		// Dimensions are 0 if array is completely empty.
227 		if (nDims > 0)
228 			return assemble!T;
229 
230 		T arr;
231 		return arr;
232 	}
233 
234 	static Oid oidForType(T)()
235 	{
236 		enforceSupportedType!T;
237 
238 		alias BT = RealType!(BaseType!T);
239 
240 		Oid typeOid = SerialiserFor!BT.oidForType!BT;
241 
242 		Oid* p = typeOid in arrayOIDs;
243 		assert(p != null, "Oid for type %s cannot be determined by ArraySerialiser".format(T.stringof));
244 
245 		return *p;
246 	}
247 
248 	static string nameForType(T)()
249 	{
250 		enforceSupportedType!T;
251 
252 		alias FT = RealType!(ForeachType!T);
253 		alias serialiser = SerialiserFor!FT;
254 		return serialiser.nameForType!FT ~ "[]";
255 	}
256 
257 	// Arrays are always created implicitly
258 	static void ensureExistence(T)(Connection c)
259 	{
260 		alias EType = BaseType!T;
261 		SerialiserFor!EType.ensureExistence!EType(c);
262 	}
263 
264 	static void addCustomOid(Oid typeOid, Oid oid)
265 	{
266 		arrayOIDs[typeOid] = oid;
267 	}
268 
269 	template ArrayDimensions(T)
270 	{
271 		static if (isSupportedType!T)
272 			enum ArrayDimensions = 1 + ArrayDimensions!(ForeachType!T);
273 		else 
274 			enum ArrayDimensions = 0;
275 	}
276 }
277 
278 unittest
279 {
280 	import std.stdio;
281 	import dpq.serialisers.composite;
282 
283 	writeln(" * ArraySerialiser");
284 
285 	writeln("	* Array of scalar types");
286 	int[2][2] arr = [[1, 2], [3, 4]];
287 	ubyte[] expected = [
288 		0, 0, 0, 2, // dims
289 		0, 0, 0, 0, // offset
290 		0, 0, 0, 23, // oid
291 
292 		0, 0, 0, 2, // dim 0 size
293 		0, 0, 0, 1, // dim 0 lBound
294 		0, 0, 0, 2, // dim 1 size
295 		0, 0, 0, 1, // dim 1 lBound
296 
297 
298 		0, 0, 0, 4, // element size
299 		0, 0, 0, 1, // element value
300 
301 		0, 0, 0, 4, // element size
302 		0, 0, 0, 2, // element value
303 
304 		0, 0, 0, 4, // element size
305 		0, 0, 0, 3, // element value
306 
307 		0, 0, 0, 4, // element size
308 		0, 0, 0, 4, // element value
309 	];
310 
311 	auto serialised = ArraySerialiser.serialise(arr);
312 	assert(serialised == expected);
313 	assert(ArraySerialiser.deserialise!(int[2][2])(serialised) == arr);
314 
315 
316 	writeln("	* Empty array");
317 
318 	int[] emptyScalarArr;
319 	serialised = ArraySerialiser.serialise(emptyScalarArr);
320 	assert(ArraySerialiser.deserialise!(int[])(serialised) == emptyScalarArr);
321 
322 
323 	writeln("	* Array of struct");
324 	struct Test
325 	{
326 		int a = 1;
327 	}
328 
329 	// An oid needs to exist for struct serialisation
330 	CompositeTypeSerialiser.addCustomOid(
331 			CompositeTypeSerialiser.nameForType!Test,
332 			999999);
333 
334 	Test[] testArr = [Test(1), Test(2)];
335 	serialised = ArraySerialiser.serialise(testArr);
336 	assert(ArraySerialiser.deserialise!(Test[])(serialised) == testArr);
337 
338 	writeln("	* Array of arrays");
339 	int[][] twoDArray = [
340 		[1, 2, 3],
341 		[4, 5, 6],
342 		[7, 8, 9]
343 	];
344 
345 	serialised = ArraySerialiser.serialise(twoDArray);
346 	assert(ArraySerialiser.deserialise!(int[][])(serialised) == twoDArray);
347 
348 	import std.datetime;
349 
350 	writeln("	* Array of SysTime");
351 	SysTime[] timeArr;
352 	timeArr ~= Clock.currTime;
353 	timeArr ~= Clock.currTime + 2.hours;
354 	timeArr ~= Clock.currTime + 24.hours;
355 
356 	serialised = ArraySerialiser.serialise(timeArr);
357 	foreach (i, time; ArraySerialiser.deserialise!(SysTime[])(serialised))
358 	{
359 		// Serialiser only works with ms accuracy, so comparing them directly 
360 		// won't work in most cases.
361 		assert(time.toUnixTime == timeArr[i].toUnixTime);
362 	}
363 
364 	writeln("	* Array of string");
365 	string[] stringArr = [
366 		"My first string.",
367 		"String numero dva",
368 		"Do I even need this many strings?",
369 		"Bye"
370 	];
371 
372 	serialised = ArraySerialiser.serialise(stringArr);
373 	assert(ArraySerialiser.deserialise!(string[])(serialised) == stringArr);
374 }
375 
376 // Element Oid => Array Oid map
377 private Oid[Oid] arrayOIDs;
378 
379 static this()
380 {
381 	// Initialise arrayOIDs with the default values
382 	arrayOIDs[Type.INT4]   = Type.INT4ARRAY;
383 	arrayOIDs[Type.INT8]   = Type.INT8ARRAY;
384 	arrayOIDs[Type.INT2]   = Type.INT2ARRAY;
385 	arrayOIDs[Type.FLOAT4] = Type.FLOAT4ARRAY;
386 	arrayOIDs[Type.TEXT] = Type.TEXTARRAY;
387 }