1 // Written in D programming language 2 /** 3 * PostgreSQL common types binary format. 4 * 5 * Copyright: © 2014 DSoftOut 6 * License: Subject to the terms of the MIT license, as written in the included LICENSE file. 7 * Authors: NCrashed <ncrashed@gmail.com> 8 */ 9 module pgator.db.pq.types.plain; 10 11 import pgator.db.pq.types.oids; 12 import vibe.data.json; 13 import std.array; 14 import std.bitmanip; 15 import std.format; 16 import std.conv; 17 18 alias uint RegProc; 19 alias uint Oid; 20 alias uint Xid; 21 alias uint Cid; 22 23 struct PQTid 24 { 25 uint blockId, blockNumber; 26 27 string toString() const 28 { 29 return text("'(",blockId, ",", blockNumber, ")'"); 30 } 31 } 32 33 bool convert(PQType type)(ubyte[] val) 34 if(type == PQType.Bool) 35 { 36 assert(val.length == 1); 37 return val[0] != 0; 38 } 39 40 /** 41 * Converts byte array into hex escaped SQL byte array. 42 */ 43 string escapeBytea(const ubyte[] arr) 44 { 45 auto builder = appender!string; 46 foreach(b; arr) 47 formattedWrite(builder, "%02X", b); 48 49 return `E'\\x`~builder.data~"'"; 50 } 51 52 ubyte[] convert(PQType type)(ubyte[] val) 53 if(type == PQType.ByteArray) 54 { 55 return val.dup; 56 } 57 58 char convert(PQType type)(ubyte[] val) 59 if(type == PQType.Char) 60 { 61 assert(val.length == 1, text("Expected 1 bytes, but got ", val.length)); 62 return val.read!char; 63 } 64 65 string convert(PQType type)(ubyte[] val) 66 if(type == PQType.Name) 67 { 68 assert(val.length == 63, text("Expected 63 bytes for name, but there are ", val.length, " bytes!")); 69 return cast(string)val.idup; 70 } 71 72 long convert(PQType type)(ubyte[] val) 73 if(type == PQType.Int8) 74 { 75 assert(val.length == 8, text("Expected 8 bytes, but got ", val.length)); 76 return val.read!long; 77 } 78 79 short convert(PQType type)(ubyte[] val) 80 if(type == PQType.Int2) 81 { 82 assert(val.length == 2, text("Expected 2 bytes, but got ", val.length)); 83 return val.read!short; 84 } 85 86 int convert(PQType type)(ubyte[] val) 87 if(type == PQType.Int4) 88 { 89 assert(val.length == 4, text("Expected 4 bytes, but got ", val.length)); 90 return val.read!int; 91 } 92 93 RegProc convert(PQType type)(ubyte[] val) 94 if(type == PQType.RegProc) 95 { 96 assert(val.length == 4, text("Expected 4 bytes, but got ", val.length)); 97 return val.read!RegProc; 98 } 99 100 string convert(PQType type)(ubyte[] val) 101 if(type == PQType.Text || type == PQType.FixedString || type == PQType.VariableString) 102 { 103 return cast(string)val.idup; 104 } 105 106 Oid convert(PQType type)(ubyte[] val) 107 if(type == PQType.Oid) 108 { 109 assert(val.length == Oid.sizeof); 110 return val.read!Oid; 111 } 112 113 PQTid convert(PQType type)(ubyte[] val) 114 if(type == PQType.Tid) 115 { 116 assert(val.length == 8, text("Expected 8 bytes, but got ", val.length)); 117 PQTid res; 118 res.blockId = val.read!uint; 119 res.blockNumber = val.read!uint; 120 return res; 121 } 122 123 Xid convert(PQType type)(ubyte[] val) 124 if(type == PQType.Xid) 125 { 126 assert(val.length == 4, text("Expected 4 bytes, but got ", val.length)); 127 return val.read!uint; 128 } 129 130 Cid convert(PQType type)(ubyte[] val) 131 if(type == PQType.Cid) 132 { 133 assert(val.length == 4, text("Expected 4 bytes, but got ", val.length)); 134 return val.read!uint; 135 } 136 137 Json convert(PQType type)(ubyte[] val) 138 if(type == PQType.Json) 139 { 140 return parseJsonString(cast(string)val.idup); 141 } 142 143 string convert(PQType type)(ubyte[] val) 144 if(type == PQType.Xml) 145 { 146 return cast(string)val.idup; 147 } 148 149 string convert(PQType type)(ubyte[] val) 150 if(type == PQType.NodeTree) 151 { 152 assert(val.length > 0); 153 return cast(string)val.idup; 154 } 155 156 float convert(PQType type)(ubyte[] val) 157 if(type == PQType.Float4) 158 { 159 assert(val.length == 4); 160 return val.read!float; 161 } 162 163 float convert(PQType type)(ubyte[] val) 164 if(type == PQType.Float8) 165 { 166 assert(val.length == 8); 167 return val.read!double; 168 } 169 170 string convert(PQType type)(ubyte[] val) 171 if(type == PQType.Unknown) 172 { 173 return convert!(PQType.Text)(val); 174 } 175 176 long convert(PQType type)(ubyte[] val) 177 if(type == PQType.Money) 178 { 179 assert(val.length == 8); 180 return val.read!long; 181 } 182 183 string convert(PQType type)(ubyte[] val) 184 if(type == PQType.Void) 185 { 186 return ""; 187 } 188 189 version(IntegrationTest2) 190 { 191 import pgator.db.pq.types.test; 192 import pgator.db.pool; 193 import std.random; 194 import std.algorithm; 195 import std.encoding; 196 import std.math; 197 import vibe.data.bson; 198 import dlogg.log; 199 import dlogg.buffered; 200 201 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 202 if(type == PQType.Bool) 203 { 204 logger.logInfo("Testing Bool..."); 205 testValue!bool(logger, pool, true, "boolean"); 206 testValue!bool(logger, pool, false, "boolean"); 207 } 208 209 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 210 if(type == PQType.ByteArray) 211 { 212 ubyte[] genRand(size_t n) 213 { 214 auto builder = appender!(ubyte[]); 215 foreach(i; 0..n) 216 builder.put(cast(ubyte)uniform(0u, 255u)); 217 return builder.data; 218 } 219 220 logger.logInfo("Testing ByteArray..."); 221 foreach(i; 0..100) 222 testValue!(ubyte[], escapeBytea)(logger, pool, genRand(i), "bytea"); 223 224 } 225 226 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 227 if(type == PQType.Char) 228 { 229 logger.logInfo("Testing Char..."); 230 alias testValue!(string, to!string, (str) { 231 str = str.strip('\''); 232 return str == `\` ? `\0` : str;}) test; 233 234 test(logger, pool, `'\0'`, `"char"`); 235 test(logger, pool, `''''`, `"char"`); 236 foreach(char c; char.min .. char.max) 237 if((['\0', '\'']).find(c).empty && isValid(`'`~c~`'`)) 238 test(logger, pool, `'`~c~`'`, `"char"`); 239 240 } 241 242 string genRandString(size_t n) 243 { 244 auto builder = appender!string; 245 immutable aphs = "1234567890qwertyuiopasdfghjklzxcvbnm"; 246 foreach(i; 0..n) 247 builder.put(aphs[uniform(0, aphs.length)]); 248 return builder.data; 249 } 250 251 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 252 if(type == PQType.Name) 253 { 254 logger.logInfo("Testing Name..."); 255 256 foreach(i; 0..100) 257 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(63)~`'`, "name"); 258 } 259 260 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 261 if(type == PQType.Text) 262 { 263 logger.logInfo("Testing Text..."); 264 265 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `''`, "text"); 266 foreach(i; 0..100) 267 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "text"); 268 } 269 270 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 271 if(type == PQType.VariableString) 272 { 273 logger.logInfo("Testing varchar[n]..."); 274 275 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `''`, "varchar"); 276 foreach(i; 0..100) 277 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "varchar(50)"); 278 } 279 280 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 281 if(type == PQType.FixedString) 282 { 283 logger.logInfo("Testing char[n]..."); 284 285 foreach(i; 0..100) 286 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "char(50)"); 287 } 288 289 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 290 if(type == PQType.CString) 291 { 292 logger.logInfo("Testing cstring..."); 293 294 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `''`, "cstring"); 295 foreach(i; 0..100) 296 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'`~genRandString(50)~`'`, "cstring"); 297 } 298 299 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 300 if(type == PQType.Unknown) 301 { 302 logger.logInfo("Testing Unknown..."); 303 304 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'Unknown'`, "unknown"); 305 } 306 307 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 308 if(type == PQType.Int8) 309 { 310 logger.logInfo("Testing Int8..."); 311 foreach(i; 0..100) 312 testValue!long(logger, pool, uniform(long.min, long.max), "Int8"); 313 } 314 315 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 316 if(type == PQType.Money) 317 { 318 logger.logInfo("Testing Money..."); 319 foreach(i; 0..100) 320 testValue!long(logger, pool, uniform(long.min, long.max), "Int8"); 321 } 322 323 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 324 if(type == PQType.Int4) 325 { 326 logger.logInfo("Testing Int4..."); 327 foreach(i; 0..100) 328 testValue!int(logger, pool, uniform(int.min, int.max), "Int4"); 329 } 330 331 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 332 if(type == PQType.Int2) 333 { 334 logger.logInfo("Testing Int2..."); 335 foreach(i; 0..100) 336 testValue!int(logger, pool, uniform(short.min, short.max), "Int2"); 337 } 338 339 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 340 if(type == PQType.Oid) 341 { 342 logger.logInfo("Testing Oid..."); 343 foreach(i; 0..100) 344 testValue!Oid(logger, pool, uniform(Oid.min, Oid.max), "Oid"); 345 } 346 347 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 348 if(type == PQType.RegProc) 349 { 350 logger.logInfo("Testing RegProc..."); 351 foreach(i; 0..100) 352 testValue!RegProc(logger, pool, uniform(RegProc.min, RegProc.max), "regproc"); 353 } 354 355 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 356 if(type == PQType.Tid) 357 { 358 logger.logInfo("Testing Tid..."); 359 foreach(i; 0..100) 360 { 361 auto testTid = PQTid(uniform(uint.min, uint.max), uniform(uint.min, uint.max)); 362 testValue!PQTid(logger, pool, testTid, "tid"); 363 } 364 } 365 366 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 367 if(type == PQType.Xid) 368 { 369 logger.logInfo("Testing Xid..."); 370 foreach(i; 0..100) 371 { 372 testValue!(Xid, (v) => "'"~v.to!string~"'")(logger, pool, uniform(Xid.min, Xid.max), "xid"); 373 } 374 } 375 376 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 377 if(type == PQType.Cid) 378 { 379 logger.logInfo("Testing Cid..."); 380 foreach(i; 0..100) 381 { // postgres trims large cid values 382 testValue!(Cid, (v) => "'"~v.to!string~"'")(logger, pool, uniform(Cid.min/4, Cid.max/4), "cid"); 383 } 384 } 385 386 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 387 if(type == PQType.Json) 388 { 389 logger.logInfo("Testing Json..."); 390 391 auto json = Json.emptyObject; 392 json.str = genRandString(10); 393 json.arr = serializeToJson([4,8,15,16,23,42]); 394 json.boolean = uniform(0,1) != 0; 395 //json.floating = cast(double)42.0; hard to compare properly 396 json.integer = cast(long)42; 397 json.nullable = null; 398 json.mapping = ["1":Json(4), "2":Json(8), "3":Json(15)]; 399 400 testValue!(Json, (v) => "'"~v.to!string~"'")(logger, pool, json, "json"); 401 } 402 403 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 404 if(type == PQType.Xml) 405 { 406 logger.logInfo("Testing Xml..."); 407 408 testValue!(string, to!string, (str) => str.strip('\''))(logger, pool, `'‹?xml version= "1.0"›'`, "xml"); 409 } 410 411 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 412 if(type == PQType.NodeTree) 413 { 414 logger.logInfo("Testing NodeTree..."); 415 logger.logInfo("Not testable"); 416 } 417 418 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 419 if(type == PQType.Float4) 420 { 421 logger.logInfo("Testing Float4..."); 422 string convFloat(float t) 423 { 424 if(t == float.infinity) return "'Infinity'"; 425 else if(t == -float.infinity) return "'-Infinity'"; 426 else if(isNaN(t)) return "'NaN'"; 427 else return t.to!string; 428 } 429 testValue!(float, convFloat)(logger, pool, float.infinity, "Float4"); 430 testValue!(float, convFloat)(logger, pool, -float.infinity, "Float4"); 431 testValue!(float, convFloat)(logger, pool, -float.nan, "Float4"); 432 433 foreach(i; 0..100) 434 testValue!(float, convFloat)(logger, pool, uniform(-100.0, 100.0), "Float4"); 435 } 436 437 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 438 if(type == PQType.Float8) 439 { 440 logger.logInfo("Testing Float8..."); 441 string convFloat(double t) 442 { 443 if(t == double.infinity) return "'Infinity'"; 444 else if(t == -double.infinity) return "'-Infinity'"; 445 else if(isNaN(t)) return "'NaN'"; 446 else return t.to!string; 447 } 448 testValue!(double, convFloat)(logger, pool, double.infinity, "Float8"); 449 testValue!(double, convFloat)(logger, pool, -double.infinity, "Float8"); 450 testValue!(double, convFloat)(logger, pool, -double.nan, "Float8"); 451 452 foreach(i; 0..100) 453 testValue!(double, convFloat)(logger, pool, uniform(-100.0, 100.0), "Float8"); 454 } 455 456 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 457 if(type == PQType.Void) 458 { 459 logger.logInfo("Testing Void..."); 460 461 pool.execTransaction(["drop function if exists pgator_test_void();"]); 462 pool.execTransaction(["CREATE FUNCTION pgator_test_void() RETURNS void AS $$ DECLARE BEGIN END; $$ LANGUAGE plpgsql;"]); 463 464 auto blogger = new shared BufferedLogger(logger); 465 auto res = queryValue(blogger, pool, "pgator_test_void()").deserializeBson!string; 466 assert(res == ""); 467 468 pool.execTransaction(["drop function if exists pgator_test_void();"]); 469 } 470 }