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 }