1 // Written in D programming language 2 /** 3 * PostgreSQL typed arrays binary data 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.array; 10 11 import pgator.db.pq.types.oids; 12 import pgator.db.connection; 13 import derelict.pq.pq; 14 import std.array; 15 import std.bitmanip; 16 import std.conv; 17 import std.traits; 18 19 import std.datetime; 20 import pgator.db.pq.types.all; 21 22 private struct Vector(T) 23 { 24 int ndim; 25 int dataoffset; 26 Oid elemtype; 27 int dim1; 28 int lbound1; 29 T[] values; 30 } 31 32 private Vector!T readVec(T, PQType type)(ubyte[] arr, shared IConnection conn) 33 { 34 if(arr.length == 0) return Vector!T(); 35 36 assert(arr.length >= 2*int.sizeof + Oid.sizeof, text( 37 "Expected min array size ", 2*int.sizeof + Oid.sizeof, ", but got ", arr.length)); 38 Vector!T vec; 39 40 vec.ndim = arr.read!int; 41 vec.dataoffset = arr.read!int; 42 vec.elemtype = arr.read!Oid; 43 if(arr.length == 0) return vec; 44 vec.dim1 = arr.read!int; 45 vec.lbound1 = arr.read!int; 46 47 auto builder = appender!(T[]); 48 while(arr.length > 0) 49 { 50 auto maybeLength = arr.read!uint; 51 // if got [255, 255, 255, 255] - there is special case for NULL in array 52 if(maybeLength == uint.max) 53 { 54 static if(is(T == class) || is(T == interface)) 55 { 56 builder.put(null); 57 } 58 else 59 { 60 builder.put(T.init); 61 } 62 continue; 63 } 64 65 // can cast to array size 66 auto length = cast(size_t)maybeLength; 67 68 static if(__traits(compiles, pgator.db.pq.types.all.convert!type(arr))) 69 { 70 auto value = pgator.db.pq.types.all.convert!type(arr[0..length]); 71 builder.put(value); 72 arr = arr[length..$]; 73 } else static if(__traits(compiles, pgator.db.pq.types.all.convert!type(arr, conn))) 74 { 75 auto value = pgator.db.pq.types.all.convert!type(arr[0..length], conn); 76 builder.put(value); 77 arr = arr[length..$]; 78 } else 79 { 80 static assert(false, "There is no convert function for libpq type: "~type.to!text); 81 } 82 } 83 vec.values = builder.data; 84 return vec; 85 } 86 87 mixin ArraySupport!( 88 PQType.Int2Vector, short[], PQType.Int2, 89 PQType.Int2Array, short[], PQType.Int2, 90 PQType.Int4Array, int[], PQType.Int4, 91 PQType.OidVector, Oid[], PQType.Oid, 92 PQType.OidArray, Oid[], PQType.Oid, 93 PQType.TextArray, string[], PQType.Text, 94 PQType.CStringArray, string[], PQType.Text, 95 PQType.Float4Array, float[], PQType.Float4, 96 PQType.BoolArray, bool[], PQType.Bool, 97 PQType.ByteArrayArray, ubyte[][], PQType.ByteArray, 98 PQType.CharArray, char[], PQType.Char, 99 PQType.NameArray, string[], PQType.Name, 100 PQType.Int2VectorArray, short[][], PQType.Int2VectorArray, 101 PQType.XidArray, Xid[], PQType.Xid, 102 PQType.CidArray, Cid[], PQType.Cid, 103 PQType.OidVectorArray, Oid[][], PQType.OidVector, 104 PQType.FixedStringArray, string[], PQType.FixedString, 105 PQType.VariableStringArray, string[], PQType.VariableString, 106 PQType.Int8Array, long[], PQType.Int8, 107 PQType.PointArray, Point[], PQType.Point, 108 PQType.LineSegmentArray, LineSegment[], PQType.LineSegment, 109 PQType.PathArray, Path[], PQType.Path, 110 PQType.BoxArray, Box[], PQType.Box, 111 PQType.Float8Array, double[], PQType.Float8, 112 PQType.AbsTimeArray, PGAbsTime[], PQType.AbsTime, 113 PQType.RelTimeArray, PGRelTime[], PQType.RelTime, 114 PQType.IntervalArray, PGInterval[], PQType.Interval, 115 PQType.PolygonArray, Polygon[], PQType.Polygon, 116 PQType.MacAddressArray, PQMacAddress[], PQType.MacAddress, 117 PQType.HostAdressArray, PQInetAddress[], PQType.HostAddress, 118 PQType.NetworkAdressArray, PQInetAddress[], PQType.NetworkAddress, 119 PQType.TimeStampArray, PGTimeStamp[], PQType.TimeStamp, 120 PQType.DateArray, Date[], PQType.Date, 121 PQType.TimeArray, PGTime[], PQType.Time, 122 PQType.TimeStampWithZoneArray, PGTimeStampWithZone[], PQType.TimeStampWithZone, 123 PQType.TimeIntervalArray, TimeInterval[], PQType.TimeInterval, 124 PQType.NumericArray, PGNumeric[], PQType.Numeric, 125 PQType.TimeWithZoneArray, PGTimeWithZone[], PQType.TimeWithZone, 126 ); 127 128 /** 129 * Template magic lives here! Generates templates and functions to work with 130 * templates. Expects input argument as triples of PQType value, corresponding 131 * D type and corresponding element type of libpq type. 132 */ 133 private mixin template ArraySupport(PairsRaw...) 134 { 135 import std.range; 136 import std.traits; 137 138 /// To work with D tuples 139 private template Tuple(E...) 140 { 141 alias Tuple = E; 142 } 143 144 /// Custom tuple for a pair 145 private template ArrayTuple(TS...) 146 { 147 static assert(TS.length == 3); 148 enum id = TS[0]; 149 alias TS[1] type; 150 enum elementType = TS[2]; 151 } 152 153 /// Converts unstructured pairs in ArrayTuple tuple 154 private template ConvertPairs(TS...) 155 { 156 static assert(TS.length % 3 == 0, "ArraySupport expected even count of arguments!"); 157 static if(TS.length == 3) 158 { 159 alias Tuple!(ArrayTuple!(TS[0], TS[1], TS[2])) ConvertPairs; 160 } else 161 { 162 static assert(is(typeof(TS[0]) == PQType), "ArraySupport expected PQType value as first triple argument!"); 163 static assert(is(TS[1]), "ArraySupport expected a type as second triple argument!"); 164 static assert(is(typeof(TS[0]) == PQType), "ArraySupport expected PQType value as third triple argument!"); 165 166 alias Tuple!(ArrayTuple!(TS[0], TS[1], TS[2]), ConvertPairs!(TS[3..$])) ConvertPairs; 167 } 168 } 169 170 /// Structured pairs 171 alias ConvertPairs!PairsRaw Pairs; 172 173 /// Checks if PQType value is actually array type 174 template IsArrayType(TS...) 175 { 176 private template genCompareExpr(US...) 177 { 178 static if(US.length == 0) 179 { 180 enum genCompareExpr = ""; 181 } else static if(US.length == 1) 182 { 183 enum genCompareExpr = "T == PQType."~US[0].id.to!string; 184 } else 185 { 186 enum genCompareExpr = "T == PQType."~US[0].id.to!string~" || " 187 ~ genCompareExpr!(US[1..$]); 188 } 189 } 190 191 static assert(TS.length > 0, "IsArrayType expected argument count > 0!"); 192 alias TS[0] T; 193 enum IsArrayType = mixin(genCompareExpr!(Pairs)); 194 } 195 196 /// Returns oid of provided element type or generates error if it is not a libpq array 197 template ArrayElementType(TS...) 198 { 199 static assert(TS.length > 0, "ArrayElementType expected argument!"); 200 201 private enum T = TS[0]; 202 203 static assert(is(typeof(TS[0]) == PQType), "ArrayElementType expected PQType value as argument!"); 204 static assert(IsArrayType!(TS[0]), TS[0].to!string~" is not a libpq array!"); 205 206 template FindArrayTuple(TS...) 207 { 208 static if(TS.length == 0) 209 { 210 static assert("Cannot find "~T.to!string~" in array types!"); 211 } else 212 { 213 static if(TS[0] == T) 214 { 215 enum FindArrayTuple = TS[3]; 216 } else 217 { 218 enum FindArrayTuple = FindArrayTuple!(TS[1..$]); 219 } 220 } 221 } 222 223 enum ArrayElementType = FindArrayTuple!TS; 224 } 225 226 /// Generates set of converting functions from ubyte[] to types in triples 227 private template genConvertFunctions(TS...) 228 { 229 private template genConvertFunction(TS...) 230 { 231 alias TS[0] T; 232 233 enum genConvertFunction = T.type.stringof ~ " convert(PQType type)(ubyte[] val, shared IConnection conn)\n" 234 "\t if(type == PQType."~T.id.to!string~")\n{\n" 235 "\t return val.readVec!("~ForeachType!(T.type).stringof~", PQType."~T.elementType.to!string~")(conn).values;\n}"; 236 } 237 238 static if(TS.length == 0) 239 { 240 enum genConvertFunctions = ""; 241 } else 242 { 243 enum genConvertFunctions = genConvertFunction!(TS[0]) ~"\n"~genConvertFunctions!(TS[1..$]); 244 } 245 } 246 247 mixin(genConvertFunctions!Pairs); 248 } 249 250 version(IntegrationTest2) 251 { 252 import pgator.db.pq.types.test; 253 import pgator.db.pq.types.plain; 254 import pgator.db.pool; 255 import std.array; 256 version(LDC){} else import std.algorithm.iteration; 257 import std.random; 258 import std.math; 259 import std.traits; 260 import dlogg.log; 261 262 string convertArray(T, bool wrapQuotes = true)(T[] ts) 263 { 264 auto builder = appender!string; 265 foreach(i,t; ts) 266 { 267 enum needQuotes = (isSomeString!T || isSomeChar!T || is(T == Xid) || is(T == Cid)) && wrapQuotes; 268 static if(needQuotes) builder.put("'"); 269 static if(isFloatingPoint!T) 270 { 271 if(t == T.infinity) builder.put("'Infinity'"); 272 else if(t == -T.infinity) builder.put("'-Infinity'"); 273 else if(isNaN(t)) builder.put("'NaN'"); 274 else builder.put(t.to!string); 275 } else 276 { 277 builder.put(t.to!string); 278 } 279 static if(needQuotes) builder.put("'"); 280 if(i != ts.length-1) 281 builder.put(", "); 282 } 283 return "ARRAY["~builder.data~"]"; 284 } 285 286 struct Name {}; 287 288 U[] randArray(T, U=T)(size_t n) 289 { 290 auto builder = appender!(U[]); 291 foreach(i; 0..n) 292 { 293 static if(isSomeChar!T) 294 { 295 immutable alph = "1234567890asdfghjkklzxcvbnm,.?!@#$%^&*()+-|"; 296 builder.put(alph[uniform(0,alph.length)]); 297 } 298 else static if(is(T == string)) 299 { 300 immutable alph = "1234567890asdfghjkklzxcvbnm,.?!@#$%^&*()+-|"; 301 auto zbuilder = appender!string; 302 foreach(j; 0..n) 303 zbuilder.put(alph[uniform(0,alph.length)]); 304 builder.put(zbuilder.data); 305 } 306 else static if(is(T == Name)) 307 { 308 immutable alph = "1234567890asdfghjkklzxcvbnm,.?!@#$%^&*()+-|"; 309 auto zbuilder = appender!string; 310 foreach(j; 0..63) 311 zbuilder.put(alph[uniform(0,alph.length)]); 312 builder.put(zbuilder.data); 313 } 314 else static if(is(T == float)) 315 { 316 builder.put(uniform(-1000.0, 1000.0)); 317 } 318 else static if(is(T == bool)) 319 { 320 builder.put(uniform!"[]"(0,1) != 0); 321 } 322 else static if(isArray!T) 323 { 324 builder.put(randArray!(ElementType!T)(n)); 325 } 326 else static if(is(T == Cid)) 327 { 328 builder.put(uniform(Cid.min/4, Cid.max/4)); 329 } 330 else static if(isFloatingPoint!T) 331 { 332 builder.put(uniform(-T.max, T.max)); 333 } 334 else 335 { 336 builder.put(uniform(T.min, T.max)); 337 } 338 } 339 return builder.data; 340 } 341 342 void testArray(T, alias typeTrans = (n, name) => name)(shared ILogger logger, shared IConnectionPool pool, string tname, size_t bn = 0, size_t en = 100) 343 { 344 logger.logInfo("Testing "~tname~"..."); 345 foreach(i; bn..en) 346 { 347 static if(is(T == ubyte[])) 348 { 349 testValue!(T[], (a) => a.map!(a => escapeBytea(a)).array.convertArray!(string, false), id)(logger, pool, randArray!T(i), typeTrans(i, tname)); 350 } else static if(isSomeChar!T) 351 { 352 testValue!(string[], convertArray)(logger, pool, randArray!T(i).map!(a => [cast(char)a].idup).array, typeTrans(i, tname)); 353 } else static if(is(T == Name)) 354 { 355 testValue!(string[], convertArray)(logger, pool, randArray!(T, string)(i), typeTrans(i, tname)); 356 } else static if(isArray!T && !isSomeString!T) 357 { 358 testValue!(T[], (a) 359 { 360 auto builder = appender!(string[]); 361 foreach(b; a) 362 { 363 builder.put(b.convertArray); 364 } 365 return builder.data.convertArray!(string, false); 366 } 367 )(logger, pool, randArray!T(i), typeTrans(i, tname)); 368 } 369 else 370 { 371 testValue!(T[], convertArray)(logger, pool, randArray!T(i), typeTrans(i, tname)); 372 } 373 } 374 } 375 376 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 377 if(type == PQType.Int2Vector) 378 { 379 testArray!short(logger, pool, "int2vector"); 380 } 381 382 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 383 if(type == PQType.OidVector) 384 { 385 testArray!Oid(logger, pool, "oidvector"); 386 } 387 388 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 389 if(type == PQType.OidArray) 390 { 391 testArray!Oid(logger, pool, "oid[]"); 392 } 393 394 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 395 if(type == PQType.Int2Array) 396 { 397 testArray!short(logger, pool, "int2[]"); 398 } 399 400 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 401 if(type == PQType.Int4Array) 402 { 403 testArray!int(logger, pool, "int4[]"); 404 } 405 406 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 407 if(type == PQType.TextArray) 408 { 409 testArray!string(logger, pool, "text[]"); 410 } 411 412 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 413 if(type == PQType.CStringArray) 414 { 415 testArray!string(logger, pool, "cstring[]"); 416 } 417 418 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 419 if(type == PQType.BoolArray) 420 { 421 testArray!bool(logger, pool, "bool[]"); 422 } 423 424 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 425 if(type == PQType.ByteArrayArray) 426 { 427 testArray!(ubyte[])(logger, pool, "bytea[]"); 428 } 429 430 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 431 if(type == PQType.CharArray) 432 { 433 testArray!char(logger, pool, "char[]"); 434 } 435 436 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 437 if(type == PQType.NameArray) 438 { 439 testArray!Name(logger, pool, "name[]"); 440 } 441 442 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 443 if(type == PQType.Int2VectorArray) 444 { 445 logger.logInfo("Not testable"); 446 //testArray!(short[])(logger, pool, "int2vector[]"); 447 } 448 449 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 450 if(type == PQType.XidArray) 451 { 452 testArray!Xid(logger, pool, "xid[]"); 453 } 454 455 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 456 if(type == PQType.CidArray) 457 { 458 testArray!Cid(logger, pool, "cid[]"); 459 } 460 461 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 462 if(type == PQType.OidVectorArray) 463 { 464 logger.logInfo("Not testable"); 465 //testArray!(Oid[])(logger, pool, "oidvector[]"); 466 } 467 468 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 469 if(type == PQType.FixedStringArray) 470 { 471 testArray!(string, (n, name) => text("char(",n,")[]"))(logger, pool, "char(n)[]", 1, 100); 472 } 473 474 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 475 if(type == PQType.VariableStringArray) 476 { 477 testArray!(string, (n, name) => text("varchar(",n,")[]"))(logger, pool, "varchar(n)[]", 1, 100); 478 } 479 480 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 481 if(type == PQType.Int8Array) 482 { 483 testArray!long(logger, pool, "int8[]"); 484 } 485 486 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 487 if(type == PQType.PointArray) 488 { 489 //testArray!string(logger, pool, "point[]"); 490 } 491 492 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 493 if(type == PQType.LineSegmentArray) 494 { 495 //testArray!string(logger, pool, "lseg[]"); 496 } 497 498 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 499 if(type == PQType.PathArray) 500 { 501 //testArray!string(logger, pool, "path[]"); 502 } 503 504 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 505 if(type == PQType.BoxArray) 506 { 507 //testArray!string(logger, pool, "box[]"); 508 } 509 510 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 511 if(type == PQType.Float8Array) 512 { 513 testArray!double(logger, pool, "float8[]"); 514 } 515 516 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 517 if(type == PQType.AbsTimeArray) 518 { 519 //testArray!string(logger, pool, "abstime[]"); 520 } 521 522 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 523 if(type == PQType.RelTimeArray) 524 { 525 //testArray!string(logger, pool, "reltime[]"); 526 } 527 528 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 529 if(type == PQType.IntervalArray) 530 { 531 //testArray!string(logger, pool, "interval[]"); 532 } 533 534 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 535 if(type == PQType.PolygonArray) 536 { 537 //testArray!string(logger, pool, "polygon[]"); 538 } 539 540 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 541 if(type == PQType.MacAddressArray) 542 { 543 //testArray!string(logger, pool, "macaddress[]"); 544 } 545 546 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 547 if(type == PQType.HostAdressArray) 548 { 549 //testArray!string(logger, pool, "inet[]"); 550 } 551 552 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 553 if(type == PQType.NetworkAdressArray) 554 { 555 //testArray!string(logger, pool, "cidr[]"); 556 } 557 558 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 559 if(type == PQType.TimeStampArray) 560 { 561 //testArray!string(logger, pool, "timestamp[]"); 562 } 563 564 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 565 if(type == PQType.DateArray) 566 { 567 //testArray!string(logger, pool, "date[]"); 568 } 569 570 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 571 if(type == PQType.TimeArray) 572 { 573 //testArray!string(logger, pool, "time[]"); 574 } 575 576 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 577 if(type == PQType.TimeStampWithZoneArray) 578 { 579 //testArray!string(logger, pool, "timestamp with zone[]"); 580 } 581 582 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 583 if(type == PQType.TimeIntervalArray) 584 { 585 //testArray!string(logger, pool, "TimeInterval[]"); 586 } 587 588 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 589 if(type == PQType.NumericArray) 590 { 591 //testArray!string(logger, pool, "numeric[]"); 592 } 593 594 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 595 if(type == PQType.TimeWithZoneArray) 596 { 597 //testArray!string(logger, pool, "time with zone[]"); 598 } 599 600 601 void test(PQType type)(shared ILogger logger, shared IConnectionPool pool) 602 if(type == PQType.Float4Array) 603 { 604 testArray!float(logger, pool, "float4[]"); 605 testValue!(float[], convertArray)(logger, pool, [float.infinity], "float4[]"); 606 testValue!(float[], convertArray)(logger, pool, [-float.infinity], "float4[]"); 607 testValue!(float[], convertArray)(logger, pool, [float.nan], "float4[]"); 608 } 609 }