1 module dpq.connection; 2 3 //import derelict.pq.pq; 4 import libpq.libpq; 5 6 import dpq.exception; 7 import dpq.result; 8 import dpq.value; 9 import dpq.attributes; 10 import dpq.querybuilder; 11 import dpq.meta; 12 import dpq.prepared; 13 import dpq.smartptr; 14 15 import std.string; 16 //import derelict.pq.pq; 17 import libpq.libpq; 18 import std.conv : to; 19 import std.traits; 20 import std.typecons; 21 22 23 version(unittest) 24 { 25 import std.stdio; 26 Connection c; 27 } 28 29 /** 30 Represents the PostgreSQL connection and allows executing queries on it. 31 32 Examples: 33 ------------- 34 auto conn = Connection("host=localhost dbname=testdb user=testuser"); 35 //conn.exec ... 36 ------------- 37 */ 38 struct Connection 39 { 40 alias ConnectionPtr = SmartPointer!(PGconn*, PQfinish); 41 42 private ConnectionPtr _connection; 43 private PreparedStatement[string] _prepared; 44 45 /** 46 Connection constructor 47 48 Params: 49 connString = connection string 50 51 See Also: 52 http://www.postgresql.org/docs/9.3/static/libpq-connect.html#LIBPQ-CONNSTRING 53 */ 54 this(string connString) 55 { 56 char* err; 57 auto opts = PQconninfoParse(cast(char*)connString.toStringz, &err); 58 59 if (err != null) 60 throw new DPQException(err.fromStringz.to!string); 61 62 _connection = new ConnectionPtr(PQconnectdb(connString.toStringz)); 63 64 if (status != CONNECTION_OK) 65 throw new DPQException(errorMessage); 66 67 _dpqLastConnection = &this; 68 } 69 70 unittest 71 { 72 c = Connection("host=127.0.0.1 dbname=test user=test"); 73 writeln(" * Database connection with connection string"); 74 assert(c.status == CONNECTION_OK); 75 } 76 77 /** 78 Close the connection manually 79 */ 80 void close() 81 { 82 _connection.clear(); 83 } 84 85 @property const(ConnStatusType) status() 86 { 87 return PQstatus(_connection); 88 } 89 90 /** Returns the name of the database currently selected */ 91 @property const(string) db() 92 { 93 return PQdb(_connection).to!string; 94 } 95 96 /** Returns the name of the current user */ 97 @property const(string) user() 98 { 99 return PQuser(_connection).to!string; 100 } 101 102 /// ditto, but password 103 @property const(string) password() 104 { 105 return PQpass(_connection).to!string; 106 } 107 108 /// ditto, but host 109 @property const(string) host() 110 { 111 return PQhost(_connection).to!string; 112 } 113 114 /// ditto, but port 115 @property const(ushort) port() 116 { 117 return PQport(_connection).fromStringz.to!ushort; 118 } 119 120 /** // FIXME: BROKEN ATM 121 Executes the given string directly 122 123 Throws on fatal query errors like bad syntax 124 125 Examples: 126 ---------------- 127 Connection conn; // An established connection 128 129 conn.exec("CREATE TABLE IF NOT EXISTS test_table"); 130 ---------------- 131 */ 132 Result exec(string command) 133 { 134 PGresult* res = PQexec(_connection, cast(const char*)command.toStringz); 135 return Result(res); 136 } 137 138 /* 139 unittest 140 { 141 auto res = c.exec("SELECT 1::INT4 AS int4, 2::INT8 AS some_long"); 142 writeln(" * exec for selecting INT4 and INT8"); 143 assert(res.rows == 1); 144 assert(res.columns == 2); 145 146 auto r = res[0]; 147 assert(r[0].as!int == 1); 148 assert(r[0].as!long == 2); 149 150 writeln(" * Row opIndex(int) and opIndex(string) equality "); 151 assert(r[0] == r["int4"]); 152 assert(r[1] == r["some_long"]); 153 154 } 155 */ 156 157 /// ditto, async 158 bool send(string command) 159 { 160 return PQsendQuery(_connection, cast(const char*)command.toStringz) == 1; 161 } 162 163 /** 164 Executes the given string with given params 165 166 Params should be given as $1, $2, ... $n in the actual command. 167 All params are sent in a binary format and should not be escaped. 168 If a param's type cannot be inferred, this method will throw an exception, 169 in this case, either specify the type using the :: (cast) notation or 170 make sure the type can be inferred by PostgreSQL in your query. 171 172 Examples: 173 ---------------- 174 Connection conn; // An established connection 175 176 conn.execParams("SELECT $1::string, $2::int, $3::double"); 177 ---------------- 178 179 See also: 180 http://www.postgresql.org/docs/9.3/static/libpq-exec.html 181 */ 182 Result execParams(T...)(string command, T params) 183 { 184 Value[] values; 185 foreach(param; params) 186 values ~= Value(param); 187 188 return execParams(command, values); 189 } 190 191 192 void sendParams(T...)(string command, T params) 193 { 194 Value[] values; 195 foreach(param; params) 196 values ~= Value(param); 197 198 execParams(command, values, true); 199 } 200 201 unittest 202 { 203 auto res = c.execParams("SELECT 1::INT4 AS int4, 2::INT8 AS some_long", []); 204 writeln("\t * execParams"); 205 writeln("\t\t * Rows and cols"); 206 207 assert(res.rows == 1); 208 assert(res.columns == 2); 209 210 writeln("\t\t * Static values"); 211 auto r = res[0]; 212 assert(r[0].as!int == 1); 213 assert(r[1].as!long == 2); 214 215 writeln("\t\t * opIndex(int) and opIndex(string) equality"); 216 assert(r[0] == r["int4"]); 217 assert(r[1] == r["some_long"]); 218 219 int int4 = 1; 220 long int8 = 2; 221 string str = "foo bar baz"; 222 float float4 = 3.14; 223 double float8 = 3.1415; 224 225 writeln("\t\t * Passed values"); 226 res = c.execParams( 227 "SELECT $1::INT4, $2::INT8, $3::TEXT, $4::FLOAT4, $5::FLOAT8", 228 int4, 229 int8, 230 str, 231 float4, 232 float8); 233 234 assert(res.rows == 1); 235 r = res[0]; 236 237 assert(r[0].as!int == int4); 238 assert(r[1].as!long == int8); 239 assert(r[2].as!string == str); 240 assert(r[3].as!float == float4); 241 assert(r[4].as!double == float8); 242 } 243 244 /// ditto, but taking an array of params, instead of variadic template 245 Result execParams(string command, Value[] params, bool async = false) 246 { 247 const char* cStr = cast(const char*) command.toStringz; 248 249 auto pTypes = params.paramTypes; 250 auto pValues = params.paramValues; 251 auto pLengths = params.paramLengths; 252 auto pFormats = params.paramFormats; 253 254 if (async) 255 { 256 PQsendQueryParams( 257 _connection, 258 cStr, 259 params.length.to!int, 260 pTypes.ptr, 261 cast(const(char*)*)pValues.ptr, 262 pLengths.ptr, 263 pFormats.ptr, 264 1); 265 266 return Result(null); 267 } 268 else 269 return Result(PQexecParams( 270 _connection, 271 cStr, 272 params.length.to!int, 273 pTypes.ptr, 274 cast(const(char*)*)pValues.ptr, 275 pLengths.ptr, 276 pFormats.ptr, 277 1)); 278 } 279 280 /// ditto, async 281 void sendParams(string command, Value[] params) 282 { 283 execParams(command, params, true); 284 } 285 286 /** 287 Returns the last error message 288 289 Examples: 290 -------------------- 291 Connection conn; // An established connection 292 293 writeln(conn.errorMessage); 294 -------------------- 295 296 */ 297 @property string errorMessage() 298 { 299 return PQerrorMessage(_connection).to!string; 300 } 301 302 unittest 303 { 304 writeln("\t * errorMessage"); 305 try 306 { 307 c.execParams("SELECT_BADSYNTAX $1::INT4", 1); 308 } 309 catch {} 310 311 assert(c.errorMessage.length != 0); 312 313 } 314 315 /** 316 Generates and runs the DDL from the given structures 317 318 Attributes from dpq.attributes should be used to define 319 primary keys, indexes, and relationships. 320 321 A custom type can be specified with the @type attribute. 322 323 Examples: 324 ----------------------- 325 Connection conn; // An established connection 326 struct User 327 { 328 @serial8 @PKey long id; 329 string username; 330 byte[] passwordHash; 331 }; 332 333 struct Article { ... }; 334 335 conn.ensureSchema!(User, Article); 336 ----------------------- 337 */ 338 void ensureSchema(T...)(bool createType = false) 339 { 340 import std.stdio; 341 string[] additional; 342 string[] relations; 343 344 foreach (type; T) 345 { 346 enum name = relationName!(type); 347 relations ~= name; 348 349 string str; 350 if (createType) 351 str = "CREATE TYPE \"" ~ name ~ "\" AS (%s)"; 352 else 353 str = "CREATE TABLE IF NOT EXISTS \"" ~ name ~ "\" (%s)"; 354 355 string cols; 356 foreach(m; serialisableMembers!type) 357 { 358 string colName = attributeName!(mixin("type." ~ m)); 359 cols ~= "\"" ~ colName ~ "\""; 360 361 // HACK: typeof a @property seems to be failing hard 362 static if (is(FunctionTypeOf!(mixin("type." ~ m)) == function)) 363 alias t = typeof(mixin("type()." ~ m)); 364 else 365 alias t = typeof(mixin("type." ~ m)); 366 367 cols ~= " "; 368 369 // Basic data types 370 static if (hasUDA!(mixin("type." ~ m), PGTypeAttribute)) 371 cols ~= getUDAs!(mixin("type." ~ m), PGTypeAttribute)[0].type; 372 else 373 { 374 alias tu = Unqual!t; 375 static if (ShouldRecurse!(mixin("type." ~ m))) 376 { 377 ensureSchema!(NoNullable!tu)(true); 378 cols ~= '"' ~ relationName!(NoNullable!tu) ~ '"'; 379 } 380 else 381 cols ~= SQLType!tu; 382 } 383 384 // Primary key 385 static if (hasUDA!(mixin("type." ~ m), PrimaryKeyAttribute)) 386 { 387 if (!createType) 388 cols ~= " PRIMARY KEY"; 389 } 390 // Index 391 else static if (hasUDA!(mixin("type." ~ m), IndexAttribute)) 392 { 393 enum uda = getUDAs!(mixin("type." ~ m), IndexAttribute)[0]; 394 additional ~= "CREATE%sINDEX \"%s\" ON \"%s\" (\"%s\")".format( 395 uda.unique ? " UNIQUE " : " ", 396 "%s_%s_index".format(name, colName), 397 name, 398 colName); 399 400 // DEBUG 401 } 402 // Foreign key 403 else static if (hasUDA!(mixin("type." ~ m), ForeignKeyAttribute)) 404 { 405 enum uda = getUDAs!(mixin("type." ~ m), ForeignKeyAttribute)[0]; 406 additional ~= 407 "ALTER TABLE \"%s\" ADD CONSTRAINT \"%s\" FOREIGN KEY (\"%s\") REFERENCES \"%s\" (\"%s\")".format( 408 name, 409 "%s_%s_fk_%s".format(name, colName, uda.relation), 410 colName, 411 uda.relation, 412 uda.pkey); 413 414 // Create an index on the FK too 415 additional ~= "CREATE INDEX \"%s\" ON \"%s\" (\"%s\")".format( 416 "%s_%s_fk_index".format(name, colName), 417 name, 418 colName); 419 420 } 421 422 cols ~= ", "; 423 } 424 425 cols = cols[0 .. $ - 2]; 426 str = str.format(cols); 427 if (createType) 428 { 429 try 430 { 431 exec(str); 432 } 433 catch {} // Do nothing, type already exists 434 } 435 else 436 exec(str); 437 } 438 foreach (cmd; additional) 439 { 440 try 441 { 442 exec(cmd); 443 } 444 catch {} // Horrible, I know, but this just means the constraint/index already exists 445 } 446 447 // After we're done creating all the types, a good idea would be to get OIDs for all of them 448 if (relations.length > 0) 449 { 450 auto res = execParams( 451 "SELECT typname, oid FROM pg_type WHERE typname IN ('%s')".format( 452 relations.join("','"))); 453 454 foreach (r; res) 455 _dpqCustomOIDs[r[0].as!string] = r[1].as!Oid; 456 } 457 458 459 } 460 461 unittest 462 { 463 // Probably needs more thorough testing, let's assume right now 464 // everything is correct if the creating was successful. 465 466 writeln("\t * ensureSchema"); 467 struct Inner 468 { 469 string innerStr; 470 int innerInt; 471 } 472 473 struct TestTable1 474 { 475 @serial8 @PK long id; 476 string str; 477 int n; 478 @embed Inner inner; 479 } 480 481 c.ensureSchema!TestTable1; 482 483 auto res = c.execParams( 484 "SELECT COUNT(*) FROM pg_catalog.pg_tables WHERE tablename = $1", 485 relationName!TestTable1); 486 487 assert(res.rows == 1); 488 assert(res[0][0].as!long == 1); 489 490 c.exec("DROP TABLE " ~ relationName!TestTable1); 491 c.exec("DROP TYPE \"" ~ relationName!Inner ~ "\" CASCADE"); 492 } 493 494 /** 495 Returns the requested structure or a Nullable null value if no rows are returned 496 497 This method queries for the given structure by its primary key. If no 498 primary key can be found, a compile-time error will be generated. 499 500 Examples: 501 ---------------------- 502 Connection conn; // An established connection 503 struct User 504 { 505 @serial @PKey int id; 506 ... 507 }; 508 509 auto user = conn.findOne!User(1); // will search by the id attribute 510 ---------------------- 511 */ 512 Nullable!T findOne(T, U)(U id) 513 { 514 return findOneBy!T(primaryKeyAttributeName!T, id); 515 } 516 517 unittest 518 { 519 writeln("\t * findOne(T)(U id), findOneBy, findOne"); 520 struct Testy 521 { 522 @serial @PK int id; 523 string foo; 524 int bar; 525 long baz; 526 int[] intArr; 527 //string[] stringArr; // TODO: string[] 528 } 529 530 c.ensureSchema!Testy; 531 532 writeln("\t\t * Null result"); 533 auto shouldBeNull = c.findOne!Testy(0); 534 assert(shouldBeNull.isNull); 535 536 c.exec("INSERT INTO " ~ relationName!Testy ~ " (id, foo, bar, baz, " ~ attributeName!(Testy.intArr) ~ ") "~ 537 "VALUES (1, 'somestr', 2, 3, '{1,2,3}')"); 538 539 writeln("\t\t * Valid result"); 540 Testy t = c.findOne!Testy(1); 541 assert(t.id == 1, `t.id == 1` ); 542 assert(t.foo == "somestr", `t.foo == "somestr"`); 543 assert(t.bar == 2, `t.bar == 2`); 544 assert(t.baz == 3, `t.baz == 3`); 545 assert(t.intArr == [1,2,3], `t.intArr == [1,2,3]`); 546 //assert(t.stringArr == ["asd", "qwe"]); 547 548 writeln("\t\t * findOne with custom filter"); 549 Testy t2 = c.findOne!Testy("id = $1", 1); 550 assert(t == t2); 551 552 c.exec("DROP TABLE " ~ relationName!Testy); 553 } 554 555 /** 556 Returns the requestes structure, searches by the given column name 557 with the given value 558 If not rows are returned, a Nullable null value is returned 559 560 Examples: 561 ---------------------- 562 Connection conn; // An established connection 563 struct User 564 { 565 @serial @PKey int id; 566 ... 567 }; 568 569 auto user = conn.findOneBy!User("id", 1); // will search by "id" 570 ---------------------- 571 */ 572 Nullable!T findOneBy(T, U)(string col, U val) 573 { 574 import std.stdio; 575 576 auto members = AttributeList!T; 577 578 QueryBuilder qb; 579 qb.select(members) 580 .from(relationName!T) 581 .where( col ~ " = {col_" ~ col ~ "}") 582 .limit(1); 583 584 qb["col_" ~ col] = val; 585 586 auto q = qb.query(this); 587 588 auto r = q.run(); 589 if (r.rows == 0) 590 return Nullable!T.init; 591 592 //return T(); 593 594 auto res = deserialise!T(r[0]); 595 return Nullable!T(res); 596 } 597 598 /** 599 Returns the requested structure, searches by the specified filter 600 with given params 601 602 The filter is not further escaped, so programmer needs to make sure 603 not to properly escape or enclose reserved keywords (like user -> "user") 604 so PostgreSQL can understand them. 605 606 If not rows are returned, a Nullable null value is returned 607 608 Examples: 609 ---------------------- 610 Connection conn; // An established connection 611 struct User 612 { 613 @serial @PKey int id; 614 string username; 615 int posts; 616 }; 617 618 auto user = conn.findOne!User("username = $1 OR posts > $2", "foo", 42); 619 if (!user.isNull) 620 { 621 ... // do something 622 } 623 ---------------------- 624 */ 625 Nullable!T findOne(T, U...)(string filter, U vals) 626 { 627 QueryBuilder qb; 628 qb.select(AttributeList!T) 629 .from(relationName!T) 630 .where(filter) 631 .limit(1); 632 633 auto q = qb.query(this); 634 auto r = q.run(vals); 635 636 if (r.rows == 0) 637 return Nullable!T.init; 638 639 auto res = deserialise!T(r[0]); 640 return Nullable!T(res); 641 } 642 643 /** 644 Returns an array of the specified type, filtered with the given filter and 645 params 646 647 If no rows are returned by PostgreSQL, an empty array is returned. 648 649 Examples: 650 ---------------------- 651 Connection conn; // An established connection 652 struct User 653 { 654 @serial @PKey int id; 655 string username; 656 int posts; 657 }; 658 659 auto users = conn.find!User("username = $1 OR posts > $2", "foo", 42); 660 foreach (u; users) 661 { 662 ... // do something 663 } 664 ---------------------- 665 */ 666 T[] find(T, U...)(string filter = "", U vals = U.init) 667 { 668 QueryBuilder qb; 669 qb.select(AttributeList!T) 670 .from(relationName!T) 671 .where(filter); 672 673 auto q = qb.query(this); 674 675 T[] res; 676 foreach (r; q.run(vals)) 677 res ~= deserialise!T(r); 678 679 return res; 680 } 681 682 unittest 683 { 684 writeln("\t * find"); 685 686 @relation("find_test") 687 struct Test 688 { 689 @serial @PK int id; 690 @attr("my_n") int n; 691 } 692 693 c.ensureSchema!Test; 694 695 Test t; 696 t.n = 1; 697 698 c.insert(t); 699 c.insert(t); 700 ++t.n; 701 c.insert(t); 702 c.insert(t); 703 c.insert(t); 704 705 Test[] ts = c.find!Test("my_n = $1", 1); 706 assert(ts.length == 2); 707 ts = c.find!Test("my_n > 0"); 708 assert(ts.length == 5); 709 ts = c.find!Test("false"); 710 assert(ts.length == 0); 711 712 c.exec("DROP TABLE find_test"); 713 } 714 715 int update(T, U...)(string filter, string update, U vals) 716 { 717 QueryBuilder qb; 718 qb.update(relationName!T) 719 .set(update) 720 .where(filter); 721 722 auto r = qb.query(this).run(vals); 723 return r.rows; 724 } 725 726 unittest 727 { 728 writeln("\t * update"); 729 730 @relation("update_test") 731 struct Test 732 { 733 @serial @PK int id; 734 int n; 735 } 736 737 c.ensureSchema!Test; 738 739 Test t; 740 t.n = 5; 741 c.insert(t); 742 743 int nUpdates = c.update!Test("n = $1", "n = $2", 5, 123); 744 assert(nUpdates == 1, `nUpdates == 1`); 745 746 t = c.findOneBy!Test("n", 123); 747 assert(t.n == 123, `t.n == 123`); 748 749 writeln("\t\t * async"); 750 c.updateAsync!Test("n = $1", "n = $2", 123, 6); 751 auto r = c.nextResult(); 752 753 assert(r.rows == 1); 754 assert(!c.findOneBy!Test("n", 6).isNull); 755 756 c.exec("DROP TABLE update_test"); 757 } 758 759 void updateAsync(T, U...)(string filter, string update, U vals) 760 { 761 QueryBuilder qb; 762 qb.update(relationName!T) 763 .set(update) 764 .where(filter); 765 766 qb.query(this).runAsync(vals); 767 } 768 769 int update(T, U)(U id, Value[string] updates, bool async = false) 770 { 771 QueryBuilder qb; 772 773 qb.update(relationName!T) 774 .set(updates) 775 .where(primaryKeyAttributeName!T, id); 776 777 auto q = qb.query(this); 778 779 if (async) 780 { 781 q.runAsync(); 782 return -1; 783 } 784 785 auto r = q.run(); 786 return r.rows; 787 } 788 789 unittest 790 { 791 writeln("\t * update with AA updates"); 792 793 @relation("update_aa_test") 794 struct Test 795 { 796 @serial @PK int id; 797 int n; 798 } 799 c.ensureSchema!Test; 800 801 Test t; 802 t.n = 1; 803 c.insert(t); 804 805 int rows = c.update!Test(1, ["n": Value(2)]); 806 assert(rows == 1, `r.rows == 1`); 807 808 c.exec("DROP TABLE update_aa_test"); 809 } 810 811 void updateAsync(T, U)(U id, Value[string] updates) 812 { 813 update!T(id, updates, true); 814 } 815 816 int update(T, U)(U id, T updates, bool async = false) 817 { 818 import dpq.attributes; 819 820 QueryBuilder qb; 821 822 qb.update(relationName!T) 823 .where(primaryKeyAttributeName!T, id); 824 825 foreach (m; serialisableMembers!T) 826 qb.set(attributeName!(mixin("T." ~ m)), __traits(getMember, updates, m)); 827 828 auto q = qb.query(this); 829 if (async) 830 { 831 qb.query(this).runAsync(); 832 return -1; 833 } 834 835 auto r = q.run(); 836 return r.rows; 837 } 838 839 unittest 840 { 841 writeln("\t * update with object"); 842 843 @relation("update_object_test") 844 struct Test 845 { 846 @serial @PK int id; 847 int n; 848 } 849 c.ensureSchema!Test; 850 851 Test t; 852 t.n = 1; 853 t.id = 1; // assumptions <3 854 855 c.insert(t); 856 857 t.n = 2; 858 c.update!Test(1, t); 859 860 t = c.findOne!Test(1); 861 assert(t.n == 2); 862 863 t.n = 3; 864 c.updateAsync!Test(1, t); 865 auto r = c.nextResult(); 866 867 writeln("\t\t * async"); 868 assert(r.rows == 1); 869 870 c.exec("DROP TABLE update_object_test"); 871 } 872 873 void updateAsync(T, U)(U id, T updates) 874 { 875 update!T(id, updates, true); 876 } 877 878 879 private void addVals(T, U)(ref QueryBuilder qb, U val) 880 { 881 if (isAnyNull(val)) 882 qb.addValue(null); 883 else 884 { 885 foreach (m; serialisableMembers!(NoNullable!T)) 886 { 887 static if (isPK!(T, m) || hasUDA!(mixin("T." ~ m), IgnoreAttribute)) 888 continue; 889 else 890 qb.addValue(__traits(getMember, val, m)); 891 } 892 } 893 } 894 895 Result insertR(T)(T val, string ret = "") 896 { 897 QueryBuilder qb; 898 qb.insert(relationName!T, AttributeList!(T, true, true)); 899 if (ret.length > 0) 900 qb.returning(ret); 901 902 addVals!T(qb, val); 903 904 return qb.query(this).run(); 905 } 906 907 bool insert(T)(T val, bool async = false) 908 { 909 QueryBuilder qb; 910 qb.insert(relationName!T, AttributeList!(T, true, true)); 911 912 addVals!T(qb, val); 913 914 if (async) 915 return qb.query(this).runAsync(); 916 917 auto r = qb.query(this).run(); 918 return r.rows > 0; 919 } 920 921 unittest 922 { 923 writeln("\t * insert"); 924 925 @relation("insert_test_inner") 926 struct Inner 927 { 928 int bar; 929 } 930 931 @relation("insert_test") 932 struct Test 933 { 934 int n; 935 Nullable!int n2; 936 @embed Inner foo; 937 } 938 c.ensureSchema!Test; 939 940 Test t; 941 t.n = 1; 942 t.n2 = 2; 943 t.foo.bar = 2; 944 945 auto r = c.insert(t); 946 assert(r == true); 947 948 auto r2 = c.insertR(t, "n"); 949 assert(r2.rows == 1); 950 assert(r2[0][0].as!int == t.n); 951 952 Test t2 = c.findOneBy!Test("n", 1); 953 assert(t2 == t, t.to!string ~ " != " ~ t2.to!string); 954 955 writeln("\t\t * async"); 956 t.n = 123; 957 t.n2.nullify; 958 c.insertAsync(t); 959 960 auto res = c.nextResult(); 961 assert(res.rows == 1); 962 t2 = c.findOneBy!Test("n", 123); 963 assert(t.n2.isNull); 964 assert(t2.n2.isNull); 965 966 c.exec("DROP TABLE insert_test"); 967 c.exec("DROP TYPE \"%s\" CASCADE".format(relationName!Inner)); 968 } 969 970 void insertAsync(T)(T val) 971 { 972 insert(val, true); 973 } 974 975 int remove(T, U)(U id) 976 { 977 QueryBuilder qb; 978 qb.remove!T 979 .where(primaryKeyAttributeName!T, id); 980 981 return qb.query(this).run().rows; 982 } 983 984 bool removeAsync(T, U)(U id) 985 { 986 QueryBuilder qb; 987 qb.remove!T 988 .where(primaryKeyAttributeName!T, id); 989 990 return qb.query(this).runAsync() == 1; 991 } 992 993 994 int remove(T, U...)(string filter, U vals) 995 { 996 QueryBuilder qb; 997 qb.remove!T 998 .where(filter); 999 1000 foreach (v; vals) 1001 qb.addValue(v); 1002 1003 return qb.query(this).run().rows; 1004 } 1005 1006 bool removeAsync(T, U...)(string filter, U vals) 1007 { 1008 QueryBuilder qb; 1009 qb.remove!T 1010 .where(filter); 1011 1012 foreach (v; vals) 1013 qb.addValue(v); 1014 1015 return qb.query(this).runAsync() == 1; 1016 } 1017 1018 unittest 1019 { 1020 @relation("remove_test") 1021 struct Test 1022 { 1023 @serial @PK int id; 1024 int n; 1025 } 1026 c.ensureSchema!Test; 1027 1028 foreach (i; 0 .. 10) 1029 c.insert(Test(0, i)); 1030 1031 writeln("\t * remove(id)"); 1032 int n = c.remove!Test(1); 1033 assert(n == 1, `n == 1`); 1034 1035 1036 writeln("\t\t * async"); 1037 c.removeAsync!Test(2); 1038 auto r = c.nextResult(); 1039 assert(r.rows == 1, `r.rows == 1`); 1040 1041 writeln("\t * remove(filter, vals...)"); 1042 n = c.remove!Test("id IN($1,$2,$3,$4,$5)", 3, 4, 5, 6, 7); 1043 assert(n == 5); 1044 1045 writeln("\t\t * async"); 1046 c.removeAsync!Test("id >= $1", 7); 1047 r = c.nextResult(); 1048 assert(r.rows == 3); 1049 1050 c.exec("DROP TABLE remove_test"); 1051 } 1052 1053 long count(T, U...)(string filter = "", U vals = U.init) 1054 { 1055 import dpq.query; 1056 auto q = Query(this); 1057 string str = `SELECT COUNT(*) FROM "%s"`.format(relationName!T); 1058 1059 if (filter.length > 0) 1060 str ~= " WHERE " ~ filter; 1061 1062 q = str; 1063 auto r = q.run(vals); 1064 1065 return r[0][0].as!long; 1066 } 1067 1068 unittest 1069 { 1070 writeln("\t * count"); 1071 1072 @relation("test_count") 1073 struct Test 1074 { 1075 @serial @PK int id; 1076 int n; 1077 } 1078 1079 c.ensureSchema!Test; 1080 1081 Test t; 1082 c.insert(t); 1083 1084 assert(c.count!Test == 1, `count == 1`); 1085 c.insert(t); 1086 assert(c.count!Test == 2, `count == 2`); 1087 1088 c.exec("DROP TABLE test_count"); 1089 } 1090 1091 bool isBusy() 1092 { 1093 return PQisBusy(_connection) == 1; 1094 } 1095 1096 unittest 1097 { 1098 writeln("\t * isBusy"); 1099 1100 assert(c.isBusy() == false); 1101 1102 c.send("SELECT 1::INT"); 1103 1104 assert(c.isBusy() == true); 1105 1106 c.nextResult(); 1107 assert(c.isBusy() == false); 1108 } 1109 1110 1111 /** 1112 Blocks until a result is read, then returns it 1113 1114 If no more results remain, a null result will be returned 1115 1116 Make sure to call this until a null is returned. 1117 */ 1118 Result nextResult() 1119 { 1120 import core.thread; 1121 1122 /* 1123 do 1124 { 1125 PQconsumeInput(_connection); 1126 //Thread.sleep(dur!"msecs"(1)); // What's a reasonable amount of time to wait? 1127 } 1128 while (isBusy()); 1129 */ 1130 1131 PGresult* res = PQgetResult(_connection); 1132 return Result(res); 1133 } 1134 1135 Result[] allResults() 1136 { 1137 Result[] res; 1138 1139 PGresult* r; 1140 while ((r = PQgetResult(_connection)) != null) 1141 res ~= Result(r); 1142 1143 return res; 1144 } 1145 1146 Result lastResult() 1147 { 1148 Result res; 1149 1150 PGresult* r; 1151 while ((r = PQgetResult(_connection)) != null) 1152 res = Result(r); 1153 1154 return res; 1155 } 1156 1157 unittest 1158 { 1159 writeln("\t * nextResult"); 1160 auto x = c.nextResult(); 1161 assert(x.isNull); 1162 1163 int int1 = 1; 1164 int int2 = 2; 1165 1166 c.sendParams("SELECT $1", int1); 1167 1168 // In every way the same as lastResult 1169 Result r, t; 1170 while(!(t = c.nextResult()).isNull) 1171 r = t; 1172 1173 assert(r.rows == 1); 1174 assert(r.columns == 1); 1175 assert(r[0][0].as!int == int1); 1176 1177 writeln("\t * lastResult"); 1178 c.sendParams("SELECT $1", int2); 1179 r = c.lastResult(); 1180 1181 assert(r.rows == 1); 1182 assert(r.columns == 1); 1183 assert(r[0][0].as!int == int2); 1184 } 1185 1186 Result prepare(T...)(string name, string command, T paramTypes) 1187 { 1188 Oid[] oids; 1189 foreach (pType; paramTypes) 1190 oids ~= pType; 1191 1192 char* cName = cast(char*) name.toStringz; 1193 char* cComm = cast(char*) command.toStringz; 1194 1195 auto p = PreparedStatement(this, name, command, oids); 1196 _prepared[name] = p; 1197 1198 return Result(PQprepare( 1199 _connection, 1200 cName, 1201 cComm, 1202 oids.length.to!int, 1203 oids.ptr)); 1204 } 1205 1206 Result execPrepared(string name, Value[] params...) 1207 { 1208 char* cStr = cast(char*) name.toStringz; 1209 1210 return Result(PQexecPrepared( 1211 _connection, 1212 cStr, 1213 params.length.to!int, 1214 cast(char**) params.paramValues.ptr, 1215 params.paramLengths.ptr, 1216 params.paramFormats.ptr, 1217 1)); 1218 } 1219 1220 Result execPrepared(T...)(string name, T params) 1221 { 1222 Value[] vals; 1223 foreach (p; params) 1224 vals ~= Value(p); 1225 1226 return execPrepared(name, vals); 1227 } 1228 1229 bool sendPrepared(string name, Value[] params...) 1230 { 1231 char* cStr = cast(char*) name.toStringz; 1232 1233 return PQsendQueryPrepared( 1234 _connection, 1235 cStr, 1236 params.length.to!int, 1237 cast(char**) params.paramValues.ptr, 1238 params.paramLengths.ptr, 1239 params.paramFormats.ptr, 1240 1) == 1; 1241 } 1242 1243 bool sendPrepared(T...)(string name, T params) 1244 { 1245 Value[] vals; 1246 foreach (p; params) 1247 vals ~= Value(p); 1248 1249 return sendPrepared(name, vals); 1250 } 1251 1252 unittest 1253 { 1254 writeln("\t * prepare"); 1255 // The result of this isn't really all that useful, but as long as it 1256 // throws on errors, it kinda is 1257 c.prepare("prepare_test", "SELECT $1", Type.INT4); 1258 1259 writeln("\t * execPrepared"); 1260 auto r = c.execPrepared("prepare_test", 1); 1261 assert(r.rows == 1); 1262 assert(r[0][0].as!int == 1); 1263 1264 writeln("\t\t * sendPrepared"); 1265 bool s = c.sendPrepared("prepare_test", 1); 1266 assert(s); 1267 1268 r = c.lastResult(); 1269 assert(r.rows == 1); 1270 assert(r[0][0].as!int == 1); 1271 } 1272 1273 ref PreparedStatement prepared(string name) 1274 { 1275 return _prepared[name]; 1276 } 1277 1278 ref PreparedStatement opIndex(string name) 1279 { 1280 return prepared(name); 1281 } 1282 } 1283 1284 1285 /** 1286 Deserialises the given Row to the requested type 1287 1288 Params: 1289 T = (template) type to deserialise into 1290 r = Row to deserialise 1291 */ 1292 T deserialise(T)(Row r, string prefix = "") 1293 { 1294 T res; 1295 foreach (m; serialisableMembers!T) 1296 { 1297 enum n = attributeName!(mixin("T." ~ m)); 1298 alias mType = Unqual!(typeof(mixin("T." ~ m))); 1299 1300 try 1301 { 1302 auto x = r[prefix ~ n].as!mType; 1303 if (!x.isNull) 1304 __traits(getMember, res, m) = cast(TypedefType!(mType)) x; 1305 } 1306 catch (DPQException e) 1307 { 1308 if (!isInstanceOf!(Nullable, mType)) 1309 throw e; 1310 } 1311 } 1312 return res; 1313 } 1314 1315 /// Hold the last created connection, not to be used outside the library 1316 package Connection* _dpqLastConnection; 1317 1318 /// Holds a list of all the OIDs of our custom types, indexed by their relationName 1319 package Oid[string] _dpqCustomOIDs; 1320