1 module dpq.value; 2 3 import dpq.result; 4 import dpq.exception; 5 import dpq.pgarray; 6 import dpq.meta; 7 import dpq.attributes; 8 import dpq.connection; 9 10 //import derelict.pq.pq; 11 import libpq.libpq; 12 13 import std.algorithm : map; 14 import std.array; 15 import std.conv : to; 16 import std.typecons : Nullable, TypedefType; 17 import std.bitmanip; 18 import std.traits; 19 import std.datetime : SysTime, DateTime; 20 21 version(unittest) import std.stdio; 22 23 enum POSTGRES_EPOCH = DateTime(2000, 1, 1); 24 25 package enum Type : Oid 26 { 27 INFER = 0, 28 BOOL = 16, 29 BYTEA = 17, 30 CHAR = 18, 31 NAME = 19, 32 INT8 = 20, 33 INT2 = 21, 34 INT2VECTOR = 22, 35 INT4 = 23, 36 REGPROC = 24, 37 TEXT = 25, 38 OID = 26, 39 TID = 27, 40 XID = 28, 41 CID = 29, 42 OIDVECTOR = 30, 43 JSON = 114, 44 XML = 142, 45 PGNODETREE = 194, 46 POINT = 600, 47 LSEG = 601, 48 PATH = 602, 49 BOX = 603, 50 POLYGON = 604, 51 LINE = 628, 52 FLOAT4 = 700, 53 FLOAT8 = 701, 54 ABSTIME = 702, 55 RELTIME = 703, 56 TINTERVAL = 704, 57 UNKNOWN = 705, 58 CIRCLE = 718, 59 CASH = 790, 60 MACADDR = 829, 61 INET = 869, 62 CIDR = 650, 63 INT2ARRAY = 1005, 64 INT4ARRAY = 1007, 65 TEXTARRAY = 1009, 66 INT8ARRAY = 1016, 67 OIDARRAY = 1028, 68 FLOAT4ARRAY = 1021, 69 ACLITEM = 1033, 70 CSTRINGARRAY = 1263, 71 BPCHAR = 1042, 72 VARCHAR = 1043, 73 DATE = 1082, 74 TIME = 1083, 75 TIMESTAMP = 1114, 76 TIMESTAMPTZ = 1184, 77 INTERVAL = 1186, 78 TIMETZ = 1266, 79 BIT = 1560, 80 VARBIT = 1562, 81 NUMERIC = 1700, 82 REFCURSOR = 1790, 83 REGPROCEDURE = 2202, 84 REGOPER = 2203, 85 REGOPERATOR = 2204, 86 REGCLASS = 2205, 87 REGTYPE = 2206, 88 REGTYPEARRAY = 2211, 89 UUID = 2950, 90 LSN = 3220, 91 TSVECTOR = 3614, 92 GTSVECTOR = 3642, 93 TSQUERY = 3615, 94 REGCONFIG = 3734, 95 REGDICTIONARY = 3769, 96 JSONB = 3802, 97 INT4RANGE = 3904, 98 RECORD = 2249, 99 RECORDARRAY = 2287, 100 CSTRING = 2275, 101 ANY = 2276, 102 ANYARRAY = 2277, 103 VOID = 2278, 104 TRIGGER = 2279, 105 EVTTRIGGER = 3838, 106 LANGUAGE_HANDLER = 2280, 107 INTERNAL = 2281, 108 OPAQUE = 2282, 109 ANYELEMENT = 2283, 110 ANYNONARRAY = 2776, 111 ANYENUM = 3500, 112 FDW_HANDLER = 3115, 113 ANYRANGE = 3831, 114 } 115 116 package Oid[string] customTypes; 117 118 struct Value 119 { 120 private 121 { 122 ubyte[] _valueBytes; 123 int _size; 124 Type _type; 125 bool _isNull; 126 } 127 128 this(typeof(null) n) 129 { 130 _isNull = true; 131 } 132 133 this(T)(T val) 134 { 135 opAssign(val); 136 } 137 138 this(T)(T* val, int len, Type type = Type.INFER) 139 { 140 _size = len; 141 _type = type; 142 143 _valueBytes = val[0 .. len].dup; 144 } 145 146 void opAssign(T)(T val) 147 if (isArray!T) 148 { 149 _size = (ForeachType!T.sizeof * val.length).to!int; 150 151 static if (is(T == ubyte[])) 152 _valueBytes = val; 153 else 154 { 155 _valueBytes = PGArray(val).toBytes(); 156 _size = _valueBytes.length.to!int; 157 } 158 159 _type = typeOid!T; 160 } 161 162 void opAssign(T)(T val) 163 if(!isArray!T) 164 { 165 if (isAnyNull(val)) 166 { 167 _size = 0; 168 _valueBytes = null; 169 _isNull = true; 170 171 return; 172 } 173 174 _valueBytes = toBytes(val); 175 _size = _valueBytes.length.to!int; 176 _type = typeOid!T; 177 } 178 179 void opAssign(string val) 180 { 181 import std.string; 182 183 _valueBytes = val.representation.dup; 184 _size = _valueBytes.length.to!int; 185 _type = Type.TEXT; 186 } 187 188 void opAssign(SysTime val) 189 { 190 _type = typeOid!SysTime; 191 _size = typeof(val.stdTime).sizeof; 192 193 _valueBytes = toBytes(val); 194 } 195 196 void opAssign(Value val) 197 { 198 _valueBytes = val._valueBytes; 199 _size = val._size; 200 _type = val._type; 201 } 202 203 /** 204 Converts the given type to an ubyte[], as PostgreSQL expects it. Ignores 205 any Nullable specifiers and Typedefs. 206 */ 207 Nullable!(ubyte[]) toBytes(T)(T val) 208 { 209 alias NT = NoNullable!T; 210 alias AT = TypedefType!NT; 211 212 if (isAnyNull(val)) 213 return Nullable!(ubyte[]).init; 214 215 return Nullable!(ubyte[])(toBytesImpl(cast(AT) val)); 216 } 217 218 private ubyte[] toBytesImpl(T)(T val) 219 if (isScalarType!T) 220 { 221 return nativeToBigEndian(val).dup; 222 } 223 224 private ubyte[] toBytesImpl(T)(T val) 225 if (isSomeString!T) 226 { 227 import std.string : representation; 228 return val.representation.dup; 229 } 230 231 private ubyte[] toBytesImpl(T)(T val) 232 if (is(T == class) || is(T == struct)) 233 { 234 alias members = serialisableMembers!T; 235 ubyte[] bytes; 236 237 size_t index = 0; 238 if (isAnyNull(val)) 239 return nativeToBigEndian(cast(int) -1).dup; 240 else 241 bytes ~= nativeToBigEndian(cast(int) members.length); 242 243 foreach (mName; members) 244 { 245 auto m = __traits(getMember, val, mName); 246 alias MT = NoNullable!(typeof(m)); 247 248 249 // Element's OID 250 bytes ~= nativeToBigEndian(cast(int) oidForType!MT); 251 252 auto bs = toBytes(m); 253 254 // Null values have their length written as -1, nothing else is written 255 if (bs.isNull) 256 bytes ~= nativeToBigEndian(cast(int) -1); 257 else 258 { 259 // Element's length in bytes 260 bytes ~= nativeToBigEndian(bs.length.to!int); 261 262 // Actual element data 263 bytes ~= bs; 264 } 265 } 266 267 return bytes; 268 } 269 270 private ubyte[] toBytesImpl(SysTime val) 271 { 272 import core.time; 273 274 // stdTime is in hnsecs, psql wants microsecs 275 long diff = val.stdTime - SysTime(POSTGRES_EPOCH).stdTime; 276 return nativeToBigEndian(diff / 10).dup; 277 } 278 279 280 281 unittest 282 { 283 import std.string; 284 285 writeln(" * value"); 286 writeln("\t * opAssign"); 287 288 int a = 0xFFFF_FFFF; 289 Value v; 290 v.opAssign(a); 291 292 assert(v._size == 4); 293 assert(v._valueBytes == [255, 255, 255, 255]); 294 assert(v._type == Type.INT4); 295 296 int[][] b = [[1], [2]]; 297 auto pga = PGArray(b); 298 299 v.opAssign(b); 300 assert(v._size == pga.toBytes().length); 301 assert(v._valueBytes == pga.toBytes()); 302 303 string str = "some string, I don't even know."; 304 v.opAssign(str); 305 306 assert(v._valueBytes == str.representation); 307 assert(v.size == str.representation.length); 308 309 Value v2; 310 v.opAssign(v2); 311 assert(v2 == v); 312 313 import std.datetime; 314 SysTime t = Clock.currTime; 315 v2 = t; 316 317 assert(v2.as!SysTime == t); 318 319 Nullable!int ni; 320 assert(Value(ni).as!int.isNull); 321 ni = 5; 322 assert(Value(ni).as!int == ni); 323 } 324 325 @property int size() 326 { 327 return _size; 328 } 329 330 @property Oid type() 331 { 332 return _type; 333 } 334 335 @property const(ubyte)* valuePointer() 336 { 337 return _valueBytes.ptr; 338 } 339 340 T as(T)() 341 if (isInstanceOf!(Nullable, T)) 342 { 343 alias RT = ReturnType!(T.get); 344 return as!(Unqual!RT); 345 } 346 347 Nullable!T as(T)() 348 if (!isInstanceOf!(Nullable, T)) 349 { 350 alias RT = Unqual!T; 351 352 if (_isNull) 353 return Nullable!RT.init; 354 355 const(ubyte)[] data = _valueBytes[0 .. _size]; 356 return fromBytes!RT(data, _size); 357 } 358 359 unittest 360 { 361 import std.typecons : Typedef; 362 363 writeln("\t * as"); 364 365 Value v = "123"; 366 assert(v.as!string == "123"); 367 368 v = 123; 369 assert(v.as!int == 123); 370 371 v = [[1, 2], [3, 4]]; 372 assert(v.as!(int[][]) == [[1, 2],[3, 4]]); 373 374 alias MyInt = Typedef!int; 375 MyInt x = 2; 376 v = Value(x); 377 writefln("Val is %s", v); 378 assert(v.as!MyInt == x, v.as!(MyInt).to!string ~ " and " ~ x.to!string ~ " are not equal"); 379 } 380 } 381 382 template typeOid(T) 383 { 384 alias TU = std.typecons.Unqual!T; 385 static if (isArray!T && !isSomeString!T) 386 { 387 alias BT = BaseType!T; 388 static if (is(BT == int)) 389 enum typeOid = Type.INT4ARRAY; 390 else static if (is(BT == long)) 391 enum typeOid = Type.INT8ARRAY; 392 else static if (is(BT == short)) 393 enum typeOid = Type.INT2ARRAY; 394 else static if (is(BT == float)) 395 enum typeOid = Type.FLOAT4ARRAY; 396 else static if (is(BT == byte) || is (BT == ubyte)) 397 enum typeOid = Type.BYTEA; 398 else 399 static assert(false, "Cannot map array type " ~ T.stringof ~ " to Oid"); 400 } 401 else 402 { 403 static if (is(TU == int)) 404 enum typeOid = Type.INT4; 405 else static if (is(TU == long)) 406 enum typeOid = Type.INT8; 407 else static if (is(TU == bool)) 408 enum typeOid = Type.BOOL; 409 else static if (is(TU == byte)) 410 enum typeOid = Type.CHAR; 411 else static if (is(TU == char)) 412 enum typeOid = Type.CHAR; 413 else static if (isSomeString!TU) 414 enum typeOid = Type.TEXT; 415 else static if (is(TU == short)) 416 enum typeOid = Type.INT2; 417 else static if (is(TU == float)) 418 enum typeOid = Type.FLOAT4; 419 else static if (is(TU == double)) 420 enum typeOid = Type.FLOAT8; 421 else static if (is(TU == SysTime)) 422 enum typeOid = Type.TIMESTAMP; 423 424 /** 425 Since unsigned types are not supported by PostgreSQL, we use signed 426 types for them. Transfer and representation in D will still work correctly, 427 but SELECTing them in the psql console, or as a string might result in 428 a negative number. 429 430 It is recommended not to use unsigned types in structures, that will 431 be used in the DB directly. 432 */ 433 else static if (is(TU == ulong)) 434 enum typeOid = Type.INT8; 435 else static if (is(TU == uint)) 436 enum typeOid = Type.INT4; 437 else static if (is(TU == ushort) || is(TU == char)) 438 enum typeOid = Type.INT2; 439 else static if (is(TU == ubyte)) 440 enum typeOid = Type.CHAR; 441 else 442 // Try to infer 443 enum typeOid = Type.INFER; 444 } 445 } 446 447 // TODO: this for arrays 448 Type oidForType(T)() 449 if (!isArray!T) 450 { 451 enum oid = typeOid!T; 452 453 static if (oid == Type.INFER) 454 { 455 Oid* p; 456 if ((p = relationName!T in _dpqCustomOIDs) != null) 457 return cast(Type) *p; 458 } 459 460 return oid; 461 } 462 463 unittest 464 { 465 writeln("\t * typeOid"); 466 467 static assert(typeOid!int == Type.INT4, "int"); 468 static assert(typeOid!string == Type.TEXT, "string"); 469 static assert(typeOid!(int[]) == Type.INT4ARRAY, "int[]"); 470 static assert(typeOid!(int[][]) == Type.INT4ARRAY, "int[][]"); 471 static assert(typeOid!(ubyte[]) == Type.BYTEA, "ubyte[]"); 472 } 473 474 Oid[] paramTypes(Value[] values) 475 { 476 return array(values.map!(v => v.type)); 477 } 478 479 int[] paramLengths(Value[] values) 480 { 481 return array(values.map!(v => v.size)); 482 } 483 484 int[] paramFormats(Value[] values) 485 { 486 return array(values.map!(v => 1)); 487 } 488 489 const(ubyte)*[] paramValues(Value[] values) 490 { 491 return array(values.map!(v => v.valuePointer)); 492 }