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 }