1 module database.postgresql.type; 2 3 import std.algorithm; 4 import std.array : appender; 5 import std.conv : parse, to; 6 import std.datetime; 7 import std.datetime.timezone; 8 import std.format: format, formattedWrite; 9 import std.traits; 10 import std.typecons; 11 12 import database.postgresql.protocol; 13 import database.postgresql.packet; 14 import database.postgresql.exception; 15 import database.postgresql.row; 16 17 struct IgnoreAttribute {} 18 struct OptionalAttribute {} 19 struct NameAttribute { const(char)[] name; } 20 struct UnCamelCaseAttribute {} 21 struct TableNameAttribute {const(char)[] name;} 22 23 @property TableNameAttribute tableName(const(char)[] name) 24 { 25 return TableNameAttribute(name); 26 } 27 28 @property IgnoreAttribute ignore() 29 { 30 return IgnoreAttribute(); 31 } 32 33 @property OptionalAttribute optional() 34 { 35 return OptionalAttribute(); 36 } 37 38 @property NameAttribute as(const(char)[] name) 39 { 40 return NameAttribute(name); 41 } 42 43 @property UnCamelCaseAttribute uncamel() 44 { 45 return UnCamelCaseAttribute(); 46 } 47 48 template isValueType(T) 49 { 50 static if (is(Unqual!T == struct) && !is(Unqual!T == PgSQLValue) && !is(Unqual!T == Date) && !is(Unqual!T == DateTime) && !is(Unqual!T == SysTime)) 51 { 52 enum isValueType = false; 53 } 54 else 55 { 56 enum isValueType = true; 57 } 58 } 59 60 template isWritableDataMember(T, string Member) 61 { 62 static if (is(TypeTuple!(__traits(getMember, T, Member)))) 63 { 64 enum isWritableDataMember = false; 65 } 66 else static if (!is(typeof(__traits(getMember, T, Member)))) 67 { 68 enum isWritableDataMember = false; 69 } 70 else static if (is(typeof(__traits(getMember, T, Member)) == void)) 71 { 72 enum isWritableDataMember = false; 73 } 74 else static if (is(typeof(__traits(getMember, T, Member)) == enum)) 75 { 76 enum isWritableDataMember = true; 77 } 78 else static if (hasUDA!(__traits(getMember, T, Member), IgnoreAttribute)) 79 { 80 enum isWritableDataMember = false; 81 } 82 else static if (isArray!(typeof(__traits(getMember, T, Member))) && !is(typeof(typeof(__traits(getMember, T, Member)).init[0]) == ubyte) && !is(typeof(__traits(getMember, T, Member)) == string)) 83 { 84 enum isWritableDataMember = false; 85 } 86 else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member)))) 87 { 88 enum isWritableDataMember = false; 89 } 90 else static if (isSomeFunction!(typeof(__traits(getMember, T, Member)))) 91 { 92 enum isWritableDataMember = false; 93 } 94 else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }()))) 95 { 96 enum isWritableDataMember = false; 97 } 98 else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export")) 99 { 100 enum isWritableDataMember = false; 101 } 102 else 103 { 104 enum isWritableDataMember = true; 105 } 106 } 107 108 template isReadableDataMember(T, string Member) 109 { 110 static if (is(TypeTuple!(__traits(getMember, T, Member)))) 111 { 112 enum isReadableDataMember = false; 113 } 114 else static if (!is(typeof(__traits(getMember, T, Member)))) 115 { 116 enum isReadableDataMember = false; 117 } 118 else static if (is(typeof(__traits(getMember, T, Member)) == void)) 119 { 120 enum isReadableDataMember = false; 121 } 122 else static if (is(typeof(__traits(getMember, T, Member)) == enum)) 123 { 124 enum isReadableDataMember = true; 125 } 126 else static if (hasUDA!(__traits(getMember, T, Member), IgnoreAttribute)) 127 { 128 enum isReadableDataMember = false; 129 } 130 else static if (isArray!(typeof(__traits(getMember, T, Member))) && !is(typeof(typeof(__traits(getMember, T, Member)).init[0]) == ubyte) && !is(typeof(__traits(getMember, T, Member)) == string)) 131 { 132 enum isReadableDataMember = false; 133 } 134 else static if (isAssociativeArray!(typeof(__traits(getMember, T, Member)))) 135 { 136 enum isReadableDataMember = false; 137 } 138 else static if (isSomeFunction!(typeof(__traits(getMember, T, Member))) /* && return type is valueType*/ ) 139 { 140 enum isReadableDataMember = true; 141 } 142 else static if (!is(typeof((){ T x = void; __traits(getMember, x, Member) = __traits(getMember, x, Member); }()))) 143 { 144 enum isReadableDataMember = false; 145 } 146 else static if ((__traits(getProtection, __traits(getMember, T, Member)) != "public") && (__traits(getProtection, __traits(getMember, T, Member)) != "export")) 147 { 148 enum isReadableDataMember = false; 149 } 150 else 151 { 152 enum isReadableDataMember = true; 153 } 154 } 155 156 struct PgSQLRawString 157 { 158 @disable this(); 159 160 this(const(char)[] data) 161 { 162 data_ = data; 163 } 164 165 @property auto length() const 166 { 167 return data_.length; 168 } 169 170 @property auto data() const 171 { 172 return data_; 173 } 174 175 private const(char)[] data_; 176 } 177 178 struct PgSQLFragment 179 { 180 @disable this(); 181 182 this(const(char)[] data) 183 { 184 data_ = data; 185 } 186 187 @property auto length() const 188 { 189 return data_.length; 190 } 191 192 @property auto data() const 193 { 194 return data_; 195 } 196 197 private const(char)[] data_; 198 } 199 200 struct PgSQLBinary 201 { 202 this(T)(T[] data) 203 { 204 data_ = (cast(ubyte*)data.ptr)[0..typeof(T[].init[0]).sizeof * data.length]; 205 } 206 207 @property auto length() const 208 { 209 return data_.length; 210 } 211 212 @property auto data() const 213 { 214 return data_; 215 } 216 217 private const(ubyte)[] data_; 218 } 219 220 struct PgSQLValue 221 { 222 package enum BufferSize = max(ulong.sizeof, (ulong[]).sizeof, SysTime.sizeof, DateTime.sizeof, Date.sizeof); 223 package this(const(char)[] name, PgColumnTypes type, void* ptr, size_t size) 224 { 225 assert(size <= BufferSize); 226 type_ = type; 227 if (type != PgColumnTypes.NULL) 228 buffer_[0..size] = (cast(ubyte*)ptr)[0..size]; 229 name_ = name; 230 } 231 232 this(T)(T) if (is(Unqual!T == typeof(null))) 233 { 234 type_ = PgColumnTypes.NULL; 235 } 236 237 this(T)(T value) if (is(Unqual!T == PgSQLValue)) 238 { 239 this = value; 240 } 241 242 this(T)(T value) if (std.traits.isFloatingPoint!T) 243 { 244 alias UT = Unqual!T; 245 246 static if (is(UT == float)) 247 { 248 type_ = PgColumnTypes.REAL; 249 buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof]; 250 } 251 else static if (is(UT == double)) 252 { 253 type_ = PgColumnTypes.DOUBLE; 254 buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof]; 255 } 256 else 257 { 258 type_ = PgColumnTypes.DOUBLE; 259 auto data = cast(double)value; 260 buffer_[0..typeof(data).sizeof] = (cast(ubyte*)&data)[0..typeof(data).sizeof]; 261 } 262 } 263 264 this(T)(T value) if (isIntegral!T || isBoolean!T) 265 { 266 alias UT = Unqual!T; 267 268 static if (is(UT == long) || is(UT == ulong)) 269 { 270 type_ = PgColumnTypes.INT8; 271 } 272 else static if (is(UT == int) || is(UT == uint) || is(UT == dchar)) 273 { 274 type_ = PgColumnTypes.INT4; 275 } 276 else static if (is(UT == short) || is(UT == ushort) || is(UT == wchar)) 277 { 278 type_ = PgColumnTypes.INT2; 279 } 280 else static if (is(UT == char) || is(UT == byte) || is(UT == ubyte)) 281 { 282 type_ = PgColumnTypes.CHAR; 283 } 284 else 285 { 286 type_ = PgColumnTypes.BOOLEAN; 287 } 288 289 buffer_[0..T.sizeof] = (cast(ubyte*)&value)[0..T.sizeof]; 290 } 291 292 this(T)(T value) if (is(Unqual!T == Date)) 293 { 294 type_ = ColumnTypes.DATE; 295 (*cast(PgSQLDate*)buffer_) = PgSQLDate(value); 296 } 297 298 this(T)(T value) if (is(Unqual!T == TimeOfDay)) 299 { 300 type_ = ColumnTypes.TIME; 301 (*cast(PgSQLTime*)buffer_) = PgSQLTime(value); 302 } 303 304 this(T)(T value) if (is(Unqual!T == DateTime)) 305 { 306 type_ = ColumnTypes.TIMESTAMP; 307 (*cast(PgSQLTimestamp*)buffer_) = PgSQLTimestamp(value); 308 } 309 310 this(T)(T value) if (is(Unqual!T == SysTime)) 311 { 312 type_ = ColumnTypes.TIMESTAMPTZ; 313 (*cast(PgSQLTimestamp*)buffer_) = PgSQLTimestamp(value); 314 } 315 316 this(T)(T value) if (isSomeString!(OriginalType!T)) 317 { 318 static assert(typeof(T.init[0]).sizeof == 1, fomat("Unsupported string type: %s", T.stringof)); 319 320 type_ = PgColumnTypes.VARCHAR; 321 322 auto slice = value[0..$]; 323 buffer_.ptr[0..typeof(slice).sizeof] = (cast(ubyte*)&slice)[0..typeof(slice).sizeof]; 324 } 325 326 this(T)(T value) if (is(Unqual!T == PgSQLBinary)) 327 { 328 type_ = PgColumnTypes.BYTEA; 329 buffer_.ptr[0..(ubyte[]).sizeof] = (cast(ubyte*)&value.data_)[0..(ubyte[]).sizeof]; 330 } 331 332 void toString(Appender)(ref Appender app) const 333 { 334 final switch(type_) with (PgColumnTypes) 335 { 336 case UNKNOWN: 337 case NULL: 338 break; 339 case BOOL: 340 app.put(*cast(bool*)buffer_.ptr ? "TRUE" : "FALSE"); 341 break; 342 case CHAR: 343 formattedWrite(&app, "%s", *cast(ubyte*)buffer_.ptr); 344 break; 345 case INT2: 346 formattedWrite(&app, "%d", *cast(short*)buffer_.ptr); 347 break; 348 case INT4: 349 formattedWrite(&app, "%d", *cast(int*)buffer_.ptr); 350 break; 351 case INT8: 352 formattedWrite(&app, "%d", *cast(long*)buffer_.ptr); 353 break; 354 case REAL: 355 formattedWrite(&app, "%g", *cast(float*)buffer_.ptr); 356 break; 357 case DOUBLE: 358 formattedWrite(&app, "%g", *cast(double*)buffer_.ptr); 359 break; 360 case POINT: 361 case LSEG: 362 case PATH: 363 case BOX: 364 case POLYGON: 365 case LINE: 366 case TINTERVAL: 367 case CIRCLE: 368 case JSONB: 369 case BYTEA: 370 formattedWrite(&app, "%s", *cast(ubyte[]*)buffer_.ptr); 371 break; 372 373 case MONEY: 374 case TEXT: 375 case NAME: 376 case BIT: 377 case VARBIT: 378 case NUMERIC: 379 case UUID: 380 case MACADDR: 381 case MACADDR8: 382 case INET: 383 case CIDR: 384 case JSON: 385 case XML: 386 case CHARA: 387 case VARCHAR: 388 formattedWrite(&app, "%s", *cast(const(char)[]*)buffer_.ptr); 389 break; 390 case DATE: 391 (*cast(PgSQLDate*)buffer_.ptr).toString(app); 392 break; 393 case TIMETZ: 394 case TIME: 395 (*cast(PgSQLTime*)buffer_.ptr).toString(app); 396 break; 397 case TIMESTAMP: 398 case TIMESTAMPTZ: 399 (*cast(PgSQLTimestamp*)buffer_.ptr).toString(app); 400 break; 401 case INTERVAL: 402 break; 403 } 404 } 405 406 string toString() const 407 { 408 auto app = appender!string; 409 toString(app); 410 return app.data; 411 } 412 413 bool opEquals(PgSQLValue other) const 414 { 415 if (isString && other.isString) 416 { 417 return peek!string == other.peek!string; 418 } 419 else if (isScalar == other.isScalar) 420 { 421 if (isFloatingPoint || other.isFloatingPoint) 422 return get!double == other.get!double; 423 return get!long == other.get!long; 424 } 425 else if (isTime == other.isTime) 426 { 427 // return get!Duration == other.get!Duration; 428 // } 429 // else if (isTimestamp == other.isTimestamp) 430 // { 431 // return get!SysTime == other.get!SysTime; 432 } 433 else if (isNull == other.isNull) 434 { 435 return true; 436 } 437 return false; 438 } 439 440 T get(T)(lazy T def) const 441 { 442 return !isNull ? get!T : def; 443 } 444 445 T get(T)() const if (isScalarType!T && !is(T == enum)) 446 { 447 switch(type_) with (PgColumnTypes) 448 { 449 case CHAR: 450 return cast(T)(*cast(char*)buffer_.ptr); 451 case INT2: 452 return cast(T)(*cast(short*)buffer_.ptr); 453 case INT4: 454 return cast(T)(*cast(int*)buffer_.ptr); 455 case INT8: 456 return cast(T)(*cast(long*)buffer_.ptr); 457 case REAL: 458 return cast(T)(*cast(float*)buffer_.ptr); 459 case DOUBLE: 460 return cast(T)(*cast(double*)buffer_.ptr); 461 default: 462 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof)); 463 } 464 } 465 466 T get(T)() const if (is(Unqual!T == SysTime)) 467 { 468 switch (type_) with (PgColumnTypes) 469 { 470 case TIMESTAMP: 471 case TIMESTAMPTZ: 472 return (*cast(PgSQLTimestamp*)buffer_.ptr).toSysTime; 473 default: 474 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof)); 475 } 476 } 477 478 T get(T)() const if (is(Unqual!T == DateTime)) 479 { 480 switch (type_) with (PgColumnTypes) 481 { 482 case TIMESTAMP: 483 case TIMESTAMPTZ: 484 return (*cast(PgSQLTimestamp*)buffer_.ptr).toDateTime; 485 default: 486 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof)); 487 } 488 } 489 490 T get(T)() const if (is(Unqual!T == TimeOfDay)) 491 { 492 switch (type_) with (PgColumnTypes) 493 { 494 case TIME: 495 case TIMETZ: 496 return (*cast(PgSQLTime*)buffer_.ptr).toTimeOfDay; 497 case TIMESTAMP: 498 case TIMESTAMPTZ: 499 return (*cast(PgSQLTimestamp*)buffer_.ptr).toTimeOfDay; 500 default: 501 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof)); 502 } 503 } 504 505 T get(T)() const if (is(Unqual!T == Date)) 506 { 507 switch (type_) with (PgColumnTypes) 508 { 509 case DATE: 510 return (*cast(PgSQLDate*)buffer_.ptr).toDate; 511 case TIMESTAMP: 512 case TIMESTAMPTZ: 513 return (*cast(PgSQLTimestamp*)buffer_.ptr).toDate; 514 default: 515 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to %s", name_, columnTypeName(type_), T.stringof)); 516 } 517 } 518 519 T get(T)() const if (is(Unqual!T == enum)) 520 { 521 return cast(T)get!(OriginalType!T); 522 } 523 524 T get(T)() const if (isArray!T && !is(T == enum)) 525 { 526 final switch(type_) with (PgColumnTypes) 527 { 528 case NUMERIC: 529 case MONEY: 530 case BIT: 531 case VARBIT: 532 case INET: 533 case CIDR: 534 case MACADDR: 535 case MACADDR8: 536 case UUID: 537 case JSON: 538 case XML: 539 case TEXT: 540 case NAME: 541 case VARCHAR: 542 case CHARA: 543 return (*cast(T*)buffer_.ptr).dup; 544 case UNKNOWN: 545 case NULL: 546 case BOOL: 547 case CHAR: 548 case INT2: 549 case INT4: 550 case INT8: 551 case REAL: 552 case DOUBLE: 553 case POINT: 554 case LSEG: 555 case PATH: 556 case BOX: 557 case POLYGON: 558 case LINE: 559 case TINTERVAL: 560 case CIRCLE: 561 case BYTEA: 562 case DATE: 563 case TIME: 564 case TIMETZ: 565 case TIMESTAMP: 566 case TIMESTAMPTZ: 567 case INTERVAL: 568 case JSONB: 569 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to array", name_, columnTypeName(type_))); 570 } 571 } 572 573 T peek(T)(lazy T def) const 574 { 575 return !isNull ? peek!(T) : def; 576 } 577 578 T peek(T)() const if (isScalarType!T) 579 { 580 return get!(T); 581 } 582 583 T peek(T)() const if (is(Unqual!T == SysTime) || is(Unqual!T == DateTime) || is(Unqual!T == Date) || is(Unqual!T == TimeOfDay)) 584 { 585 return get!(T); 586 } 587 588 T peek(T)() const if (isArray!T && !is(T == enum)) 589 { 590 final switch(type_) with (PgColumnTypes) 591 { 592 case NUMERIC: 593 case MONEY: 594 case BIT: 595 case VARBIT: 596 case INET: 597 case CIDR: 598 case MACADDR: 599 case MACADDR8: 600 case UUID: 601 case JSON: 602 case XML: 603 case TEXT: 604 case NAME: 605 case VARCHAR: 606 case CHARA: 607 return *cast(T*)buffer_.ptr; 608 case UNKNOWN: 609 case NULL: 610 case BOOL: 611 case CHAR: 612 case INT2: 613 case INT4: 614 case INT8: 615 case REAL: 616 case DOUBLE: 617 case POINT: 618 case LSEG: 619 case PATH: 620 case BOX: 621 case POLYGON: 622 case LINE: 623 case TINTERVAL: 624 case CIRCLE: 625 case BYTEA: 626 case DATE: 627 case TIME: 628 case TIMETZ: 629 case TIMESTAMP: 630 case TIMESTAMPTZ: 631 case INTERVAL: 632 case JSONB: 633 throw new PgSQLErrorException(format("Cannot convert '%s' from %s to array", name_, columnTypeName(type_))); 634 } 635 } 636 637 bool isNull() const 638 { 639 return type_ == PgColumnTypes.NULL; 640 } 641 642 bool isUnknown() const 643 { 644 return type_ == PgColumnTypes.UNKNOWN; 645 } 646 647 PgColumnTypes type() const 648 { 649 return type_; 650 } 651 652 bool isString() const 653 { 654 final switch(type_) with (PgColumnTypes) 655 { 656 case UNKNOWN: 657 case NULL: 658 case BOOL: 659 case CHAR: 660 case INT2: 661 case INT4: 662 case INT8: 663 case REAL: 664 case DOUBLE: 665 case POINT: 666 case LSEG: 667 case PATH: 668 case BOX: 669 case POLYGON: 670 case LINE: 671 case TINTERVAL: 672 case CIRCLE: 673 case BYTEA: 674 break; 675 case NUMERIC: 676 case MONEY: 677 case BIT: 678 case VARBIT: 679 case INET: 680 case CIDR: 681 case MACADDR: 682 case MACADDR8: 683 case UUID: 684 case JSON: 685 case XML: 686 case TEXT: 687 case NAME: 688 case VARCHAR: 689 case CHARA: 690 return true; 691 case DATE: 692 case TIME: 693 case TIMETZ: 694 case TIMESTAMP: 695 case TIMESTAMPTZ: 696 case INTERVAL: 697 case JSONB: 698 return false; 699 } 700 return false; 701 } 702 703 bool isScalar() const 704 { 705 final switch(type_) with (PgColumnTypes) 706 { 707 case UNKNOWN: 708 return false; 709 case NULL: 710 return false; 711 case BOOL: 712 case CHAR: 713 case INT2: 714 case INT4: 715 case INT8: 716 case REAL: 717 case DOUBLE: 718 return true; 719 case POINT: 720 case LSEG: 721 case PATH: 722 case BOX: 723 case POLYGON: 724 case LINE: 725 case MONEY: 726 case TINTERVAL: 727 case CIRCLE: 728 case MACADDR: 729 case INET: 730 case CIDR: 731 case JSON: 732 case XML: 733 case TEXT: 734 case NAME: 735 case MACADDR8: 736 case BYTEA: 737 case CHARA: 738 case VARCHAR: 739 case DATE: 740 case TIME: 741 case TIMETZ: 742 case TIMESTAMP: 743 case TIMESTAMPTZ: 744 case INTERVAL: 745 case BIT: 746 case VARBIT: 747 case NUMERIC: 748 case UUID: 749 case JSONB: 750 return false; 751 } 752 } 753 754 bool isFloatingPoint() const 755 { 756 return (type_ == PgColumnTypes.REAL) || (type_ == PgColumnTypes.DOUBLE); 757 } 758 759 bool isTime() const 760 { 761 return (type_ == PgColumnTypes.TIME) || (type_ == PgColumnTypes.TIMETZ); 762 } 763 764 bool isDate() const 765 { 766 return (type_ == PgColumnTypes.DATE); 767 } 768 769 bool isTimestamp() const 770 { 771 return (type_ == PgColumnTypes.TIMESTAMP) || (type_ == PgColumnTypes.TIMESTAMPTZ); 772 } 773 774 private: 775 776 PgColumnTypes type_ = PgColumnTypes.NULL; 777 ubyte[7] pad_; 778 ubyte[BufferSize] buffer_; 779 const(char)[] name_; 780 } 781 782 struct PgSQLColumn 783 { 784 ushort length; 785 FormatCode format; 786 PgColumnTypes type; 787 int modifier; 788 const(char)[] name; 789 } 790 791 alias PgSQLHeader = PgSQLColumn[]; 792 793 struct PgSQLDate 794 { 795 ushort year; 796 ubyte month; 797 ubyte day; 798 799 Date toDate() const 800 { 801 return Date(year, month, day); 802 } 803 804 void toString(W)(ref W writer) const 805 { 806 formattedWrite(writer, "%04d-%02d-%02d", year, month, day); 807 } 808 } 809 810 struct PgSQLTime 811 { 812 uint usec; 813 ubyte hour; 814 ubyte minute; 815 ubyte second; 816 byte hoffset; 817 byte moffset; 818 819 Duration toDuration() const 820 { 821 auto total = (cast(int)hour + cast(int)hoffset) * 3600_000_000L + 822 (cast(int)minute + cast(int)moffset) * 60_000_000L + 823 second * 1_000_000L + 824 usec; 825 return dur!"usecs"(total); 826 } 827 828 TimeOfDay toTimeOfDay() const 829 { 830 return TimeOfDay(cast(int)hour + cast(int)hoffset, cast(int)minute + cast(int)moffset, second); 831 } 832 833 void toString(W)(ref W writer) const 834 { 835 formattedWrite(writer, "%02d:%02d:%02d", hour, minute, second); 836 if (usec) 837 { 838 uint usecabv = usec; 839 if ((usecabv % 1000) == 0) 840 usecabv /= 1000; 841 if ((usecabv % 100) == 0) 842 usecabv /= 100; 843 if ((usecabv % 10) == 0) 844 usecabv /= 10; 845 formattedWrite(writer, ".%d", usecabv); 846 } 847 848 if (hoffset | moffset) 849 { 850 if ((hoffset < 0) || (moffset < 0)) 851 { 852 formattedWrite(writer, "-%02d", -cast(int)(this.hoffset)); 853 if (moffset) 854 formattedWrite(writer, ":%02d", -cast(int)(this.moffset)); 855 } 856 else 857 { 858 formattedWrite(writer, "+%02d", hoffset); 859 if (moffset) 860 formattedWrite(writer, ":%02d", moffset); 861 } 862 } 863 } 864 } 865 866 struct PgSQLTimestamp 867 { 868 PgSQLDate date; 869 PgSQLTime time; 870 871 SysTime toSysTime() const 872 { 873 if (time.hoffset | time.moffset) 874 { 875 const offset = time.hoffset.hours + time.moffset.minutes; 876 return SysTime(DateTime(date.year, date.month, date.day, time.hour, time.minute, time.second), time.usec.usecs, new immutable(SimpleTimeZone)(offset)); 877 } 878 else 879 { 880 return SysTime(DateTime(date.year, date.month, date.day, time.hour, time.minute, time.second), time.usec.usecs); 881 } 882 } 883 884 TimeOfDay toTimeOfDay() const 885 { 886 return time.toTimeOfDay(); 887 } 888 889 Date toDate() const 890 { 891 return date.toDate(); 892 } 893 894 DateTime toDateTime() const 895 { 896 return DateTime(date.toDate(), time.toTimeOfDay()); 897 } 898 899 void toString(W)(ref W writer) const 900 { 901 date.toString(writer); 902 writer.put(' '); 903 time.toString(writer); 904 } 905 } 906 907 private void skip(ref const(char)[] x, char ch) 908 { 909 if (x.length && (x.ptr[0] == ch)) 910 { 911 x = x[1..$]; 912 } 913 else 914 { 915 throw new PgSQLProtocolException("Bad datetime string format"); 916 } 917 } 918 919 private void skipFront(ref const(char)[] x) 920 { 921 if (x.length) 922 { 923 x = x[1..$]; 924 } 925 else 926 { 927 throw new PgSQLProtocolException("Bad datetime string format"); 928 } 929 } 930 931 auto parsePgSQLDate(ref const(char)[] x) 932 { 933 auto year = x.parse!ushort; 934 x.skip('-'); 935 auto month = x.parse!ubyte; 936 x.skip('-'); 937 auto day = x.parse!ubyte; 938 return PgSQLDate(year, month, day); 939 } 940 941 auto parsePgSQLTime(ref const(char)[] x) 942 { 943 auto hour = x.parse!ubyte; 944 x.skip(':'); 945 auto minute = x.parse!ubyte; 946 x.skip(':'); 947 auto second = x.parse!ubyte; 948 uint usecs; 949 950 if (x.length && (*x.ptr == '.')) 951 { 952 x.skipFront(); 953 954 const len = x.length; 955 const frac = x.parse!uint; 956 switch (len - x.length) 957 { 958 case 1: usecs = frac * 100_000; break; 959 case 2: usecs = frac * 10_000; break; 960 case 3: usecs = frac * 1_000; break; 961 case 4: usecs = frac * 100; break; 962 case 5: usecs = frac * 10; break; 963 case 6: break; 964 default: throw new PgSQLProtocolException("Bad datetime string format"); 965 } 966 } 967 968 byte hoffset; 969 byte moffset; 970 971 if (x.length) 972 { 973 auto sign = *x.ptr == '-' ? -1 : 1; 974 x.skipFront(); 975 976 hoffset = cast(byte)(sign * x.parse!ubyte); 977 if (x.length) 978 { 979 x.skip(':'); 980 moffset = cast(byte)(sign * x.parse!ubyte); 981 } 982 } 983 984 return PgSQLTime(usecs, hour, minute, second, hoffset, moffset); 985 } 986 987 auto parsePgSQLTimestamp(ref const(char)[] x) 988 { 989 auto date = parsePgSQLDate(x); 990 x.skipFront(); 991 auto time = parsePgSQLTime(x); 992 993 return PgSQLTimestamp(date, time); 994 } 995 996 void eatValueText(ref InputPacket packet, ref const PgSQLColumn column, ref PgSQLValue value) 997 { 998 auto length = packet.eat!uint; 999 if (length != uint.max) 1000 { 1001 auto svalue = packet.eat!(const(char)[])(length); 1002 1003 final switch(column.type) with (PgColumnTypes) 1004 { 1005 case UNKNOWN: 1006 case NULL: 1007 value = PgSQLValue(column.name, column.type, null, 0); 1008 break; 1009 case BOOL: 1010 auto x = *svalue.ptr == 't'; 1011 value = PgSQLValue(column.name, column.type, &x, 1); 1012 break; 1013 case CHAR: 1014 value = PgSQLValue(column.name, column.type, cast(void*)svalue.ptr, 1); 1015 break; 1016 case INT2: 1017 auto x = svalue.to!short; 1018 value = PgSQLValue(column.name, column.type, &x, 2); 1019 break; 1020 case INT4: 1021 auto x = svalue.to!int; 1022 value = PgSQLValue(column.name, column.type, &x, 4); 1023 break; 1024 case INT8: 1025 auto x = svalue.to!long; 1026 value = PgSQLValue(column.name, column.type, &x, 8); 1027 break; 1028 case REAL: 1029 auto x = svalue.to!float; 1030 value = PgSQLValue(column.name, column.type, &x, 4); 1031 break; 1032 case DOUBLE: 1033 auto x = svalue.to!double; 1034 value = PgSQLValue(column.name, column.type, &x, 8); 1035 break; 1036 case POINT: 1037 case LSEG: 1038 case PATH: 1039 case BOX: 1040 case POLYGON: 1041 case LINE: 1042 case TINTERVAL: 1043 case CIRCLE: 1044 break; 1045 1046 case NUMERIC: 1047 case MONEY: 1048 case BIT: 1049 case VARBIT: 1050 case INET: 1051 case CIDR: 1052 case MACADDR: 1053 case MACADDR8: 1054 case UUID: 1055 case JSON: 1056 case XML: 1057 case TEXT: 1058 case NAME: 1059 case BYTEA: 1060 case VARCHAR: 1061 case CHARA: 1062 value = PgSQLValue(column.name, column.type, &svalue, typeof(svalue).sizeof); 1063 break; 1064 1065 case DATE: 1066 auto x = parsePgSQLDate(svalue); 1067 value = PgSQLValue(column.name, column.type, &x, x.sizeof); 1068 break; 1069 case TIME: 1070 case TIMETZ: 1071 auto x = parsePgSQLTime(svalue); 1072 value = PgSQLValue(column.name, column.type, &x, x.sizeof); 1073 break; 1074 case TIMESTAMP: 1075 case TIMESTAMPTZ: 1076 auto x = parsePgSQLTimestamp(svalue); 1077 value = PgSQLValue(column.name, column.type, &x, x.sizeof); 1078 break; 1079 case INTERVAL: 1080 case JSONB: 1081 break; 1082 } 1083 } 1084 else 1085 { 1086 value = PgSQLValue(column.name, PgColumnTypes.NULL, null, 0); 1087 } 1088 }