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 version(unittest) 321 import dpq2.conv.to_d_types : as; 322 323 unittest 324 { 325 Value v = toValue(cast(short) 123); 326 327 assert(v.oidType == OidType.Int2); 328 assert(v.as!short == 123); 329 } 330 331 unittest 332 { 333 Value v = toValue(-123.456); 334 335 assert(v.oidType == OidType.Float8); 336 assert(v.as!double == -123.456); 337 } 338 339 unittest 340 { 341 Value v = toValue("Test string"); 342 343 assert(v.oidType == OidType.Text); 344 assert(v.as!string == "Test string"); 345 } 346 347 // string Null values 348 @system unittest 349 { 350 { 351 import core.exception: AssertError; 352 import std.exception: assertThrown; 353 354 auto v = Nullable!string.init.toValue; 355 assert(v.oidType == OidType.Text); 356 assert(v.isNull); 357 358 assertThrown!AssertError(v.as!string); 359 assert(v.as!(Nullable!string).isNull); 360 } 361 362 { 363 string s; 364 auto v = s.toValue; 365 assert(v.oidType == OidType.Text); 366 assert(!v.isNull); 367 } 368 } 369 370 unittest 371 { 372 immutable ubyte[] buf = [0, 1, 2, 3, 4, 5]; 373 Value v = toValue(buf); 374 375 assert(v.oidType == OidType.ByteArray); 376 assert(v.as!(const ubyte[]) == buf); 377 } 378 379 unittest 380 { 381 Value t = toValue(true); 382 Value f = toValue(false); 383 384 assert(t.as!bool == true); 385 assert(f.as!bool == false); 386 } 387 388 unittest 389 { 390 Value v = toValue(Nullable!long(1)); 391 Value nv = toValue(Nullable!bool.init); 392 393 assert(!v.isNull); 394 assert(v.oidType == OidType.Int8); 395 assert(v.as!long == 1); 396 397 assert(nv.isNull); 398 assert(nv.oidType == OidType.Bool); 399 } 400 401 unittest 402 { 403 import std.datetime : DateTime; 404 405 Value v = toValue(Nullable!TimeStamp(TimeStamp(DateTime(2017, 1, 2)))); 406 407 assert(!v.isNull); 408 assert(v.oidType == OidType.TimeStamp); 409 } 410 411 unittest 412 { 413 // Date: '2018-1-15' 414 auto d = Date(2018, 1, 15); 415 auto v = toValue(d); 416 417 assert(v.oidType == OidType.Date); 418 assert(v.as!Date == d); 419 } 420 421 unittest 422 { 423 auto d = immutable Date(2018, 1, 15); 424 auto v = toValue(d); 425 426 assert(v.oidType == OidType.Date); 427 assert(v.as!Date == d); 428 } 429 430 unittest 431 { 432 // Date: '2000-1-1' 433 auto d = Date(2000, 1, 1); 434 auto v = toValue(d); 435 436 assert(v.oidType == OidType.Date); 437 assert(v.as!Date == d); 438 } 439 440 unittest 441 { 442 // Date: '0010-2-20' 443 auto d = Date(10, 2, 20); 444 auto v = toValue(d); 445 446 assert(v.oidType == OidType.Date); 447 assert(v.as!Date == d); 448 } 449 450 unittest 451 { 452 // Date: max (always fits into Postgres Date) 453 auto d = Date.max; 454 auto v = toValue(d); 455 456 assert(v.oidType == OidType.Date); 457 assert(v.as!Date == d); 458 } 459 460 unittest 461 { 462 // Date: min (overflow) 463 import std.exception: assertThrown; 464 import dpq2.value: ValueConvException; 465 466 auto d = Date.min; 467 assertThrown!ValueConvException(d.toValue); 468 } 469 470 unittest 471 { 472 // DateTime 473 auto d = const DateTime(2018, 2, 20, 1, 2, 3); 474 auto v = toValue(d); 475 476 assert(v.oidType == OidType.TimeStamp); 477 assert(v.as!DateTime == d); 478 } 479 480 unittest 481 { 482 // Nullable!DateTime 483 import std.typecons : nullable; 484 auto d = nullable(DateTime(2018, 2, 20, 1, 2, 3)); 485 auto v = toValue(d); 486 487 assert(v.oidType == OidType.TimeStamp); 488 assert(v.as!(Nullable!DateTime) == d); 489 490 d.nullify(); 491 v = toValue(d); 492 assert(v.oidType == OidType.TimeStamp); 493 assert(v.as!(Nullable!DateTime).isNull); 494 } 495 496 unittest 497 { 498 // TimeOfDay: '14:29:17' 499 auto tod = TimeOfDay(14, 29, 17); 500 auto v = toValue(tod); 501 502 assert(v.oidType == OidType.Time); 503 assert(v.as!TimeOfDay == tod); 504 } 505 506 unittest 507 { 508 // SysTime: '2017-11-13T14:29:17.075678Z' 509 auto t = SysTime.fromISOExtString("2017-11-13T14:29:17.075678Z"); 510 auto v = toValue(t); 511 512 assert(v.oidType == OidType.TimeStampWithZone); 513 assert(v.as!SysTime == t); 514 } 515 516 unittest 517 { 518 import core.time : usecs; 519 import std.datetime.date : DateTime; 520 521 // TimeStamp: '2017-11-13 14:29:17.075678' 522 auto t = TimeStamp(DateTime(2017, 11, 13, 14, 29, 17), 75_678.usecs); 523 auto v = toValue(t); 524 525 assert(v.oidType == OidType.TimeStamp); 526 assert(v.as!TimeStamp == t); 527 } 528 529 unittest 530 { 531 auto j = Json(["foo":Json("bar")]); 532 auto v = j.toValue; 533 534 assert(v.oidType == OidType.Json); 535 assert(v.as!Json == j); 536 537 auto nj = Nullable!Json(j); 538 auto nv = nj.toValue; 539 assert(nv.oidType == OidType.Json); 540 assert(!nv.as!(Nullable!Json).isNull); 541 assert(nv.as!(Nullable!Json).get == j); 542 } 543 544 unittest 545 { 546 import dpq2.conv.to_d_types : as; 547 char[2] arr; 548 auto v = arr.toValue(); 549 assert(v.oidType == OidType.Text); 550 assert(!v.isNull); 551 552 auto varr = v.as!string; 553 assert(varr.length == 2); 554 }