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