1 module dpq.serialisers.array;
2 
3 import std.typecons : Nullable;
4 import std.bitmanip;
5 import std.traits;
6 import std.conv : to;
7 
8 import dpq.meta;
9 import dpq.serialisation;
10 import dpq.exception;
11 
12 import libpq.libpq;
13 
14 
15 
16 /*-------------------------------------------------------------------------
17  *
18  * array.h
19  *	  Declarations for Postgres arrays.
20  *
21  * A standard varlena array has the following internal structure:
22  *	  <vl_len_>		- standard varlena header word
23  *	  <ndim>		- number of dimensions of the array
24  *	  <dataoffset>	- offset to stored data, or 0 if no nulls bitmap
25  *	  <elemtype>	- element type OID
26  *	  <dimensions>	- length of each array axis (C array of int)
27  *	  <lower bnds>	- lower boundary of each dimension (C array of int)
28  *	  <null bitmap> - bitmap showing locations of nulls (OPTIONAL)
29  *	  <actual data> - whatever is the stored data
30  *-------------------------------------------------------------------------
31  */
32 /**
33 	reverse-enigneering the source
34 
35 	Name         | size in B       | notes
36 	------------------------------------
37 	ndim         | 4               | more than 0, less than MAXDIM
38 	data_offset  | 4               | data offset in bytes, if > 0, reads null bitmap
39 	element_type | sizeof Oid      | cannot be spec_element_type, whatever that is
40 
41   --- ndim times
42 	dim          | 4               | appends to array of dim -- if lbound + dim - 1 < lbound, fail (???)
43 	lBound       | 4               | appends to array of lBound
44 	---
45 
46 	--- nitems (calculated) times
47 	itemlen      | 4               | if itemlen is < -1 or itemlen > more than buffer remainder
48 	                               | -1 means null (?)
49 	value        | itemlen         | element's receiveproc is called
50 	---
51 
52 */
53 
54 struct ArraySerialiser
55 {
56 	static bool isSupportedType(T)()
57 	{
58 		return isArray!T;
59 	}
60 
61 	static Nullable!(ubyte[]) serialise(T)(T val)
62 	{
63 		static assert (
64 				isSupportedType!T,
65 				"'%s' is not supported by ArraySerialiser".format(T.stringof));
66 
67 		alias RT = Nullable!(ubyte[]);
68 		ubyte[] result;
69 		import std.stdio;
70 	
71 		// ndim
72 		result ~= nativeToBigEndian(cast(int) ArrayDimensions!T);
73 
74 		// data offset is 0, no NULL bitmap
75 		result ~= nativeToBigEndian(cast(int) 0);
76 
77 		// OID of the array elements
78 		result ~= nativeToBigEndian(cast(int) oidForType!(RealType!(BaseType!(T))));
79 
80 		// Dimension size for every dimension
81 		int[] dimSizes;
82 		void setDimensions(T)(T data, int dim = 0)
83 		{
84 			// Remember the dimension size, make sure array is not jagged
85 			if (dimSizes.length <= dim)
86 				dimSizes ~= data.length.to!int;
87 			else if (dimSizes[dim] != data.length)
88 				throw new DPQException("Multidimensional arrays must have sub-arrays with matching dimensions.");
89 
90 			// Loop through array
91 			static if (isArray!(RealType!(ForeachType!T)))
92 			{
93 				foreach (v; data)
94 				{
95 					if (isAnyNull(v))
96 						throw new DPQException(
97 								"Multidimensional array can not have NULL sub-arrays");
98 					setDimensions(v, dim + 1);
99 				}
100 			}
101 		}
102 		setDimensions(val);
103 
104 		foreach(dimSize; dimSizes)
105 		{
106 			// Dimension size
107 			result ~= nativeToBigEndian(cast(int) dimSize);
108 			// Lower bound
109 			result ~= nativeToBigEndian(cast(int) 1);
110 		}
111 
112 		// Writes all the values, left-to-right to data
113 		void write(T)(T data)
114 		{
115 			static if (isArray!(RealType!(ForeachType!T)))
116 				foreach (v; data)
117 					write(v);
118 			else
119 			{
120 				foreach (v; data)
121 				{
122 					auto bs = toBytes(v);
123 
124 					if (bs.isNull)
125 						result ~= nativeToBigEndian(cast(int) -1); // NULL element
126 					else
127 					{
128 						result ~= nativeToBigEndian(cast(int) bs.length);
129 						result ~= bs;
130 					}
131 				}
132 			}
133 		}
134 		write(val);
135 
136 		return RT(result);
137 	}
138 
139 	static T deserialise(T)(const(ubyte)[] bytes)
140 	{
141 		static assert (
142 				isSupportedType!T,
143 				"'%s' is not supported by ArraySerialiser".format(T.stringof));
144 
145 		// Basic array info
146 		int nDims = bytes.read!int;
147 		int offset = bytes.read!int;
148 		Oid oid = bytes.read!int;
149 
150 
151 		int[] dimSizes;
152 		int[] lowerBounds;
153 		dimSizes.length = nDims;
154 		lowerBounds.length = nDims;
155 
156 		// Read dimension sizes and lower bounds
157 		foreach (i; 0 .. nDims)
158 		{
159 			dimSizes[i] = bytes.read!int;
160 			lowerBounds[i] = bytes.read!int;
161 		}
162 		
163 		// I don't know what to do with this.
164 		ubyte[] nullBitmap;
165 		foreach (i; 0 .. offset)
166 			nullBitmap ~= bytes.read!ubyte;
167 
168 		// Offset used for reading the actual data later
169 		TI assemble(TI)(int dim = 0)
170 		{
171 			TI arr;
172 			alias FT = RealType!(ForeachType!TI);
173 
174 			// Recurse into the next dimension
175 			static if (isArray!FT)
176 			{
177 				static if (isDynamicArray!TI)
178 					arr.length = dimSizes[dim];
179 
180 				assert(
181 						dimSizes[dim] == arr.length, 
182 						"Array sizes do not match, you probably specified an incorrect static array size somewhere.");
183 
184 				foreach (i; 0 .. dimSizes[dim])
185 					arr[i] = assemble!FT(dim + 1);
186 
187 				return arr;
188 			}
189 			// last dimension
190 			else
191 			{
192 				TI inner;
193 				static if (isDynamicArray!T)
194 					inner.length = dimSizes[dim];
195 
196 				// For each of the elements, read its size, then their actual value
197 				foreach (i; 0 .. dimSizes[dim])
198 				{
199 					int len = bytes.read!int;
200 					// We're using "global" offset here, because we're reading the array left-to-right
201 					inner[i] = fromBytes!FT(bytes[0 .. len], len);
202 
203 					// "Consume" the array
204 					bytes = bytes[len .. $];
205 				}
206 				return inner;
207 			}
208 		}
209 
210 		return assemble!T;
211 	}
212 }
213 
214 unittest
215 {
216 	import std.stdio;
217 	writeln(" * ArraySerialiser");
218 
219 	int[2][2] arr = [[1, 2], [3, 4]];
220 	ubyte[] expected = [
221 		0, 0, 0, 2, // dims
222 		0, 0, 0, 0, // offset
223 		0, 0, 0, 23, // oid
224 
225 		0, 0, 0, 2, // dim 0 size
226 		0, 0, 0, 1, // dim 0 lBound
227 		0, 0, 0, 2, // dim 1 size
228 		0, 0, 0, 1, // dim 1 lBound
229 
230 
231 		0, 0, 0, 4, // element size
232 		0, 0, 0, 1, // element value
233 
234 		0, 0, 0, 4, // element size
235 		0, 0, 0, 2, // element value
236 
237 		0, 0, 0, 4, // element size
238 		0, 0, 0, 3, // element value
239 
240 		0, 0, 0, 4, // element size
241 		0, 0, 0, 4, // element value
242 	];
243 
244 	auto serialised = ArraySerialiser.serialise(arr);
245 	assert(serialised == expected);
246 	assert(ArraySerialiser.deserialise!(int[2][2])(serialised) == arr);
247 }