1 /// 2 module dpq2.conv.from_d_types; 3 4 @safe: 5 6 public import dpq2.conv.arrays : isArrayType, toValue, isStaticArrayString; 7 public import dpq2.conv.geometric : isGeometricType, toValue; 8 import dpq2.conv.time : POSTGRES_EPOCH_DATE, TimeStamp, TimeStampUTC; 9 import dpq2.oids : detectOidTypeFromNative, oidConvTo, OidType; 10 import dpq2.value : Value, ValueFormat; 11 12 import std.bitmanip: nativeToBigEndian, BitArray, append; 13 import std.datetime.date: Date, DateTime, TimeOfDay; 14 import std.datetime.systime: SysTime; 15 import std.datetime.timezone: LocalTime, TimeZone, UTC; 16 import std.traits: isImplicitlyConvertible, isNumeric, isInstanceOf, OriginalType, Unqual, isSomeString; 17 import std.typecons : Nullable; 18 import std.uuid: UUID; 19 import vibe.data.json: Json; 20 import money: currency; 21 22 /// Converts Nullable!T to Value 23 Value toValue(T)(T v) 24 if (is(T == Nullable!R, R) && !(isArrayType!(typeof(v.get))) && !isGeometricType!(typeof(v.get))) 25 { 26 if (v.isNull) 27 return Value(ValueFormat.BINARY, detectOidTypeFromNative!T); 28 else 29 return toValue(v.get); 30 } 31 32 /// ditto 33 Value toValue(T)(T v) 34 if (is(T == Nullable!R, R) && (isArrayType!(typeof(v.get)))) 35 { 36 import dpq2.conv.arrays : arrToValue = toValue; // deprecation import workaround 37 import std.range : ElementType; 38 39 if (v.isNull) 40 return Value(ValueFormat.BINARY, detectOidTypeFromNative!(ElementType!(typeof(v.get))).oidConvTo!"array"); 41 else 42 return arrToValue(v.get); 43 } 44 45 /// ditto 46 Value toValue(T)(T v) 47 if (is(T == Nullable!R, R) && isGeometricType!(typeof(v.get))) 48 { 49 import dpq2.conv.geometric : geoToValue = toValue; // deprecation import workaround 50 51 if (v.isNull) 52 return Value(ValueFormat.BINARY, detectOidTypeFromNative!T); 53 else 54 return geoToValue(v.get); 55 } 56 57 /// 58 Value toValue(T)(T v) 59 if(isNumeric!(T)) 60 { 61 return Value(v.nativeToBigEndian.dup, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 62 } 63 64 /// Convert money.currency to PG value 65 /// 66 /// Caution: here is no check of fractional precision while conversion! 67 /// See also: PostgreSQL's "lc_monetary" description and "money" package description 68 Value toValue(T)(T v) 69 if(isInstanceOf!(currency, T) && T.amount.sizeof == 8) 70 { 71 return Value(v.amount.nativeToBigEndian.dup, OidType.Money, false, ValueFormat.BINARY); 72 } 73 74 unittest 75 { 76 import dpq2.conv.to_d_types: PGTestMoney; 77 78 const pgtm = PGTestMoney(-123.45); 79 80 Value v = pgtm.toValue; 81 82 assert(v.oidType == OidType.Money); 83 assert(v.as!PGTestMoney == pgtm); 84 } 85 86 /// Convert std.bitmanip.BitArray to PG value 87 Value toValue(T)(T v) @trusted 88 if(is(Unqual!T == BitArray)) 89 { 90 import std.array : appender; 91 import core.bitop : bitswap; 92 93 size_t len = v.length / 8 + (v.length % 8 ? 1 : 0); 94 auto data = cast(size_t[])v; 95 auto buffer = appender!(const ubyte[])(); 96 buffer.append!uint(cast(uint)v.length); 97 foreach (d; data[0 .. v.dim]) 98 { 99 // DMD Issue 19693 100 version(DigitalMars) 101 auto ntb = nativeToBigEndian(softBitswap(d)); 102 else 103 auto ntb = nativeToBigEndian(bitswap(d)); 104 foreach (b; ntb[0 .. len]) 105 { 106 buffer.append!ubyte(b); 107 } 108 109 } 110 return Value(buffer.data.dup, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 111 } 112 113 /// Reverses the order of bits - needed because of dmd Issue 19693 114 /// https://issues.dlang.org/show_bug.cgi?id=19693 115 package N softBitswap(N)(N x) pure 116 if (is(N == uint) || is(N == ulong)) 117 { 118 import core.bitop : bswap; 119 // swap 1-bit pairs: 120 enum mask1 = cast(N) 0x5555_5555_5555_5555L; 121 x = ((x >> 1) & mask1) | ((x & mask1) << 1); 122 // swap 2-bit pairs: 123 enum mask2 = cast(N) 0x3333_3333_3333_3333L; 124 x = ((x >> 2) & mask2) | ((x & mask2) << 2); 125 // swap 4-bit pairs: 126 enum mask4 = cast(N) 0x0F0F_0F0F_0F0F_0F0FL; 127 x = ((x >> 4) & mask4) | ((x & mask4) << 4); 128 129 // reverse the order of all bytes: 130 x = bswap(x); 131 132 return x; 133 } 134 135 @trusted unittest 136 { 137 import std.bitmanip : BitArray; 138 139 auto varbit = BitArray([1,0,1,1,0]); 140 141 Value v = varbit.toValue; 142 143 assert(v.oidType == OidType.VariableBitString); 144 assert(v.as!BitArray == varbit); 145 146 // test softBitswap 147 assert (softBitswap!uint( 0x8000_0100 ) == 0x0080_0001); 148 foreach (i; 0 .. 32) 149 assert (softBitswap!uint(1 << i) == 1 << 32 - i - 1); 150 151 assert (softBitswap!ulong( 0b1000000000000000000000010000000000000000100000000000000000000001) 152 == 0b1000000000000000000000010000000000000000100000000000000000000001); 153 assert (softBitswap!ulong( 0b1110000000000000000000010000000000000000100000000000000000000001) 154 == 0b1000000000000000000000010000000000000000100000000000000000000111); 155 foreach (i; 0 .. 64) 156 assert (softBitswap!ulong(1UL << i) == 1UL << 64 - i - 1); 157 158 } 159 160 /** 161 Converts types implicitly convertible to string to PG Value. 162 Note that if string is null it is written as an empty string. 163 If NULL is a desired DB value, Nullable!string can be used instead. 164 */ 165 Value toValue(T)(T v, ValueFormat valueFormat = ValueFormat.BINARY) @trusted 166 if(isSomeString!T || isStaticArrayString!T) 167 { 168 static if(is(T == string)) 169 { 170 import std.string : representation; 171 172 static assert(isImplicitlyConvertible!(T, string)); 173 auto buf = (cast(string) v).representation; 174 175 if(valueFormat == ValueFormat.TEXT) buf ~= 0; // for prepareArgs only 176 177 return Value(buf, OidType.Text, false, valueFormat); 178 } 179 else 180 { 181 // convert to a string 182 import std.conv : to; 183 return toValue(v.to!string, valueFormat); 184 } 185 } 186 187 /// Constructs Value from array of bytes 188 Value toValue(T)(T v) 189 if(is(T : immutable(ubyte)[])) 190 { 191 return Value(v, detectOidTypeFromNative!(ubyte[]), false, ValueFormat.BINARY); 192 } 193 194 /// Constructs Value from boolean 195 Value toValue(T : bool)(T v) @trusted 196 if (!is(T == Nullable!R, R)) 197 { 198 immutable ubyte[] buf = [ v ? 1 : 0 ]; 199 200 return Value(buf, detectOidTypeFromNative!T, false, ValueFormat.BINARY); 201 } 202 203 /// Constructs Value from Date 204 Value toValue(T)(T v) 205 if (is(Unqual!T == Date)) 206 { 207 import std.conv: to; 208 import dpq2.value; 209 import dpq2.conv.time: POSTGRES_EPOCH_JDATE; 210 211 long mj_day = v.modJulianDay; 212 213 // max days isn't checked because Phobos Date days value always fits into Postgres Date 214 if (mj_day < -POSTGRES_EPOCH_JDATE) 215 throw new ValueConvException( 216 ConvExceptionType.DATE_VALUE_OVERFLOW, 217 "Date value doesn't fit into Postgres binary Date", 218 __FILE__, __LINE__ 219 ); 220 221 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 222 long days = mj_day - mj_pg_epoch; 223 224 return Value(nativeToBigEndian(days.to!int).dup, OidType.Date, false); 225 } 226 227 /// Constructs Value from TimeOfDay 228 Value toValue(T)(T v) 229 if (is(Unqual!T == TimeOfDay)) 230 { 231 long us = ((60L * v.hour + v.minute) * 60 + v.second) * 1_000_000; 232 233 return Value(nativeToBigEndian(us).dup, OidType.Time, false); 234 } 235 236 /// Constructs Value from TimeStamp or from TimeStampUTC 237 Value toValue(T)(T v) 238 if (is(Unqual!T == TimeStamp) || is(Unqual!T == TimeStampUTC)) 239 { 240 long us; /// microseconds 241 242 if(v.isLater) // infinity 243 us = us.max; 244 else if(v.isEarlier) // -infinity 245 us = us.min; 246 else 247 { 248 enum mj_pg_epoch = POSTGRES_EPOCH_DATE.modJulianDay; 249 long j = modJulianDayForIntYear(v.date.year, v.date.month, v.date.day) - mj_pg_epoch; 250 us = (((j * 24 + v.time.hour) * 60 + v.time.minute) * 60 + v.time.second) * 1_000_000 + v.fracSec.total!"usecs"; 251 } 252 253 return Value( 254 nativeToBigEndian(us).dup, 255 is(Unqual!T == TimeStamp) ? OidType.TimeStamp : OidType.TimeStampWithZone, 256 false 257 ); 258 } 259 260 private auto modJulianDayForIntYear(const int year, const ubyte month, const short day) pure 261 { 262 // Wikipedia magic: 263 264 const a = (14 - month) / 12; 265 const y = year + 4800 - a; 266 const m = month + a * 12 - 3; 267 268 const jd = day + (m*153+2)/5 + y*365 + y/4 - y/100 + y/400 - 32045; 269 270 return jd - 2_400_001; 271 } 272 unittest 273 { 274 assert(modJulianDayForIntYear(1858, 11, 17) == 0); 275 assert(modJulianDayForIntYear(2010, 8, 24) == 55_432); 276 assert(modJulianDayForIntYear(1999, 7, 6) == 51_365); 277 } 278 279 /++ 280 Constructs Value from DateTime 281 It uses Timestamp without TZ as a resulting PG type 282 +/ 283 Value toValue(T)(T v) 284 if (is(Unqual!T == DateTime)) 285 { 286 return TimeStamp(v).toValue; 287 } 288 289 /++ 290 Constructs Value from SysTime 291 Note that SysTime has a precision in hnsecs and PG TimeStamp in usecs. 292 It means that PG value will have 10 times lower precision. 293 And as both types are using long for internal storage it also means that PG TimeStamp can store greater range of values than SysTime. 294 +/ 295 Value toValue(T)(T v) 296 if (is(Unqual!T == SysTime)) 297 { 298 long us = (v - SysTime(POSTGRES_EPOCH_DATE, UTC())).total!"usecs"; 299 300 return Value(nativeToBigEndian(us).dup, OidType.TimeStampWithZone, false); 301 } 302 303 /// Constructs Value from UUID 304 Value toValue(T)(T v) 305 if (is(Unqual!T == UUID)) 306 { 307 return Value(v.data.dup, OidType.UUID); 308 } 309 310 /// Constructs Value from Json 311 Value toValue(T)(T v) 312 if (is(Unqual!T == Json)) 313 { 314 auto r = toValue(v.toString); 315 r.oidType = OidType.Json; 316 317 return r; 318 } 319 320 Value toRecordValue(Value[] elements) 321 { 322 import std.array : appender; 323 auto buffer = appender!(ubyte[])(); 324 buffer ~= nativeToBigEndian!int(cast(int)elements.length)[]; 325 foreach (element; elements) 326 { 327 buffer ~= nativeToBigEndian!int(element.oidType)[]; 328 if (element.isNull) { 329 buffer ~= nativeToBigEndian!int(-1)[]; 330 } else { 331 buffer ~= nativeToBigEndian!int(cast(int)element.data.length)[]; 332 buffer ~= element.data; 333 } 334 } 335 336 return Value(buffer.data.idup, OidType.Record); 337 } 338 339 version(unittest) 340 import dpq2.conv.to_d_types : as, deserializeRecord; 341 342 unittest 343 { 344 import std.stdio; 345 Value[] vals = [toValue(17.34), toValue(Nullable!long(17)), toValue(Nullable!long.init)]; 346 Value v = vals.toRecordValue; 347 assert(deserializeRecord(v) == vals); 348 } 349 350 unittest 351 { 352 Value v = toValue(cast(short) 123); 353 354 assert(v.oidType == OidType.Int2); 355 assert(v.as!short == 123); 356 } 357 358 unittest 359 { 360 Value v = toValue(-123.456); 361 362 assert(v.oidType == OidType.Float8); 363 assert(v.as!double == -123.456); 364 } 365 366 unittest 367 { 368 Value v = toValue("Test string"); 369 370 assert(v.oidType == OidType.Text); 371 assert(v.as!string == "Test string"); 372 } 373 374 // string Null values 375 @system unittest 376 { 377 { 378 import core.exception: AssertError; 379 import std.exception: assertThrown; 380 381 auto v = Nullable!string.init.toValue; 382 assert(v.oidType == OidType.Text); 383 assert(v.isNull); 384 385 assertThrown!AssertError(v.as!string); 386 assert(v.as!(Nullable!string).isNull); 387 } 388 389 { 390 string s; 391 auto v = s.toValue; 392 assert(v.oidType == OidType.Text); 393 assert(!v.isNull); 394 } 395 } 396 397 unittest 398 { 399 immutable ubyte[] buf = [0, 1, 2, 3, 4, 5]; 400 Value v = toValue(buf); 401 402 assert(v.oidType == OidType.ByteArray); 403 assert(v.as!(const ubyte[]) == buf); 404 } 405 406 unittest 407 { 408 Value t = toValue(true); 409 Value f = toValue(false); 410 411 assert(t.as!bool == true); 412 assert(f.as!bool == false); 413 } 414 415 unittest 416 { 417 Value v = toValue(Nullable!long(1)); 418 Value nv = toValue(Nullable!bool.init); 419 420 assert(!v.isNull); 421 assert(v.oidType == OidType.Int8); 422 assert(v.as!long == 1); 423 424 assert(nv.isNull); 425 assert(nv.oidType == OidType.Bool); 426 } 427 428 unittest 429 { 430 import std.datetime : DateTime; 431 432 Value v = toValue(Nullable!TimeStamp(TimeStamp(DateTime(2017, 1, 2)))); 433 434 assert(!v.isNull); 435 assert(v.oidType == OidType.TimeStamp); 436 } 437 438 unittest 439 { 440 // Date: '2018-1-15' 441 auto d = Date(2018, 1, 15); 442 auto v = toValue(d); 443 444 assert(v.oidType == OidType.Date); 445 assert(v.as!Date == d); 446 } 447 448 unittest 449 { 450 auto d = immutable Date(2018, 1, 15); 451 auto v = toValue(d); 452 453 assert(v.oidType == OidType.Date); 454 assert(v.as!Date == d); 455 } 456 457 unittest 458 { 459 // Date: '2000-1-1' 460 auto d = Date(2000, 1, 1); 461 auto v = toValue(d); 462 463 assert(v.oidType == OidType.Date); 464 assert(v.as!Date == d); 465 } 466 467 unittest 468 { 469 // Date: '0010-2-20' 470 auto d = Date(10, 2, 20); 471 auto v = toValue(d); 472 473 assert(v.oidType == OidType.Date); 474 assert(v.as!Date == d); 475 } 476 477 unittest 478 { 479 // Date: max (always fits into Postgres Date) 480 auto d = Date.max; 481 auto v = toValue(d); 482 483 assert(v.oidType == OidType.Date); 484 assert(v.as!Date == d); 485 } 486 487 unittest 488 { 489 // Date: min (overflow) 490 import std.exception: assertThrown; 491 import dpq2.value: ValueConvException; 492 493 auto d = Date.min; 494 assertThrown!ValueConvException(d.toValue); 495 } 496 497 unittest 498 { 499 // DateTime 500 auto d = const DateTime(2018, 2, 20, 1, 2, 3); 501 auto v = toValue(d); 502 503 assert(v.oidType == OidType.TimeStamp); 504 assert(v.as!DateTime == d); 505 } 506 507 unittest 508 { 509 // Nullable!DateTime 510 import std.typecons : nullable; 511 auto d = nullable(DateTime(2018, 2, 20, 1, 2, 3)); 512 auto v = toValue(d); 513 514 assert(v.oidType == OidType.TimeStamp); 515 assert(v.as!(Nullable!DateTime) == d); 516 517 d.nullify(); 518 v = toValue(d); 519 assert(v.oidType == OidType.TimeStamp); 520 assert(v.as!(Nullable!DateTime).isNull); 521 } 522 523 unittest 524 { 525 // TimeOfDay: '14:29:17' 526 auto tod = TimeOfDay(14, 29, 17); 527 auto v = toValue(tod); 528 529 assert(v.oidType == OidType.Time); 530 assert(v.as!TimeOfDay == tod); 531 } 532 533 unittest 534 { 535 // SysTime: '2017-11-13T14:29:17.075678Z' 536 auto t = SysTime.fromISOExtString("2017-11-13T14:29:17.075678Z"); 537 auto v = toValue(t); 538 539 assert(v.oidType == OidType.TimeStampWithZone); 540 assert(v.as!SysTime == t); 541 } 542 543 unittest 544 { 545 import core.time : usecs; 546 import std.datetime.date : DateTime; 547 548 // TimeStamp: '2017-11-13 14:29:17.075678' 549 auto t = TimeStamp(DateTime(2017, 11, 13, 14, 29, 17), 75_678.usecs); 550 auto v = toValue(t); 551 552 assert(v.oidType == OidType.TimeStamp); 553 assert(v.as!TimeStamp == t); 554 } 555 556 unittest 557 { 558 auto j = Json(["foo":Json("bar")]); 559 auto v = j.toValue; 560 561 assert(v.oidType == OidType.Json); 562 assert(v.as!Json == j); 563 564 auto nj = Nullable!Json(j); 565 auto nv = nj.toValue; 566 assert(nv.oidType == OidType.Json); 567 assert(!nv.as!(Nullable!Json).isNull); 568 assert(nv.as!(Nullable!Json).get == j); 569 } 570 571 unittest 572 { 573 import dpq2.conv.to_d_types : as; 574 char[2] arr; 575 auto v = arr.toValue(); 576 assert(v.oidType == OidType.Text); 577 assert(!v.isNull); 578 579 auto varr = v.as!string; 580 assert(varr.length == 2); 581 }