1 module dpq.serialisation; 2 3 // TODO: merge all serialisers' imports 4 import dpq.serialisers.composite; 5 import dpq.serialisers.array; 6 import dpq.serialisers.scalar; 7 import dpq.serialisers.systime; 8 import dpq.serialisers..string; 9 10 import dpq.meta; 11 import dpq.attributes; 12 import dpq.value : Type; 13 14 import std.datetime : SysTime, DateTime; 15 import std.typecons : Nullable, TypedefType; 16 import std.traits; 17 import std.bitmanip; 18 import std..string : format; 19 import std.meta; 20 21 import libpq.libpq; 22 23 24 /** 25 Converts the given type to an ubyte[], as PostgreSQL expects it. Ignores 26 any Nullable specifiers and Typedefs. 27 */ 28 package Nullable!(ubyte[]) toBytes(T)(T val) 29 { 30 alias AT = RealType!T; 31 32 if (isAnyNull(val)) 33 return Nullable!(ubyte[]).init; 34 35 alias serialiser = SerialiserFor!AT; 36 return serialiser.serialise(cast(AT) val); 37 } 38 39 /*****************************************************************************/ 40 41 struct SerialiserAttribute(alias T) 42 { 43 alias serialiser = T; 44 } 45 46 SerialiserAttribute!T serialiser(alias T)() 47 { 48 return SerialiserAttribute!T(); 49 } 50 51 template SerialiserFor(T) 52 if (isBuiltinType!T) 53 { 54 static if (isSomeString!T) 55 alias SerialiserFor = StringSerialiser; 56 else static if (isArray!T) 57 alias SerialiserFor = ArraySerialiser; 58 else static if (isScalarType!T) 59 alias SerialiserFor = ScalarSerialiser; 60 } 61 62 template SerialiserFor(alias T) 63 if (!isBuiltinType!T) 64 { 65 import std.meta; 66 67 alias UDAs = getUDAs!(T, SerialiserAttribute); 68 69 // First see if a custom serialiser is specified for the type 70 static if (UDAs.length > 0) 71 alias SerialiserFor = UDAs[0].serialiser; 72 else 73 { 74 // Otherwise, pick one from the bunch of pre-set ones. 75 static if (isArray!T) 76 alias SerialiserFor = ArraySerialiser; 77 // Support for SysTime 78 else static if (is(T == SysTime)) 79 alias SerialiserFor = SysTimeSerialiser; 80 else static if (is(T == class) || is(T == struct)) 81 alias SerialiserFor = CompositeTypeSerialiser; 82 else 83 static assert(false, "Cannot find serialiser for " ~ T.stringof); 84 } 85 } 86 87 unittest 88 { 89 import std.stdio; 90 91 writeln(" * SerialiserFor"); 92 93 struct Test1 {} 94 95 static assert(is(SerialiserFor!int == ScalarSerialiser)); 96 static assert(is(SerialiserFor!Test1 == CompositeTypeSerialiser)); 97 static assert(is(SerialiserFor!(int[][]) == ArraySerialiser)); 98 static assert(is(SerialiserFor!(Test1[][]) == ArraySerialiser)); 99 100 @serialiser!Test1() struct Test2 {} 101 102 static assert(is(SerialiserFor!Test2 == Test1)); 103 } 104 105 package T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0) 106 if (isInstanceOf!(Nullable, T)) 107 { 108 alias AT = RealType!T; 109 110 return T(fromBytes!AT(bytes, len)); 111 } 112 113 package Nullable!T fromBytes(T)(const(ubyte)[] bytes, size_t len = 0) 114 if (!isInstanceOf!(Nullable, T)) 115 { 116 if (len == -1) 117 return Nullable!T.init; 118 119 alias AT = RealType!T; 120 121 return Nullable!T(cast(T) fromBytesImpl!AT(bytes, len)); 122 } 123 124 package T fromBytesImpl(T)(const(ubyte)[] bytes, size_t len) 125 { 126 alias serialiser = SerialiserFor!T; 127 return Nullable!T(serialiser.deserialise!T(bytes[0 .. len])); 128 } 129 130 unittest 131 { 132 import std.bitmanip; 133 import std..string; 134 import std.stdio; 135 136 writeln(" * fromBytes"); 137 138 int x = 123; 139 140 const (ubyte)[] bs = nativeToBigEndian(x); 141 assert(fromBytes!int(bs, x.sizeof) == x); 142 143 x = -555; 144 bs = nativeToBigEndian(x); 145 assert(fromBytes!int(bs, x.sizeof) == x); 146 147 x = int.min; 148 bs = nativeToBigEndian(x); 149 assert(fromBytes!int(bs, x.sizeof) == x); 150 151 x = int.max; 152 bs = nativeToBigEndian(x); 153 assert(fromBytes!int(bs, x.sizeof) == x); 154 155 string s = "some random string"; 156 assert(fromBytes!string(s.representation, s.representation.length) == s); 157 158 s = ""; 159 assert(fromBytes!string(s.representation, s.representation.length) == s); 160 } 161 162 /*****************************************************************************/ 163 164 bool isAnyNull(T)(T val) 165 { 166 static if (is(T == class)) 167 return val is null; 168 else static if (isInstanceOf!(Nullable, T)) 169 return val.isNull; 170 else 171 return false; 172 } 173 174 // TODO: this for arrays 175 Type oidForType(T)() 176 if (!isArray!T) 177 { 178 import dpq.connection : _dpqCustomOIDs; 179 enum oid = typeOid!T; 180 181 static if (oid == Type.INFER) 182 { 183 Oid* p; 184 if ((p = relationName!T in _dpqCustomOIDs) != null) 185 return cast(Type) *p; 186 } 187 188 return oid; 189 } 190 191 template typeOid(T) 192 { 193 alias TU = std.typecons.Unqual!T; 194 static if (isArray!T && !isSomeString!T) 195 { 196 alias BT = BaseType!T; 197 static if (is(BT == int)) 198 enum typeOid = Type.INT4ARRAY; 199 else static if (is(BT == long)) 200 enum typeOid = Type.INT8ARRAY; 201 else static if (is(BT == short)) 202 enum typeOid = Type.INT2ARRAY; 203 else static if (is(BT == float)) 204 enum typeOid = Type.FLOAT4ARRAY; 205 else static if (is(BT == byte) || is (BT == ubyte)) 206 enum typeOid = Type.BYTEA; 207 else 208 static assert(false, "Cannot map array type " ~ T.stringof ~ " to Oid"); 209 } 210 else 211 { 212 static if (is(TU == int)) 213 enum typeOid = Type.INT4; 214 else static if (is(TU == long)) 215 enum typeOid = Type.INT8; 216 else static if (is(TU == bool)) 217 enum typeOid = Type.BOOL; 218 else static if (is(TU == byte)) 219 enum typeOid = Type.CHAR; 220 else static if (is(TU == char)) 221 enum typeOid = Type.CHAR; 222 else static if (isSomeString!TU) 223 enum typeOid = Type.TEXT; 224 else static if (is(TU == short)) 225 enum typeOid = Type.INT2; 226 else static if (is(TU == float)) 227 enum typeOid = Type.FLOAT4; 228 else static if (is(TU == double)) 229 enum typeOid = Type.FLOAT8; 230 else static if (is(TU == SysTime)) 231 enum typeOid = Type.TIMESTAMP; 232 233 /** 234 Since unsigned types are not supported by PostgreSQL, we use signed 235 types for them. Transfer and representation in D will still work correctly, 236 but SELECTing them in the psql console, or as a string might result in 237 a negative number. 238 239 It is recommended not to use unsigned types in structures, that will 240 be used in the DB directly. 241 */ 242 else static if (is(TU == ulong)) 243 enum typeOid = Type.INT8; 244 else static if (is(TU == uint)) 245 enum typeOid = Type.INT4; 246 else static if (is(TU == ushort) || is(TU == char)) 247 enum typeOid = Type.INT2; 248 else static if (is(TU == ubyte)) 249 enum typeOid = Type.CHAR; 250 else 251 // Try to infer 252 enum typeOid = Type.INFER; 253 } 254 } 255 256 unittest 257 { 258 import std.stdio; 259 writeln("\t * typeOid"); 260 261 static assert(typeOid!int == Type.INT4, "int"); 262 static assert(typeOid!string == Type.TEXT, "string"); 263 static assert(typeOid!(int[]) == Type.INT4ARRAY, "int[]"); 264 static assert(typeOid!(int[][]) == Type.INT4ARRAY, "int[][]"); 265 static assert(typeOid!(ubyte[]) == Type.BYTEA, "ubyte[]"); 266 }