1 /** 2 Primitives used for marshalling. 3 4 Copyright: Copyright Boris-Barboris 2017. 5 License: MIT 6 Authors: Boris-Barboris 7 */ 8 9 module dpeq.marshalling; 10 11 import std.algorithm: canFind; 12 import std.exception: enforce; 13 import std.bitmanip: nativeToBigEndian, bigEndianToNative; 14 import std.conv: to; 15 import std.meta; 16 import std.traits; 17 import std.typecons: Nullable, Tuple; 18 import std.variant; 19 import std.uuid: UUID; 20 21 import dpeq.constants; 22 import dpeq.exceptions; 23 24 25 26 /// Value type descriptor, constisting of an oid of the type itself and a 27 /// boolean flag wich indicates wether the value can be null. 28 struct FieldSpec 29 { 30 ObjectID typeId; 31 bool nullable; 32 } 33 34 35 /** Default compile-time one-to-many mapper, wich for ObjectID of some Postgress type 36 * gives it's native type representation, and marshalling and demarshalling 37 * functions. You can extend it with two custom mappers: Pre and Post. */ 38 template DefaultFieldMarshaller(FieldSpec field, alias Pre = NopMarshaller, 39 alias Post = PromiscuousStringMarshaller) 40 { 41 static if (Pre!field.canDigest) 42 { 43 // must alias to native type representing this value 44 alias type = Pre!field.type; 45 46 // By default, this format code will be passed in Bind message 47 // as parameter formatCode. 48 enum formatCode = Pre!field.formatCode; 49 50 // demarshaller should accept all formatCodes that make sense for this 51 // type, and it must throw in unhandled case. 52 alias demarshal = Pre!field.demarshal; 53 54 // marshaller only needs to support one formatCode, mentioned above 55 alias marshal = Pre!field.marshal; 56 } 57 else static if (StaticFieldMarshaller!field.canDigest) 58 { 59 alias type = StaticFieldMarshaller!field.type; 60 enum formatCode = StaticFieldMarshaller!field.formatCode; 61 alias demarshal = StaticFieldMarshaller!field.demarshal; 62 alias marshal = StaticFieldMarshaller!field.marshal; 63 } 64 else static if (Post!field.canDigest) 65 { 66 alias type = Post!field.type; 67 enum formatCode = Post!field.formatCode; 68 alias demarshal = Post!field.demarshal; 69 alias marshal = Post!field.marshal; 70 } 71 else 72 static assert(0, "Unknown typeId " ~ field.typeId.to!string ~ 73 ", cannot (de)marshal"); 74 } 75 76 /// Can't marshal shit 77 template NopMarshaller(FieldSpec type) 78 { 79 enum canDigest = false; 80 } 81 82 /// This is a fallback marshaller that simply accepts any type as a string 83 template PromiscuousStringMarshaller(FieldSpec field) 84 { 85 mixin MarshTemplate!(string, FormatCode.Text, "StringField"); 86 } 87 88 /// Types handled by dpeq natively 89 template StaticFieldMarshaller(FieldSpec field) 90 { 91 static if (field.typeId == StaticPgTypes.BOOLEAN) 92 mixin MarshTemplate!(bool, FormatCode.Binary, "FixedField"); 93 else static if (field.typeId == StaticPgTypes.BIGINT) 94 mixin MarshTemplate!(long, FormatCode.Binary, "FixedField"); 95 else static if (field.typeId == StaticPgTypes.SMALLINT) 96 mixin MarshTemplate!(short, FormatCode.Binary, "FixedField"); 97 else static if (field.typeId == StaticPgTypes.INT) 98 mixin MarshTemplate!(int, FormatCode.Binary, "FixedField"); 99 else static if (field.typeId == StaticPgTypes.OID) 100 mixin MarshTemplate!(int, FormatCode.Binary, "FixedField"); 101 else static if (field.typeId == StaticPgTypes.VARCHAR) 102 mixin MarshTemplate!(string, FormatCode.Text, "StringField"); 103 else static if (field.typeId == StaticPgTypes.CHARACTER) 104 mixin MarshTemplate!(string, FormatCode.Text, "StringField"); 105 else static if (field.typeId == StaticPgTypes.TEXT) 106 mixin MarshTemplate!(string, FormatCode.Text, "StringField"); 107 else static if (field.typeId == StaticPgTypes.UUID) 108 mixin MarshTemplate!(UUID, FormatCode.Binary, "UuidField"); 109 else static if (field.typeId == StaticPgTypes.REAL) 110 mixin MarshTemplate!(float, FormatCode.Binary, "FixedField"); 111 else static if (field.typeId == StaticPgTypes.DOUBLE) 112 mixin MarshTemplate!(double, FormatCode.Binary, "FixedField"); 113 else 114 enum canDigest = false; 115 } 116 117 // to prevent code duplication 118 mixin template MarshTemplate(NativeT, FormatCode fcode, string suffix) 119 { 120 enum canDigest = true; 121 enum formatCode = fcode; 122 static if (field.nullable) 123 { 124 alias type = Nullable!NativeT; 125 mixin("alias demarshal = demarshalNullable" ~ suffix ~ "!(NativeT);"); 126 mixin("alias marshal = marshalNullable" ~ suffix ~ "!(NativeT);"); 127 } 128 else 129 { 130 alias type = NativeT; 131 mixin("alias demarshal = demarshal" ~ suffix ~ "!(NativeT);"); 132 mixin("alias marshal = marshal" ~ suffix ~ "!(NativeT);"); 133 } 134 } 135 136 137 template FCodeOfFSpec(alias Marsh = DefaultFieldMarshaller) 138 { 139 template F(FieldSpec spec) 140 { 141 enum F = Marsh!spec.formatCode; 142 } 143 } 144 145 /// Utility template to quickly get an array of format codes from an array of 146 /// FieldSpecs 147 template FSpecsToFCodes(FieldSpec[] specs, alias Marsh = DefaultFieldMarshaller) 148 { 149 enum FSpecsToFCodes = [staticMap!(FCodeOfFSpec!Marsh.F, aliasSeqOf!specs)]; 150 } 151 152 /* 153 /////////////////////////////////////////////////////////////////////////// 154 // Marshaller implementations. Marshaller writes only body of the data 155 // and returns count of bytes written, -1 if it's a null value, 156 // and -2 if the buffer is too small to fit whole value. 157 /////////////////////////////////////////////////////////////////////////// 158 */ 159 160 pragma(inline, true) 161 int marshalNull(ubyte[] to) 162 { 163 return -1; // special case, -1 length is null value in eq protocol. 164 } 165 166 // I don't really know how versatile are these functions, so let's keep 167 // them FixedField instead of NumericField 168 169 int marshalNullableFixedField(T)(ubyte[] to, in Nullable!T ptr) 170 { 171 if (ptr.isNull) 172 return marshalNull(to); 173 return marshalFixedField!T(to, ptr.get); 174 } 175 176 int marshalFixedField(T)(ubyte[] to, in T val) 177 { 178 if (T.sizeof > to.length) 179 return -2; 180 auto arr = nativeToBigEndian!T(val); 181 to[0 .. arr.length] = arr; 182 return arr.length; 183 } 184 185 int marshalNullableStringField(Dummy = void)(ubyte[] to, in Nullable!string val) 186 { 187 if (val.isNull) 188 return marshalNull(to); 189 return marshalStringField(to, val.get); 190 } 191 192 int marshalStringField(Dummy = void)(ubyte[] to, in string s) 193 { 194 if (s.length > to.length) 195 return -2; 196 for (int i = 0; i < s.length; i++) 197 to[i] = cast(const(ubyte)) s[i]; 198 return s.length.to!int; 199 } 200 201 /// Service function, used for marshalling of protocol messages. 202 /// Data strings are passed without trailing nulls. 203 int marshalCstring(ubyte[] to, in string s) 204 { 205 if (s.length + 1 > to.length) 206 return -2; 207 for (int i = 0; i < s.length; i++) 208 to[i] = cast(const(ubyte)) s[i]; 209 to[s.length] = cast(ubyte)0; 210 return (s.length + 1).to!int; 211 } 212 213 int marshalNullableUuidField(Dummy = void)(ubyte[] to, in Nullable!UUID val) 214 { 215 if (val.isNull) 216 return marshalNull(to); 217 return marshalUuidField(to, val.get); 218 } 219 220 int marshalUuidField(Dummy = void)(ubyte[] to, in UUID val) 221 { 222 if (to.length < 16) 223 return -2; 224 for (int i = 0; i < 16; i++) 225 to[i] = val.data[i]; 226 return 16; 227 } 228 229 /* 230 ////////////////////////////////////////////////////////////////////////////// 231 // Demarshalling implementations. Demarshallers take byte array that contains 232 // data body, it's format code and length according to field prefix. 233 ////////////////////////////////////////////////////////////////////////////// 234 */ 235 236 237 /// Simple demarshal of some numeric type. 238 pragma(inline) 239 T demarshalNumber(T = int)(const(ubyte)[] from) 240 if (isNumeric!T) 241 { 242 return bigEndianToNative!T(from[0 .. T.sizeof]); 243 } 244 245 /// psql uses `t` and `f` for boolean 246 bool to(T: bool)(in string s) 247 { 248 if (s == "t") 249 return true; 250 if (s == "f") 251 return false; 252 throw new PsqlMarshallingException("Unable to unmarshal bool from string " ~ s); 253 } 254 255 T demarshalFixedField(T)(const(ubyte)[] from, in FormatCode fCode, in int len) 256 { 257 enforce!PsqlMarshallingException(len > 0, "zero-sized fixed non-nullable field"); 258 if (fCode == FormatCode.Binary) 259 { 260 enforce!PsqlMarshallingException(len == T.sizeof, "Field size mismatch"); 261 return bigEndianToNative!T(from[0 .. T.sizeof]); 262 } 263 else if (fCode == FormatCode.Text) 264 return demarshalString(from[0 .. len]).to!T; 265 else 266 throw new PsqlMarshallingException("Unsupported FormatCode"); 267 } 268 269 Nullable!T demarshalNullableFixedField(T) 270 (const(ubyte)[] from, in FormatCode fCode, in int len) 271 { 272 if (len == -1) 273 return Nullable!T.init; 274 if (fCode == FormatCode.Binary) 275 { 276 enforce!PsqlMarshallingException(len == T.sizeof, "Field size mismatch, " ~ 277 T.stringof ~ ", actual = " ~ len.to!string); 278 return Nullable!T(bigEndianToNative!T(from[0 .. T.sizeof])); 279 } 280 else if (fCode == FormatCode.Text) 281 return Nullable!T(demarshalString(from[0 .. len]).to!T); 282 else 283 throw new PsqlMarshallingException("Unsupported FormatCode"); 284 } 285 286 string demarshalStringField(Dummy = void) 287 (const(ubyte)[] from, in FormatCode fc, in int len) 288 { 289 assert(fc == FormatCode.Text, "binary string?"); 290 enforce!PsqlMarshallingException(len >= 0, "null string in non-nullable demarshaller"); 291 if (len == 0) 292 return ""; 293 string res = (cast(immutable(char)*)(from.ptr))[0 .. len.to!size_t]; 294 return res; 295 } 296 297 /// returns inplace-constructed string without allocations. Hacky. 298 Nullable!string demarshalNullableStringField(Dummy = void) 299 (const(ubyte)[] from, in FormatCode fc, in int len) 300 { 301 assert(fc == FormatCode.Text, "binary string?"); 302 if (len == -1) 303 return Nullable!string.init; 304 if (len == 0) 305 return Nullable!string(""); 306 string res = (cast(immutable(char)*)(from.ptr))[0 .. len.to!size_t]; 307 return Nullable!string(res); 308 } 309 310 /// dpeq utility function 311 string demarshalString(const(ubyte)[] from) 312 { 313 return (cast(immutable(char)*)(from.ptr))[0 .. from.length]; 314 } 315 316 /// dpeq utility function. Demarshal zero-terminated string from byte buffer. 317 string demarshalProtocolString(const(ubyte)[] from, ref size_t length) 318 { 319 size_t l = 0; 320 while (from[l]) 321 { 322 l++; 323 if (l >= from.length) 324 throw new PsqlMarshallingException("Null-terminated string is not " ~ 325 "null-terminated"); 326 } 327 length = l + 1; 328 if (l == 0) 329 return string.init; 330 return demarshalString(from[0..l]); 331 } 332 333 Nullable!UUID demarshalNullableUuidField(Dummy = void) 334 (const(ubyte)[] from, in FormatCode fc, in int len) 335 { 336 if (len == -1) 337 return Nullable!UUID.init; 338 return Nullable!UUID(demarshalUuidField(from, fc, len)); 339 } 340 341 UUID demarshalUuidField(Dummy = void) 342 (const(ubyte)[] from, in FormatCode fc, in int len) 343 { 344 enforce!PsqlMarshallingException(len > 0, "null uuid in non-nullable demarshaller"); 345 if (fc == FormatCode.Binary) 346 { 347 enforce!PsqlMarshallingException(len == 16, "uuid is not 16-byte"); 348 ubyte[16] data; 349 for (int i = 0; i < 16; i++) 350 data[i] = from[i]; 351 return UUID(data); 352 } 353 else if (fc == FormatCode.Text) 354 { 355 scope string val = (cast(immutable(char)*)(from.ptr))[0 .. len.to!size_t]; 356 return UUID(val); 357 } 358 else 359 throw new PsqlMarshallingException("Unsupported FormatCode"); 360 } 361 362 363 /* 364 ///////////////////////////////////////////////////////////////// 365 // Dynamic conversion code, suitable for dynamic typing 366 ///////////////////////////////////////////////////////////////// 367 */ 368 369 /// prototype of a nullable variant demarshaller, used in converter 370 alias VariantDemarshaller = 371 NullableVariant function(const(ubyte)[] buf, in FormatCode fc, in int len); 372 373 /// std.variant.Variant subtype that is better suited for holding SQL null. 374 /// Null NullableVariant is essentially a valueless Variant instance. 375 struct NullableVariant 376 { 377 Variant variant; 378 alias variant this; 379 380 this(T)(T value) 381 { 382 variant = value; 383 } 384 385 /// this property will be true if psql return null in this column 386 @safe bool isNull() const { return !variant.hasValue; } 387 388 string toString() 389 { 390 if (isNull) 391 // may conflict with "null" string, but this can be said about any string 392 return "null"; 393 else 394 return variant.toString(); 395 } 396 } 397 398 NullableVariant wrapToVariant(alias f)(const(ubyte)[] buf, in FormatCode fc, in int len) 399 { 400 auto nullableResult = f(buf, fc, len); 401 if (nullableResult.isNull) 402 return NullableVariant(); 403 else 404 return NullableVariant(nullableResult.get); 405 } 406 407 /// Default converter hash map. You can extend it, or define your own. 408 class VariantConverter(alias Marsh = DefaultFieldMarshaller) 409 { 410 static immutable VariantDemarshaller[ObjectID] demarshallers; 411 412 @disable private this(); 413 414 shared static this() 415 { 416 VariantDemarshaller[ObjectID] aa; 417 // iterate over StaticPgTypes and take demarshallers from StaticFieldMarshaller 418 foreach (em; __traits(allMembers, StaticPgTypes)) 419 { 420 // this assumes nullable return fields. Variant will wrap Nullable 421 // of some native type. 422 enum FieldSpec spec = 423 FieldSpec(__traits(getMember, StaticPgTypes, em), true); 424 /*pragma(msg, "registering default demarshaller for ", 425 StaticFieldMarshaller!spec.type, " in hash table");*/ 426 aa[spec.typeId] = &wrapToVariant!(Marsh!spec.demarshal); 427 } 428 demarshallers = cast(immutable VariantDemarshaller[ObjectID]) aa; 429 } 430 431 static NullableVariant demarshal( 432 const(ubyte)[] fieldBody, ObjectID type, FormatCode fc, int len) 433 { 434 immutable(VariantDemarshaller)* func = type in demarshallers; 435 if (func) 436 return (*func)(fieldBody, fc, len); 437 else 438 { 439 // fallback to nullable string demarshaller 440 if (fc == FormatCode.Text) 441 return demarshallers[StaticPgTypes.VARCHAR](fieldBody, fc, len); 442 else 443 throw new PsqlMarshallingException( 444 "Unable to deduce demarshaller for binary format of a type " ~ 445 type.to!string); 446 } 447 } 448 }