1 module dpq.value; 2 3 import dpq.result; 4 import dpq.exception; 5 import dpq.pgarray; 6 import dpq.meta; 7 8 import derelict.pq.pq; 9 10 import std.algorithm : map; 11 import std.array; 12 import std.conv : to; 13 import std.typecons : Nullable; 14 import std.bitmanip; 15 import std.traits; 16 import std.datetime : SysTime, DateTime; 17 18 version(unittest) import std.stdio; 19 20 enum POSTGRES_EPOCH = DateTime(2000, 1, 1); 21 22 package enum Type : Oid 23 { 24 INFER = 0, 25 BOOL = 16, 26 BYTEA = 17, 27 CHAR = 18, 28 NAME = 19, 29 INT8 = 20, 30 INT2 = 21, 31 INT2VECTOR = 22, 32 INT4 = 23, 33 REGPROC = 24, 34 TEXT = 25, 35 OID = 26, 36 TID = 27, 37 XID = 28, 38 CID = 29, 39 OIDVECTOR = 30, 40 JSON = 114, 41 XML = 142, 42 PGNODETREE = 194, 43 POINT = 600, 44 LSEG = 601, 45 PATH = 602, 46 BOX = 603, 47 POLYGON = 604, 48 LINE = 628, 49 FLOAT4 = 700, 50 FLOAT8 = 701, 51 ABSTIME = 702, 52 RELTIME = 703, 53 TINTERVAL = 704, 54 UNKNOWN = 705, 55 CIRCLE = 718, 56 CASH = 790, 57 MACADDR = 829, 58 INET = 869, 59 CIDR = 650, 60 INT2ARRAY = 1005, 61 INT4ARRAY = 1007, 62 TEXTARRAY = 1009, 63 OIDARRAY = 1028, 64 FLOAT4ARRAY = 1021, 65 ACLITEM = 1033, 66 CSTRINGARRAY = 1263, 67 BPCHAR = 1042, 68 VARCHAR = 1043, 69 DATE = 1082, 70 TIME = 1083, 71 TIMESTAMP = 1114, 72 TIMESTAMPTZ = 1184, 73 INTERVAL = 1186, 74 TIMETZ = 1266, 75 BIT = 1560, 76 VARBIT = 1562, 77 NUMERIC = 1700, 78 REFCURSOR = 1790, 79 REGPROCEDURE = 2202, 80 REGOPER = 2203, 81 REGOPERATOR = 2204, 82 REGCLASS = 2205, 83 REGTYPE = 2206, 84 REGTYPEARRAY = 2211, 85 UUID = 2950, 86 LSN = 3220, 87 TSVECTOR = 3614, 88 GTSVECTOR = 3642, 89 TSQUERY = 3615, 90 REGCONFIG = 3734, 91 REGDICTIONARY = 3769, 92 JSONB = 3802, 93 INT4RANGE = 3904, 94 RECORD = 2249, 95 RECORDARRAY = 2287, 96 CSTRING = 2275, 97 ANY = 2276, 98 ANYARRAY = 2277, 99 VOID = 2278, 100 TRIGGER = 2279, 101 EVTTRIGGER = 3838, 102 LANGUAGE_HANDLER = 2280, 103 INTERNAL = 2281, 104 OPAQUE = 2282, 105 ANYELEMENT = 2283, 106 ANYNONARRAY = 2776, 107 ANYENUM = 3500, 108 FDW_HANDLER = 3115, 109 ANYRANGE = 3831, 110 } 111 112 struct Value 113 { 114 private 115 { 116 ubyte[] _valueBytes; 117 int _size; 118 Type _type; 119 bool _isNull; 120 } 121 122 this(typeof(null) n) 123 { 124 _isNull = true; 125 } 126 127 this(T)(T val) 128 { 129 opAssign(val); 130 } 131 132 this(T)(T* val, int len, Type type = Type.INFER) 133 { 134 _size = len; 135 _type = type; 136 137 _valueBytes = val[0 .. len].dup; 138 } 139 140 this(Value val) 141 { 142 opAssign(val); 143 } 144 145 void opAssign(T)(T val) 146 if (isArray!T) 147 { 148 _size = (ForeachType!T.sizeof * val.length).to!int; 149 150 static if (is(T == ubyte[])) 151 _valueBytes = val; 152 else 153 { 154 _valueBytes = PGArray(val).toBytes(); 155 _size = _valueBytes.length.to!int; 156 } 157 158 _type = typeOid!T; 159 } 160 161 void opAssign(T)(T val) 162 if(!isArray!T) 163 { 164 _size = val.sizeof; 165 166 //_valueBytes = new ubyte[_size]; 167 //write(_valueBytes, val, 0); 168 _valueBytes = nativeToBigEndian(val).dup; 169 170 _type = typeOid!T; 171 } 172 173 void opAssign(string val) 174 { 175 import std.string; 176 177 _valueBytes = val.representation.dup; 178 _size = _valueBytes.length.to!int; 179 _type = Type.TEXT; 180 } 181 182 void opAssign(SysTime val) 183 { 184 import core.time; 185 186 _type = typeOid!SysTime; 187 // stdTime is in hnsecs, psql wants microsecs 188 long diff = val.stdTime - SysTime(POSTGRES_EPOCH).stdTime; 189 _valueBytes = nativeToBigEndian(diff / 10).dup; 190 _size = typeof(val.stdTime).sizeof; 191 } 192 193 void opAssign(Value val) 194 { 195 _valueBytes = val._valueBytes; 196 _size = val._size; 197 _type = val._type; 198 } 199 200 unittest 201 { 202 import std.string; 203 204 writeln(" * value"); 205 writeln("\t * opAssign"); 206 207 int a = 0xFFFF_FFFF; 208 Value v; 209 v.opAssign(a); 210 211 assert(v._size == 4); 212 assert(v._valueBytes == [255, 255, 255, 255]); 213 assert(v._type == Type.INT4); 214 215 int[][] b = [[1], [2]]; 216 auto pga = PGArray(b); 217 218 v.opAssign(b); 219 assert(v._size == pga.toBytes().length); 220 assert(v._valueBytes == pga.toBytes()); 221 222 string str = "some string, I don't even know."; 223 v.opAssign(str); 224 225 assert(v._valueBytes == str.representation); 226 assert(v.size == str.representation.length); 227 228 Value v2; 229 v.opAssign(v2); 230 assert(v2 == v); 231 232 233 import std.datetime; 234 SysTime t = Clock.currTime; 235 v2 = t; 236 237 assert(v2.as!SysTime == t); 238 } 239 240 @property int size() 241 { 242 return _size; 243 } 244 245 @property Oid type() 246 { 247 return _type; 248 } 249 250 @property const(ubyte)* valuePointer() 251 { 252 return _valueBytes.ptr; 253 } 254 255 Nullable!T as(T)() 256 { 257 if (_isNull) 258 return Nullable!T.init; 259 260 const(ubyte)[] data = _valueBytes[0 .. _size]; 261 return fromBytes!T(data, _size); 262 } 263 264 unittest 265 { 266 writeln("\t * as"); 267 268 Value v = "123"; 269 assert(v.as!string == "123"); 270 271 v = 123; 272 assert(v.as!int == 123); 273 274 v = [[1, 2], [3, 4]]; 275 assert(v.as!(int[][]) == [[1, 2],[3, 4]]); 276 } 277 } 278 279 template typeOid(T) 280 { 281 alias TU = std.typecons.Unqual!T; 282 static if (isArray!T && !isSomeString!T) 283 { 284 alias BT = BaseType!T; 285 static if (is(BT == int)) 286 enum typeOid = Type.INT4ARRAY; 287 else static if (is(BT == short)) 288 enum typeOid = Type.INT2ARRAY; 289 else static if (is(BT == float)) 290 enum typeOid = Type.FLOAT4ARRAY; 291 else static if (is(BT == byte) || is (BT == ubyte)) 292 enum typeOid = Type.BYTEA; 293 else 294 static assert(false, "Cannot map array type " ~ T.stringof ~ " to Oid"); 295 } 296 else 297 { 298 static if (is(TU == int)) 299 enum typeOid = Type.INT4; 300 else static if (is(TU == long)) 301 enum typeOid = Type.INT8; 302 else static if (is(TU == bool)) 303 enum typeOid = Type.BOOL; 304 else static if (is(TU == byte)) 305 enum typeOid = Type.CHAR; 306 else static if (is(TU == char)) 307 enum typeOid = Type.CHAR; 308 else static if (isSomeString!TU) 309 enum typeOid = Type.TEXT; 310 else static if (is(TU == short)) 311 enum typeOid = Type.INT2; 312 else static if (is(TU == float)) 313 enum typeOid = Type.FLOAT4; 314 else static if (is(TU == double)) 315 enum typeOid = Type.FLOAT8; 316 else static if (is(TU == SysTime)) 317 enum typeOid = Type.TIMESTAMP; 318 319 /** 320 Since unsigned types are not supported by PostgreSQL, we use signed 321 types for them. Transfer and representation in D will still work correctly, 322 but SELECTing them in the psql console, or as a string might result in 323 a negative number. 324 325 It is recommended not to use unsigned types in structures, that will 326 be used in the DB directly. 327 */ 328 else static if (is(TU == ulong)) 329 enum typeOid = Type.INT8; 330 else static if (is(TU == uint)) 331 enum typeOid = Type.INT4; 332 else static if (is(TU == ushort) || is(TU == char)) 333 enum typeOid = Type.INT2; 334 else static if (is(TU == ubyte)) 335 enum typeOid = Type.CHAR; 336 else 337 // Try to infer 338 enum typeOid = Type.INFER; 339 } 340 } 341 342 unittest 343 { 344 writeln("\t * typeOid"); 345 346 static assert(typeOid!int == Type.INT4, "int"); 347 static assert(typeOid!string == Type.TEXT, "string"); 348 static assert(typeOid!(int[]) == Type.INT4ARRAY, "int[]"); 349 static assert(typeOid!(int[][]) == Type.INT4ARRAY, "int[][]"); 350 static assert(typeOid!(ubyte[]) == Type.BYTEA, "ubyte[]"); 351 } 352 353 Oid[] paramTypes(Value[] values) 354 { 355 return array(values.map!(v => v.type)); 356 } 357 358 int[] paramLengths(Value[] values) 359 { 360 return array(values.map!(v => v.size)); 361 } 362 363 int[] paramFormats(Value[] values) 364 { 365 return array(values.map!(v => 1)); 366 } 367 368 const(ubyte)*[] paramValues(Value[] values) 369 { 370 return array(values.map!(v => v.valuePointer)); 371 }